Webpack高级

108 阅读1分钟

开发体验

SourceMap

默认情况下,浏览器的js报错只能定位到打包后的js文件,无法定位到源代码

配置sourcemap后,可以直接定位到源代码

开发模式:打包编译速度快,只包含行映射,不包含列映射

module.exports = {
  // 其他省略
  mode: "development",
  devtool: "cheap-module-source-map",
};

生产模式:包含行/列映射,但打包速度慢

module.exports = {
  // 其他省略
  mode: "production",
  devtool: "source-map",
};

提升打包速度

模块热替换

hot module replacement, 在程序运行中,替换、添加或删除模块,而无需重新加载整个页面

module.exports = {
  // 其他省略
  devServer: {
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
    hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)
  },
};

需要注意的是:

  • css文件需要经过style-loader处理,不能单独提取css文件,文件哈希值变了
  • js文件需要手动指明热模块替换的文件,在vue和react中,社区已经实现了相应的loader
  •   // 判断是否支持HMR功能,js文件需要手动指定热替换文件
      if (module.hot) {
        module.hot.accept("./js/add.js", function () {
          console.log('file changed')
        });
      }
    

Oneof

默认情况下rules中的loader会逐个匹配,即使已经找到了匹配成功的loader。

Oneof可以保证只匹配一个loader,其余的不继续匹配了

Include/Exclude

只对文件处理/不处理,如在对js文件处理事,需要排除node_modules下面的文件

同时存在时,exclude优先级高于include

Cache

每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快

{
  test: /.m?js$/i,
  exclude: /(node_modules|bower_components)/,
  loader: 'babel-loader',
  options: {
    cacheDirectory: true, // 开启babel编译缓存
    cacheCompression: false, // 缓存文件不要压缩
  }
},

Thread

多进程打包,在js特别耗时的操作中使用。

减少代码体积

Tree shaking

production默认是开启了tree shaking的。需要注意的是tree shaking是依赖 es module分析的。webpack默认使用terser-webpack-plugin进行代码压缩,当需要自定义terser配置时,需要手动安装插件。

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()],
  },
};

webpack是没有删除未使用代码的,它这是根据依赖图,分析哪些模块和函数是没有被使用的,真正执行代码删除操作的是terser插件。

详细参考:juejin.cn/post/712787…

Babel

Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下会被添加到每一个需要它的文件中。你可以将这些辅助代码作为一个独立模块,来避免重复引入。

@babel/plugin-transform-runtime: 禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用

可以参考:zhuanlan.zhihu.com/p/147083132

{
  test: /.m?js$/i,
  exclude: /(node_modules|bower_components)/,
  loader: 'babel-loader',
  options: {
    plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
  }
},

Image minimizer

压缩项目中静态图片,在线链接无法压缩。

image-minimizer-webpack-plugin: 用来压缩图片的插件

优化代码运行性能

Code split

分割文件 + 按需加载: 打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。

参考:webpack.docschina.org/plugins/spl…

  • 为 Vendor 单独打包(Vendor 指第三方的库或者公共的基础组件,因为 Vendor 的变化比较少,单独打包利于缓存

  • 为不同入口的业务代码打包,也就是代码分割异步加载(同理,也是为了缓存和加载速度)

  • 为异步公共加载的代码打一个的包(如路由懒加载,每个路由下单独打包,import动态加载)

    document.getElementById('webpack').onclick = () => { // webpackChunkName: "dynamic":这是webpack动态导入模块命名的方式 // "dynamic"将来就会作为[name]的值显示。 import(/* webpackChunkName: "dynamic" */ './js/dynamic.js').then(({ dynamicImport }) => { console.log('here') console.log(dynamicImport()) }) }

Preload/Prefetch

使用 import 动态语法来进行代码按需加载,但是其加载速度不够好,比如:是用户点击按钮时才加载这个资源的,如果资源体积很大,那么用户会感觉到明显卡顿效果。

我们想在浏览器空闲时间,加载后续需要使用的资源。我们就需要用上 PreloadPrefetch 技术。

  • 两者均为加载资源,并不会“执行”

  • preload 告诉浏览器立即加载资源;prefetch 告诉浏览器在空闲时才开始加载资源;一般首屏资源用preload,路由资源用prefetch

  • Preload只能加载当前页面需要使用的资源,Prefetch可以加载当前页面资源,也可以加载下一个页面需要使用的资源

    new PreloadWebpackPlugin({ rel: "preload", // preload兼容性更好 as: "script", // rel: 'prefetch' // prefetch兼容性更差 }),