写点什么

webpack 高级配置

作者:Geek_02d948
  • 2022-11-02
    浙江
  • 本文字数:3946 字

    阅读完需:约 13 分钟

摇树(tree shaking)

我主要是想说摇树失败的原因(tree shaking 失败的原因),先讲下摇树本身效果

什么是摇树?

举个例子


首先 webpack.config.js 配置


const webpack = require("webpack");
/** * @type {webpack.Configuration} */module.exports = { mode: "production"};
复制代码


在固定 a.js 用 esm 导出,b.js 用 commonjs 导出不变动


// a.jsexport function f1() {  console.log("11111");}export function f2() {  console.log("22222");}
// b.jsexports.f3 = function () { console.log("33333");};exports.f4 = function () { console.log("44444");};
复制代码

例子 1:import a.js 和 require b.js

// index.jsimport { f1 } from "./a";import { f3 } from "./b";console.log(f1);console.log(f3);
复制代码


打包结果:a.j 和 b.js 都摇树了,只输出了 f1 和 f3。所以导入用import,导出esm和commonjs都可以


例子 2:import a.js 和 import b.js

// index.jsimport { f1 } from "./a";const { f3 } = require("./b");console.log(f1);console.log(f3);
复制代码


打包结果:a.js 摇,b.js 没摇,输出了 f1 、f3、f4。所以导入用require不成功



结论:


摇树只能import,导出用esm和commonjs都可以


因为摇树发生在编译阶段,只支持 esm 的 import,不支持 commonjs 的 require,因为 esm 是编译时,commonjs 是运行时

摇树失败的原因

三方面可能导致失败:


1、代码没用 import 引入


2、webpack 配置没开启摇树


3、副作用(sideEffects)


4、babel 配置 preset-env 没写 module:false 参数

代码没用 import 引入

这一点上面已经说明,必须用 import 导入,导出用 esm 或者 commonjs 都行

webpack 配置没开启摇树

开启摇树两步:


1、usedExports 设置 true,标记无用代码,esm 导出的没使用到的导出函数标记为unused harmony export f2,commonjs 导出的没使用的导出函数赋值为__webpack_unused_export__


2、terser-webpack-plugin 插件做代码压缩去除无用代码,根据一步两种标记,压缩代码会去除


const webpack = require("webpack");
/** * @type {webpack.Configuration} */module.exports = { mode: "none", optimization:{ usedExports:true }};
复制代码


  • mode: production模式下,默认开启摇树,不用做任何配置,由源码看出nonedevelopment不会开启摇树,需要手动加这两步,注意要设置minimize:true,或者放到 plugins 中


看 webpack 源码默认配置,参考 webpack 视频讲解:进入学习


副作用(sideEffects)

先来解释下什么是副作用:修改当前作用域之外的行为都叫副作用,比如在函数内部,修改 dom,修改全局对象等等


这条主要是针对引入三方包,三方包package.json的sideEffects字段默认true表示有副作用,可以设置为 false 表示没有副作用,设置为数组列出有副作用的文件


webpack.config.js设置sideEffects:true表示检查三方包的sideEffects字段,webpack 在用 userExports 标记无用代码时,如果判断不出库中代码是否有副作用,就不会标记,则压缩的时候也没法清除,如果判断有副作用,则更不会标记清除


  • mode: production模式下,默认开启摇树,不用做任何配置,usedExports: true


const webpack = require("webpack");const TerserPlugin = require("terser-webpack-plugin");
/** * @type {webpack.Configuration} */module.exports = { mode: "none", optimization:{ sideEffects:true, usedExports:true }, plugins:[ new TerserPlugin() ]};
复制代码

babel 配置 preset-env 没写 module:false 参数

在文章 我掌握的 Babel 配置 中详细讲解了 module: false 参数,简单说不设置 false 时,只针对babel相关的runtime包的引入会使用 require,设置了 false 引入会使用 import,就能让 webpack 去摇树,回到第一点上


module.exports = {  presets: [    [      "@babel/preset-env",      {        modules: false      },    ],  ]};
复制代码

拆包(splitChunks)

splitChunks 是 webpack 配置下 optimization 下的配置,即优化。看单词理解意思就是拆分多个 chunk。

什么是 chunk

webpack 的本质是把多个 js 模块合并到一个 js 中,即一个入口得到一个输出 js 文件(bundle.js)。


但是导致的问题是,如果这个 bundle.js 文件很大,那么浏览器请求的时候,导致请求时间很长,首屏长时间白屏。


所以优化手段就是把 bundle.js 文件拆分成多个小的 js 文件,同时请求,首屏当然就更快渲染显示。


所以入口文件,chunk 文件,输出文件三者的关系从原来的一个入口文件对应一个chunk最后输出一个bundle文件改变为一个入口文件对应多个chunk最后输出多个bundle文件

三种方式获得 chunk

  • 1、入口文件可以生成 chunk,入口文件即 webpack 配置的entry选项;

  • 2、异步请求 import 函数调用 或者 require.ensure 可以生成 chunk;如:import 函数即我们在写 vue-router 时写的异步请求路由方式,这里webpackChunkName可以魔法定义 chunk 名,也可不写


import(/* webpackChunkName: "AboutPage" */'./view/about.vue')
复制代码


  • 3、webpack 配置 splitChunks 手动拆分生成 chunk,最后独立输出到 js 文件

splitChunks 配置

简单配置,把 react 相关包都单独提到一个文件


{   optimization: {    splitChunks: {      chunks: "all", // initial、async和all      cacheGroups: {        react: {          name: "react",          test: /[\\/]react(\w)*[\\/]/i,          priority: 10        },        lodash: {          name: "lodash",          test: /[\\/]lodash(\w)*[\\/]/i,          priority: 20,          minChunks:3        },      },    },  },}
复制代码


先来看下 webpack 默认的 splitChunks 参数



看图production非production模式下有参数不一样,下面这些参数表示自动拆包的条件:


  • chunks


重要:拆包的范围,默认 async,只针对异步请求的,即上面第二条的 import 函数调用的 chunk 里面;initial 表示只针对初始化入口 entry 的;all 表示最大包含 async + entry


  • cacheGroups


重要:自定义拆包规则,name 是 chunk 名,test 正则包名,priority 优先级(因为同一个包可能符合多个拆包规则,会处理给优先级高的);看图可知,默认会有两个包规则,defaultVendors规则表示node_modules会拆到一个 chunk 包,default规则表示只有被两个即以上 chunk 引用就要拆到一个 chunk 包


  • minChunks


拆分前必须共享模块的最小 chunks 数,可以不用修改


  • maxAsyncRequests


浏览器发送异步请求时,最大不超过 30 个请求,即上面第二条的 import 函数调用,可以不用修改


  • maxInitialRequests


浏览器请求入口 entry 时,最大不超过 30 个,可以不用修改

热更新

我们主要是说明热更新的 module.hot.accept()


先来了解一下热更新怎么配置的?

热更新配置

装包


npm i -D webpack-dev-server html-webpack-plugin
复制代码


webpack.config.js


const webpack = require("webpack");const HtmlWebpackPlugin = require('html-webpack-plugin');
/** * @type {webpack.Configuration} */module.exports = { mode: "development", devServer: { port: 3000, open: true, hot: true, }, plugins: [ new HtmlWebpackPlugin(), ]};
复制代码


package.json


"scripts": {    "serve": "webpack serve",},
复制代码

结论

到此热更新配置完成,正常写代码,但是发现问题了,此时更新页面是整个刷新页面的,并不是局部刷新,怎么回事呢,原来需要在每个文件中最后加上module.hot.accept()才会触发局部更新,accept 可以接受两个参数,依赖和回调


exports.f3 = function () {  console.log("33333");};exports.f4 = function () {  console.log("44444");};if (module.hot) {  module.hot.accept();}
复制代码


随即产生了另一个疑问,这太麻烦了吧,每个文件文件都需要去加module.hot.accept(),但是我们在实际写下项目的时候怎么没有写这句呢?


原因是不论 css、vue、react 的 loader 都帮我们自动加了这句。


css 有 style-loader,react 有 react-hot-loader,vue 有 vue-loader。


对于 jsx 文件,有vue-jsx-hot-loader


{    test:/\.jsx?$/,    use:['babel-loader','vue-jsx-hot-loader']}
复制代码

按需加载

一段时间以来,我一直把 tree shaking 和按需加载混为一谈,其实应该分开理解,这里我主要是想说第三方包的按需加载,比如使用 element-ui、lodash、vant


tree shaking 的前提是使用 import 导入,但是按需加载并不需要


还有一个点需要注意:如果是我们封装的库,如组件库,导出格式根据文件类型不同,如是 js 文件可以为 commonjs + es5、esm + es5;如是 vue 或 react 文件,esm/commonjs + es6/es5 任意都行,因为我们用 babel-loader 时会排除 node_modules 目录不编译,vue-loader 等会去编译 vue 文件


使用 babel 插件


npm install babel-plugin-component -D
复制代码


babel.config.js


module.exports = {  presets: [    [      "@babel/preset-env",      {        useBuiltIns: "usage",        corejs: 2,        modules: false,      },    ],  ],  plugins: [    ["@babel/plugin-transform-runtime"],    [      "babel-plugin-import",      {        libraryName: "vant",        libraryDirectory: "es",        style: true,      },      "vant",    ],    [      "babel-plugin-import",      {        libraryName: "antd",        style: true, // or 'css'      },    ],    [      "babel-plugin-import",      {        "libraryName": "lodash",        "libraryDirectory": "",        "camel2DashComponentName": false,  // default: true      },      "lodash",    ],    [      "babel-plugin-component",      {        libraryName: "element-ui",        styleLibraryName: "theme-chalk",      },      "element-ui",    ],  ],};
复制代码


完毕!


用户头像

Geek_02d948

关注

还未添加个人签名 2022-09-08 加入

还未添加个人简介

评论

发布
暂无评论
webpack高级配置_webpack_Geek_02d948_InfoQ写作社区