webpack中文网(三)tree shaking(副作用、压缩输出)、生产环境构建(webpack-merge、指定环境)、代码分离(防止重复、动态导入)

webpack中文网(三)tree shaking(副作用、压缩输出)、生产环境构建(webpack-merge、指定环境)、代码分离(防止重复、动态导入)

文章目录

  • webpack中文网(三)tree shaking(副作用、压缩输出)、生产环境构建(webpack-merge、指定环境)、代码分离(防止重复、动态导入)
    • 1. tree shaking
      • 添加一个通用模块
      • 将文件标记为无副作用(side-effect-free)
      • 压缩输出
      • 结论
        • 进一步阅读
    • 2. 生产环境构建
      • 配置
      • NPM Scripts
      • Minification
      • source map
      • 指定环境
      • Split CSS
      • CLI 替代选项
    • 3. 代码分离
      • 入口起点(entry points)
      • 防止重复(prevent duplication)
      • 动态导入(dynamic imports)
      • bundle 分析(bundle analysis)
      • 下一步

总结:

  • tree shaking

    • 通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)
    • 通过 package.json"sideEffects" 属性作为标记,向 compiler (编译程序)提供提示,表明项目中的哪些文件是 “pure(纯的 ES2015 模块)”,由此可以安全地删除文件中未使用的部分。
      • [副作用」的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。
    • 注意,任何导入的文件都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似 css-loader 并导入 CSS 文件,则需要将其添加到 side effect 列表中,以免在生产模式中无意中将它删除
    • 压缩输出
      • 引入一个能够删除未引用代码(dead code)的压缩工具(minifier)(例如 UglifyJSPlugin
      • 从 webpack 4 开始,也可以通过 "mode" 配置选项轻松切换到压缩输出,只需设置为 "production"
  • 生产环境构建

    • 配置

      • 由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置

      • 为了将这些配置合并在一起,我们将使用一个名为 webpack-merge 的工具。

        • webpack.common.js 中,我们设置了 entryoutput 配置,并且在其中引入这两个环境公用的全部插件。

        • webpack.dev.js 中,我们为此环境添加了推荐的 devtool(强大的 source map)和简单的 devServer 配置。(npm scripts 中 start:实时重新加载功能

          "start": "webpack-dev-server --open --config webpack.dev.js",
          
        • 最后,在 webpack.prod.js 中,我们引入了之前在 tree shaking 指南中介绍过的 UglifyJSPlugin。(npm scripts 中 build:压缩打包功能

          "build": "webpack --config webpack.prod.js"
          
    • source map

      • 避免在生产中使用 inline-***eval-***,因为它们可以增加 bundle 大小,并降低整体性能。
    • 指定环境

      • 许多 library 将通过与 process.env.NODE_ENV 环境变量关联,以决定 library 中应该引用哪些内容。
      • 当使用 process.env.NODE_ENV === 'production' 时,一些 library 可能针对具体用户的环境进行代码优化,从而删除或添加一些重要代码
      • 我们可以使用 webpack 内置的 DefinePlugin 为所有的依赖定义这个变量
  • 代码分离

    • 代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
    • 三种方法
      • 入口起点:使用 entry 配置手动地分离代码。
        • 如果入口 chunks 之间包含重复的模块,那些重复模块都会被引入到各个 bundle 中。
        • 这种方法不够灵活,并且不能将核心应用程序逻辑进行动态拆分代码。
      • 防止重复:使用 CommonsChunkPlugin 去重和分离 chunk。下面是一些对代码分离有帮助的插件和loader:
        • ExtractTextPlugin: 用于将 CSS 从主应用程序中分离。
        • bundle-loader: 用于分离代码和延迟加载生成的 bundle。
        • promise-loader: 类似于 bundle-loader ,但是使用的是 promises。
      • 动态导入(await import.then):通过模块的内联函数调用来分离代码。
        • chunkFilename,它决定非入口 chunk 的名称
        • 不再使用静态导入 lodash,而是通过使用动态导入(import)来分离一个 chunk
        • 由于 import() 会返回一个 promise,因此它可以和 async 函数一起使用。

1. tree shaking

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 importexport。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup。

新的 webpack 4 正式版本,扩展了这个检测能力,通过 package.json"sideEffects" 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 “pure(纯的 ES2015 模块)”,由此可以安全地删除文件中未使用的部分。

本指南的继承自起步指南。如果你尚未阅读该指南,请先行阅读。

添加一个通用模块

在我们的项目中添加一个新的通用模块文件 src/math.js,此文件导出两个函数:

project

webpack-demo
|- package.json
|- webpack.config.js
|- /dist
  |- bundle.js
  |- index.html
|- /src
  |- index.js
+ |- math.js
|- /node_modules

src/math.js

export function square(x) {
  return x * x;
}

export function cube(x) {
  return x * x * x;
}

接着,更新入口脚本,使用其中一个新方法,并且为了简单,将 lodash 删除:

src/index.js

- import _ from 'lodash';
+ import { cube } from './math.js';

  function component() {
-   var element = document.createElement('div');
+   var element = document.createElement('pre');

-   // lodash 是由当前 script 脚本 import 导入进来的
-   element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+   element.innerHTML = [
+     'Hello webpack!',
+     '5 cubed is equal to ' + cube(5)
+   ].join('\n\n');

    return element;
  }

  document.body.appendChild(component());

注意,我们并未从 src/math.js 模块中 import 导入 square 方法。这个功能是所谓的“未引用代码(dead code)”,也就是说,应该删除掉未被引用的 export。现在让我们运行我们的npm 脚本 npm run build,并检查输出的 bundle:

dist/bundle.js (around lines 90 - 100)

/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* unused harmony export square */
/* harmony export (immutable) */ __webpack_exports__["a"] = cube;
function square(x) {
  return x * x;
}

function cube(x) {
  return x * x * x;
}

注意,上面的 unused harmony export square 注释。如果你看下面的代码,你会注意到 square 没有被导入,但是,它仍然被包含在 bundle 中。我们将在下一节中解决这个问题。

将文件标记为无副作用(side-effect-free)

在一个纯粹的 ESM 模块世界中,识别出哪些文件有副作用很简单。然而,我们的项目无法达到这种纯度,所以,此时有必要向 webpack 的 compiler 提供提示哪些代码是“纯粹部分”。

这种方式是通过 package.json 的 "sideEffects" 属性来实现的。

{
  "name": "your-project",
  "sideEffects": false
}

如同上面提到的,如果所有代码都不包含副作用,我们就可以简单地将该属性标记为 false,来告知 webpack,它可以安全地删除未用到的 export 导出。

「副作用」的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。

如果你的代码确实有一些副作用,那么可以改为提供一个数组:

{
  "name": "your-project",
  "sideEffects": [
    "./src/some-side-effectful-file.js"
  ]
}

数组方式支持相关文件的相对路径、绝对路径和 glob 模式。它在内部使用 micromatch。

注意,任何导入的文件都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似 css-loader 并导入 CSS 文件,则需要将其添加到 side effect 列表中,以免在生产模式中无意中将它删除:

{
  "name": "your-project",
  "sideEffects": [
    "./src/some-side-effectful-file.js",
    "*.css"
  ]
}

最后,还可以在 module.rules 配置选项 中设置 "sideEffects"

压缩输出

通过如上方式,我们已经可以通过 importexport 语法,找出那些需要删除的“未使用代码(dead code)”,然而,我们不只是要找出,还需要在 bundle 中删除它们。为此,我们将使用 -p(production) 这个 webpack 编译标记,来启用 uglifyjs 压缩插件。

注意,--optimize-minimize 标记也会在 webpack 内部调用 UglifyJsPlugin

从 webpack 4 开始,也可以通过 "mode" 配置选项轻松切换到压缩输出,只需设置为 "production"

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
- }
+ },
+ mode: "production"
};

注意,也可以在命令行接口中使用 --optimize-minimize 标记,来使用 UglifyJSPlugin

准备就绪后,然后运行另一个命令 npm run build,看看输出结果有没有发生改变。

你发现 dist/bundle.js 中的差异了吗?显然,现在整个 bundle 都已经被精简过,但是如果仔细观察,则不会看到 square 函数被引入,但会看到 cube 函数的修改版本(function r(e){return e*e*e}n.a=r)。现在,随着 tree shaking 和代码压缩,我们的 bundle 减小几个字节!虽然,在这个特定示例中,可能看起来没有减少很多,但是,在具有复杂的依赖树的大型应用程序上运行时,tree shaking 或许会对 bundle 产生显著的体积优化。

结论

为了学会使用 tree shaking,你必须……

  • 使用 ES2015 模块语法(即 importexport)。
  • 在项目 package.json 文件中,添加一个 “sideEffects” 入口。
  • 引入一个能够删除未引用代码(dead code)的压缩工具(minifier)(例如 UglifyJSPlugin)。

你可以将应用程序想象成一棵树。绿色表示实际用到的源码和 library,是树上活的树叶。灰色表示无用的代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。

如果你对优化输出很感兴趣的话,请进入到下一个指南,来了解生产环境中进行构建的详细细节。


进一步阅读
  • webpack 4 beta — try it today!
  • Debugging Optimization Bailouts
  • Issue 6074 - Add support for more complex selectors for sideEffects

2. 生产环境构建

在本指南中,我们将深入一些最佳实践,并且使用工具,将网站或应用程序构建到生产环境中。

以下示例来源于 tree shaking 和 开发。在继续之前,请确保你已经熟悉这些指南中所介绍的概念/配置。

配置

开发环境(development)生产环境(production)的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置

虽然,以上我们将生产环境开发环境做了略微区分,但是,请注意,我们还是会遵循不重复原则(Don’t repeat yourself - DRY),保留一个“通用”配置。为了将这些配置合并在一起,我们将使用一个名为 webpack-merge 的工具。通过“通用”配置,我们不必在环境特定(environment-specific)的配置中重复代码。

我们先从安装 webpack-merge 开始,并将之前指南中已经成型的那些代码再次进行分离:

npm install --save-dev webpack-merge

project

  webpack-demo
  |- package.json
- |- webpack.config.js
+ |- webpack.common.js
+ |- webpack.dev.js
+ |- webpack.prod.js
  |- /dist
  |- /src
    |- index.js
    |- math.js
  |- /node_modules

webpack.common.js

+ const path = require('path');
+ const CleanWebpackPlugin = require('clean-webpack-plugin');
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
+
+ module.exports = {
+   entry: {
+     app: './src/index.js'
+   },
+   plugins: [
+     new CleanWebpackPlugin(['dist']),
+     new HtmlWebpackPlugin({
+       title: 'Production'
+     })
+   ],
+   output: {
+     filename: '[name].bundle.js',
+     path: path.resolve(__dirname, 'dist')
+   }
+ };

webpack.dev.js

+ const merge = require('webpack-merge');
+ const common = require('./webpack.common.js');
+
+ module.exports = merge(common, {
+   devtool: 'inline-source-map',
+   devServer: {
+     contentBase: './dist'
+   }
+ });

webpack.prod.js

+ const merge = require('webpack-merge');
+ const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
+ const common = require('./webpack.common.js');
+
+ module.exports = merge(common, {
+   plugins: [
+     new UglifyJSPlugin()
+   ]
+ });

现在,在 webpack.common.js 中,我们设置了 entryoutput 配置,并且在其中引入这两个环境公用的全部插件。在 webpack.dev.js 中,我们为此环境添加了推荐的 devtool(强大的 source map)和简单的 devServer 配置。最后,在 webpack.prod.js 中,我们引入了之前在 tree shaking 指南中介绍过的 UglifyJSPlugin

注意,在环境特定的配置中使用 merge() 很容易地包含我们在 devprod 中的常见配置。webpack-merge 工具提供了多种合并(merge)的高级功能,但是在我们的用例中,无需用到这些功能。

NPM Scripts

现在,我们把 scripts 重新指向到新配置。我们将 npm start 定义为开发环境脚本,并在其中使用 webpack-dev-server,将 npm run build 定义为生产环境脚本:

package.json

  {
    "name": "development",
    "version": "1.0.0",
    "description": "",
    "main": "webpack.config.js",
    "scripts": {
-     "start": "webpack-dev-server --open",
+     "start": "webpack-dev-server --open --config webpack.dev.js", //实时重新加载功能
-     "build": "webpack"
+     "build": "webpack --config webpack.prod.js" //打包功能
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
      "clean-webpack-plugin": "^0.1.17",
      "css-loader": "^0.28.4",
      "csv-loader": "^2.1.1",
      "express": "^4.15.3",
      "file-loader": "^0.11.2",
      "html-webpack-plugin": "^2.29.0",
      "style-loader": "^0.18.2",
      "webpack": "^3.0.0",
      "webpack-dev-middleware": "^1.12.0",
      "webpack-dev-server": "^2.9.1",
      "webpack-merge": "^4.1.0",
      "xml-loader": "^1.2.1"
    }
  }

随意运行这些脚本,然后查看输出结果的变化,然后我们继续添加一些生产环境配置。

Minification

注意,虽然 UglifyJSPlugin 是代码压缩方面比较好的选择,但是还有一些其他可选择项。以下有几个同样很受欢迎的插件:

  • BabelMinifyWebpackPlugin
  • ClosureCompilerPlugin

如果决定尝试以上这些,只要确保新插件也会按照 tree shake 指南中所陈述的,具有删除未引用代码(dead code)的能力足矣。

source map

我们鼓励你在生产环境中启用 source map,因为它们对调试源码(debug)和运行基准测试(benchmark tests)很有帮助。虽然有如此强大的功能,然而还是应该针对生成环境用途,选择一个构建快速的推荐配置(具体细节请查看 devtool)。对于本指南,我们将在生产环境中使用 source-map 选项,而不是我们在开发环境中用到的 inline-source-map

webpack.prod.js

  const merge = require('webpack-merge');
  const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
  const common = require('./webpack.common.js');

  module.exports = merge(common, {
+   devtool: 'source-map',
    plugins: [
-     new UglifyJSPlugin()
+     new UglifyJSPlugin({
+       sourceMap: true
+     })
    ]
  });

避免在生产中使用 inline-***eval-***,因为它们可以增加 bundle 大小,并降低整体性能。

指定环境

许多 library 将通过与 process.env.NODE_ENV 环境变量关联,以决定 library 中应该引用哪些内容。例如,当不处于生产环境中时,某些 library 为了使调试变得容易,可能会添加额外的日志记录(log)和测试(test)。其实,当使用 process.env.NODE_ENV === 'production' 时,一些 library 可能针对具体用户的环境进行代码优化,从而删除或添加一些重要代码。我们可以使用 webpack 内置的 DefinePlugin 为所有的依赖定义这个变量:

webpack.prod.js

+ const webpack = require('webpack');
  const merge = require('webpack-merge');
  const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
  const common = require('./webpack.common.js');

  module.exports = merge(common, {
    devtool: 'source-map',
    plugins: [
      new UglifyJSPlugin({
        sourceMap: true
-     })
+     }),
+     new webpack.DefinePlugin({
+       'process.env.NODE_ENV': JSON.stringify('production')
+     })
    ]
  });

技术上讲,NODE_ENV 是一个由 Node.js 暴露给执行脚本的系统环境变量。通常用于决定在开发环境与生产环境(dev-vs-prod)下,服务器工具、构建脚本和客户端 library 的行为。然而,与预期不同的是,无法在构建脚本 webpack.config.js 中,将 process.env.NODE_ENV 设置为 "production",请查看 #2537。因此,例如 process.env.NODE_ENV === 'production' ? '[name].[hash].bundle.js' : '[name].bundle.js' 这样的条件语句,在 webpack 配置文件中,无法按照预期运行。

如果你正在使用像 react 这样的 library,那么在添加此 DefinePlugin 插件后,你应该看到 bundle 大小显著下降。还要注意,任何位于 /src 的本地代码都可以关联到 process.env.NODE_ENV 环境变量,所以以下检查也是有效的:

src/index.js

  import { cube } from './math.js';
+
+ if (process.env.NODE_ENV !== 'production') {
+   console.log('Looks like we are in development mode!');
+ }

  function component() {
    var element = document.createElement('pre');

    element.innerHTML = [
      'Hello webpack!',
      '5 cubed is equal to ' + cube(5)
    ].join('\n\n');

    return element;
  }

  document.body.appendChild(component());

Split CSS

正如在管理资源中最后的 加载 CSS 小节中所提到的,通常最好的做法是使用 ExtractTextPlugin 将 CSS 分离成单独的文件。在插件文档中有一些很好的实现例子。disable 选项可以和 --env 标记结合使用,以允许在开发中进行内联加载,推荐用于热模块替换和构建速度。

CLI 替代选项

以上描述也可以通过命令行实现。例如,--optimize-minimize 标记将在后台引用 UglifyJSPlugin。和以上描述的 DefinePlugin 实例相同,--define process.env.NODE_ENV="'production'" 也会做同样的事情。并且,webpack -p 将自动地调用上述这些标记,从而调用需要引入的插件。

这些简便方式虽然都很不错,但是我们通常建议只使用配置方式,因为在这两种场景中下,配置方式能够更好地帮助你了解自己正在做的事情。配置方式还可以让你更方便地控制这两个插件中的其他选项。

3. 代码分离

本指南继续沿用起步和管理输出中的代码示例。。请确保你至少已熟悉其中提供的示例。

代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。

有三种常用的代码分离方法:

  • 入口起点:使用 entry 配置手动地分离代码。
  • 防止重复:使用 CommonsChunkPlugin 去重和分离 chunk。
  • 动态导入:通过模块的内联函数调用来分离代码。

入口起点(entry points)

这是迄今为止最简单、最直观的分离代码的方式。不过,这种方式手动配置较多,并有一些陷阱,我们将会解决这些问题。先来看看如何从 main bundle 中分离另一个模块:

project

webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- /src
  |- index.js
+ |- another-module.js
|- /node_modules

another-module.js

import _ from 'lodash';

console.log(
  _.join(['Another', 'module', 'loaded!'], ' ')
);

webpack.config.js

const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    index: './src/index.js',
    another: './src/another-module.js'
  },
  plugins: [
    new HTMLWebpackPlugin({
      title: 'Code Splitting'
    })
  ],
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};

这将生成如下构建结果:

Hash: 309402710a14167f42a8
Version: webpack 2.6.1
Time: 570ms
            Asset    Size  Chunks                    Chunk Names
  index.bundle.js  544 kB       0  [emitted]  [big]  index
another.bundle.js  544 kB       1  [emitted]  [big]  another
   [0] ./~/lodash/lodash.js 540 kB {0} {1} [built]
   [1] (webpack)/buildin/global.js 509 bytes {0} {1} [built]
   [2] (webpack)/buildin/module.js 517 bytes {0} {1} [built]
   [3] ./src/another-module.js 87 bytes {1} [built]
   [4] ./src/index.js 216 bytes {0} [built]

正如前面提到的,这种方法存在一些问题:

  • 如果入口 chunks 之间包含重复的模块,那些重复模块都会被引入到各个 bundle 中。
  • 这种方法不够灵活,并且不能将核心应用程序逻辑进行动态拆分代码。

以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在 ./src/index.js 中也引入过 lodash,这样就在两个 bundle 中造成重复引用。接着,我们通过使用 CommonsChunkPlugin 来移除重复的模块。

防止重复(prevent duplication)

CommonsChunkPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。让我们使用这个插件,将之前的示例中重复的 lodash 模块去除:

webpack.config.js

  const path = require('path');
+ const webpack = require('webpack');
  const HTMLWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: {
      index: './src/index.js',
      another: './src/another-module.js'
    },
    plugins: [
      new HTMLWebpackPlugin({
        title: 'Code Splitting'
-     })
+     }),
+     new webpack.optimize.CommonsChunkPlugin({
+       name: 'common' // 指定公共 bundle 的名称。
+     })
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

这里我们使用 CommonsChunkPlugin 之后,现在应该可以看出,index.bundle.js 中已经移除了重复的依赖模块。需要注意的是,CommonsChunkPlugin 插件将 lodash 分离到单独的 chunk,并且将其从 main bundle 中移除,减轻了大小。执行 npm run build 查看效果:

Hash: 70a59f8d46ff12575481
Version: webpack 2.6.1
Time: 510ms
            Asset       Size  Chunks                    Chunk Names
  index.bundle.js  665 bytes       0  [emitted]         index
another.bundle.js  537 bytes       1  [emitted]         another
 common.bundle.js     547 kB       2  [emitted]  [big]  common
   [0] ./~/lodash/lodash.js 540 kB {2} [built]
   [1] (webpack)/buildin/global.js 509 bytes {2} [built]
   [2] (webpack)/buildin/module.js 517 bytes {2} [built]
   [3] ./src/another-module.js 87 bytes {1} [built]
   [4] ./src/index.js 216 bytes {0} [built]

以下是由社区提供的,一些对于代码分离很有帮助的插件和 loaders:

  • ExtractTextPlugin: 用于将 CSS 从主应用程序中分离。
  • bundle-loader: 用于分离代码和延迟加载生成的 bundle。
  • promise-loader: 类似于 bundle-loader ,但是使用的是 promises。

CommonsChunkPlugin 插件还可以通过使用显式的 vendor chunks 功能,从应用程序代码中分离 vendor 模块。

动态导入(dynamic imports)

当涉及到动态代码拆分时,webpack 提供了两个类似的技术。对于动态导入,第一种,也是优先选择的方式是,使用符合 ECMAScript 提案 的 import() 语法。第二种,则是使用 webpack 特定的 require.ensure。让我们先尝试使用第一种……

import() 调用会在内部用到 promises。如果在旧有版本浏览器中使用 import(),记得使用 一个 polyfill 库(例如 es6-promise 或 promise-polyfill),来 shim Promise

在我们开始本节之前,先从配置中移除掉多余的 entryCommonsChunkPlugin,因为接下来的演示中并不需要它们:

webpack.config.js

  const path = require('path');
- const webpack = require('webpack');
  const HTMLWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: {
+     index: './src/index.js'
-     index: './src/index.js',
-     another: './src/another-module.js'
    },
    plugins: [
      new HTMLWebpackPlugin({
        title: 'Code Splitting'
-     }),
+     })
-     new webpack.optimize.CommonsChunkPlugin({
-       name: 'common' // 指定公共 bundle 的名称。
-     })
    ],
    output: {
      filename: '[name].bundle.js',
+     chunkFilename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

注意,这里使用了 chunkFilename,它决定非入口 chunk 的名称。想了解 chunkFilename 更多信息,请查看 output 相关文档。接着,更新我们的项目,移除掉那些现在不会用到的文件:

project

webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- /src
  |- index.js
- |- another-module.js
|- /node_modules

现在,我们不再使用静态导入 lodash,而是通过使用动态导入来分离一个 chunk:

src/index.js

- import _ from 'lodash';
-
- function component() {
+ function getComponent() {
-   var element = document.createElement('div');
-
-   // Lodash, now imported by this script
-   element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+   return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
+     var element = document.createElement('div');
+
+     element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+
+     return element;
+
+   }).catch(error => 'An error occurred while loading the component');
  }

- document.body.appendChild(component());
+ getComponent().then(component => {
+   document.body.appendChild(component);
+ })

注意,在注释中使用了 webpackChunkName。这样做会导致我们的 bundle 被命名为 lodash.bundle.js ,而不是 [id].bundle.js 。想了解更多关于 webpackChunkName 和其他可用选项,请查看 import() 相关文档。让我们执行 webpack,查看 lodash 是否会分离到一个单独的 bundle:

Hash: a27e5bf1dd73c675d5c9
Version: webpack 2.6.1
Time: 544ms
           Asset     Size  Chunks                    Chunk Names
lodash.bundle.js   541 kB       0  [emitted]  [big]  lodash
 index.bundle.js  6.35 kB       1  [emitted]         index
   [0] ./~/lodash/lodash.js 540 kB {0} [built]
   [1] ./src/index.js 377 bytes {1} [built]
   [2] (webpack)/buildin/global.js 509 bytes {0} [built]
   [3] (webpack)/buildin/module.js 517 bytes {0} [built]

由于 import() 会返回一个 promise,因此它可以和 async 函数一起使用。但是,需要使用像 Babel 这样的预处理器和Syntax Dynamic Import Babel Plugin。下面是如何通过 async 函数简化代码:

src/index.js

- function getComponent() {
+ async function getComponent() {
-   return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
-     var element = document.createElement('div');
-
-     element.innerHTML = _.join(['Hello', 'webpack'], ' ');
-
-     return element;
-
-   }).catch(error => 'An error occurred while loading the component');
+   var element = document.createElement('div');
+   const _ = await import(/* webpackChunkName: "lodash" */ 'lodash');
+
+   element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+
+   return element;
  }

  getComponent().then(component => {
    document.body.appendChild(component);
  });

bundle 分析(bundle analysis)

如果我们以分离代码作为开始,那么就以检查模块作为结束,分析输出结果是很有用处的。官方分析工具 是一个好的初始选择。下面是一些社区支持(community-supported)的可选工具:

  • webpack-chart: webpack 数据交互饼图。
  • webpack-visualizer: 可视化并分析你的 bundle,检查哪些模块占用空间,哪些可能是重复使用的。
  • webpack-bundle-analyzer: 一款分析 bundle 内容的插件及 CLI 工具,以便捷的、交互式、可缩放的树状图形式展现给用户。

下一步

关于「如何在真正的应用程序和缓存中 import() 导入」以及学习「如何更加高效地分离代码」的具体示例,请查看懒加载。

你可能感兴趣的:(前端工程师1——汇总,前端工程师5——webpack,node.js,javascript,node.js,vue.js,webpack)