14-代码分离

代码分离(Code Splitting)是webpack一个非常重要的特性。它主要的目的是将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文件。比如默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载, 就会影响首页的加载速度。代码分离可以分出出更小的bundle,以及控制资源加载优先级,提供代码的加载性能

Webpack中常用的代码分离有三种模式:

  • 多入口起点:使用entry配置手动分离代码;
  • 防止重复:使用Entry Dependencies或者SplitChunksPlugin去重和分离代码;
  • 动态导入:通过模块的内联函数调用来分离代码

多入口起点

配置一个index.js和main.js的入口;他们分别有自己的代码逻辑。

注意修改output,负责output的filename写死则会报错。

entry: {
    main: "./src/main.js",
    index: "./src/index.js"
  },
  output: {
    path: resolve(__dirname, './dist'),
    filename: "[name].bundle.js"
  },

Entry Dependencies

假如我们的index.js和main.js都依赖两个库:lodash、dayjs,如果我们单纯的进行入口分离,那么打包后的两个bunlde都有会有一份lodash和dayjs;事实上我们可以对他们进行共享

entry: {
    main: { import: "./src/main.js", dependOn: "shared" },
    index: { import: "./src/index.js", dependOn: "shared" },
    shared: ["lodash", "dayjs"]
},
output: {
    path: resolveApp("./dist"),
    filename: "[name].bundle.js"
},

在代码中引入dayjs好lodash按照上一步多入口打包我们可以观察到生成的打包文件index.bundle.js和main.bundle.js的大小比使用shared的大,使用shared之后可以观察到整体的体积更小,shared的dayjs和lodash被打包在shared.bundle.js中。

SplitChunks

另外一种分包的模式是splitChunk,它是使用SplitChunksPlugin来实现的:因为该插件webpack已经默认安装和集成,所以我们并不需要单独安装和直接使用该插件;只需要提供SplitChunksPlugin相关的配置信息即可;Webpack提供了SplitChunksPlugin默认的配置,我们也可以手动来修改它的配置。比如默认配置中,chunks仅仅针对于异步(async)请求,我们可以设置为initial或者all。

const TerserPlugin = require('terser-webpack-plugin'); 
module.exports = {
  // ...其他配置
  optimization: {
    // 对代码进行压缩相关的操作
    minimizer: [
      new TerserPlugin({
        extractComments: false,
      }),
    ],
    splitChunks: {
      // async异步导入
      // initial同步导入
      // all 异步/同步导入
      chunks: "all",  
  	}
  }
};

SplitChunks自定义配置

Chunks:

  • 默认值是async
  • 另一个值是initial,表示对同步的代码进行处理
  • all表示对同步和异步代码都进行处理

minSize:

  • 拆分包的大小, 至少为minSize;
  • 如果一个包拆分出来达不到minSize,那么这个包就不会拆分;

maxSize:

  • 将大于maxSize的包,拆分为不小于minSize的包;

minChunks:

  • 至少被引入的次数,默认是1;
  • 如果我们写一个2,但是引入了一次,那么不会被单独拆分;

name:设置拆包的名称

  • 可以设置一个名称,也可以设置为false;
  • 设置为false后,需要在cacheGroups中设置名称

cacheGroups

  • 用于对拆分的包就行分组,比如一个lodash在拆分之后,并不会立即打包,而是会等到有没有其他符合规则的包一起来打 包;
  • test属性:匹配符合规则的包;
  • name属性:拆分包的name属性;
  • filename属性:拆分包的名称,可以自己使用placeholder属性;
   splitChunks: {
      // async异步导入
      // initial同步导入
      // all 异步/同步导入
      chunks: "all",
      // 最小尺寸: 如果拆分出来一个, 那么拆分出来的这个包的大小最小为minSize
      minSize: 20000,
      // 将大于maxSize的包, 拆分成不小于minSize的包
      maxSize: 20000,
      // minChunks表示引入的包, 至少被导入了几次
      minChunks: 1,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          filename: "[id]_vendors.js",
          // name: "vendor-chunks.js",
          priority: -10
        },
        // bar: {
        //   test: /bar_/,
        //   filename: "[id]_bar.js"
        // }
        default: {
          minChunks: 2,
          filename: "common_[id].js",
          //优先级
          priority: -20
        }
      }
    },

动态导入

另外一个代码拆分的方式是动态导入时,webpack提供了两种实现动态导入的方式

  • 第一种,使用ECMAScript中的 import() 语法来完成,也是目前推荐的方式
  • 第二种,使用webpack遗留的 require.ensure,目前已经不推荐使用;

动态导入的文件命名:

  • 因为动态导入通常是一定会打包成独立的文件的,所以并不会再cacheGroups中进行配置;
  • 那么它的命名我们通常会在output中,通过 chunkFilename 属性来命名
 output: {
    path: resolveApp("./build"),
    filename: "[name].bundle.js",
    chunkFilename: "[name].[hash:6].chunk.js"
 },

但是,你会发现默认情况下我们获取到的 [name] 是和id的名称保持一致的:

  • 如果我们希望修改name的值,可以通过magic comments(魔法注释)的方式.
import(/* webpackChunkName:"testES" */"./js/testES.js").then(res=>{
  console.log(res);
})

optimization.chunkIds

optimization.chunkIds配置用于告知webpack模块的id采用什么算法生成。

  • 有三个比较常见的值:
    • natural:按照数字的顺序使用id;
    • named:development下的默认值,一个可读的名称的id;
    • deterministic:确定性的,在不同的编译中不变的短数字id
  • 最佳实践
    • 开发过程中,我们推荐使用named;
    • 打包过程中,我们推荐使用deterministic;
module.exports = {
  //...
  optimization: {
    chunkIds: 'named',
  },
};

代码的懒加载(比如路由懒加载)

动态import使用最多的一个场景是懒加载

  • 封装一个component.js,返回一个component对象
  • 我们可以在一个按钮点击时,加载这个对象
const element = document.createElement('div');

element.innerHTML = "Hello Element";

export default element;
const button = document.createElement("button");
button.innerHTML = "加载元素";
button.addEventListener("click", () => {
  // prefetch -> 魔法注释(magic comments)
    /* webpackPrefetch: true */
    /* webpackPreload: true */
  import(
    /* webpackChunkName: 'element' */
    /* webpackPrefetch: true */
    "./element"
  ).then(({default: element}) => {
    document.body.appendChild(element);
  })
});

Prefetch和Preload

webpack v4.6.0+ 增加了对预获取和预加载的支持。

  • 在声明 import 时,使用下面这些内置指令,来告知浏览器:
    • prefetch(预获取):将来某些导航下可能需要的资源
    • preload(预加载):当前导航下可能需要资源
  • 与 prefetch 指令相比,preload 指令有许多不同之处
    • preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开 始加载
    • preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
    • preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。

externals

通常一些比较出名的开源框架都会将打包后的源码放到一些比较出名的、免费的CDN服务器上。

 externals: {
    // window._
    lodash: "_",
    // window.dayjs
    dayjs: "dayjs"
  },
  
  //index.html
    <!-- ejs中的if判断 -->
  <% if (process.env.NODE_ENV === 'production') { %> 
  <script src="https://unpkg.com/dayjs@1.8.21/dayjs.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
  <% } %> 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值