Node.js理论实践之《Webpack原理及优化》

工做原理

基本概念

  • Entry:入口,Webpack 执行构建的第一步将从 Entry 开始。
  • Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出全部依赖的模块。
  • Chunk:代码块,一个chunk由多个模块组合而成,用于代码合并与分割。
  • Loader:模块转换器,用于将模块的原内容按照需求转换成新内容。
  • Plugin:插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件能够监听这些事件的发生,在特定时机作对应的事情。

流程归纳

初始化参数 ——> 开始编译 ——> 肯定入口 ——> 编译模块 ——> 完成模块编译 ——> 输出资源 ——> 输出完成javascript

  1. 初始化参数:从配置文件(默认webpack.config.js)和shell语句中读取与合并参数,得出最终的参数
  2. 开始编译(compile):用上一步获得的参数初始化Compiler对象,加载全部配置的插件Plugin,经过执行对象的run方法开始执行编译
  3. 肯定入口:根据配置中的entry找出全部的入口文件
  4. 编译模块:从入口文件出发,调用全部配置的Loader对模块进行编译,再找出该模块依赖的模块,再递归本步骤直到全部入口依赖的文件都通过处理
  5. 完成编译模块:通过第四步以后,获得了每一个模块被翻译以后的最终内容以及他们之间的依赖关系
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的chunk,再将每一个chunk转换成一个单独的文件加入输出列表中,这是能够修改输出内容的最后机会
  7. 输出完成:在肯定好输出内容后,根据配置(webpack.config.js && shell)肯定输出的路径和文件名,将文件的内容写入文件系统中(fs)

总结一下,Webpack的构建流程能够分为如下三大阶段java

  1. 初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler。
  2. 编译:从 Entry 发出,针对每一个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理。
  3. 输出:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统。

bundle.js

bundle.js实际上是一个当即执行函数,bundle.js能直接运行在浏览器中的缘由在于输出的文件中经过__webpack_require__函数定义了一个能够在浏览器中执行的加载函数来模拟Node.js中的require语句。 并且Webpack 作了缓存优化,执行加载过的模块不会再执行第二次,执行结果会缓存在内存中,当某个模块第二次被访问时会直接去内存中读取被缓存的返回值。node

(function(modules){
    //模拟require语句
    function __webpack_require__(){}
    //执行存放全部模块数组中的第0个模块(main.js)
    __webpack_require_[0]
})([/*存放全部模块的数组*/])
复制代码

性能优化

减小Webpack打包时间

  1. 优化Loader:优化 Loader 的文件搜索范围(exclude掉node_modules)、将Babel编译过的文件缓存起来(loader: 'babel-loader?cacheDirectory=true')。 对于 Loader 来讲,影响打包效率首当其冲必属 Babel 了。由于 Babel 会将代码转为字符串生成 AST,而后对 AST 继续进行转变最后再生成新的代码项目越大,转换代码越多,效率就越低
  2. HappyPack: 能够将Loader的同步执行转换为并行的。
module: {
  loaders: [
    {
      test: /\.js$/,
      include: [resolve('src')],
      exclude: /node_modules/,
      // id 后面的内容对应下面
      loader: 'happypack/loader?id=happybabel'
    }
  ]
},
plugins: [
  new HappyPack({
    id: 'happybabel',
    loaders: ['babel-loader?cacheDirectory'],
    // 开启 4 个线程
    threads: 4
  })
]
复制代码
  1. DllPlugin: 能够将特定的类库提早打包而后引入。极大的减小打包类库的次数,只有当类库更新版本才有须要从新打包。
// 单独配置在一个文件中
// webpack.dll.conf.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
  entry: {
    // 想统一打包的类库
    vendor: ['react']
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].dll.js',
    library: '[name]-[hash]'
  },
  plugins: [
    new webpack.DllPlugin({
      // name 必须和 output.library 一致
      name: '[name]-[hash]',
      // 该属性须要与 DllReferencePlugin 中一致
      context: __dirname,
      path: path.join(__dirname, 'dist', '[name]-manifest.json')
    })
  ]
}

// 使用 DllReferencePlugin 将依赖文件引入项目中
// webpack.conf.js
module.exports = {
  // ...省略其余配置
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      // manifest 就是以前打包出来的 json 文件
      manifest: require('./dist/vendor-manifest.json'),
    })
  ]
}
复制代码
  1. 代码压缩:webpack3中使用 webpack-parallel-uglify-plugin 来并行运行 UglifyJS(单线程),webpack4中将mode设置为production则默认开启压缩。

减小Webpack打包后的文件体积

  1. 按需加载:每一个路由页面单独打包为一个文件、loadash 这种大型类库一样能够使用这个功能。
  2. Scope Hoisting: 会分析出模块之间的依赖关系,尽量的把打包出来的模块合并到一个函数中去。Webpack4 中开启这个功能,只须要启用 optimization.concatenateModules
// test.js
export const a = 1
// index.js
import { a } from './test.js'
复制代码

打包上面两个文件后,生成代码相似这样:react

[
  /* 0 */
  function (module, exports, require) {
    //...
  },
  /* 1 */
  function (module, exports, require) {
    //...
  }
]
复制代码

若是使用Scope Hositing,会生成这样的相似代码:webpack

[
  /* 0 */
  function (module, exports, require) {
    //...
  }
]
复制代码
  1. Tree Shaking:能够实现删除项目中未被引用的代码。Webpack4的生产环境默认开启这个功能。
// test.js
export const a = 1
export const b = 2
// index.js
import { a } from './test.js'
复制代码

test 文件中的变量 b 若是没有在项目中使用到的话,就不会被打包到文件中。web

相关文章
相关标签/搜索