webpack基础篇(五):代码分离(Code Splitting)

代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,会极大缩减加载时间。

常用的代码分离方法有三种:

  • 入口起点:使用 entry 配置手动地分离代码。

  • 防止重复:使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离chunk

  • 动态导入:通过模块的内联函数 import 调用来分离代码。


1. 入口起点

入口起点(entry points)

这是迄今为止最简单直观的分离代码的方式。不过,这种方式手动配置较多,并有一些隐患,我们将会解决这些问题。先来看看如何从 main bundle 中分离 another module(另一个模块):

src 目录下创建 another-module.js 文件:

src/another-module.js

import _ from 'lodash'

console.log(_.join(['another', 'module', 'chunk'], ' '));

这个模块依赖了 lodash ,需要安装一下:

npm install lodash

webpack.config.js

module.exports = {
    
    
  entry: {
    
     // 配置多入口文件
    index: './src/index.js',
    another: './src/another_module.js'
  },
   output: {
    
    
      filename: 'bundle.js',
      path: path.resolve(__dirname, './dist'),
    },
}

执行webpack命令,可以看到报错了 ̄□ ̄||
在这里插入图片描述

这个错误表明发生了冲突,多个入口文件打包后出现了相同的filename,所以我们要对多个入口文件设置多个出口不同文件名文件

webpack.config.js

module.exports = {
    
    
  entry: {
    
    
    index: './src/index.js',
    another: './src/another_module.js'
  },
  output: {
    
    
    filename: '[name].bundle.js', // 对应多个出口文件名
    path: path.resolve(__dirname, './dist'),
  },
}

执行webpack命令,可以看到不报错了,并且dist输出了两个js文件
在这里插入图片描述
在这里插入图片描述
文件another.bundle.js来源于entry.another,即src/another.js,文件大小为1.37M,因为被lodash被打包进去了

文件index.bundle.js来源于entry.index,即src/index.js,文件大小为96kb

查看dist/app.html可以看到,两个js文件已经被写入script中
在这里插入图片描述

执行npx webpack-dev-server可以看到,js文件加载也是正常的,控制台也能打印出another module chunk

但是,如果我们的其他入口也需要使用lodash呢?

src/index.js

import _ from 'lodash'

console.log(_.join(['index', 'module', 'chunk'], ' '));

执行webpack命令,可以看到打包成功
在这里插入图片描述

但是index.bundle.js明显变大了很多,这是因为它也将lodash打包进去了

执行npx webpack-dev-server,可以看到控制台打印输出了another module chunk index module chunk
在这里插入图片描述

问题

我们发现,lodash在两个引用文件中都被打包了,我们期望lodash应该是公用的,但是使用这种方式造成了重复打包问题


2. 防止重复

2.1 配置 entry 提取公用依赖

webpack.config.js

module.exports = {
    
    
  entry: {
    
    
    index: {
    
    
      import: './src/index.js', // 启动时需加载的模块
      dependOn: 'shared', // 当前入口所依赖的入口
    },
    another: {
    
    
      import: './src/another_module.js',
      dependOn: 'shared',
    },

    shared: 'lodash' // 当上面两个模块有lodash这个模块时,就提取出来并命名为shared chunk
  },
  output: {
    
    
    filename: '[name].bundle.js', // 对应多个出口文件名
    path: path.resolve(__dirname, './dist'),
  },
}

执行webpack命令,可以看到打包结果
在这里插入图片描述

已经提取出来shared.bundle.js,即为提取打包了lodash公用模块

index.bundle.js another.bundle.js体积也变小

查看dist/index.html可以看到三个文件都被加载了

执行npx webpack-dev-server可以看到页面加载正常,打开控制台可以看到打印结果也正常输出了


2.2 SplitChunksPlugin

SplitChunksPlugin能自动的帮助我们做公共模块的抽离

webpack.config.js

module.exports = {
    
    
  entry: {
    
     // 多入口
    index: './src/index.js',
    another: './src/another_module.js',
  },
  output: {
    
    
    filename: '[name].bundle.js', // 对应多个出口文件名
    path: path.resolve(__dirname, './dist'),
  },
  optimization: {
    
    
    splitChunks: {
    
     // 代码分割
      // include all types of chunks
      chunks: 'all' 
    }
  },
}

执行webpack可以看到打包结果,文件已经被分割为chunks
在这里插入图片描述

执行npx webpack-dev-server可以看到代码也是正常加载的


3. 动态导入

当涉及到动态代码拆分时,webpack 提供了两个类似的技术。第一种,也是推荐选择的方式是,使用符合 ECMAScript 提案 的 import() 语法 来实现动态导入。第二种,则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure 。

这里让我们尝试使用第一种方式

首先,我们将之前的代码注释一部分

webpack.config.js

module.exports = {
    
    
  entry: {
    
     // 多入口
    index: './src/index.js',
    // another: './src/another_module.js',
  },
  output: {
    
    
    filename: '[name].bundle.js', // 对应多个出口文件名
    path: path.resolve(__dirname, './dist'),
  },
  optimization: {
    
    
    // splitChunks: {
    
    
    //   // include all types of chunks
    //   chunks: 'all'
    // }
  },
}

src.index.js

// import _ from 'lodash'
//
// console.log(_.join(['index', 'module', 'chunk'], ' '));

在 src 下创建 async-module.js 文件:

function getComponent() {
    
    
  // import 返回 Promise
  // 加载一个模块
  return import('lodash').then(({
     
      default: _ }) => {
    
    
    const element = document.createElement('div');
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    return element
  }).
  catch((error) =>'An error occurred while loading the component')
}
getComponent().then(component => {
    
    
  document.body.appendChild(component)
})

src/index.js

import './async-module';

执行webpack,可以看到公用模块也已经被抽离了
在这里插入图片描述

执行npx webpack-dev-server,可以看到页面上加载了一个Hello webpack

这表明动态导入能实现抽离模块

那么如果动态导入静态导入一起使用会发生什么呢

src/index.js将之前的注释解开

import _ from 'lodash'

console.log(_.join(['index', 'module', 'chunk'], ' '));

执行webpack命令成功,但是我们发现并没有实现代码分离
在这里插入图片描述

这说明一旦我们加入了静态资源时,我们需要开启optimization.splitChunks.chunks

module.exports = {
    
    
  // ...
  optimization: {
    
    
    splitChunks: {
    
    
      // include all types of chunks
      chunks: 'all'
    }
  },
}

执行webpack命令,可以看到打包成功,打包结果
在这里插入图片描述

可以看到已经抽离出了公用模块

执行npx webpack-dev-server可以看到资源加载成功

再次开启多入口entry

webpack.config.js

module.exports = {
    
    
  entry: {
    
     // 多入口
    index: './src/index.js',
    another: './src/another_module.js',
  },
  output: {
    
    
    filename: '[name].bundle.js', // 对应多个出口文件名
    path: path.resolve(__dirname, './dist'),
  },
  optimization: {
    
    
    splitChunks: {
    
     // 代码分割
      // include all types of chunks
      chunks: 'all' 
    }
  },
}

执行webpack可以看到打包结果,文件已经被分割为chunks
在这里插入图片描述

执行npx webpack-dev-server可以看到资源依旧加载成功


4. 懒加载

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。

我们之前创建过src/math.js

export function add (x, y) {
    
    
  return x + y
}

export function reduce (x, y) {
    
    
  return x - y
}

src/index.js

const button = document.createElement('button')
button.textContent = '点击执行加法运算'
button.addEventListener('click', () => {
    
    
 // 魔法注释 webpackChunkName 修改懒加载打包文件名
 // 即使不使用 webpackChunkName,webpack 5 也会自动在 development 模式下分配有意义的文件名。
 import(/* webpackChunkName: 'math' */ './math.js').then(({
     
      add }) => {
    
    
  console.log(add(4, 5))
 })
}) 
document.body.appendChild(button)

执行webpack可以看到多出了一个新的js文件,打开可以看到这个文件里包含了我们写入的add、reduce函数。可见这个模块已经被单独的抽离了
在这里插入图片描述

执行npx webpack-dev-server,可以看到页面上已经有了一个按钮
在这里插入图片描述

上图可以看到,点击按钮后才加载math.bundle.js并执行了函数打印输出结果


5. 预获取、预加载

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

在声明 import 时,使用下面这些内置指令,可以让 webpack 输出 “resource hint(资源提示)”,来告知浏览器:

  • prefetch(预获取):将来某些导航下可能需要的资源(当页面所有内容都加载完毕后,在网络空闲的时候,加载资源)

  • preload(预加载):当前导航下可能需要资源

5.1 prefetch

src/index.js

const button = document.createElement('button')
button.textContent = '点击执行加法运算'
button.addEventListener('click', () => {
    
    
 // webpackPrefetch: true 在动态引入时开始预获取
 import(/* webpackChunkName: 'math', webpackPrefetch: true */ './math.js').then(({
     
      add }) => {
    
    
  console.log(add(4, 5))
 })
})
document.body.appendChild(button)

执行npx webpack-dev-server,可以看到math.bundle.js已经预先获取了
在这里插入图片描述

5.2 preload

与 prefetch 指令相比,preload 指令有许多不同之处:

  • preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
  • preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
  • preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
  • 浏览器支持程度不同。


源码地址:https://gitee.com/yanhuakang/webpack-test

如果有用,就点个赞吧(\*^▽^\*)

猜你喜欢

转载自blog.csdn.net/qq_41887214/article/details/121646392
今日推荐