最近在写一下对本身的思考,发现还有两篇草稿没发,记得应该是去年年初的时候写的,后来公司一直忙,就不多来社区了,今天先发一篇了,这篇记得当时还找到做者,加了微信css
webpack 版本不一样,配置也会有一些地方不同的,这里是 webpack 4html
为了尽量少的让文件被 Loader 处理,能够经过 include 去命中只有哪些文件须要被处理。
复制代码
用过 Windows 系统的人应该会常常看到以 .dll 为后缀的文件,这些文件称为动态连接库,
在一个动态连接库中能够包含给其余模块调用的函数和数据。vue
要给 Web 项目构建接入动态连接库的思想,须要完成如下事情:node
为何给 Web 项目构建接入动态连接库的思想后,会大大提高构建速度呢? 缘由在于包含大量复用模块的动态连接库只须要编译一次,
在以后的构建过程当中被动态连接库包含的模块将不会在从新编译,
而是直接使用动态连接库中的代码react
分解任务和管理线程的事情 HappyPack 都会帮你作好webpack
用过 UglifyJS 的你必定会发如今构建用于开发环境的代码时很快就能完成,
但在构建用于线上的代码时构建一直卡在一个时间点迟迟没有反应,其实卡住的这个时候就是在进行代码压缩。git
因为压缩 JavaScript 代码须要先把代码解析成用 Object 抽象表示的 AST 语法树,
再去应用各类规则分析和处理 AST,致使这个过程计算量巨大,耗时很是多。github
为何不把在4-3 使用 HappyPack中介绍过的多进程并行处理的思想也引入到代码压缩中呢?web
ParallelUglifyPlugin 就作了这个事情。
当 Webpack 有多个 JavaScript 文件须要输出和压缩时,本来会使用 UglifyJS 去一个个挨着压缩再输出,
可是 ParallelUglifyPlugin 则会开启多个子进程,把对多个文件的压缩工做分配给多个子进程去完成,
每一个子进程其实仍是经过 UglifyJS 去压缩代码,可是变成了并行执行。
因此 ParallelUglifyPlugin 能更快的完成对多个文件的压缩工做。正则表达式
使用 ParallelUglifyPlugin 也很是简单,把原来 Webpack 配置文件中内置的 UglifyJsPlugin 去掉后,再替换成 ParallelUglifyPlugin,
不过看到 GitHub 上说是支持并行的,uglifyjs-webpack-plugin/#parallel
要让 Webpack 开启监听模式,有两种方式: 在配置文件
webpack.*.config.js
中设置watch: true
。 在执行启动Webpack
命令时,带上--watch
参数,完整命令是webpack --watch
文件监听工做原理: 在 Webpack 中监听一个文件发生变化的原理是定时的去获取这个文件的最后编辑时间,
每次都存下最新的最后编辑时间,若是发现当前获取的和最后一次保存的最后编辑时间不一致,就认为该文件发生了变化。
配置项中的 watchOptions.poll 就是用于控制定时检查的周期,具体含义是每隔多少毫秒检查一次。
watchOptions: {
// 不监听的 node_modules 目录下的文件
ignored: /node_modules/,
}
复制代码
webpack 内置插件
HotModuleReplacementPlugin
,
配置 devServer
// these devServer options should be customized in /config/index.js devServer: { clientLogLevel: 'warning', historyApiFallback: { rewrites: [ { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, ], }, hot: true, 复制代码
要优化模块热替换的构建性能,思路和在4-5 使用自动刷新中提到的很相似:
监听更少的文件,忽略掉 node_modules 目录下的文件。
可是其中提到的关闭默认的 inline 模式手动注入代理客户端的优化方法不能用于在使用模块热替换的状况下,
缘由在于模块热替换的运行依赖在每一个 Chunk 中都包含代理客户端的代码。
要在 Webpack 中接入 UglifyJS 须要经过插件的形式,目前有两个成熟的插件,分别是:
UglifyJsPlugin:经过封装 UglifyJS 实现压缩。
ParallelUglifyPlugin:多进程并行处理压缩
把 cssnano 接入到 Webpack 中也很是简单,由于 css-loader 已经将其内置了,
要开启 cssnano 去压缩代码只须要开启 css-loader 的 minimize 选项
const path = require('path'); const {WebPlugin} = require('web-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { module: { rules: [ { test: /\.css$/,// 增长对 CSS 文件的支持 // 提取出 Chunk 中的 CSS 代码到单独的文件中 use: ExtractTextPlugin.extract({ // 经过 minimize 选项压缩 CSS 代码 use: ['css-loader?minimize'] }), }, ] }, plugins: [ // 用 WebPlugin 生成对应的 HTML 文件 new WebPlugin({ template: './template.html', // HTML 模版文件所在的文件路径 filename: 'index.html' // 输出的 HTML 的文件名称 }), new ExtractTextPlugin({ filename: `[name]_[contenthash:8].css`,// 给输出的 CSS 文件名称加上 Hash 值 }), ], } 复制代码
以前的相对路径,都变成了绝对的指向 CDN 服务的 URL 地址,配置中的path 也须要换成 CDN 地址前缀
Tree Shaking 能够用来剔除 JavaScript 中用不上的死代码。它依赖静态的 ES6 模块化语法
Webpack 内置了专门用于提取多个 Chunk 中公共部分的插件 CommonsChunkPlugin,CommonsChunkPlugin 大体使用方法以下:
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin'); new CommonsChunkPlugin({ // 从哪些 Chunk 中提取 chunks: ['a', 'b'], // 提取出的公共部分造成一个新的 Chunk,这个新 Chunk 的名称 name: 'common' }) 复制代码
router 按需加载
在前面的优化方法中提到了代码压缩和分块,这些都是在网络加载层面的优化,
除此以外还能够优化代码在运行时的效率,Prepack 就是为此而生。
Prepack 由 Facebook 开源,它采用较为激进的方法:
在保持运行结果一致的状况下,改变源代码的运行逻辑,输出性能更高的 JavaScript 代码。
实际上 Prepack 就是一个部分求值器,编译代码时提早将计算结果放到编译后的代码中,而不是在代码运行时才去求值。
Prepack 经过在编译阶段预先执行了源码获得执行结果,再直接把运行结果输出来以提高性能
Prepack 的工做原理和流程大体以下:
经过 Babel 把 JavaScript 源码解析成抽象语法树(AST),以方便更细粒度地分析源码;
Prepack 实现了一个 JavaScript 解释器,用于执行源码。
借助这个解释器 Prepack 才能掌握源码具体是如何执行的,并把执行过程当中的结果返回到输出中。
从表面上看去这彷佛很是美好,但实际上 Prepack 还不够成熟与完善。
Prepack 目前还处于初期的开发阶段,局限性也很大,例如:
接入 Webpack
const PrepackWebpackPlugin = require('prepack-webpack-plugin').default; module.exports = { plugins: [ new PrepackWebpackPlugin() ] }; 复制代码
Scope Hoisting 可让 Webpack 打包出来的代码文件更小、运行的更快, 它又译做 "做用域提高",
是在 Webpack3 中新推出的功能
好处是:
代码体积更小,由于函数申明语句会产生大量代码;
代码在运行时由于建立的函数做用域更少了,内存开销也随之变小。
Scope Hoisting 的实现原理其实很简单: 分析出模块之间的依赖关系,尽量的把打散的模块合并到一个函数中去,但前提是不能形成代码冗余。
所以只有那些被引用了一次的模块才能被合并。
因为 Scope Hoisting 须要分析出模块之间的依赖关系,所以源码必须采用 ES6 模块化语句,否则它将没法生效。
缘由和4-10 使用 TreeShaking 中介绍的相似。
为了更简单直观的分析输出结果,社区中出现了许多可视化的分析工具。
这些工具以图形的方式把结果更加直观的展现出来,让你快速看到问题所在。
两种分析工具:
stats.json
在启动 Webpack 时带上以上两个参数,启动命令以下:
webpack --profile --json > stats.json,
复制代码
若是没有问题,你会发现项目中多出了一个 stats.json 文件。
这个 stats.json 文件是给后面介绍的可视化分析工具使用的。
但是我在 vue 项目中使用时出现了一个问题
web>webpack --profile --json > stats.json No configuration file found and no output filename configured via CLI option. A configuration file could be named 'webpack.config.js' in the current directory . Use --help to display the CLI options. 复制代码
webpack --config ./build/webpack.dev.conf.js --json > stats.json
复制代码
webpack --profile --json 会输出字符串形式的 JSON,
stats.json 是 UNIX/Linux 系统中的管道命令,
含义是把 webpack --profile --json 输出的内容经过管道输出到 stats.json 文件中。
打开 Webpack Analyse 连接的网页后,你就会看到一个弹窗提示你上传 JSON 文件,
也就是须要上传上面讲到的 stats.json 文件
webpack-bundle-analyzer
发现 vue-cli 2 版本中 webpack.prod.conf.js 里面有关因而否开启 webpack-bundle-analyzer 配置; 也就是说
npm run build --report
的时候,BundleAnalyzerPlugin
能以可视化的方式展现打包结果;
若是单独使用 webpack-bundle-analyzer:
按照开发环境和线上环境为该项目配置了两份文件,下面是使用
webpack4
版本
const path = require('path'); const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin'); const {AutoWebPlugin} = require('web-webpack-plugin'); const HappyPack = require('happypack'); // 自动寻找 pages 目录下的全部目录,把每个目录当作一个单页应用 const autoWebPlugin = new AutoWebPlugin('./src/pages', { // HTML 模版文件所在的文件路径 template: './template.html', // 提取出全部页面公共的代码 commonsChunk: { // 提取出公共代码 Chunk 的名称 name: 'common', }, }); module.exports = { // AutoWebPlugin 会找为寻找到的全部单页应用,生成对应的入口配置, // autoWebPlugin.entry 方法能够获取到生成入口配置 entry: autoWebPlugin.entry({ // 这里能够加入你额外须要的 Chunk 入口 base: './src/base.js', }), output: { filename: '[name].js', }, resolve: { // 使用绝对路径指明第三方模块存放的位置,以减小搜索步骤 // 其中 __dirname 表示当前工做目录,也就是项目根目录 modules: [path.resolve(__dirname, 'node_modules')], // 针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件,使用 Tree Shaking 优化 // 只采用 main 字段做为入口文件描述字段,以减小搜索步骤 mainFields: ['jsnext:main', 'main'], }, module: { rules: [ { // 若是项目源码中只有 js 文件就不要写成 /\.jsx?$/,提高正则表达式性能 test: /\.js$/, // 使用 HappyPack 加速构建 use: ['happypack/loader?id=babel'], // 只对项目根目录下的 src 目录中的文件采用 babel-loader include: path.resolve(__dirname, 'src'), }, { test: /\.js$/, use: ['happypack/loader?id=ui-component'], include: path.resolve(__dirname, 'src'), }, { // 增长对 CSS 文件的支持 test: /\.css$/, use: ['happypack/loader?id=css'], }, ] }, plugins: [ autoWebPlugin, // 使用 HappyPack 加速构建 new HappyPack({ id: 'babel', // babel-loader 支持缓存转换出的结果,经过 cacheDirectory 选项开启 loaders: ['babel-loader?cacheDirectory'], }), new HappyPack({ // UI 组件加载拆分 id: 'ui-component', loaders: [{ loader: 'ui-component-loader', options: { lib: 'antd', style: 'style/index.css', camel2: '-' } }], }), new HappyPack({ id: 'css', // 如何处理 .css 文件,用法和 Loader 配置中同样 loaders: ['style-loader', 'css-loader'], }), // 4-11提取公共代码 new CommonsChunkPlugin({ // 从 common 和 base 两个现成的 Chunk 中提取公共的部分 chunks: ['common', 'base'], // 把公共的部分放到 base 中 name: 'base' }), ], watchOptions: { // 4-5使用自动刷新:不监听的 node_modules 目录下的文件 ignored: /node_modules/, } }; 复制代码
const path = require('path'); const DefinePlugin = require('webpack/lib/DefinePlugin'); const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin'); const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const {AutoWebPlugin} = require('web-webpack-plugin'); const HappyPack = require('happypack'); const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); // 自动寻找 pages 目录下的全部目录,把每个目录当作一个单页应用 const autoWebPlugin = new AutoWebPlugin('./src/pages', { // HTML 模版文件所在的文件路径 template: './template.html', // 提取出全部页面公共的代码 commonsChunk: { // 提取出公共代码 Chunk 的名称 name: 'common', }, // 指定存放 CSS 文件的 CDN 目录 URL stylePublicPath: '//css.cdn.com/id/', }); module.exports = { // AutoWebPlugin 会找为寻找到的全部单页应用,生成对应的入口配置, // autoWebPlugin.entry 方法能够获取到生成入口配置 entry: autoWebPlugin.entry({ // 这里能够加入你额外须要的 Chunk 入口 base: './src/base.js', }), output: { // 给输出的文件名称加上 Hash 值 filename: '[name]_[chunkhash:8].js', path: path.resolve(__dirname, './dist'), // 指定存放 JavaScript 文件的 CDN 目录 URL publicPath: '//js.cdn.com/id/', }, resolve: { // 使用绝对路径指明第三方模块存放的位置,以减小搜索步骤 // 其中 __dirname 表示当前工做目录,也就是项目根目录 modules: [path.resolve(__dirname, 'node_modules')], // 只采用 main 字段做为入口文件描述字段,以减小搜索步骤 mainFields: ['jsnext:main', 'main'], }, module: { rules: [ { // 若是项目源码中只有 js 文件就不要写成 /\.jsx?$/,提高正则表达式性能 test: /\.js$/, // 使用 HappyPack 加速构建 use: ['happypack/loader?id=babel'], // 只对项目根目录下的 src 目录中的文件采用 babel-loader include: path.resolve(__dirname, 'src'), }, { test: /\.js$/, use: ['happypack/loader?id=ui-component'], include: path.resolve(__dirname, 'src'), }, { // 增长对 CSS 文件的支持 test: /\.css$/, // 提取出 Chunk 中的 CSS 代码到单独的文件中 use: ExtractTextPlugin.extract({ use: ['happypack/loader?id=css'], // 指定存放 CSS 中导入的资源(例如图片)的 CDN 目录 URL publicPath: '//img.cdn.com/id/' }), }, ] }, plugins: [ autoWebPlugin, // 4-14开启ScopeHoisting new ModuleConcatenationPlugin(), // 4-3使用HappyPack new HappyPack({ // 用惟一的标识符 id 来表明当前的 HappyPack 是用来处理一类特定的文件 id: 'babel', // babel-loader 支持缓存转换出的结果,经过 cacheDirectory 选项开启 loaders: ['babel-loader?cacheDirectory'], }), new HappyPack({ // UI 组件加载拆分 id: 'ui-component', loaders: [{ loader: 'ui-component-loader', options: { lib: 'antd', style: 'style/index.css', camel2: '-' } }], }), new HappyPack({ id: 'css', // 如何处理 .css 文件,用法和 Loader 配置中同样 // 经过 minimize 选项压缩 CSS 代码 loaders: ['css-loader?minimize'], }), new ExtractTextPlugin({ // 给输出的 CSS 文件名称加上 Hash 值 filename: `[name]_[contenthash:8].css`, }), // 4-11提取公共代码 new CommonsChunkPlugin({ // 从 common 和 base 两个现成的 Chunk 中提取公共的部分 chunks: ['common', 'base'], // 把公共的部分放到 base 中 name: 'base' }), new DefinePlugin({ // 定义 NODE_ENV 环境变量为 production 去除 react 代码中的开发时才须要的部分 'process.env': { NODE_ENV: JSON.stringify('production') } }), // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码 new ParallelUglifyPlugin({ // 传递给 UglifyJS 的参数 uglifyJS: { output: { // 最紧凑的输出 beautify: false, // 删除全部的注释 comments: false, }, compress: { // 在UglifyJs删除没有用到的代码时不输出警告 warnings: false, // 删除全部的 `console` 语句,能够兼容ie浏览器 drop_console: true, // 内嵌定义了可是只用到一次的变量 collapse_vars: true, // 提取出出现屡次可是没有定义成变量去引用的静态值 reduce_vars: true, } }, }), ] }; 复制代码
吴浩麟拥有本书的著做权。
其它人不能将本书用于商用用途,不能转载,不能以任何形式发行,违者将追究法律责任。