随着 Web 技术的发展,Web 应用变得越来越庞大复杂。直接编写 HTML、CSS、JavaScript 开发 Web 应用的方式已经无法应对当前 Web 应用的发展。为了更好地开发和维护 Web 应用,JavaScript 社区制定了一系列的模块化规范帮助我们在前端世界里进行模块化开发,模块化是指将一个复杂的系统分解为多个模块以方便开发和维护,其中比较著名的规范有 CommonJS 、AMD、ES6 Module。然而不管是哪一种规范,都不能直接在浏览器中运行。CommonJS 需要转换成 ES5,AMD 需要有相应的模块 API 的实现(RequireJS是一个不错的选择),而 ES6 Module 虽然是 ECMA 制定的标准,但并非所有的浏览器都实现了,所以在使用时还是需要转换。除此之外,各种新语言、新框架层出不穷,这些新技术带来了各种各样的语法,为了能让它们运行在浏览器环境中,也需要使用特定的规则将它们转换成浏览器能直接执行的代码。

完成这个转换的过程就叫做构建。构建任务将这一系列的转换过程用代码去实现,让转化过程自动化地执行,让我们能更愉快地使用新技术。构建通常包括以下内容:

  • 代码转换:将 Vue 单文件模块编译成 JavaScript,将 SCSS 编译成 CSS 等。
  • 文件优化:压缩图片和代码。
  • 代码分割:抽取公共代码异步加载,节省流量,提高加载速度。
  • 模块合并:整合相互依赖的各个模块。

Webpack 是一个功能强大的构建工具,它能处理几乎所有的文件,对于 Webpack 来说,所有类型的文件都是模块,你除了可以引入 JavaScript 模块外,还能把 CSS、图片等类型的文件当作模块引入使用。这样的好处是能清晰地描述各个模块间的依赖关系,经过 Webpack 的处理,最终会输出能在浏览器中运行使用的静态资源。

通常我们通过文件 webpack.config.js 来配置 Webpack,该文件告诉 Webpack 项目入口在哪里,该如何处理不同类型的模块,最终生成的结果存放在哪里。一个简单的配置文件示例如下:

const path = require( 'path' );

module.exports = {
  entry : './main.js',

  output : {
    filename : 'app.js',
    path     : path.resolve( __dirname, './dist' )
  }
};

如你所见,这个配置文件只是简单地返回一个对象,其中 entry 指定了项目的入口,Webpack 会从这个文件开始,识别出代码中的模块导入语句,递归地找出入口文件的所有依赖模块,并将这些模块打包到一个单独的文件中。这个新的文件的配置信息通过 output 来定义,在上面的例子中,该文件的文件名为 app.js,存放在 dist 目录下。

前面我们提到过,Webpack 把一切文件都看作模块,我们可以想引入 JavaScript 模块一样引入 CSS,只需要 require( '[css-file-name].css' ) 就可以了。但是这样引入后直接执行 Webpack 构建任务是会出错的,Webpack 并不能直接解析 CSS 文件,我们需要 Loader 来帮助处理 CSS。

module.exports = {
  module : {
    rules : [ {
      test : /\.css$/,
      use  : [ 'style-laoder', 'css-loader' ]
    } ]
  }
};

Loader 就是 Webpack 所使用的模块转换器,它把特殊的模块转换成 JavaScript 文件。上面的例子告诉 Webapck,使用 style-loader 和 css-loader 来处理所有以 .css 结尾的文件。其实 loader 并没有大家想象的那么复杂,css-loader 将 css 文件中的样式以字符串的形式存起来,style-loader 在运行时通过向 html 注入 style 标签的形式将字符串注入。就是这么简单,你应该发现了,这两个过程是存在先后顺序的,通过 use 定义的 loader 的执行顺序是从后往前执行,不要忘记了哦。

Webpack 的生态非常丰富,提供了各种各样的 Loader,总有一款能满足你的需求。除此之外,Webpack 还提供了钩子,让我们在构建流程中的特定时间自定义一些功能。听起来很麻烦?不要担心,你可以直接使用现成的 Plugin。举个例子,对于 Webpack 生成的结果,我们需要手动引入到 html 文件中才能使用,这是不是很麻烦呢?还好我们有 HtmlWebpackPlugin,它能根据我们的配置自动实现这个步骤。

module.exports = {
  plugins : [ new HtmlWebpackPlugin( {
    filename : 'app.html',
    hash     : true,
    template : 'app.html'
  } ) ]
};

每个 Loader 和 Plugin 的具体用法可以参考对应的文档,这里就不在赘述。

Webpack 的使用就是这么简单,只需要按照自己的需求配置相应的选项,就能愉快地玩耍啦~

最后总结一下,Webpack 启动后会从配置文件的 entry 中读取入口,然后从入口开始递归地解析依赖,每找到一个依赖,就根据配置的 Loader 去找出对应的转换规则并进行转换,转换完成后,再解析出当前模块的依赖,重复上述过程。这些依赖会根据 entry 分组,一个 entry 依赖的模块分为一组,称为一个 chunk,chunk 最后被转换成文件输出。在整个流程中,Plugin 定义的逻辑会在适当的时候执行,这主要取决的 Plugin 使用了 Webpack 的哪些钩子。