Home  >  Article  >  Web Front-end  >  Detailed explanation of webpack practical cases

Detailed explanation of webpack practical cases

php中世界最好的语言
php中世界最好的语言Original
2018-05-25 11:26:152038browse

This time I will bring you a detailed explanation of webpack practical cases. What are the precautions for webpack practical use? The following is a practical case, let's take a look.

webpack in action

View all documentation pages: Full stack development for more information.

I worked hard and worked overtime to finally sort out this document. I also studied it in depth and consolidated my knowledge, but it was too tiring and affected the time and quality of sleep. Geeks just want to do things to the extreme. Once they start, they must reach the end.

Original link: Webpack actual combat, the original advertising modal box is blocked, and the reading experience is not good, so I organized it into this article for easy search.

This chapter teaches you how to use Webpack to solve common scenarios in actual projects.

Divided into the following categories according to different scenarios:

  • Use new languages ​​to develop projects:

    • Use ES6 language

    • Using TypeScript language

    • Using Flow inspector

    • Using SCSS language

    • Using PostCSS

  • Use the new framework to develop your project:

    • Use React framework

    • Use Vue framework

    • Use Angular2 framework

  • Build with Webpack Single-page application:

    • Generate HTML for single-page application

    • ##Manage multiple single-page applications

  • Use Webpack to build projects with different running environments:

    • Build isomorphic applications

    • Build Electron Application

    • Build Npm module

    • Build offline application

  • Webpack combined with other tools Use them together to get the best of each:

    • With Npm Script

    • Check the code

    • Start Webpack through Node.js API

    • Use Webpack Dev Middleware

  • Use Webpack to load special types of resources:

    • Load Image

    • Load SVG

    • Load Source Map

Use TypeScript language

Since this article does not recommend using TypeScript, ES6 is sufficient to complete most tasks. Original link: Using TypeScript language

Using Angular2 framework

Angular2 is not within the scope of my technology stack, so this chapter will not be included. If you are interested, check out the original text: Using Angular2 framework

Use ES6 language

Usually we need to convert the code written in ES6 into the currently well-supported ES5 code. This includes two things:

  1. Convert the new ES6 syntax is implemented using ES5. For example, ES6's

    class syntax is implemented using ES5's prototype.

  2. Inject polyfill into the new API. For example, when using the new

    fetch API, only by injecting the corresponding polyfill can low-end browsers run normally.

Babel

Babel can easily accomplish the above two things.

Babel is a JavaScript compiler that can convert ES6 code to ES5 code, allowing you to use the latest language features without worrying about compatibility issues, and can be flexibly expanded according to needs through the plug-in mechanism.

During the compilation process of Babel, the configuration

will be read from the .babelrc file in the project root directory. .babelrc is a JSON format file with the following content: The

{
  "plugins": [
    [
      "transform-runtime",
      {
        "polyfill": false
      }
    ]
   ],
  "presets": [
    [
      "es2015",
      {
        "modules": false
      }
    ],
    "stage-2",
    "react"
  ]
}

Plugins

plugins property tells Babel which plugins to use, and plugins can control how the code is converted.

The full name of the corresponding plug-in transform-runtime in the above configuration file is called babel-plugin-transform-runtime, that is, add it in front After installing babel-plugin-, to make Babel run normally we must install it first:

npm i -D babel-plugin-transform-runtime

babel-plugin-transform-runtime is the one officially provided by Babel Plug-ins are used to reduce redundant code.

When Babel converts ES6 code to ES5 code, it usually requires some auxiliary functions written in ES5 to complete the implementation of the new syntax. For example, when converting the class extent syntax, it will be added to the converted ES5 The _extent auxiliary function is injected into the code to implement inheritance:

function _extent(target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i];
    for (var key in source) {
      if (Object.prototype.hasOwnProperty.call(source, key)) {
        target[key] = source[key];
      }
    }
  }
  return target;
}

This will cause each file using the class extent syntax to be injected with duplicate _extent The auxiliary function code, babel-plugin-transform-runtime is used to not inject the auxiliary function content into the file, but to inject an import statement:

var _extent = require(&#39;babel-runtime/helpers/_extent&#39;);

This way Reduce the file size of code compiled by Babel.

At the same time, it should be noted that babel-plugin-transform-runtime injects the require('babel-runtime/helpers/_extent') statement into the compiled In the code, you need to install the babel-runtime dependency on your project before the code can run normally. That is to say, babel-plugin-transform-runtime and babel-runtime need to be used together. ## must be required after using babel-plugin-transform-runtime #babel-runtime.

Presets

presets The attribute tells Babel which new syntax features are used in the source code to be converted. A Presets provides a set of new syntax features. Supported, multiple Presets can be superimposed.

Presets is actually a collection of Plugins, and each Plugin completes the conversion work of a new grammar. Presets are organized according to ECMAScript drafts and can usually be divided into the following three categories:

  1. Features that have been written into the ECMAScript standard, because new features have been added every year In the standard;

    • #env contains the latest features in all current ECMAScript standards.

  2. Features proposed by the community but not yet written into the ECMAScript standard are divided into the following four types:

    • stage0 It’s just a beautiful and radical idea. There is a Babel plug-in that supports these features, but I’m not sure whether it will be set as a standard;

    • stage1 Features worthy of inclusion in the standard;

    • stage2 This feature specification has been drafted and will be included in the standard;

    • stage3 This feature specification has been finalized, and major browser manufacturers and the Node.js community have begun to implement it;

    • stage4 will be added to the standard in the next year.

  3. In order to support the syntax in some specific application scenarios, it has nothing to do with the ECMAScript standard. For example,

    babel-preset-react is to support React development. JSX syntax.

In actual application, you need to install the corresponding Plugins or Presets according to the syntax used in the project source code.

Connect to Babel

Since what Babel does is convert code, you should connect to Babel through Loader. The Webpack configuration is as follows:

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [&#39;babel-loader&#39;],
      },
    ]
  },
  // 输出 source-map 方便直接调试 ES6 源码
  devtool: &#39;source-map&#39;
};

The configuration hits the project directory Download all JavaScript files and call Babel through

babel-loader to complete the conversion work. Before re-executing the build, you need to install the newly introduced dependencies:

# Webpack 接入 Babel 必须依赖的模块
npm i -D babel-core babel-loader 
# 根据你的需求选择不同的 Plugins 或 Presets
npm i -D babel-preset-env

Use SCSS language

SCSS allows you to write CSS in a more flexible way. It is a CSS preprocessor. The syntax is similar to CSS, but it adds variables, logic and other programming elements. The code is similar to this:

$blue: #1875e7; 
p {
  color: $blue;
}

SCSS is also called SASS. The difference is that the SASS syntax is similar to Ruby, while the SCSS syntax is Similar to CSS, SCSS will be preferred by front-end engineers who are familiar with CSS.

采用 SCSS 去写 CSS 的好处在于可以方便地管理代码,抽离公共的部分,通过逻辑写出更灵活的代码。 和 SCSS 类似的 CSS 预处理器还有 LESS 等。

使用 SCSS 可以提升编码效率,但是必须把 SCSS 源代码编译成可以直接在浏览器环境下运行的 CSS 代码。

node-sass 核心模块是由 C++ 编写,再用 Node.js 封装了一层,以供给其它 Node.js 调用。 node-sass 还支持通过命令行调用,先安装它到全局:

npm i -g node-sass

再执行编译命令:

# 把 main.scss 源文件编译成 main.css
node-sass main.scss main.css

你就能在源码同目录下看到编译后的 main.css 文件。

接入 Webpack

Webpack 接入 sass-loader 相关配置如下:

module.exports = {
  module: {
    rules: [
      {
        // 增加对 SCSS 文件的支持
        test: /\.scss/,
        // SCSS 文件的处理顺序为先 sass-loader 再 css-loader 再 style-loader
        use: [&#39;style-loader&#39;, &#39;css-loader&#39;, &#39;sass-loader&#39;],
      },
    ]
  },
};

以上配置通过正则 /\.scss/ 匹配所有以 .scss 为后缀的 SCSS 文件,再分别使用3个 Loader 去处理。具体处理流程如下:

  1. 通过 sass-loader 把 SCSS 源码转换为 CSS 代码,再把 CSS 代码交给 css-loader 去处理。

  2. css-loader 会找出 CSS 代码中的 @importurl() 这样的导入语句,告诉 Webpack 依赖这些资源。同时还支持 CSS Modules、压缩 CSS 等功能。处理完后再把结果交给 style-loader 去处理。

  3. style-loader 会把 CSS 代码转换成字符串后,注入到 JavaScript 代码中去,通过 JavaScript 去给 DOM 增加样式。如果你想把 CSS 代码提取到一个单独的文件而不是和 JavaScript 混在一起,可以使用1-5 使用Plugin 中介绍过的 ExtractTextPlugin。

由于接入 sass-loader,项目需要安装这些新的依赖:

# 安装 Webpack Loader 依赖
npm i -D  sass-loader css-loader style-loader
# sass-loader 依赖 node-sass
npm i -D node-sass

使用Flow检查器

Flow 是一个 Facebook 开源的 JavaScript 静态类型检测器,它是 JavaScript 语言的超集。

你所需要做的就是在需要的地方加上类型检查,例如在两个由不同人开发的模块对接的接口出加上静态类型检查,能在编译阶段就指出部分模块使用不当的问题。 同时 Flow 也能通过类型推断检查出 JavaScript 代码中潜在的 Bug。

Flow 使用效果如下:

// @flow
// 静态类型检查
function square1(n: number): number {
  return n * n;
}
square1(&#39;2&#39;); // Error: square1 需要传入 number 作为参数
// 类型推断检查
function square2(n) {
  return n * n; // Error: 传入的 string 类型不能做乘法运算
}
square2(&#39;2&#39;);
需要注意的时代码中的第一行 // @flow 告诉 Flow 检查器这个文件需要被检查。

使用 Flow

Flow 检测器由高性能跨平台的 OCaml 语言编写,它的可执行文件可以通过:

npm i -D flow-bin

安装,安装完成后通过先配置 Npm Script:

"scripts": {
   "flow": "flow"
}

再通过 npm run flow 去调用 Flow 执行代码检查。

除此之外你还可以通过:

npm i -g flow-bin

把 Flow 安装到全局后,再直接通过 flow 命令去执行代码检查。

安装成功后,在项目根目录下执行 Flow 后,Flow 会遍历出所有需要检查的文件并对其进行检查,输出错误结果到控制台。

采用了 Flow 静态类型语法的 JavaScript 是无法直接在目前已有的 JavaScript 引擎中运行的,要让代码可以运行需要把这些静态类型语法去掉。

// 采用 Flow 的源代码
function foo(one: any, two: number, three?): string {}
// 去掉静态类型语法后输出代码
function foo(one, two, three) {}

有两种方式可以做到这点:

  1. flow-remove-types 可单独使用,速度快。

  2. babel-preset-flow 与 Babel 集成。

集成 Webpack

由于使用了 Flow 项目一般都会使用 ES6 语法,所以把 Flow 集成到使用 Webpack 构建的项目里最方便的方法是借助 Babel。

  1. 安装 npm i -D babel-preset-flow 依赖到项目。

  2. 修改 .babelrc 配置文件,加入 Flow Preset:

    "presets": [
    ...[],
    "flow"
    ]

往源码里加入静态类型后重新构建项目,你会发现采用了 Flow 的源码还是能正常在浏览器中运行。

要明确构建的目的只是为了去除源码中的 Flow 静态类型语法,而代码检查和构建无关。 许多编辑器已经整合 Flow,可以实时在代码中高亮指出 Flow 检查出的问题。

使用PostCSS

PostCSS 是一个 CSS 处理工具,和 SCSS 不同的地方在于它通过插件机制可以灵活的扩展其支持的特性,而不是像 SCSS 那样语法是固定的。 PostCSS 的用处非常多,包括给 CSS 自动加前缀、使用下一代 CSS 语法等,目前越来越多的人开始用它,它很可能会成为 CSS 预处理器的最终赢家。

PostCSS 和 CSS 的关系就像 Babel 和 JavaScript 的关系,它们解除了语法上的禁锢,通过插件机制来扩展语言本身,用工程化手段给语言带来了更多的可能性。

PostCSS 和 SCSS 的关系就像 Babel 和 TypeScript 的关系,PostCSS 更加灵活、可扩张性强,而 SCSS 内置了大量功能而不能扩展。

给 CSS 自动加前缀,增加各浏览器的兼容性:

/*输入*/
h1 {
  display: flex;
}
/*输出*/
h1 {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
}

使用下一代 CSS 语法:

/*输入*/
:root {
  --red: #d33;
}
h1 {
  color: var(--red);
}
/*输出*/
h1 { 
  color: #d33;
}

PostCSS 全部采用 JavaScript 编写,运行在 Node.js 之上,即提供了给 JavaScript 代码调用的模块,也提供了可执行的文件。

在 PostCSS 启动时,会从目录下的 postcss.config.js 文件中读取所需配置,所以需要新建该文件,文件内容大致如下:

module.exports = {
  plugins: [
    // 需要使用的插件列表
    require(&#39;postcss-cssnext&#39;)
  ]
}

其中的 postcss-cssnext 插件可以让你使用下一代 CSS 语法编写代码,再通过 PostCSS 转换成目前的浏览器可识别的 CSS,并且该插件还包含给 CSS 自动加前缀的功能。

目前 Chrome 等现代浏览器已经能完全支持 cssnext 中的所有语法,也就是说按照 cssnext 语法写的 CSS 在不经过转换的情况下也能在浏览器中直接运行。

接入 Webpack

虽然使用 PostCSS 后文件后缀还是 .css 但这些文件必须先交给 postcss-loader 处理一遍后再交给 css-loader

接入 PostCSS 相关的 Webpack 配置如下:

module.exports = {
  module: {
    rules: [
      {
        // 使用 PostCSS 处理 CSS 文件
        test: /\.css/,
        use: [&#39;style-loader&#39;, &#39;css-loader&#39;, &#39;postcss-loader&#39;],
      },
    ]
  },
};

接入 PostCSS 给项目带来了新的依赖需要安装,如下:

# 安装 Webpack Loader 依赖
npm i -D postcss-loader css-loader style-loader
# 根据你使用的特性安装对应的 PostCSS 插件依赖
npm i -D postcss-cssnext

使用React框架

React 语法特征

使用了 React 项目的代码特征有 JSX 和 Class 语法,例如:

class Button extends Component {
  render() {
    return <h1>Hello,Webpack</h1>
  }
}
在使用了 React 的项目里 JSX 和 Class 语法并不是必须的,但使用新语法写出的代码看上去更优雅。

其中 JSX 语法是无法在任何现有的 JavaScript 引擎中运行的,所以在构建过程中需要把源码转换成可以运行的代码,例如:

// 原 JSX 语法代码
return <h1>Hello,Webpack</h1>
// 被转换成正常的 JavaScript 代码
return React.createElement('h1', null, 'Hello,Webpack')

React 与 Babel

要在使用 Babel 的项目中接入 React 框架是很简单的,只需要加入 React 所依赖的 Presets babel-preset-react

通过以下命令:

# 安装 React 基础依赖
npm i -D react react-dom
# 安装 babel 完成语法转换所需依赖
npm i -D babel-preset-react

安装新的依赖后,再修改 .babelrc 配置文件加入 React Presets

"presets": [
    "react"
],

就完成了一切准备工作。

再修改 main.js 文件如下:

import * as React from 'react';
import { Component } from 'react';
import { render } from 'react-dom';
class Button extends Component {
  render() {
    return <h1>Hello,Webpack</h1>
  }
}
render(<Button/>, window.document.getElementById('app'));

重新执行构建打开网页你将会发现由 React 渲染出来的 Hello,Webpack

React 与 TypeScript

TypeScript 相比于 Babel 的优点在于它原生支持 JSX 语法,你不需要重新安装新的依赖,只需修改一行配置。 但 TypeScript 的不同在于:

  • 使用了 JSX 语法的文件后缀必须是 tsx

  • 由于 React 不是采用 TypeScript 编写的,需要安装 reactreact-dom 对应的 TypeScript 接口描述模块 @types/react@types/react-dom 后才能通过编译。

修改 TypeScript 编译器配置文件 tsconfig.json 增加对 JSX 语法的支持,如下:

{
  "compilerOptions": {
    "jsx": "react" // 开启 jsx ,支持 React
  }
}

由于 main.js 文件中存在 JSX 语法,再把 main.js 文件重命名为 main.tsx,同时修改文件内容为在上面 React 与 Babel 里所采用的 React 代码。 同时为了让 Webpack 对项目里的 tstsx 原文件都采用 awesome-typescript-loader 去转换, 需要注意的是 Webpack Loader 配置的 test 选项需要匹配到 tsx 类型的文件,并且 extensions 中也要加上 .tsx,配置如下:

module.exports = {
  // TS 执行入口文件
  entry: './main',
  output: {
    filename: 'bundle.js',
    path: path.resolve(dirname, './dist'),
  },
  resolve: {
    // 先尝试 ts,tsx 后缀的 TypeScript 源码文件 
    extensions: ['.ts', '.tsx', '.js',] 
  },
  module: {
    rules: [
      {
        // 同时匹配 ts,tsx 后缀的 TypeScript 源码文件 
        test: /\.tsx?$/,
        loader: 'awesome-typescript-loader'
      }
    ]
  },
  devtool: 'source-map',// 输出 Source Map 方便在浏览器里调试 TypeScript 代码
};

通过npm i react react-dom @types/react @types/react-dom安装新的依赖后重启构建,重新打开网页你将会发现由 React 渲染出来的 Hello,Webpack

使用Vue框架

Vue是一个渐进式的 MVVM 框架,相比于 React、Angular 它更灵活轻量。 它不会强制性地内置一些功能和语法,你可以根据自己的需要一点点地添加功能。 虽然采用 Vue 的项目能用可直接运行在浏览器环境里的代码编写,但为了方便编码大多数项目都会采用 Vue 官方的单文件组件的写法去编写项目。

Vue 的单文件组件通过一个类似 HTML 文件的 .vue 文件就能描述清楚一个组件所需的模版、样式、逻辑。

main.js 入口文件:

import Vue from 'vue'
import App from './App.vue'
new Vue({
  el: '#app',
  render: h => h(App)
});

入口文件创建一个 Vue 的根实例,在 ID 为 app 的 DOM 节点上渲染出上面定义的 App 组件。

接入 Webpack

目前最成熟和流行的开发 Vue 项目的方式是采用 ES6 加 Babel 转换,这和基本的采用 ES6 开发的项目很相似,差别在于要解析 .vue 格式的单文件组件。 好在 Vue 官方提供了对应的 vue-loader 可以非常方便的完成单文件组件的转换。

修改 Webpack 相关配置如下:

module: {
  rules: [
    {
      test: /\.vue$/,
      use: ['vue-loader'],
    },
  ]
}

安装新引入的依赖:

# Vue 框架运行需要的库
npm i -S vue
# 构建所需的依赖
npm i -D vue-loader css-loader vue-template-compiler

在这些依赖中,它们的作用分别是:

  • vue-loader:解析和转换 .vue 文件,提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template,再分别把它们交给对应的 Loader 去处理。

  • css-loader:加载由 vue-loader 提取出的 CSS 代码。

  • vue-template-compiler:把 vue-loader 提取出的 HTML 模版编译成对应的可执行的 JavaScript 代码,这和 React 中的 JSX 语法被编译成 JavaScript 代码类似。预先编译好 HTML 模版相对于在浏览器中再去编译 HTML 模版的好处在于性能更好。

使用 TypeScript 编写 Vue 应用

从 Vue 2.5.0+ 版本开始,提供了对 TypeScript 的良好支持,使用 TypeScript 编写 Vue 是一个很好的选择,因为 TypeScript 能检查出一些潜在的错误。

新增 tsconfig.json 配置文件,内容如下:

{
  "compilerOptions": {
    // 构建出 ES5 版本的 JavaScript,与 Vue 的浏览器支持保持一致
    "target": "es5",
    // 开启严格模式,这可以对 `this` 上的数据属性进行更严格的推断
    "strict": true,
    // TypeScript 编译器输出的 JavaScript 采用 es2015 模块化,使 Tree Shaking 生效
    "module": "es2015",
    "moduleResolution": "node"
  }
}

修改 App.vue 脚本部分内容如下:

<!--组件逻辑-->
<script lang="ts">
  import Vue from "vue";
  // 通过 Vue.extend 启用 TypeScript 类型推断
  export default Vue.extend({
    data() {
      return {
        msg: 'Hello,Webpack',
      }
    },
  });
</script>

注意 script 标签中的 lang="ts" 是为了指明代码的语法是 TypeScript。

修改 main.ts 执行入口文件为如下:

import Vue from 'vue'
import App from './App.vue'
new Vue({
  el: '#app',
  render: h => h(App)
});

由于 TypeScript 不认识 .vue 结尾的文件,为了让其支持 import App from './App.vue' 导入语句,还需要以下文件 vue-shims.d.ts 去定义 .vue 的类型:

// 告诉 TypeScript 编译器 .vue 文件其实是一个 Vue  
declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}

Webpack 配置需要修改两个地方,如下:

const path = require('path');
module.exports = {
  resolve: {
    // 增加对 TypeScript 的 .ts 和 .vue 文件的支持
    extensions: ['.ts', '.js', '.vue', '.json'],
  },
  module: {
    rules: [
      // 加载 .ts 文件
      {
        test: /\.ts$/,
        loader: 'ts-loader',
        exclude: /node_modules/,
        options: {
          // 让 tsc 把 vue 文件当成一个 TypeScript 模块去处理,以解决 moudle not found 的问题,tsc 本身不会处理 .vue 结尾的文件
          appendTsSuffixTo: [/\.vue$/],
        }
      },
    ]
  },
};

除此之外还需要安装新引入的依赖:npm i -D ts-loader typescript

为单页应用生成HTML

引入问题

在使用 React 框架中,是用最简单的 Hello,Webpack 作为例子让大家理解, 这个例子里因为只输出了一个 bundle.js 文件,所以手写了一个 index.html 文件去引入这个 bundle.js,才能让应用在浏览器中运行起来。

在实际项目中远比这复杂,一个页面常常有很多资源要加载。接下来举一个实战中的例子,要求如下:

  1. 项目采用 ES6 语言加 React 框架。

  2. 给页面加入 Google Analytics,这部分代码需要内嵌进 HEAD 标签里去。

  3. 给页面加入 Disqus 用户评论,这部分代码需要异步加载以提升首屏加载速度。

  4. 压缩和分离 JavaScript 和 CSS 代码,提升加载速度。

在开始前先来看看该应用最终发布到线上的代码。

可以看到部分代码被内嵌进了 HTML 的 HEAD 标签中,部分文件的文件名称被打上根据文件内容算出的 Hash 值,并且加载这些文件的 URL 地址也被正常的注入到了 HTML 中。

解决方案

推荐一个用于方便地解决以上问题的 Webpack 插件 web-webpack-plugin。 该插件已经被社区上许多人使用和验证,解决了大家的痛点获得了很多好评,下面具体介绍如何用它来解决上面的问题。

首先,修改 Webpack 配置。

以上配置中,大多数都是按照前面已经讲过的内容增加的配置,例如:

  • 增加对 CSS 文件的支持,提取出 Chunk 中的 CSS 代码到单独的文件中,压缩 CSS 文件;

  • 定义 NODE_ENV 环境变量为 production,以去除源码中只有开发时才需要的部分;

  • 给输出的文件名称加上 Hash 值;

  • 压缩输出的 JavaScript 代码。

但最核心的部分在于 plugins 里的:

new WebPlugin({
  template: './template.html', // HTML 模版文件所在的文件路径
  filename: 'index.html' // 输出的 HTML 的文件名称
})

其中 template: './template.html' 所指的模版文件 template.html 的内容是:

<head>
  <meta charset="UTF-8">
  <!--注入 Chunk app 中的 CSS-->
  <link rel="stylesheet" href="app?_inline">
  <!--注入 google_analytics 中的 JavaScript 代码-->
  <script src="./google_analytics.js?_inline"></script>
  <!--异步加载 Disqus 评论-->
  <script src="https://pe-into-webpack.disqus.com/embed.js" async></script>
</head>
<body>
<p id="app"></p>
<!--导入 Chunk app 中的 JS-->
<script src="app"></script>
<!--Disqus 评论容器-->
<p id="disqus_thread"></p>
</body>

该文件描述了哪些资源需要被以何种方式加入到输出的 HTML 文件中。

<link rel="stylesheet" href="app?_inline"> 为例,按照正常引入 CSS 文件一样的语法来引入 Webpack 生产的代码。href 属性中的 app?_inline 可以分为两部分,前面的 app 表示 CSS 代码来自名叫 app 的 Chunk 中,后面的 _inline 表示这些代码需要被内嵌到这个标签所在的位置。

同样的 <script src="./google_analytics.js?_inline"></script> 表示 JavaScript 代码来自相对于当前模版文件 template.html 的本地文件 ./google_analytics.js, 而且文件中的 JavaScript 代码也需要被内嵌到这个标签所在的位置。

也就是说资源链接 URL 字符串里问号前面的部分表示资源内容来自哪里,后面的 querystring 表示这些资源注入的方式。

除了 _inline 表示内嵌外,还支持以下属性:

  • _dist 只有在生产环境下才引入该资源;

  • _dev 只有在开发环境下才引入该资源;

  • _ie 只有IE浏览器才需要引入的资源,通过 [if IE]>resource<![endif] 注释实现。

这些属性之间可以搭配使用,互不冲突。例如 app?_inline&_dist 表示只在生产环境下才引入该资源,并且需要内嵌到 HTML 里去。

WebPlugin 插件还支持一些其它更高级的用法,详情可以访问该项目主页阅读文档。

管理多个单页应用

引入问题

在开始前先来看看该应用最终发布到线上的代码。

See the Pen 管理多个单页应用 by whjin (@whjin) on CodePen.


AutoWebPlugin 会找出 pages 目录下的2个文件夹 indexlogin,把这两个文件夹看成两个单页应用。 并且分别为每个单页应用生成一个 Chunk 配置和 WebPlugin 配置。 每个单页应用的 Chunk 名称就等于文件夹的名称,也就是说 autoWebPlugin.entry() 方法返回的内容其实是:

{
  "index":["./pages/index/index.js","./common.css"],
  "login":["./pages/login/index.js","./common.css"]
}

但这些事情 AutoWebPlugin 都会自动为你完成,你不用操心,明白大致原理即可。

template.html 模版文件如下:

<html>
<head>
  <meta charset="UTF-8">
  <!--在这注入该页面所依赖但没有手动导入的 CSS-->
  <!--STYLE-->
  <!--注入 google_analytics 中的 JS 代码-->
  <script src="./google_analytics.js?_inline"></script>
  <!--异步加载 Disqus 评论-->
  <script src="https://pe-into-webpack.disqus.com/embed.js" async></script>
</head>
<body>
<p id="app"></p>
<!--在这注入该页面所依赖但没有手动导入的 JavaScript-->
<!--SCRIPT-->
<!--Disqus 评论容器-->
<p id="disqus_thread"></p>
</body>
</html>

由于这个模版文件被当作项目中所有单页应用的模版,就不能再像上一节中直接写 Chunk 的名称去引入资源,因为需要被注入到当前页面的 Chunk 名称是不定的,每个单页应用都会有自己的名称。 <!--STYLE--><!--SCRIPT--> 的作用在于保证该页面所依赖的资源都会被注入到生成的 HTML 模版里去。

web-webpack-plugin 能分析出每个页面依赖哪些资源,例如对于 login.html 来说,插件可以确定该页面依赖以下资源:

  • 所有页面都依赖的公共 CSS 代码 common.css

  • 所有页面都依赖的公共 JavaScrip 代码 common.js

  • 只有这个页面依赖的 CSS 代码 login.css

  • 只有这个页面依赖的 JavaScrip 代码 login.css

由于模版文件 template.html 里没有指出引入这些依赖资源的 HTML 语句,插件会自动将没有手动导入但页面依赖的资源按照不同类型注入到 <!--STYLE--><!--SCRIPT--> 所在的位置。

  • CSS 类型的文件注入到 <!--STYLE--> 所在的位置,如果 <!--STYLE--> 不存在就注入到 HTML HEAD 标签的最后;

  • JavaScrip 类型的文件注入到 <!--SCRIPT--> 所在的位置,如果 <!--SCRIPT--> 不存在就注入到 HTML BODY 标签的最后。

如果后续有新的页面需要开发,只需要在 pages 目录下新建一个目录,目录名称取为输出 HTML 文件的名称,目录下放这个页面相关的代码即可,无需改动构建代码。

Since AutoWebPlugin is indirectly implemented through the WebPlugin mentioned in the previous section, all functions supported by WebPlugin AutoWebPlugin are supported .

Building isomorphic applications

Isomorphic applications refer to applications that write one code but can run in the browser and server at the same time.

Understanding Isomorphic Applications

Now most views of single-page applications are rendered on the browser side through JavaScript code, but the disadvantages of rendering on the browser side are:

  • Search engines cannot include your webpage because the displayed data is rendered asynchronously on the browser side, and most crawlers cannot obtain this data.

  • For complex single-page applications, the rendering process requires a large amount of calculations, which may cause performance problems for low-end mobile devices. Users can clearly perceive the rendering delay of the first screen.

In order to solve the above problems, some people have asked whether the JavaScript rendering code that originally only ran in the browser can also run on the server side, and then render the HTML with content on the server side. return. This allows search engine crawlers to directly crawl HTML with data, while also reducing the first screen rendering time. Due to the popularity and maturity of Node.js, as well as the proposal and implementation of virtual DOM, this assumption is possible.

In fact, all current mainstream front-end frameworks support isomorphism, including React, Vue2, and Angular2. Among them, the first and most mature isomorphism solution is React. Since there are more React users and they are very similar, this section only introduces how to use Webpack to build React isomorphic applications.

The core of the operating principle of isomorphic applications lies in virtual DOM. Virtual DOM means not directly operating DOM but describing the original DOM structure through JavaScript Object. When the DOM needs to be updated, the DOM tree is not directly manipulated, but the JavaScript Object is updated and then mapped to DOM operations.

The advantages of virtual DOM are:

  • Because operating the DOM tree is a time-consuming operation, minimizing DOM tree operations can optimize web page performance. The DOM Diff algorithm can find the minimum difference between two different Objects and obtain the minimum DOM operation;

  • The virtual DOM can not only be expressed by operating the DOM tree during rendering As a result, there can also be other representation methods, such as rendering the virtual DOM into a string (server-side rendering), or rendering into a UI component native to the mobile app (React Native).

Taking React as an example, the core module react is responsible for managing the life cycle of React components, and the specific rendering work can be left to react-dom The module is responsible.

react-dom There are 2 ways to choose from when rendering the virtual DOM tree:

  • Through render() Function to manipulate the browser DOM tree to display the results.

  • Calculate a string representing the HTML form of the virtual DOM through renderToString().

The ultimate goal of building isomorphic applications is to build two JavaScript codes from one project source code, one for running on the browser side and one for running on Node. js environment to render HTML. The JavaScript code used to run in the Node.js environment needs to pay attention to the following points:

  • cannot contain the API provided by the browser environment, for example, use document DOM operations, because Node.js does not support these APIs;

  • cannot contain CSS code, because the purpose of server-side rendering is to render HTML content, and rendering CSS code will add additional calculations The amount affects the server-side rendering performance;

  • cannot combine the third-party modules and Node.js native modules in node_modules like the output code used in the browser environment. (such as the fs module) is packaged in, but these modules need to be introduced through the CommonJS specification.

  • You need to export a rendering function through the CommonJS specification to execute this rendering function in the HTTP server and render the HTML content and return it.

Solution

webpack.config.js used to build the browser environment code. Leave the configuration file unchanged and create a new one specifically for Construct the configuration file for server-side rendering code webpack_server.config.js, the content is as follows:

See the Pen webpack_server.config.js by whjin (@whjin) on CodePen.


主进程启动后会一直驻留在后台运行,你眼睛所看得的和操作的窗口并不是主进程,而是由主进程新启动的窗口子进程。

应用从启动到退出有一系列生命周期事件,通过 electron.app.on() 函数去监听生命周期事件,在特定的时刻做出反应。 例如在 app.on('ready') 事件中通过 BrowserWindow 去展示应用的主窗口。

启动的窗口其实是一个网页,启动时会去加载在 loadURL 中传入的网页地址。 每个窗口都是一个单独的网页进程,窗口之间的通信需要借助主进程传递消息。

总体来说开发 Electron 应用和开发 Web 应用很相似,区别在于 Electron 的运行环境同时内置了浏览器和 Node.js 的 API,在开发网页时除了可以使用浏览器提供的 API 外,还可以使用 Node.js 提供的 API。

接入 Webpack

接下来做一个简单的 Electron 应用,要求为应用启动后显示一个主窗口,在主窗口里有一个按钮,点击这个按钮后新显示一个窗口,且使用 React 开发网页。

由于 Electron 应用中的每一个窗口对应一个网页,所以需要开发2个网页,分别是主窗口的 index.html 和新打开的窗口 login.html

需要改动的地方如下:

  • 在项目根目录下新建主进程的入口文件 main.js,内容和上面提到的一致;

  • 主窗口网页的代码如下:

See the Pen main.js by whjin (@whjin) on CodePen.


重新执行构建后,你将会在项目目录下看到一个新目录 lib,里面放着要发布到 Npm 仓库的最终代码。

发布到 Npm

在把构建出的代码发布到 Npm 仓库前,还需要确保你的模块描述文件 package.json 是正确配置的。

由于构建出的代码的入口文件是 ./lib/index.js,需要修改 package.json 中的 main 字段如下:

{
  "main": "lib/index.js",
  "jsnext:main": "src/index.js"
}

其中 jsnext:main 字段用于指出采用 ES6 编写的模块入口文件所在的位置。

修改完毕后在项目目录下执行 npm publish 就能把构建出的代码发布到 Npm 仓库中(确保已经 npm login 过)。

如果你想让发布到 Npm 上去的代码保持和源码的目录结构一致,那么用 Webpack 将不在适合。 因为源码是一个个分割的模块化文件,而 Webpack 会把这些模块组合在一起。 虽然 Webpack 输出的文件也可以是采用 CommonJS 模块化语法的,但在有些场景下把所有模块打包成一个文件发布到 Npm 是不适合的。 例如像 Lodash 这样的工具函数库在项目中可能只用到了其中几个工具函数,如果所有工具函数打包在一个文件中,那么所有工具函数都会被打包进去,而保持模块文件的独立能做到只打包进使用到的。 还有就是像 UI 组件库这样由大量独立组件组成的库也和 Lodash 类似。
所以 Webpack 适合于构建完整不可分割的 Npm 模块。

构建离线应用

离线应用的核心是离线缓存技术,历史上曾先后出现2种离线离线缓存技术,它们分别是:

  1. AppCache 又叫 Application Cache,目前已经从 Web 标准中删除,请尽量不要使用它。

  2. Service Workers 是目前最新的离线缓存技术,是 Web Worker 的一部分。 它通过拦截网络请求实现离线缓存,比 AppCache 更加灵活。它也是构建 PWA 应用的关键技术之一。

认识 Service Workers

Service Workers 是一个在浏览器后台运行的脚本,它生命周期完全独立于网页。它无法直接访问 DOM,但可以通过 postMessage 接口发送消息来和 UI 进程通信。 拦截网络请求是 Service Workers 的一个重要功能,通过它能完成离线缓存、编辑响应、过滤响应等功能。

Service Workers 兼容性

目前 Chrome、Firefox、Opera 都已经全面支持 Service Workers,但对于移动端浏览器就不太乐观了,只有高版本的 Android 支持。 由于 Service Workers 无法通过注入 polyfill 去实现兼容,所以在你打算使用它前请先调查清楚你的网页的运行场景。

判断浏览器是否支持 Service Workers 的最简单的方法是通过以下代码:

// 如果 navigator 对象上存在 serviceWorker 对象,就表示支持
if (navigator.serviceWorker) {
  // 通过 navigator.serviceWorker 使用
}

注册 Service Workers

要给网页接入 Service Workers,需要在网页加载后注册一个描述 Service Workers 逻辑的脚本。 代码如下:

if (navigator.serviceWorker) {
  window.addEventListener('DOMContentLoaded',function() {
    // 调用 serviceWorker.register 注册,参数 /sw.js 为脚本文件所在的 URL 路径
      navigator.serviceWorker.register('/sw.js');
  });
}

一旦这个脚本文件被加载,Service Workers 的安装就开始了。这个脚本被安装到浏览器中后,就算用户关闭了当前网页,它仍会存在。 也就是说第一次打开该网页时 Service Workers 的逻辑不会生效,因为脚本还没有被加载和注册,但是以后再次打开该网页时脚本里的逻辑将会生效。

在 Chrome 中可以通过打开网址 chrome://inspect/#service-workers 来查看当前浏览器中所有注册了的 Service Workers。

使用 Service Workers 实现离线缓存

Service Workers 在注册成功后会在其生命周期中派发出一些事件,通过监听对应的事件在特点的时间节点上做一些事情。

在 Service Workers 脚本中,引入了新的关键字 self 代表当前的 Service Workers 实例。

在 Service Workers 安装成功后会派发出 install 事件,需要在这个事件中执行缓存资源的逻辑,实现代码如下:

// 当前缓存版本的唯一标识符,用当前时间代替
var cacheKey = new Date().toISOString();
// 需要被缓存的文件的 URL 列表
var cacheFileList = [
  '/index.html',
  '/app.js',
  '/app.css'
];
// 监听 install 事件
self.addEventListener('install', function (event) {
  // 等待所有资源缓存完成时,才可以进行下一步
  event.waitUntil(
    caches.open(cacheKey).then(function (cache) {
      // 要缓存的文件 URL 列表
      return cache.addAll(cacheFileList);
    })
  );
});

接下来需要监听网络请求事件去拦截请求,复用缓存,代码如下:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    // 去缓存中查询对应的请求
    caches.match(event.request).then(function(response) {
        // 如果命中本地缓存,就直接返回本地的资源
        if (response) {
          return response;
        }
        // 否则就去用 fetch 下载资源
        return fetch(event.request);
      }
    )
  );
});

以上就实现了离线缓存。

更新缓存

线上的代码有时需要更新和重新发布,如果这个文件被离线缓存了,那就需要 Service Workers 脚本中有对应的逻辑去更新缓存。 这可以通过更新 Service Workers 脚本文件做到。

浏览器针对 Service Workers 有如下机制:

  1. 每次打开接入了 Service Workers 的网页时,浏览器都会去重新下载 Service Workers 脚本文件(所以要注意该脚本文件不能太大),如果发现和当前已经注册过的文件存在字节差异,就将其视为“新服务工作线程”。

  2. 新 Service Workers 线程将会启动,且将会触发其 install 事件。

  3. 当网站上当前打开的页面关闭时,旧 Service Workers 线程将会被终止,新 Service Workers 线程将会取得控制权。

  4. 新 Service Workers 线程取得控制权后,将会触发其 activate 事件。

新 Service Workers 线程中的 activate 事件就是最佳的清理旧缓存的时间点,代码如下:

// 当前缓存白名单,在新脚本的 install 事件里将使用白名单里的 key 
var cacheWhitelist = [cacheKey];
self.addEventListener('activate', function(event) {
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          // 不在白名单的缓存全部清理掉
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            // 删除缓存
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

最终完整的代码 Service Workers 脚本代码如下:

See the Pen Service Workers by whjin (@whjin) on CodePen.


以上配置有2点需要注意:

  • 由于 Service Workers 必须在 HTTPS 环境下才能拦截网络请求实现离线缓存,使用在 DevServer https 中提到的方式去实现 HTTPS 服务。

  • serviceworker-webpack-plugin 插件为了保证灵活性,允许使用者自定义 sw.js,构建输出的 sw.js 文件中会在头部注入一个变量 serviceWorkerOption.assets 到全局,里面存放着所有需要被缓存的文件的 URL 列表。

需要修改上面的 sw.js 文件中写成了静态值的 cacheFileList 为如下:

// 需要被缓存的文件的 URL 列表
var cacheFileList = global.serviceWorkerOption.assets;

以上已经完成所有文件的修改,在重新构建前,先安装新引入的依赖:

npm i -D serviceworker-webpack-plugin webpack-dev-server

安装成功后,在项目根目录下执行 webpack-dev-server 命令后,DevServer 将以 HTTPS 模式启动。

搭配Npm Script

Npm Script 是一个任务执行者。 Npm 是在安装 Node.js 时附带的包管理器,Npm Script 则是 Npm 内置的一个功能,允许在 package.json 文件里面使用 scripts 字段定义任务:

{
  "scripts": {
    "dev": "node dev.js",
    "pub": "node build.js"
  }
}

里面的 scripts 字段是一个对象,每一个属性对应一段脚本,以上定义了两个任务 devpub。 Npm Script 底层实现原理是通过调用 Shell 去运行脚本命令,例如执行 npm run pub 命令等同于执行命令 node build.js

Npm Script 还有一个重要的功能是能运行安装到项目目录里的 node_modules 里的可执行模块,例如在通过命令:

npm i -D webpack

将 Webpack 安装到项目中后,是无法直接在项目根目录下通过命令 webpack 去执行 Webpack 构建的,而是要通过命令 ./node_modules/.bin/webpack 去执行。

Npm Script 能方便的解决这个问题,只需要在 scripts 字段里定义一个任务,例如:

{
  "scripts": {
    "build": "webpack"
  }
}

Npm Script 会先去项目目录下的 node_modules 中寻找有没有可执行的 webpack 文件,如果有就使用本地的,如果没有就使用全局的。 所以现在执行 Webpack 构建只需要通过执行 npm run build 去实现。

Webpack 为什么需要 Npm Script

Webpack 只是一个打包模块化代码的工具,并没有提供任何任务管理相关的功能。 但在实际场景中通常不会是只通过执行 webpack 就能完成所有任务的,而是需要多个任务才能完成。

  1. 在开发阶段为了提高开发体验,使用 DevServer 做开发,并且需要输出 Source Map 以方便调试,同时还需要开启自动刷新功能。

  2. 为了减小发布到线上的代码尺寸,在构建出发布到线上的代码时,需要压缩输出的代码。

  3. 在构建完发布到线上的代码后,需要把构建出的代码提交给发布系统。

可以看出要求1和要求2是相互冲突的,其中任务3又依赖任务2。要满足以上三个要求,需要定义三个不同的任务。

接下来通过 Npm Script 来定义上面的3个任务:

"scripts": {
  "dev": "webpack-dev-server --open",
  "dist": "NODE_ENV=production webpack --config webpack_dist.config.js",
  "pub": "npm run dist && rsync dist"
},

含义分别是:

  • dev 代表用于开发时执行的任务,通过 DevServer 去启动构建。所以在开发项目时只需执行 npm run dev

  • dist 代表构建出用于发布到线上去的代码,输出到 dist 目录中。其中的 NODE_ENV=production 是为了在运行任务时注入环境变量。

  • pub 代表先构建出用于发布到线上去的代码,再同步 dist 目录中的文件到发布系统(如何同步文件需根据你所使用的发布系统而定)。所以在开发完后需要发布时只需执行 npm run pub

使用 Npm Script 的好处是把一连串复杂的流程简化成了一个简单的命令,需要时只需要执行对应的那个简短的命令,而不用去手动的重复整个流程。 这会大大的提高我们的效率和降低出错率。

检查代码

检查代码和 Code Review 很相似,都是去审视提交的代码可能存在的问题。 但 Code Review 一般通过人去执行,而检查代码是通过机器去执行一些自动化的检查。 自动化的检查代码成本更低,实施代价更小。

检查代码主要检查以下几项:

  • 代码风格:让项目成员强制遵守统一的代码风格,例如如何缩紧、如何写注释等,保障代码可读性,不把时间浪费在争论如何写代码更好看上;

  • 潜在问题:分析出代码在运行过程中可能出现的潜在 Bug。

目前已经有成熟的工具可以检验诸如 JavaScript、TypeScript、CSS、SCSS 等常用语言。

检查 JavaScript

目前最常用的 JavaScript 检查工具是 ESlint ,它不仅内置了大量常用的检查规则,还可以通过插件机制做到灵活扩展。

ESlint 的使用很简单,在通过:npm i -g eslint

按照到全局后,再在项目目录下执行:eslint init

来新建一个 ESlint 配置文件 .eslintrc,该文件格式为 JSON。

如果你想覆盖默认的检查规则,或者想加入新的检查规则,你需要修改该文件,例如使用以下配置:

{
    // 从 eslint:recommended 中继承所有检查规则
    "extends": "eslint:recommended",
    // 再自定义一些规则     
    "rules": {
        // 需要在每行结尾加 ;        
        "semi": ["error", "always"],
        // 需要使用 "" 包裹字符串         
        "quotes": ["error", "double"]
    }
}

写好配置文件后,再执行:

eslint yourfile.js

去检查 yourfile.js 文件,如果你的文件没有通过检查,ESlint 会输出错误原因,例如:

检查 TypeScript

TSLint 是一个和 ESlint 相似的 TypeScript 代码检查工具,区别在于 TSLint 只专注于检查 TypeScript 代码。

TSLint 和 ESlint 的使用方法很相似,首先通过:npm i -g tslint

安装到全局,再去项目根目录下执行:tslint --init

生成配置文件 tslint.json,在配置好后,再执行:tslint yourfile.ts去检查 yourfile.ts 文件。

检查 CSS

stylelint 是目前最成熟的 CSS 检查工具,内置了大量检查规则的同时也提供插件机制让用户自定义扩展。 stylelint 基于 PostCSS,能检查任何 PostCSS 能解析的代码,诸如 SCSS、Less 等。

首先通过npm i -g stylelint

安装到全局后,去项目根目录下新建 .stylelintrc 配置文件, 该配置文件格式为 JSON,其格式和 ESLint 的配置相似,例如:

{
  // 继承 stylelint-config-standard 中的所有检查规则
  "extends": "stylelint-config-standard",
  // 再自定义检查规则  
  "rules": {
    "at-rule-empty-line-before": null
  }
}

配置好后,再执行stylelint "yourfile.css"去检查 yourfile.css 文件。

结合 Webpack 检查代码

结合 ESLint

eslint-loader 可以方便的把 ESLint 整合到 Webpack 中,使用方法如下:

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        // node_modules 目录的下的代码不用检查
        exclude: /node_modules/,
        loader: 'eslint-loader',
        // 把 eslint-loader 的执行顺序放到最前面,防止其它 Loader 把处理后的代码交给 eslint-loader 去检查
        enforce: 'pre',
      },
    ],
  },
}

接入 eslint-loader 后就能在控制台中看到 ESLint 输出的错误日志了。

结合 TSLint

tslint-loader 是一个和 eslint-loader 相似的 Webpack Loader, 能方便的把 TSLint 整合到 Webpack,其使用方法如下:

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        // node_modules 目录的下的代码不用检查
        exclude: /node_modules/,
        loader: 'tslint-loader',
        // 把 tslint-loader 的执行顺序放到最前面,防止其它 Loader 把处理后的代码交给 tslint-loader 去检查
        enforce: 'pre',
      },
    ],
  },
}

结合 stylelint

StyleLintPlugin 能把 stylelint 整合到 Webpack,其使用方法很简单,如下:

const StyleLintPlugin = require('stylelint-webpack-plugin');
module.exports = {
  // ...
  plugins: [
    new StyleLintPlugin(),
  ],
}

一些建议

把代码检查功能整合到 Webpack 中会导致以下问题:

  • 由于执行检查步骤计算量大,整合到 Webpack 中会导致构建变慢;

  • 在整合代码检查到 Webpack 后,输出的错误信息是通过行号来定位错误的,没有编辑器集成显示错误直观;

为了避免以上问题,还可以这样做:

  • 使用集成了代码检查功能的编辑器,让编辑器实时直观地显示错误;

  • 把代码检查步骤放到代码提交时,也就是说在代码提交前去调用以上检查工具去检查代码,只有在检查都通过时才提交代码,这样就能保证提交到仓库的代码都是通过了检查的。

如果你的项目是使用 Git 管理,Git 提供了 Hook 功能能做到在提交代码前触发执行脚本。

husky 可以方便快速地为项目接入 Git Hook, 执行npm i -D husky

安装 husky 时,husky 会通过 Npm Script Hook 自动配置好 Git Hook,你需要做的只是在 package.json 文件中定义几个脚本,方法如下:

{
  "scripts": {
    // 在执行 git commit 前会执行的脚本  
    "precommit": "npm run lint",
    // 在执行 git push 前会执行的脚本  
    "prepush": "lint",
    // 调用 eslint、stylelint 等工具检查代码
    "lint": "eslint && stylelint"
  }
}

precommitprepush 你需要根据自己的情况选择一个,无需两个都设置。

通过 Node.js API 启动 Webpack

Webpack 除了提供可执行的命令行工具外,还提供可在 Node.js 环境中调用的库。 通过 Webpack 暴露的 API,可直接在 Node.js 程序中调用 Webpack 执行构建。

通过 API 去调用并执行 Webpack 比直接通过可执行文件启动更加灵活,可用在一些特殊场景,下面将教你如何使用 Webpack 提供的 API。

Webpack 其实是一个 Node.js 应用程序,它全部通过 JavaScript 开发完成。 在命令行中执行 webpack 命令其实等价于执行 node ./node_modules/webpack/bin/webpack.js

安装和使用 Webpack 模块

在调用 Webpack API 前,需要先安装它:

npm i -D webpack

安装成功后,可以采用以下代码导入 Webpack 模块:

const webpack = require('webpack');
// ES6 语法
import webpack from "webpack";

导出的 webpack 其实是一个函数,使用方法如下:

webpack({
  // Webpack 配置,和 webpack.config.js 文件一致
}, (err, stats) => {
  if (err || stats.hasErrors()) {
    // 构建过程出错
  }
  // 成功执行完构建
});

如果你的 Webpack 配置写在 webpack.config.js 文件中,可以这样使用:

// 读取 webpack.config.js 文件中的配置
const config = require('./webpack.config.js');
webpack(config , callback);

以监听模式运行

以上使用 Webpack API 的方法只能执行一次构建,无法以监听模式启动 Webpack,为了在使用 API 时以监听模式启动,需要获取 Compiler 实例,方法如下:

See the Pen Compiler  by whjin (@whjin) on CodePen.


从以上代码可以看出,从 webpack-dev-middleware 中导出的 webpackMiddleware 是一个函数,该函数需要接收一个 Compiler 实例。Webpack API 导出的 webpack 函数会返回一个Compiler 实例。

webpackMiddleware 函数的返回结果是一个 Expressjs 的中间件,该中间件有以下功能:

  • 接收来自 Webpack Compiler 实例输出的文件,但不会把文件输出到硬盘,而是保存在内存中;

  • 往 Expressjs app 上注册路由,拦截 HTTP 收到的请求,根据请求路径响应对应的文件内容;

通过 webpack-dev-middleware 能够将 DevServer 集成到你现有的 HTTP 服务器中,让你现有的 HTTP 服务器能返回 Webpack 构建出的内容,而不是在开发时启动多个 HTTP 服务器。 这特别适用于后端接口服务采用 Node.js 编写的项目。

Webpack Dev Middleware 支持的配置项

在 Node.js 中调用 webpack-dev-middleware 提供的 API 时,还可以给它传入一些配置项,方法如下:

See the Pen Webpack Dev Middleware by whjin (@whjin) on CodePen.


第2步:修改 HTTP 服务器代码 server.js 文件,接入 webpack-hot-middleware 中间件,修改如下:

See the Pen server.js by whjin (@whjin) on CodePen.