Webpack4核心剖析

栏目: 编程工具 · 发布时间: 6年前

内容简介:Webpack提出了

Webpack 是现代Web应用程序的静态模块打包工具,它会递归的构建应用程序各个模块的依赖关系图,然后将所有模块打包成一个或多个 bundle 。截止本文成文之时,Webpack已经更新至 4.0.1 版本,增加了诸多打包和执行性能相关的支持,是目前应用最广泛、社区最活跃的Web前端代码打包方案。

Webpack4核心剖析

Webpack提出了 入口 entry输出 output加载器 loader插件 plugin 这四个核心概念,本文将会在简单介绍Webpack相关基础概念之后,对其原生实现的 import 模块导入机制进行分析,以清晰的展现Wepback在底层所进行的工作;最后逐步备注笔者在开发、生产环境下使用到的各类插件和加载器,并分享在 avesrhino 两个开源脚手架项目当中( 分别基于Vue2和React16 )所使用到的最佳配置实践。

入口entry

entry 属性用来指定webpack应该使用哪个模块作为构建工作的起点,进入起点后webpack将会寻找入口起点直接或间接的依赖,并最后输出到称为 bundle 的文件。Webpack中的 entry 属性值可以是一个或多个字符串、一个对象或数组。

下面的示例代码当中只定义了一个路径字符串:

module.exports = {
  entry: './app.js'
};

而下面的示例代码则通过传入对象指明了多个入口点,实质上Webpack会使用 CommonsChunkPlugin 从应用bundle中提取 vendor 引用到 vendor bundle ,并将引用 vendor 的部分替换为 __webpack_require__() 调用。

const config = {
  entry: {
    app: './app.js',
    vendors: './jquery.js'
  }
};

Webpack内置的 CommonsChunkPlugin 可以为每个页面的共享代码创建bundle,使得即使在多页面应用下,也能够复用入口起点之间的大量代码和模块。

输出output

Webpack的output属性指定其所创建的bundles输出到何处以及如何命名,

const path = require('path');

module.exports = {
  entry: './app.js',
  output: {
    filename: 'app.bundle.js'  // filename属性用于指定bundle的名称
    path: path.resolve(__dirname, 'build'),  // path属性指定输出bundle的位置
  }
};

Webpack中的 chunk ([tʃʌŋk] n.大块,厚块,数据块)是指 一个独立的文件 ,如果创建了多个 chunk ,则应该使用 占位符 去确保每个文件具有唯一的名称。

const path = require('path');

// 将带有hash字符串后缀的app.js和search.js保存至build目录下
module.exports = {
  {
    entry: {
      app: './app.js',
      mobile: './mobile.js'
    },
    output: {
      filename: '[name][chunkhash].js',
      path: path.resolve(__dirname, 'build'),
    }
  }
};

output 属性的 filename 拥有如下5个占位符:

占位符 描述
[id] 模块标识符。
[name] 模块名称。
[hash] 模块标识符的hash值。
[chunkhash] chunk 内容的hash值。
[query] 模块的 query ,例如文件名 ? 后的字符串。

加载器loader

Webpack的loader加载器机制用于处理非JavaScript文件,并将其转换为Webpack能够处理的有效模块。即将JavaScript之外的其它类型文件,转换为 bundle 可以直接引用的模块;例如:将文件从TypeScript转换为JavaScript,或者把内联图片转换为data URL,甚至允许在JavaScript模块内引入CSS文件。

Webpack配置文件中的 module 属性主要用于加载各种模块,可以通过其内嵌的 rules 属性指定加载某种类型文件时所需要使用到的 loader 加载器。

const path = require('path');

module.exports = {
  entry: {
    app: './app.js',
    mobile: './mobile.js'
  },
  output: {
    filename: '[name][chunkhash].js',
    path: path.resolve(__dirname, 'build'),
  }
  module: {
    // 首先需要npm install ts-loader css-loader
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader'
      }, {
        test: /\.css$/,
        use: 'css-loader'
      },
    ]
  }
};

rules 下的 use 属性指定了需要使用的目标加载器,而 test 属性则用于标识需要加载器进行处理的文件。根据需要,开发人员还可以对同一类文件应用多个 loader 进行处理。

{
  test: /\.css$/,
  use: [
    {
      loader: 'style-loader',
    }, {
      loader: 'css-loader',
      options: {
        modules: true
      }
    }
  ]
}

loader 还可以在 import 语句中内联进行使用,用于针对特定 import 语句使用指定的加载器。

import Styles from 'style-loader!css-loader?modules!./styles.css';

加载器loader的解析是通过 resolver 库进行的, resolver 用于帮助Webpack找到 bundle 中需要引入的模块代码,这些代码包含在每条 require / import 语句当中。Webpack打包模块时会使用到自家的开源项目 enhanced-resolve 来解析引入的文件路径。

插件plugin

如同上面所描述的那样,loader用于转换某些类型的模块,而插件plugin得益于其丰富的接口,可以用来处理加载器loader无法实现的更加丰富的任务,例如:打包优化与压缩、重新定义环境变量等等。

Webpack中通过 plugins 属性来使用插件。

module.exports = {
  entry: {
    app: './app.js',
    mobile: './mobile.js'
  },
  output: {
    filename: '[name][chunkhash].js',
    path: path.resolve(__dirname, 'build'),
  }
  module: {
    // 首先需要npm install ts-loader css-loader
    rules: [
      { test: /\.ts$/, use: 'ts-loader' },
      { test: /\.css$/, use: 'css-loader' },
    ]
  },
  // 使用new操作符来创建一个插件实例
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    new HtmlWebpackPlugin({
      template: './index.html'
    })
  ]
};

Webpack的插件plugin本质是一个拥有 apply 属性的JavaScript对象,这个 apply 属性会被Webpack的 compiler 对象调用,而该对象可以在整个Webpack编译生命周期内进行访问。

// LogOnBuildPlugin.js
function LogOnBuildPlugin() {
   // ... ...
};

// 注意下面的apply原型属性和compiler对象
LogOnBuildPlugin.prototype.apply = function(compiler) {
  compiler.plugin('run', function(compiler, callback) {
    console.info("Webpack构建过程开始!");
    callback();
  });
};

Webpack模块

相比NodeJS的模块机制,Webpack模块所涵盖的范围显然更加丰富:

  • ES2015的 import 语句
  • CommonJS的 require() 语句
  • AMD的 define / require 语句
  • css/sass/less中的 @import 语句。
  • CSS文件中的 url(...) 或HTML文件中的 <img src=...> 所指向的图片资源链接。

Webpack通过 enhanced-resolve 可以解析三种文件路径:

  • 绝对路径:文件在操作系统上的绝对路径。

    import "/home/common.js";
    import "C:\\Users\\common.js";
    
  • 相对路径:相对于当前引入操作的文件的位置。

    import "./source/demo.js";
    import "../demo.js"
    
  • 模块路径:在 resolve.modules 中指定的目录内搜索,默认是 ["node_modules"]

    import Vue from "vue";
    import React from "react";
    

打包策略

Webpack构建的应用程序中,主要存在以下三种类型的代码:

  1. 开发人员编写的业务代码。
  2. 引入的第三方 vendor 库。
  3. Webpack运行时与所有模块进行交互的 manifest[ˈmænɪfest] n.清单 )。

manifest 是浏览器运行时,Webpack用来连接模块所需的加载和解析逻辑( 无论 import 还是 require 模块语法,最后都会被Webpack转换为指向 模块标识符 __webpack_require__ 方法 )。

为了有效规避缓存问题,并最大化浏览器渲染性能,可以考虑将上述三种类型代码单独打包到三类文件。

在Webpack 3.0可以通过 CommonsChunkPlugin 插件完成这件工作。该插件可以将公共的依赖模块提取为一个新的的 chunk 文件,通过将公共模块分拆并且合成之后,便于应用进行缓存和后续使用,避免浏览器重复加载并运行同一段功能代码。

new webpack.optimize.CommonsChunkPlugin({
  name: string, names: string[], // 通用chunk的名称。
  filename: string,       // 公共chunk的文件名模板,可以包含与output.filename相同的占位符。
  chunks: string[],       // 通过chunk名称选择chunks的来源,其中chunk必须是公共chunk的子模块。
  children: boolean,      // 如果设置为true,所有公共chunk的子级模块会被选择。
  deepChildren: boolean,  // 如果设置为true,所有公共chunk的全部子孙模块都会被选择。
  async: boolean|string,  // 设置为true会创建一个异步的公共chunk,它会作为name的子模块以及chunks的兄弟模块,并与chunks并行被加载。
  minSize: number,        // 公共chunk创建之前,所有公共模块的最小文件尺寸。
  minChunks: number|Infinity|function(module, count) -> boolean,  // 传入公共chunk之前,所需要包含chunks的最少数量。
})
entry: {
  app: './app.js',
  vendor: ['vue', 'vuex', 'vue-router', 'element-ui', 'axios']
},
plugins: [
  new webpack.optimize.CommonsChunkPlugin({
    names: ['vendor', 'manifest']
  }),
],

Webpack4当中已经废弃了 CommonsChunkPlugin 的使用,转而采用更加简单明了的 optimization.splitChunksoptimization.runtimeChunk 属性完成类似工作,下面的章节马上会进行介绍。

optimization

Webpack4开始提供一个依赖于 mode 属性进行配置和优化的选项,代替过去 CommonsChunkPluginUglifyjsWebpackPlugin 等第三方插件的使用。

minimize

让Webpack使用 UglifyjsWebpackPlugin 进行最小化打包。Webpack配置对象的 mode 属性为 production 时该属性默认为 true

module.exports = {
  optimization: {
    minimize: false
  }
}

minimizer

通过提供一个或多个不同的 UglifyjsWebpackPlugin 实例来指定一个新的压缩器。

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = {=
  optimization: {
    minimizer: [
      new UglifyJsPlugin({
        /* 自定义配置 */
      })
    ]
  }
}

splitChunks

Webpack4.0为动态导入模块提供了一个新的通用代码文件打包策略,具体配置项可以参考本文中的章节。

runtimeChunk

设置该属性为 true 时,可以添加额外的代码块至每个只在运行时包含的Webpack入口点。该属性可以通过提供一个字符串值来使用插件的预设模式:

single : 建立一个所有代码块共享的运行时文件。

multiple : 为多个通用代码块建立多个运行时文件。

设置该属性为对象时,它只可能去提供 name 属性,为运行时代码块提供可替代的名称或命名工厂。

该属性默认值为 false ,表示每个入口代码块都嵌入到运行时。

module.exports = {
  optimization: {
    runtimeChunk: {
      name: entrypoint => `runtimechunk~${entrypoint.name}`
    }
  }
}

noEmitOnErrors

当编译阶段出现错误时,使用 optimization.noEmitOnErrors 跳过 emitting 阶段,从而确保不会有错误的资源被 emittedstats 中的 emitted 标志对于所有资源都是 false 的。

module.exports = {
  optimization: {
    noEmitOnErrors: true
  }
};

nodeEnv

告诉Webpack设置 process.env.NODE_ENV 为指定的字符串, optimization.nodeEnv 底层使用了 DefinePlugin 插件,除非设置为 false 。如果Webpack对象设置了 mode 属性,那么 optimization.nodeEnv 默认为 mode 属性的值,否则将会回退为 "production"

  • 任意字符串:需要设置到 process.env.NODE ENV 的值。
  • false :不设置或修改 process.env.NODE_ENV 的值。

SplitChunksPlugin

DefinePlugin

webpack.DefinePlugin

ModuleConcatenationPlugin

附上 英文原文链接

过去webpack打包的时候,每个module都会被包装到独立的函数闭包,这些包装函数会让JavaScript在浏览器中执行更缓慢。经过比较, Closure CompilerRollupJS 提升、连接全部模块作用域到一个闭包的方式,会让代码在浏览器中执行得更加迅速。因此,Webpack3当中提供了如下plugin来开启类似特性。

new webpack.optimize.ModuleConcatenationPlugin()

Webpack3作用域提升的实现依赖于ECMAScript模块语法,因此Webpack会基于开发人员当前使用的模块系统回滚到过去的打包方式。

不使用ModuleConcatenationPlugin打包的文件

➜  bundles git:(master) ll
总用量 8.7M
-rw-rw-r-- 1 hank hank  24K 9月   6 11:44 0.210c1525bfe14cba4ee0.js
-rw-rw-r-- 1 hank hank  61K 9月   6 11:44 0.210c1525bfe14cba4ee0.js.map
-rw-rw-r-- 1 hank hank 5.9K 9月   6 11:44 1.d4b8545dc3dc93af7f6a.js
-rw-rw-r-- 1 hank hank  28K 9月   6 11:44 1.d4b8545dc3dc93af7f6a.js.map
-rw-rw-r-- 1 hank hank  765 9月   6 11:44 2.57378280a5698f76c576.js
-rw-rw-r-- 1 hank hank 6.4K 9月   6 11:44 2.57378280a5698f76c576.js.map
-rw-rw-r-- 1 hank hank 244K 9月   6 11:44 app.13fca0a1be03d8b4a15b.js
-rw-rw-r-- 1 hank hank 710K 9月   6 11:44 app.13fca0a1be03d8b4a15b.js.map
-rw-rw-r-- 1 hank hank 1.4K 9月   6 11:44 app.24004a5d2621177eb1e6bcdabc919636.css
-rw-rw-r-- 1 hank hank 1.9K 9月   6 11:44 app.24004a5d2621177eb1e6bcdabc919636.css.map
-rw-rw-r-- 1 hank hank 1.6K 9月   6 11:44 manifest.c3fe0dc0d40ea3fac1a5.js
-rw-rw-r-- 1 hank hank  15K 9月   6 11:44 manifest.c3fe0dc0d40ea3fac1a5.js.map
-rw-rw-r-- 1 hank hank 983K 9月   6 11:44 vendor.128582091fc1c542ae63.js
-rw-rw-r-- 1 hank hank 6.7M 9月   6 11:44 vendor.128582091fc1c542ae63.js.map

使用ModuleConcatenationPlugin打包的文件

➜  bundles git:(master) ✗ ll
总用量 8.7M
-rw-rw-r-- 1 hank hank  23K 9月   6 11:47 0.331a96e6fa24027ff29f.js
-rw-rw-r-- 1 hank hank  60K 9月   6 11:47 0.331a96e6fa24027ff29f.js.map
-rw-rw-r-- 1 hank hank 5.8K 9月   6 11:47 1.241c026697aa61a8ca09.js
-rw-rw-r-- 1 hank hank  27K 9月   6 11:47 1.241c026697aa61a8ca09.js.map
-rw-rw-r-- 1 hank hank  714 9月   6 11:47 2.731ff84fbfc108bc253b.js
-rw-rw-r-- 1 hank hank 5.9K 9月   6 11:47 2.731ff84fbfc108bc253b.js.map
-rw-rw-r-- 1 hank hank 1.4K 9月   6 11:47 app.24004a5d2621177eb1e6bcdabc919636.css
-rw-rw-r-- 1 hank hank 1.9K 9月   6 11:47 app.24004a5d2621177eb1e6bcdabc919636.css.map
-rw-rw-r-- 1 hank hank 244K 9月   6 11:47 app.d041e73d9c21f495b099.js
-rw-rw-r-- 1 hank hank 709K 9月   6 11:47 app.d041e73d9c21f495b099.js.map
-rw-rw-r-- 1 hank hank 1.6K 9月   6 11:47 manifest.46788d45af925b1aa79a.js
-rw-rw-r-- 1 hank hank  15K 9月   6 11:47 manifest.46788d45af925b1aa79a.js.map
-rw-rw-r-- 1 hank hank 983K 9月   6 11:47 vendor.154264c34d147a00c862.js
-rw-rw-r-- 1 hank hank 6.7M 9月   6 11:47 vendor.154264c34d147a00c862.js.map

结论:文件尺寸有一定程度上的缩小。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Getting Real

Getting Real

Jason Fried、Heinemeier David Hansson、Matthew Linderman / 37signals / 2009-11-18 / USD 24.99

Getting Real details the business, design, programming, and marketing principles of 37signals. The book is packed with keep-it-simple insights, contrarian points of view, and unconventional approaches......一起来看看 《Getting Real》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具