Webpack的学习

271 阅读19分钟

Webpack是什么?

Webpack 是一个用于现代JavaScript 应用程序的静态模块打包工具,你可以用它来做很多事。


Webpack可以做什么?

  • 你可以将你的JS文件Bundle成单一的文件。

BundleJS文件可以让我们撰写模块化的JavaScript。

  • 在你的前端代码里使用npm packages。

可以在代码中使用npm这个大型的远程包仓库。可以使用或者发布packages。有很多我们需要的组件模块。

  • 撰写JavaScript ES6或者ES7。

使用ES6等,给JavaScript加入新功能,新特性,让代码书写更加方便。

  • Minify或优化代码。

减少文件大小,优化代码可读性,优化加载过程与时间。

  • 将LESS或SCSS转化为CSS。

使用更好的方法来撰写css。

  • 使用 HMR(Hot Module Replacement)。

热更新,可以让你一边编写代码,在不需要刷新网页的情况下即可展示在网页上。增加开发速度。

等等等等。


安装

打开webpack官网 webpack.js.org/,此篇博客基于webpack版本5.73.0编写。

首先要安装最新版本的node.js。

可以将webpack安装在全局,亦可安装在本地。若要安装在全局,可使用命令行输入

npm install -g webpack

来使用webpack的大部功能。但然而webpack的有些功能,像是优化的plugins,则需要安装在本地。

官网的建议是对于大多数的项目进行本地安装。不推荐全局安装 webpack。因为这会将你项目中的 webpack 锁定到指定版本,并且在使用不同的 webpack 版本的项目中,可能会导致构建失败。

这里以官网要求,也以我新创建的项目为例。 新建一个项目文件夹webpack-demo, 在目标文件夹命令行使用npm init -y初始化文件夹,并且生成一个package.json

mkdir webpack-demo
cd webpack-demo
npm init -y

接下来进行webpack的本地安装,由于webpack并不是我的文件运行必要的,所以安装在dev下。

npm install --save-dev webpack 

然后还要安装webpack的命令行工具。

npm install --save-dev webpack-cli

这个时候就可以在package.json里看到安装好的webpack和命令行工具。

webpack安装.png


起步

这时候我就来初始化我的项目了。 使用mkdir src创建一个src路径。进入这个路径。

使用touch index.js创建一个index.js。编辑器打开这个文件。 写入

function component(){
const element = document.creatElement('div');
element.innerHtml = _.join(['Hello','webpack'],' ');
return element;
}

document.body.appendChild(component());

注意, _.join()是lodash或者underscore库提供的方法,应当在HTML的<script>标签中加载这两个库之一。才能使用element.innerHtml = _.join(['Hello','webpack'],' ')这行代码。

那么该如何让这段代码运行呢?

回到上一层目录

使用touch index.html创建index.html。 这个时候的路径结构是这样的

刚开始的结构.png

编辑器打开index.html, 写入

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Getting Started</title>
    <script src="https://unpkg.com/lodash@4.17.20"></script>
  </head>
  <body>
    <script src="./index.js"></script>
  </body>
</html>

导入了lodash库以及刚刚我们写的index.js,即可运行。 页面上会显示拼接成的字符串 Hello webpack

这种方法很简单,也符合我们的直觉。但是还是要有注意的点。 我们还需要调整 package.json 文件,以便确保我们安装包是私有的(private),并且移除 main 入口。这可以防止意外发布你的代码。 也就是

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
 + "private":"true",       加上这句
 - "main": "index.js",     去掉这句,不需要给别人用,不需要别人引入
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.73.0",
    "webpack-cli": "^4.10.0"
  }
}

并且,上面这个例子里还有<script>标签之间的隐式依赖关系。说人话就是,index.js里使用_的时候,并没有在index.js里声明引入lodash。而是在index.html里才引入了lodash。这就导致index.js在执行之前要先依赖html中的lodash。因为是隐式声明,所有index.js默认已经存在了一个叫做_的全局变量。

但是这样的话会出现一些问题

  • 没有办法立即执行,脚本执行需要依赖外部扩展库。
  • 如果依赖不存在,亦或者上面的例子里,引入lodashindex.js的脚本顺序错误也会导致程序无法运行。
  • 如果依赖被引入,但是并未被使用,那就是没用的,浏览器会被迫下载依赖的代码,造成浪费。

这时我们就要用webpack进行处理,创建一个 bundle 文件。

在目标文件夹下新建一个路径叫做dist。 现在的路径结构是这样的。

现在的结构.png

开发引入模块化之后,我们就不需要在html里引入了。 首先执行npm install --save lodash来安装lodash包。 这时lodash显示已经被安装在Dependency里。

lodash安装完毕.png

在安装一个要打包到生产环境的安装包时,例如项目里运行依赖的包和模块,你应该使用 npm install --save,如果你在安装一个用于开发环境的安装包(例如,linter, 测试库等),你应该使用 npm install --save-dev

这时我们来使用ES6的模块化写法importindex.js脚本里引入刚刚安装好的lodash包。

+ import _ from 'lodash';   加上这句,即可直接依赖lodash包,使用全局变量_。
function component(){
const element = document.creatElement('div');
element.innerHtml = _.join(['Hello','webpack'],' ');
return element;
}

document.body.appendChild(component());

并且修改dist目录下的index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Getting Started</title>
  - <script src="https://unpkg.com/lodash@4.17.20"></script>  去掉这句,因为已经安装了。
  </head>
  <body>
  -  <script src="./index.js"></script>  去掉这句,增加下一句。
  +  <script src="main.js"></script>     webpack会以脚本作为起点,输出一个main.js
  </body>
</html>

在这个设置中,index.js 显式要求引入的 lodash 必须存在,然后将它绑定为 _(是仅在当前作用域下的变量,没有全局作用域污染)。通过声明模块所需的依赖,webpack 能够利用这些信息去构建依赖图,然后使用图生成一个优化过的,会以正确顺序执行的 bundle。

在 webpack 4 中,可以无须任何配置使用,所以可以直接打包。

命令行输入npx webpack,将会以src中的脚本为入口(实际工作使用上需要配置),输出为main.js

webpack npx.png 这时候浏览器打开dist中的index.html,页面也能成功显示Hello webpack


webpack配置

当我们想实现各种各样想要实现的功能,例如修改main.js的命名,选择脚本入口等等,单单的命令行并不能让我们能实现这些功能。这时候我们就需要对webpack进行配置。

我们这时创建一个webpack.config.js,在本地主目录下。

创建配置.png

webpack.config.js里写入

const path = require('path');

module.exports = {
entry:'./src/index.js',
output:{
filename:'bundle.js,
path:path.resolve(__dirname,'dist')
}
};

上面的配置文件里,先是引入了path模块。path模块上有一个方法是path.resolve([from ...], to),将 to 参数解析为绝对路径,给定的路径的序列是从右往左被处理的,后面每个 path 被依次解析,直到构造完成一个绝对路径。

配置文件里确定了自身暴露的内容有:entry入口output输出output中的filename文件名(这里改成了bundle.js而不是main.js),和使用了path.resolve([from ...], to)方法的输出路径path:path.resolve(__dirname,'dist')__dirname是指向被执行 js 文件的绝对路径,也就是webpack-demo这个文件夹。

这次要使用配置文件执行npx webpack --config webpack.config.js,就可以输出bundle.jsdist路径里。

bundle.png

将html中的src改为bundle.js,页面也能成功显示Hello webpack

这是最简单的配置。我们可以通过配置方式指定 loader 规则(loader rules)、插件(plugins)、解析选项(resolve options),以及许多其他增强功能。

而且,如果你认为npx webpack --config webpack.config.js这句话实在太长了,每次手打都觉得很累的情况下,可以把这句话加到package.jsonscripts里。

配置.png 这样的话,只要写一句npm run build即可完成命令。 顺便,这句话里,npx--config webpack.config.js也可以省略。


资源管理 Asset Management

首先,为了能在JS模块里引入一个CSS文件,我们需要安装两个工具。当然也是装在dev下。

npm install --sav-dev style-loader css-loader

然后我们需要修改配置文件,加上module

const path = require('path');

module.exports = {
entry:'./src/index.js',
module:{
rules:[
{
test:/\.css$/,
use:[
'style-loader','css-loader']
  }
 ]
},
output:{
filename:'bundle.js,
path:path.resolve(__dirname,'dist')
}
};

这里的module里有rules规则rules是个数组,use也是个数组。使用test进行正则表达式匹配,index.jsimport的文件,凡是以.css结尾的都被提供给style-loadercss-loader去处理。

接下来在src路径下新建style.css文件。 打开style.css,写入

.hello {
color:red;
}

这个时候,我们就可以不在HTML里操作CSS的引入了,而是打开index.js。 引入style.css(现在,当该模块运行时,含有 CSS 字符串的 <style> 标签,将被插入到 html 文件的 <head> 中。),并且在函数里新增的div添加一个叫hello的class。

import _ from 'lodash';
import './style.css;'
function component(){
const element = document.creatElement('div');
element.innerHtml = _.join(['Hello','webpack'],' ');
element.classList.add('hello')
return element;
}

document.body.appendChild(component());

现在,运行我们的构建命令npm run build

这时,浏览器再打开index.html就会发现,div变红了。

变红.png

css-loader是读取CSS文件,将里面的东西拿出来。

style-loader是在html文件的<head>里创建一个<style>标签,并且把刚刚拿出来的东西放进去。

这里需要注意一点,是先进行css-loader读取文件,再用style-loader解析插入文件。所以顺序不可错误。 loader的解析顺序是从下到上,从右往左,编译顺序应该是先用css-loader编译css代码,再用style-loader放入到网页的style标签里面去。 所以css-loader在右,style-loader在左,按照指定顺序排列。


加载图片

我们的背景和图标这些文件,要如何处理呢? 这时候需要安装新的包

npm install --save-dev file-loader

和处理CSS同理。 进行配置文件中rules的添加。

const path = require('path');

module.exports = {
entry:'./src/index.js',
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader']
  },
  {
  test:/(.png|.svg|.jpg|.gif)$/,
  use:['file-loader']
  }
 ]
},
output:{
filename:'bundle.js,
path:path.resolve(__dirname,'dist')
}
};

这时候,挑一张你喜欢的图片,记住要是png,svg,jpg,gif格式的,放入到src文件夹下。 我选择了logo.jpg 假设我们想在JS中使用这张图片,就要导入logo.jpg。并且创建一个Image标签。并将图像添加到我们这个div。

import _ from 'lodash';
import './style.css;'
imoort logo from './logo.jpg';
function component(){
const element = document.creatElement('div');
element.innerHtml = _.join(['Hello','webpack'],' ');
element.classList.add('hello');

const myLogo = new Image();
myLogo.src = logo;
element.appendChild(myLogo);

return element;
}

document.body.appendChild(component());

在进一步,我们在CSS里也使用这个图片看看。

.hello {
color:red;
background:url('./logo.jpg');
}

这时再次进行我们的打包命令,可以发现,图片已经被成功插入了。

插入图片.png


加载字体和数据

这个时候我们就知道了,想要引入某种类型的文件,就要在配置文件里加载对应的loader

如果我们要加载字体的话,file-loader也可以胜任。让我们来增加配置文件里的testuse吧。

      {
       test: /.(woff|woff2|eot|ttf|otf)$/,
        use: [
          'file-loader'
    ]
    }

和之前一样,正则表达式确认结尾为字体文件的,就会被提供给file-loader去处理。

接着,在src目录下放入我们要用的字体文件。接下来将字体使用@font-face在css里导入。

+ @font-face {
+   font-family: 'MyFont';
+   src:  url('./my-font.woff') format('woff');
+   font-weight: 600;
+   font-style: normal;
+ }

  .hello {
    color: red;
+   font-family: 'MyFont';
    background: url('./logo.png');
  }

这时候我们只要再次打包,字体文件也被导入了。

数据导入也是一样的道理。

如 JSON 文件,CSV、TSV 和 XML。

我们需要安装新的能处理数据的loader

npm install --save-dev csv-loader xml-loader

其他的操作和上面的一模一样,最后只用在index.js里加上import data form '导入的文件名的相对路径'

file-loader还有更复杂的用法,具体可参考v4.webpack.js.org/loaders/fil…


管理输出

首先,在src里创建一个print.js

写入

export default function printMe(){
  console.log('I get called form print.js!')
}

然后在index.js中引入它。

import printMe from './print.js'

由于print.js使用的暴露方法是export default,所以引入的时候可以直接使用名字,也不用解构赋值。

然后我们要使用JS来创建一个按钮,然后使人点击这个按钮就可以调用printMe()

  const btn = document.createElement('button');
  btn.innerHTML = 'Click me and check the console!';
  btn.onclick = printMe;
  };

  element.appendChild(btn);

并且打开配置文件,将entryoutput进行修改。

entry:{
  app:'./src/index.js',
  print:'./src/print.js'
},

output:{
  filename:'[name].bundle.js',
  path:path.resolve(__dirname,'dist')
}

这样就可以有两个入口,分别打包生成两个bundle.js,分别是app.bundle.jsprint.bundle.js。即可抽离公共资源。

这时我们进行HtmlWebpackPlugin的设定,来让index.html也能完美匹配我们生成的JS。

首先安装插件 html-webpack-plugin

npm install --save-dev html-webpack-plugin

接下来在配置文件里引入这个插件包。

const HtmlWebpackPlugin = require('html-webpack-plugin');

这个包的作用是用来生成一个带有引入我们的生成的JS文件的HTML文件。

接下来,在配置文件的module.exports里新增一个叫plugins的数组。

加入这个功能

plugins:[
new HtmlWebpackPlugin({
  title:'Output Management'
})
]

并且,我们在output里,加入clean:true即可清除dist文件夹中的东西,再将我们打包的东西放进去。

output:{
  filename:'[name].bundle.js',
  path:path.resolve(__dirname,'dist'),
  clean:true
}

这个时候我们再次进行npm run build,就会得到一个新的index.html

新的html.png

用浏览器打开它,发现一切都达成了我们想做的。

打包完成.png

那如果,我想要的html文件并不是简单引入了我的JS文件,而是需要别的DOM结构的,该怎么办呢?

这个时候可以使用模板,template

让我们在src里新建一个文件夹叫做public,在public中新建一个index.html

新建模板.png

我们在模板中增加我们需要的东西。

模板内容.png

这时候我们打开配置文件,在plugins中的new HtmlWebpackPlugin加入template:'./src/public/index.html'

还需要安装一个包

npm install --save html-loader

并且在配置文件的rules中加入一条规则

{
    test: /\.html$/,
    loader: 'html-loader'
  }

也就是说,以.html结尾的文件会交给html-loader处理,并且通过html-webpack-plugin成为生成HTML文件的模板。

这时我们再npm run build

会得到满意的结果。

满意的模板结果.png

其他关于html-webpack-plugin的复杂用法可以参考github.com/jantimon/ht…

  • loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中。
  • plugin 赋予了 webpack 各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader 无法实现的其他事。

开发环境

source map

在上面的学习我们知道,打包是将一大堆文件打包到一起放入dist文件夹的bundle.js里。可是当报错的时候,我们就很难从这一大堆打包到一起的东西里找到错误的地方了。这个时候就需要一些方法处理这个问题。

如果调试的时候会自动对应到我们的源代码,会不会方便很多呢? 这就要提到我们开发中非常重要的一个功能:source map

让我们首先先调整我们的配置文件吧。

设置modedevelopment, 并且将我们生成的html文件的标题改为Development

模板里的标题也改为Development

修改开发配置.png

接下来就是使用source map的时候了。

在这里使用inline-source-map,可用于说明目的(尽管不适用于生产)。

在配置文件里增加一行

devtool:'inline-source-map',

也就是变成这样:

使用source map.png

这个时候,我们故意弄一些错误出来,比如将print.js中改为:

export default function printMe(){
  console.log('I get called form print.js!')
  console.error('发生错误!')
}

这个时候,来运行我们的npm run build吧。

点击按钮,会发现报错了,并且调试还能指向我们的源代码print.js.

错误调试.png

这就是source map的作用。

开发工具

每次编译打包都需要执行我们亲爱的npm run build,显得非常麻烦。但是webpack给我们提供了几个开发工具让我们能够自动编译代码,不需要我们再手动输入命令。

有以下三种:

  1. webpack's Watch Mode
  2. webpack-dev-server
  3. webpack-dev-middleware

Watch Mode

我们可以指示 webpack "watch" 依赖图中的所有文件以进行更改。如果其中一个文件被更新,代码将被重新编译,所以不必手动运行整个构建。

如何使用呢?

让我们打开package.jsonscripts里新增一行"watch":"webpack --wathc"

也就是变成:

watch.png

这个时候我们运行npm run watch

会有奇特的效果。

我们随意更改一个文件的内容,例如将刚刚的print.js内容改为:

image.png

这时保存文件,他就自己进行了编译。

打开html文件,查看,发生确实变更生效了。

image.png

唯一的缺点是必须刷新浏览器才能看到更改。那如果我们就想更改之后立刻看见该怎么做呢?

来试试webpack-dev-server吧。

webpack-dev-server

webpack-dev-server提供了一个基本的 Web 服务器和使用实时重新加载的能力。

我们需要先安装这个包。

npm install --save-dev webpack-dev-server

接着修改配置文件,来告诉开发服务器(dev-server)在哪里查找文件。


 devServer: {
   static: './dist',
 },
 optimization: {
   runtimeChunk: 'single',
   },

以上配置告知 webpack-dev-server,在 localhost:8080 下建立服务,将 dist 目录下的文件,作为可访问文件。

但是为什么要加上这个optimization呢?我们在单个 HTML 页面上有多个入口点。没有这个,我们可能会遇到麻烦。代码拆分的知识可以讲解这点。

接下来就是在package.json里增加命令打开这个服务器了

image.png

此时我们执行npm run start,即可看到一个服务器被开启了,并且将 dist 目录下的文件,作为可访问文件。

image.png

浏览器打开http://localhost:8080/即可看见页面。

这时我们随意修改内容,即可实时呈现。

webpack-dev-middleware

webpack-dev-middleware一般是和第三方的node.js的server结合在一起使用。

在这里比如说我们使用node.js的express框架,就可以使用webpack-dev-middleware

首先,让我们安装expresswebpack-dev-middleware

npm install --save-dev express webpack-dev-middleware

然后我们要调整我们的配置文件。

output里加上

publicPath: '/'

publicPath 也会在服务器脚本用到,以确保文件资源能够在 http://localhost:3000 下正确访问。

接着配置我们的express服务器。

首先在文件夹里新建一个server.js

image.png

然后配置这个服务器。

image.png

现在在package.json里增加一个npm script

"server": "node server.js"

此时运行npm run server 即可发现:

image.png

浏览器打开http://localhost:3000 即可观察到效果。


模块热替换 Hot Module Replacement

举个例子:

我将index.js里关于按钮的操作做了一个改变。

image.png

并且让print.js打印出来:

image.png

这时点击按钮就会打印count++

image.png 但是此时,我们随意更改模块中的内容,例如给print.js,让他多打印一段话:

image.png

这时页面自动刷新,但是我们的计数又从头开始了。

image.png 这样就会丢掉我们可能好不容易得到的的中间状态。

那么有没有这么一种功能,能保留我们得到的状态,不用完全刷新,更改模块只要这个更改的内容自己变动,而别的状态不变呢?

这时我们就要用到模块热替换HMR

webpack-dev-serverv4.0.0 开始,热模块更换默认启用。

启用此功能实际上相当简单。而我们要做的,就是更新 webpack-dev-server 的配置,和使用 webpack 内置的 HMR 插件。我们还要删除掉 print.js 的入口起点,因为它现在正被 index.js 模块使用。

首先去掉入口的print.js

接着在devServer里加一个hot:true

接下来我们要指定某个模块使用HMR。

index.js中加入

if(module.hot){
module.hot.accept('./print.js')
}

这时修改print.js模块的内容就可以保存中间状态而不会完全刷新。

image.png

如果是通过 Node.js API的话,就不要把hot:true放在配置文件的devServer里了,而是作为option中的第二个参数传递。

例如:


const server = new webpackDevServer(compiler, options);
const options = {
  static: './dist',
  hot: true,
  host: 'localhost'
};

HMR修改样式表

修改样式表并不用像修改JS一样需要指定模块使用,进行玩HMR基本配置即可使用。

我们之前在学习css-loaderstyle-loader的时候已经完成了对样式表的配置。

这时随意更改样式表也不会完全刷新,改变中间状态。

image.png

image.png


tree shaking

顾名思义,就是摇树。把树上枯萎的叶子都摇下来,留下来的都是有用的好叶子。

也就是webpack可以将引用的模块里没有被使用的代码删除掉,让代码瘦身,提高编译性能,提高代码性能,节省资源。

那么怎么做呢?

首先在src目录下新建一个文件math.js

写入

export function square(x){
return x*x;
}

export function cube(x){
return x*x*x;
}

这里暴露了两个接口,cubesquare

紧接着我们在index.js里导入cube,并且打印5的立方。

import { cube } from './math.js';

console.log('5的平方是' + cube(5));

由于刚刚我们设置的模块热替换的模块是print.js,我们修改了index.js,需要重新运行npm run start

得到结果:

image.png

这时我们打开打包好的app.bundle.js

image.png

搜索cube,发现,确实被引入了。可是我们没有用到的square,也被引入了。

image.png

这里的square就是死代码。

那么如何处理这个问题呢?有一个非常简单的方法,那就是压缩输出。

压缩输出

操作非常简单,因为从webpack4开始,其他摇树方法就显得比较复杂了,而轻松修改一下mode,即可完成摇树。

我们打开配置文件

mode"development"修改为"production"

再次进行打包,再观察已经被破坏粉碎的bundle.js,发现:

image.png

只剩下被撕裂的cube了,square毫无踪影。


生产环境构建

开发环境development和生产环境production的构建目标差异很大。之前我们接触的开发环境,更多的是在想方设法构建一个方便快捷的开发环境。例如可以实时重新加载的devServer,可以调试寻根溯源的source map,可以保存中间状态修改模块内容的模块热替换HMR。而在生产环境中,我们要集中注意力在更小的捆绑包bundle,更轻量的source map,更优化的资源,来提高整个代码的效率。我们需要为每个单独的环境来编写单独的配置文件。

怎么做呢?

这里我们就可以这么想,我们需要一个大家都能用到的同样配置,然后再对各个环境的特点进行细分。

于是,我们把配置文件分为三个。一个是通用配置,一个是开发环境配置,还有一个是生产环境配置

于是我们在主目录下新增三个JS配置文件,并且删除原先的配置文件:

image.png

想要让这三个配置文件能进行一个互补的作用,我们需要再安装一个包:

npm install --save-dev webpack-merge

接着,我们在webpack.common.js里加上开发环境和生产环境都要用到的配置:

image.png 可以看到,我将入口,输出,插件,模块规则都放入了common中,这些是不论什么环境下都要用到的基本功能。

接下来我们来配置webpack.dev.js

image.png

可以看到我把开发环境中需要的mode:'development'以及source map还有及时刷新服务器和HMR放了进去。

merge(common,{})的方法来自于引入的webpack-merge包,目的是将花括号里头的内容和引入的common的内容合道一起,形成一个补集的功能,很好理解吧。

接着我们来配置webpack.prod.js

image.png

配置完毕后,我们还要调整一下我们的package.json里的npm script,我们让start成为开发模式打包,build成为生产模式打包。

image.png

指定环境

从 webpack v4 开始,指定mode的话,DefinePlugin会自动为我们配置是哪一种。如果是生产环境中,process.env.NODE_ENV === 'production' 时,一些库可能针对具体用户的环境进行代码优化,从而删除或添加一些重要代码。

并且,我们再次修改生产模式的配置文件,加上devtool: 'source-map'

image.png 这样的话,生产模式下我们也可以对源码进行调试了。避免在生产模式下使用inline-source-map或者是eval-开头的,因为会增大包的大小,降低性能。


代码分离

代码分离是为了把代码分离到不同的bundle内,按需加载或者并行加载。它可用于实现更小的捆绑包并控制资源负载优先级,如果使用得当,将对加载时间产生重大影响。

有三种通用的代码分离方法

  • 入口点entry:使用配置手动拆分代码。
  • 防止重复:使用dependOnsplitChunksPlugin去删除重复数据和拆分chunk。
  • 动态导入:通过模块的内联函数调用来拆分代码。

入口点entry

这是最简单的代码分离的方法啦。

我们首先在src目录下创建一个another-module.js,然后让他引入lodash库,并使用lodash库带的_.join()方法。

image.png

然后我们修改刚刚我们merge完的配置文件。这里需要使用开发环境配置。我们修改webpack.common.js,将entry改为index和another-module两个js文件。

image.png

然后执行npm run start使用开发环境,会生成两个bundle.js

image.png

之前我们在 ./src/index.js 中也引入过 lodash,这样就在两个 bundle 中造成重复引用。这种方法也不能支持动态拆分。

防止重复

dependOn:'shared'

dependOn:'shared'允许在chunk之间共享模块。

所以我们这里继续修改刚刚的入口。

image.png

这样就能让两个chunk都能共享lodash包。

如果我们要在单个 HTML 页面上使用多个入口点,optimization.runtimeChunk: 'single'也需要。

我们也在webpack.common.js里加上这个。

image.png

值 "single" 会创建一个在所有生成 chunk 之间共享的运行时文件。

此时再次运行npm run start,会发现生成了四个bundle.js

image.png 这样就避免了lodash的重复加载。但是还是尽量避免页面使用多个引入的入口点。

splitChunksPlugin

这个插件允许我们将公共依赖项提取到现有的条目块或全新的模块中。

让我们在webpack.dev.js增加这个优化选项。

image.png

并且把webpack.common.js的入口改回去:

image.png

运行npm run start

image.png

会发现删除了重复的依赖项another.bundle.jslodash变成了一个单独的模块,而且主包中重复的它没有了。

动态导入

动态代码拆分,webpack提供支持两种技术,一种是ES提案的import()语法,一种是webpack自己特定的require.ensure

首先我们知道import()引入会在内部用到promise,这样就好办了。

在一切开始之前,我们把没用的东西删掉。

image.png

./src/another-module.js也删掉。

现在,我们来动态导入lodash

修改index.js

image.png

可以看到import调用了一个promise对象,也就是说,如果这个包如果用到了lodash,那就加载。

image.png 可以看到大小有1.37MB的lodash包被分离出来了。

还有一种async,await的写法,其实和promise一样,只是换了一种写法。


async function getComponent() {
const { default: _ } = await import('lodash');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
 }
 getComponent().then((element) => {
   document.body.appendChild(element);
 });

还有很多内容,本博客就先说到这里啦,以后的学习中会写更多。