【使你的页面飞起来】6-构建优化

372 阅读6分钟

1. 用户感知优化

  1. 友好错误提示 friendly-errors-webpack-plugin

  2. 进度条 progress-bar-webpack-plugin

  3. 编译完成弹出通知 webpack-build-notifier

  4. 设置窗口标题 set-iterm2-badge

  5. 直观显示webpack构建 webpack-dashboard/plugin

2. 构建速度优化

2.1 缩小命名范围

通过 testincludeexclude 三个配置项来命中 Loader 要应用规则的文件。为了尽可能少地让文件被 Loader 处理。

  1. 通过 include 去命中只有哪些文件需要被处理
  2. resolve.modules resolve.modules 的默认值是node_modules,含义是先去当前目录的 node_modules 目录下去找我们想找的模块,如果没找到就去上一级目录 ../node_modules中找,再没有就去 ../../node_modules 中找,以此类推。

可以指明存放第三方模块的绝对路径,以减少寻找

resolve : {
    // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
    // 其中, dirname 表示当前工作目录,也就是项目根目录
    modules: [path.resolve(__dirname,'node_modules')]
}
  1. resolve.mainFields 配置第三方模块使用哪个入口文件,为了减少搜索步骤,在明确第 方模块的入口文件描述字段时,我们可以将它设置得尽量少

  2. resolve.alia

  3. resolve.extensions

有些 JavaScript 运行环境可能内置了一些全局变量或者模块,例如在script标签里通过以下代码引入 jQuery:

<script src=”path/to/jquery.js"></script>

这时,全局变量 jQuery 就会被注入网页的 JavaScript 运行环境里。

如果想在使用模块化的源代码里导入和使用 jQuery ,则可能需要这样:

import $ from 'jquery';
$('.my-element');

构建后我们会发现输出的 Chunk 里包含的 jQuery 库的内容,这导致 jQuery 库出现 了两次,浪费加载流量,最好是 Chunk 里不会包含 jQuery 库的内容

externals 可以告诉我们 webpack 在 javascript 运行环境中已经配置里哪些全局变量,不用将这些全局变量打包到代码中二十直接使用它们。

module.exports = {
    externals: {
        // 将导入语句里的 Jquery 替换成运行环境里的全局变量Jquery
        Jquery: 'Jquery'
    }
}
  1. module.noParse 忽略对部分没采用模块化的文件的递归解析处理,提高构建性能。
  • 提高构建速度
  • 直接通知webpack忽略较大的库
  • 被忽略的库不能有import, require, define的引入方式
  1. parser 因为 Webpack 是以模块化的 JavaScript 文件为入口的,所以内置了对模块化 JavaScript的解析功能,支持 AMO, CornmonJS SystemJS ES6 parser 属性可以更细粒度地配置哪些模块语法被解析、哪些不被解析。同 noParse 配置项的区别在于,parser 可以精确到语法层面,而 noParse 只能控制哪些文件不被解析。 parser 的使用方法如下:
module:{
    rules:[{
        test∶/\.js$/,
        use:['babel-loader'],
        parser:{
            amd∶false//禁用AMD
            commonjs∶ false//禁用CommonJS 
            system∶ false// 禁用SystemJS
            harmony∶false//禁用 ES6 import/export 
            requireInclude∶false//禁用require.include 
            requireEnsure∶false//禁用require.ensure 
            requireContext∶false//禁用require.context 
            browserify∶false// 禁用browserify 
            requireJs∶false//禁用 requirejs
        }
    }]
}

2.2 分析每个loader和plugin执行时长

speed-measure-webpack-plugin

  1. happyPack多核编译 Happy Pack ( github.com/amireh/happ… )将任务分解给多个子进程去并发执行,子进程处理完后再将结果发送给主进程。

  2. 缓存cache-loader

  3. dllplugin

  • 将网页依赖的基础模块抽离出来,打包到一个个单独的动态链接库中。在一个动态链接库中可以包含多个模块。
  • 需要导入的模块存在于某个动态链接库中时,这个模块不能被再次打包,而是去动态链接库中获取。
  • 页面依赖的所有动态链接库都需要被加载。

接入webpack

  • DllPlugin 插件: 用于打包出一个个单独的动态链接库文件
  • DllReferencePlugin 插件:用于在主要的配置文件中引入 DllPlugin 插件打包好的动态链接库文件。
  1. 少用loader/plugin

  2. source-map

  • 开发环境下,cheap-module-eval-source-map
  • 生产环境:hidden-source-map
  1. 优化文件监听,忽略掉不需要监听的文件

在开启监听模式时,默认情况下会监听配置的 Entry 文件和所有 Entry 递归依赖的文件在这些文件中会有很多存在于 node_modules 下,因为如今的 Web 项目会依赖大量的第三方模块, 所以在大多数情况下我们都不可能去编辑 node_modules 下的文件,而是编辑自己建立的源码文件,而一个很大的优化点就是忽略 node_modules 下的文件,不监昕它们。

相关配置如下:

module.export = {
    watchOptions : {
    // 不监听的 node_modules 目录下的文件
    ignored: /node_modules/,
}

3. 打包输出质量

  1. 区分环境 通过环境变量的值去判断执行哪个分支

if (true) {
    console.log('你正在使用线上环境');
} else {
    console.log('你正在使用开发环境');
}

使用Uglifyjs可以压缩为

console.log('你正在使用线上环境');

另外很多包对生产环境与开发环境有优化

  1. 压缩JS
  • UglifyJsPlugin :通过封装 UglifyJS 实现压缩
  • ParallelUglifyPlugin: 多进程并行处理压缩 webpack-parallel-uglify-plugin

压缩ES6: uglifyjs-webpack-plugin@beta

uglifyjs-webpack-plugin

  • 新版, 支持ES6 terser-webpack-plugin
  • 减小JS文件体积

作用域提升(合并作用域)

  • 代码体积减小
  • 提高执行效率
  • 同样注意Babel的modules配置(只支持import)
  1. Babel7优化配置
  • 在需要的地方引|入polyfill (@babel/polyfill)
  • 辅助函数的按需引入
presets: [
    [
        '@babel/preset-env',
        {
            "useBuiltIns": "usage", // polyfill按需引入
        }
    ],
],
  • @babel/plugin-transform-runtime 复用polyfill,而不是每次都生成一个polyfill函数
  1. 压缩css mini-css-extract-plugin optimize-css-assets-webpack-plugin

  2. cdn加速

module.exports = {
    // 省略 entry 配置 ...
    output: {
        // 为输出的 JavaScript 文件名加上 Hash
        filename: '[name][chunkhash:8].js',
        path: path.resolve(_dirname, '.dist'),
        指定存放 JavaScript 文件的 CDN 目录 URL
        publicPath: '//js.cdn.com/id/',
    }
}
  1. tree shaking
  • 基于ES6 import export
  • package.json中配置sideEffects
// 排除掉不需要tree-shaking的文件
"sideEffects": [
    "*.css"
  ]
  • 注意Babel默认配置的影响
presets: [
    [
        '@babel/preset-env',
        {
            modules: false, // 不需要把import转为其它语法,因为tree-shaking只支持import
        }
    ],
    '@babel/preset-react'
],

mainFields 配置采用哪个字段作为模块的入口描述,我们需要使用第三方包的es6版本

module.exports = {
    resolve: {
        // 针对Npm中的第三方模块优先采用jsnext∶main中指向的 ES6模块化语法的文件
        mainFields:['jsnext:main','browser','main']
    }
}

以上配置的含义是优先使用jsnext∶main作为入口,如果不存在,jsnext∶main就会采用 browser 或者 main 并将其作为入口。虽然并不是每个 npm 中的第三方模块都会提供ES6模块化语法的代码,但对于已提供了的代码要尽量优化。

  1. 提取公共代码

  2. 分割按需加载(异步组件,异步路由)

  3. prepack 优化代码运行效率(不成熟,不要用语线上)prepack-webpack-plugin

  4. scope hoisting 分析模块依赖关系,将可能将被打散的模块合到一个函数中

  5. 持久化缓存

  • 每个打包的资源文件有唯一的hash值
  • 修改后只有受影响的文件hash变化
  • 充分利用浏览器缓存
  1. 基于webpack的应用大小监测与分析
  • Stats分析与可视化图
  • webpack- bundle-analyzer 进行体积分析
  • speed-measure-webpack-plugin 速度分析