webpack5:基础篇

603 阅读28分钟

0、课程介绍

为什么学习Webpack?

image.png

为什么是Webpack呢?

image.png

而全新版本webpadk5则是具备了比以往版本更强大的功能,甚至是诸多企业级前端工程化的技术选型的不二选择。

学习Webpack5的前提?

_ 前端基础知识:htm、css、es6以及以上的版本
_ nodejs和工程化

课程安排?

image.png

image.png

课程收获?

image.png

第一章: 为什么需要webpack

1.0 为什么需要webpack

我们先回顾一下历史,在打包工具出现之前,我们是如何的在web 网页中去使用JavaScript代码的?

我们先来看一个文档:

image.png

这是一个html文档,在这个文档中,我们共加载了11个js文件,这11个js文件,我们又可以分为两部分来看:

第一部分:是引入的外部或者叫第三方的库以及框架文件。这里我们引入了jquery lodash bootstrap 等等。jquery和lodash是第三方的js库,可以提高我们项目的开发效率。bootstrap是第三方的U框架,可以协助我们做页面的布局,以及交互等等。

第二部分是我们自己的JavaScript文件,是项目的业务代码。比如commonjs,一些公共的文件,user 用户相关的,authentication授权相关的,像product产品,inventory库存,payment付款,checkout 结算,shipping物流等等。可见,这是一个电商平台的一些js文件。

那么一般情况下,我们自己的业务代码可能会需要依赖上面的第三方的代码。根据我们js在浏览器上的加载特性,那么所有的js代码,是从上至下来加载的。因此如果我们的业务代码,依赖上面的那些库,或者是框架代码,那么顺序一定不能颠倒。那假如我们把两组文件加载的顺序来颠倒一下,那么这个项目可能就崩溃掉了。这种传统的方式对我们开发人员来说的话,会有很大的开发的心智负担,代码也很难扩展。

我们把这些文件按照一些预定的顺序来合并到一个文件里,不就解决问题了吗?

image.png

他只加载了一个js文件,这个文件包含了11个文件,虽然解决了我们上个页面加载多个js的问题,但可能会导致其他的问题:作用域问题、文件太大、可读性差、可维护性弱等。

作用域问题:我们使用过jquery lodash bootstrap同学应该知道,这些库文件会分别在window对象上面来绑定全局的变量,比如jqurey可能会绑定$,lodash可能会绑定下滑线_等等。就连我们自己的业务文件,可能也会在全局上面绑定一些变量。这些变量会严重的污染我们的window对象,使我们的window对象变得臃肿,这就是变量的作用域问题。

文件太大:如果我们11个文件分散加载,那么页面会随着文件的加载而逐渐来显示内容。可如果我们将这11个文件合并成一个js文件,那这个脚本会带来网络瓶颈,用户得等待一段时间才能看到页面的内容,会有短暂的白屏,用户体验非常差。

可读性差、可维护性弱:如果我们把所有代码都合并在一个超大的文件里,那么对于程序的可读性以及可维护性就带来了灾难,最终是加重了我们编程人员的负担。

那如何解决这些问题?那我们接下来将继续研究。

1.1 如何解决作用域问题

IIFE

早先前我们使用的是grunt以及gulp 两个工具, 来管理我们的项目资源,这两个工具我们称之为任务执行器,他们是将所有的项目文件拼接在一起。其实事实上呢,是利用了js的立即调用函数表达式,(立即调用函数表达式 Immediately invoked function expressions IIFE)。这样就解决了大型项目的作用域问题。当脚本被封装在IIFE内部的时候,我们可以安全的拼接,或者是组合所有的文件了,而不必担心作用域冲突。

image.png

那什么是IIFE?
IIFE 既解决了作用域的问题,又可以在外部去暴露我们想暴露的内容。

image.png

如果我们想要去修改我们两段代码里的一段代码,那么我们得需要重新编译这段代码。假如说我们这个代码呢有10,000行,但是我只改了一行,那么这个文件也会重新编译,或者说这个文件也会被同时的加载。

image.png

我们为了使用lodash的一个join方法,我们却把整个 lodash 这个大的库文件全都加载下来了。那么我们能不能想一个办法把这个文件给拆成一个一个的方法的模块呢?或者说,我们来实现一个这个方法的懒加载呢?如果是我们通过手工的方法来实现,那工作量可就大了,所以下个视频我们来研究如何的去实现代码的拆分。

1.2 如何解决代码拆分问题

image.png

node.js

我们先感谢一下node.js,它使javascript模块诞生了。我们知道node.js是一个javascript运行环境,我们可以在浏览器环境之外的计算机或者是服务器上使用它。

webpack其实就是运行在node.js 中的。

当node.js发布的时候,一个新的时代就开始了。

既然我们不是在浏览器中运行javascript,那么现在就没有了可以添加到我们浏览器的html文件,以及我们编写的script标签了。那么node.js究竟是如何的去加载新的代码文件?

common.js

common.js 的问世引入了一个叫做require的机制,他允许在我们当前的文件中,去加载和使用某个模块,导入需要的每个模块。这个开箱即用的功能帮助我们解决了代码的拆分问题。

我们通过代码来给大家演示一下。

image.png

现在node.js俨然已经成为一种语言,一个平台,乃至快速开发和创建应用的一种方式了,他接管了我们整个JavaScript世界。

通过刚才的例子我们看到,虽然commonjs是node.js项目的绝佳模块拆分的解决方案,但是浏览器是不支持这个模块化的。我们在node.js里边开箱即用的一个加载模块的方法require(),在浏览器上失效了。

那我们该如何的解决这个何题?下一个小节我们再见。

1.3 如何让浏览器支持模块

在早期,我们使用类似于像browserify、requirejs这样的打包工具分别来将CommonJS、AMD 模块化语法的代码转换为能够在浏览器中运行的代码。

image.png

Webpack可以帮助我们打包javascript的应用程序,并且同时支持es的模块化以及commonjs,还可以扩展支持很多的静态资源打包,比如像图片、字体文件、样式文件等等。

1.4 webpack与竞品

image.png

下面我们来看一下webpack、PARCEL、rollup.js、Vite的 pk。

PARCEL号称是零配置,用户一般无需做其他的配置开箱即用。

rollup.js用标准化的格式来编写代码,比如像es6,通过减少无用的代码,来尽可能的缩小包的体积。一般只能用来打包js。

如果你想要构建一个简单的应用,并且让他快速的运行起来,那你可以使用PARCEL。

如果你想要构建一个类库,只需要导入很少的第三方的库,那你可以使用rollup。

如果你想要构建一个复杂的应用,并且想集成很多的第三方的库,并且还要拆分代码,还要使用静态的资源文件,还要支持common.js、esmodule等等,那只能是webpack了。

最后的一匹黑马那就是Vite,在刚刚结束的这个VueConf 2021大会里边,除了Vue3.0以外,另外的一个亮点就是下一代的构建工具Vite。Vite将成为Vue的现代标配,甚至最近还推出了一个框架叫petite Vue,从开发、编译、发布、demo,几乎全部都使用的是Vite来完成的。Vite这种基于esmodule的构建方式,日益的受到了用户的青睐,不仅可以按需编译、热模块的更新等等,它还有其他的丝滑的开发体验,以及和Vue3的完美结合。

webpack、Vite 作为前端热门的工程化工具,他们各自有各自的使用场景,其实并不存在取代的这一说法。

第二章: 小试webpack

2.1 开发准备

在进入Webpack世界之前,我们先来用原生的方法构建一个web应用。

//hello-world.js
function helloworld(){
    console.log('hello world')
}
//index.js
helloworld()
//index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <script src="./hello-world.js" type="text/javascript" charset="utf-8"></script>
        <script src="./index.js" type="text/javascript" charset="utf-8"></script>
    </body>
</html>

在浏览器中直接打开index.html,控制台输出:

image.png

原始应用 存在的问题:如果js文件非常多,它们的引入顺序将难以维护。

2.2 安装Webpack

安装Node.js

在开始之前,请确保安装了Node.js的最新版本。使用Node.js 最新的长期支持版本(LTS -Long Term Support),是理想的起步。使用旧版本,你可能遇到各种问题,因为它们可能缺少webpack功能,或者缺少相关package。

检查Node.js是否安装成功:在命令行中,执行node -v,如果打印出版本号,则说明安装成功。

注意:Node.js V14版本 只支持Windows 8.1、Windows Server 2012 R2或更高版本。Windows7装不了???

Node.js 自带 npm。npm 全称为node package manager,它是node的一个包管理工具。

检查npm版本:npm -v

image.png

webpack是基于node来开发的,所以安装webpack就得需要通过npm。把我们的webpack当成是一个node的包,来进行安装和管理。

安装webpack

webpack的安装有两种情况:一种我们可以在全局里安装webpack,另一种我们可以在本地的工作目录下安装webpack。

webpack含两个包:一个是webpack主包,一个是webpack-cli。

webpack-cli 表示我们可以在命令行里边执行webpack命令。

1.全局安装

npm install webpack webpack-cli --global

全局安装的好处:可以让我们在任何的目录下面去执行webpack。 检查是否安装成功:

webpack -v

image.png

卸载全局的webpack:

npm uninstall webpack webpack-cli --global

2.本地安装(推荐)

我们并不推荐大家在全局里安装webpack,因为这样会使你项目的webpack锁定到某个版本里,并且你在使用不同的webpack版本的项目里边可能会导致构建失败。另外还有一个问题,如果是一个团队协作的项目,你的小伙伴如果不知道在全局里安装webpack,构建也会有问题。推荐大家还是在本地的工作目录下去安装webpack。

生成 包管理文件 package.json

npm init -y

本地安装webpack

npm install webpack webpack-cli --save-dev

基本的目录结构如下:
image.png

image.png

2.3 运行Webpack

准备测试项目

1.使用 esmodule 模块化语法,编写以下测试代码。

//hello-world.js

function helloworld(){
    console.log('hello world')
}

//导出模块
export {
    helloworld
}
//index.js

//导入模块
import { helloworld } from './hello-world.js'

helloworld()

image.png

2.执行打包命令。
在项目 根目录 命令行 执行:

webpack

3.打包结果。
报错了。

查看webpack打包信息:

webpack --stats detailed

方式1:使用全局的webpack进行打包

直接执行打包命令 webpack,默认会去使用全局安装的webpack。如果全局没有安装webpack,也不会使用本地的,而是会报错。

方式2:使用本地的webpack进行打包 (推荐)

需要使用一个新的工具npx,npx依托于npm,npm自带npx。 npx的作用:观察我们当前文件夹里面有没有你想要去运行的这个命令,如果没有则往上一层父级目录中查找,如果有则执行。

npx webpack

2.4 自定义Webpack配置

查看命令行参数

实际上,webpack-cli给我们提供了丰富的终端命令行指令,可以通过 webpack --help 来查看:

image.png

方式1:命令行参数

1.执行打包命令,同时添加一些参数,来自定义打包配置。
npx webpack --entry ./src/js/index.js --mode production

--entry 指定入口文件
--mode 指定打包模式

2.执行结果。

image.png

image.png

可以发现,打包成功,输出了一个目录dist,以及一个文件main.js。

缺点: 添加命令行参数的方式,不直观,也不方便,而且还不能保存下来。

方式2:配置文件(推荐)

1、在项目根目录中创建 webpack.config.js文件。

注意:
1.配置文件的名字不能修改,Node.js会自动读取。
2.必须使用CommonJS模块化语法。

2、编写配置文件内容。

image.png

const path = require('path')

module.exports = {
    //指定入口文件
    entry: './src/js/index.js',
    //指定打包输出目录
    output:{
        //指定输出文件名
        filename: 'bundle.js',
        //指定输出路径
        path: path.resolve(__dirname, './dist')
    },
    mode: 'none'
}

path模块
path.resolve()方法
__dirname 表示webpack.config.js所在的绝对路径。

3、执行打包命令:npx webpack image.png

4、打包结果。
image.png image.png

可以看到,打包成功,输出了一个dist/bundle.js文件。

5、引入打包文件。
在index.html中,引入打包文件 bundle.js。

//index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <script src="../dist/bundle.js" type="text/javascript" charset="utf-8"></script>
    </body>
</html>

6、浏览器执行。

image.png

可以看到,代码正常执行,也就说明webpack打包已经成功了。

缺点:在html文件中,手动引入打包文件,不方便,如何才能做自动引入呢?

第三章: 自动引入资源

3.1 什么是插件

webpadk就像一条生产线,他要经过一系列的处理流程以后,才能将源文件转化成输出的结果。 入口文件 还可以依赖于其他的js模块,其中被依赖的js模块可能还依赖其他的js模块。并且js模块还可以引入css文件。

webpack会把整个依赖的关系都记录下来,然后交给webpadk编译器。webpack编译器经过加工以后会生成目标文件,比如css和js文件。

webpack编译的过程需要应用一些工具来帮忙,这些工具可以帮助webpack来执行一些特定的任务,比如:打包优化、资原管理等等。这些工具就是我们所谓plugins 插件。

image.png

webpack插件有三种类型:
1.Community 社区插件
2.Webpack 官方插件
3.Webpack Contrib 第三方插件

查看webpack有哪些插件? webpack Plugins

image.png

3.2 使用HtmlWebpackPlugin

plugins选项

webpack中使用插件的步骤
0 . 安装插件。
1 . webpack.config.js中,添加一个plugins选项。
2 . webpack.config.js中,引入插件,再在plugins选项中,通过new来实例化插件的构造函数。
3 . 实例化插件的构造函数时,可以传入一个配置对象,来进行一些插件配置。

1、首先安装插件:

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

image.png

2、并且调整webpack.config.js 文件:

const path = require('path')
//引入插件
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    entry: './src/js/index.js',
    output:{
        filename: 'bundle.js',
        path: path.resolve(__dirname, './dist')
    },
    mode: 'none',
    //配置插件
    plugins: [
        new HtmlWebpackPlugin()
    ]
}

3、打包:

npx webpack

4、打包结果

image.png

image.png

image.png

可以看到,打包生成了一个新的文件index.html,并且它自动引入了bundle.js。

插件的配置对象

HtmlWebpackPlugin默认会创建一个新的空白的html文件,然后在head标签中,自动引入bundle.js。如果我们想在我们自己的html文件的body标签中自动引入bundle.js,该怎么办呢?

我们可以给插件传入一个配置对象,来对插件进行一些配置。

//webpack.config.js

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

module.exports = {
	entry...
	output...
	mode...
	plugins: [
            new HtmlWebpackPlugin({ //配置对象
                //指定html模板
                template: './index.html',
                //指定输出名字
                filename: 'app.html',
                //指定在body标签中引入打包文件
                inject: 'body'
            })
	]
}

3.3 清理dist

如何才能自动地将上一次打包的输出文件清除呢? 在output选项,添加一个配置即可:clean:true

//webpack.config.js

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

module.exports = {
    entry: './src/js/index.js',
    output:{
        filename: 'bundle.js',
        path: path.resolve(__dirname, './dist'),
        //自动删除上一次的打包文件
        clean: true
    },
    mode: 'none',
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'app.html',
            inject: 'body'
        })
    ]
}

第四章: 搭建开发环境

4.1 mode选项

可以通过搭建一个开发环境(也称本地服务器)解决以下的重复性的人工问题,以提高开发效率:
1.手动在浏览器中打开html文件。
2.每次修改代码,需要手动重新执行打包命令。

mode选项的作用:指定打包的模式。

'development' 表示开发模式。

mode: 'development'

4.2 使用source map

source-map的作用:当js代码出错时,点击浏览器控制台右侧的报错行数,可以精确定位到源代码的出错位置,而不是打包文件中的出错位置,有助于我们调试代码。

配置文件中,添加一个devtool选项。

 devtool: 'inline-source-map' 

4.3 使用watch mode

在每次编译代码时,手动运行 npx webpack 会显得很麻烦。如果才能修改源代码后,自动进行打包呢?

执行打包命令时,添加一个命令行参数watch即可。

npx webpack --watch

如果其中一个文件被更新,代码将被重新编译,所以你不必再去手动运行整个构建。

--watch参数的作用:打包命令不会立即结束,而是光标一直在闪烁,只要修改源代码并且保存,就会自动重新执行打包,继续监听下一次修改。

Ctrl+C:可以终止命令行窗口中执行的命令。

4.4 使用webpack-dev-server

webpack-dev-server为你提供了一个基本的 web server,并且具有 live reloading(实时重新加载) 功能。

注意:
1、需要手动创建dist目录。
2、有时候自动打开浏览器后,在页面上看不到dist目录中的文件,需要手动在地址栏访问app.html文件。

1、先安装。

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

2、修改配置文件,添加一个devServer选项,告知 dev server,从什么位置查找文件。

//...

module.exports = {
    //...
    // dev-server
    devServer: {
        static: './dist'
    }
}

以上配置告知 webpack-dev-server ,将 dist 目录下的文件作为 web 服务的根目录。

提示:webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文件保留在内存中,然后将它们 serve 到 server 中,就好像它们是挂载在 server根路径上的真实文件一样。

3、执行命令。

npx webpack serve

npx webpack-dev-server --open

或者

npx webpack serve --open

--open:自动打开浏览器,并访问 http://localhost:8080/ 页面。

4、在浏览器里可以直接访问 http://localhost:8080/ ,来查看页面。

watch mode 和 webpack-dev-server的区别
--watch:监听源文件的修改,自动执行打包命令。
webpack-dev-server:不仅可以监听源文件的修改,自动执行打包命令。还可以自动刷新浏览器,方便查看最新的js代码的执行效果。

所以,后者完全可以取代前者来使用。

小结

经过第2、3、4章的学习,我们在配置文件中,进行了以下的设置:

//webpack.config.js

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

module.exports = {
    entry: './src/js/index.js',
    output:{
        filename: 'bundle.js',
        path: path.resolve(__dirname, './dist'),
        clean: true
    },
    mode: 'development',
    devtool: 'inline-source-map',
    devServer: {
        static: './dist'
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'app.html',
            inject: 'body'
        })
    ]
}

第五章: 资源模块

5.1 资源模块介绍

到目前为止我们的项目只能加载JS,那现在我们能不能尝试混合一些其他的资源呢?比如像 images,看看webpack是如何处理的?

其实 webpack最出色的功能之一就是除了引入js,还可以使用内置的资源模块 asset modules,来引入任何的其他类型资源。

资源模块(asset module) 是一种模块类型,它允许我们应用Webpack来打包其他资源文件(如字体,图标等)。

资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些loader:

  • asset/resource 导出一个单独的文件并导出 URL。
  • asset/inline 导出一个资源的 data URI。
  • asset/source 导出资源的源代码。
  • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。

5.2 Resource资源

原理:

  • js文件中,import导入图片等资源时,可以得到它的url。
  • 以url的方式来使用图片。
  • 打包时,会把图片等资源以单独的文件的形式输出到dist目录中。

module选项

配置文件中,添加一个新的module选项,来配置资源模块。

rules选项

rules数组:里面可以配置多个对象,来去加载不同类型的文件。
test属性:值为一个正则表达式,指定要匹配的文件类型。
type属性:指定资源模块类型。

处理图片资源

webpack在打包时,如何处理图片模块呢?

原理:图片也可以看成一个js模块,在js文件中,可以对图片进行导入、使用和打包。

1、在入口文件中,使用esmodule模块化语法,引入图片。

image.png

//index.js

import { helloworld } from './hello-world.js'

//导入图片
import imgsrc from './assets/img-1.png'

helloworld()

//imgsrc是图片的url
const img = document.createElement('img') 
img.src = imgsrc 
document.body.appendChild(img)

2、修改配置文件。

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

module.exports = {
    ...
    //配置资源模块
    module: {
        rules: [
            //处理图片资源
            {
                test: /\.png$/,
                type: 'asset/resource'
            }
        ]
    }
}

3、执行打包命令。

npx webpack

注意:
我在使用webpack-dev-server时,执行npm install --save-dev webpack-dev-server打包命令后,打包成功,但是访问8080端口时,无法访问页面,不知道为什么?所以,这里在配置文件中,注释掉devServer选项,就不使用本地服务器来自动编译和自动打开浏览器了。

4、查看打包结果。

image.png

image.png

5、查看浏览器执行效果。

复制打包输出的html文件的绝对路径,在浏览器地址栏中打开。

image.png

自定义输出目录和文件名

webpack默认把图片资源打包到dist目录下,并且帮助我们自动的起好文件名。那我们能不能自己定义图片的输出目录和文件名呢?

有两种方法:

方法1:assetModuleFilename属性

output选项中,添加一个新的属性assetModuleFilename,可以指定图片的输出目录和文件名。

指定输出目录和文件名:

output:{
    ...
    assetModuleFilename: 'images/test.png'
},

但是我们不可能一一为每个资源都指定名字,所以需要设置成自动生成名字。 我们可以使用webpack系统自带的默认的生成文件名的方法:
contenthash 表示根据文件的内容来生成一个哈希的字符串。
ext 表示使用资源原来的扩展名。

output:{
    ...
    assetModuleFilename: 'images/[contenthash][ext]'
},

方法2:generator属性

配置方式和assetModuleFilename属性是一样的。

...
module: {
        rules: [
            {
                test: /\.png$/,
                type: 'asset/resource',
                //指定资源的输出路径和文件名
                generator: {
                    filename: 'images/[contenthash][ext]'
                }
            }
        ]
    }

注意:generator属性优先级高于assetModuleFilename属性,如果两者同时设置了,则只有前者生效。

5.3 inline资源

原理:将资源文件(如svg图片)转换为data uri,嵌入到bundle.js,而没有单独输出成一个文件。

1.引入svg图片。

image.png

//index.js

...
//入口文件中,引入svg图片 
import logosvg from '../assets/webpack-logo.svg'

...

const img2 = document.createElement('img') 
img2.style.cssText = 'width: 600px; height: 200px'
img2.src = logosvg
document.body.appendChild(img2)

2.修改配置文件。

...
module: {
    rules: [
        ...
        //打包svg图片
        {
            test: /\.svg$/,
            type: 'asset/inline',
        }
    ]
}

3.打包。 image.png

image.png

4.浏览器查看效果。

image.png

5.4 source资源

source资源,导出资源的源代码。

特点:webpack会将 asset/source资源 的源代码 原样嵌入到js中,而不会单独输出成一个文件。

1.修改配置文件,添加:

...
module: {
    rules: [
        ...
        //打包asset/source资源,如txt
        {
            test: /\.txt$/,
            type: 'asset/source'
        }
    ]
}

2.引入txt

//example.txt

hello webpack
//index.js

...

//导入txt
import exampleTxt from '../assets/example.txt'

...

const block = document.createElement('div')
block.textContent = exampleTxt
block.style.cssText = 'width: 200px; height: 200px; background: aliceblue'
document.body.appendChild(block)

3.打包。

npx webpack

image.png

image.png

4.浏览器执行效果。

image.png

5.5 通用数据类型

通用资源类型asset, 在导出一个 data URI 和发送一个单独的文件之间自动选择。

module: {
    rules: [
        test: /\.jpg/,
        type: 'asset' //设置通用数据类型
    ]
}

现在,webpack 将按照默认条件,自动地在 resource 和 inline 之间进行选择: 小于 8kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类型。

可以通过在 webpack 配置的 module rules 层级中,设置Rule.parser.dataUrlCondition.maxSize 选项来修改阈值的大小。

rules: [
    {
        test: /\.jpg/,
        type: 'asset',
        parser: {
            dataUrlCondition: {
                //设置阈值
                maxSize: 4 * 1024 // 4kb
            }
        }
    }
]

1.修改配置文件。

...
module: {
    rules: [
        {
            test: /\.png$/,
            //设置通用数据类型
            type: 'asset',
            generator: {
                filename: 'images/[contenthash][ext]'
            },
            parser: {
                dataUrlCondition: {
                    //设置阈值
                    maxSize: 6*1024 // 6kb
                }
            }
        },
        ...
    ]
}                              

2.引入png。

//index.js

//导入png图片
import imgsrc from '../assets/pic1.png' //4kb
import img2src from '../assets/pic2.png' //13kb

const img = document.createElement('img') 
img.src = imgsrc 
document.body.appendChild(img)

const img2 = document.createElement('img') 
img2.src = img2src 
document.body.appendChild(img2)

3.打包。

image.png

image.png

4.浏览器执行效果。

image.png

小结

webpad给我们提供了四种资原类型,来加载除了js以外的资源,比如图片等。

resource资源:asset/resource。
它可以生成一个单独的文件,并导出url。这个url是个资源路径,可以在业务代码里边直接使用。

inline资源: asset/inline。
它导出一个资源的dataUrl。 比如可以将一个svg转换为一个base64编码的字符串,在业务代码中可以直接使用这个字符串。

source资源:asset/source。
它可以导出资源的源代码。

通用资源类型: asset。

经过前5章的学习,我们学会了以下配置:

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

module.exports = {
    entry: './src/js/index.js',
    output:{
        filename: 'bundle.js',
        path: path.resolve(__dirname, './dist'),
        clean: true,
        //assetModuleFilename: 'images/[contenthash][ext]'
    },
    mode: 'development',
    devtool: 'inline-source-map',
    /* 
    devServer: {
        static: './dist'
    } */
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'app.html',
            inject: 'body'
        })
    ],
    module: {
        rules: [
            {
                test: /\.png$/,
                //type: 'asset/resource',
                type: 'asset',
                generator: {
                    filename: 'images/[contenthash][ext]'
                },
                parser: {
                    dataUrlCondition: {
                        maxSize: 6*1024 // 6kb
                    }
                }
            },
            {
                test: /\.svg$/,
                type: 'asset/inline',
            },
            {
                test: /\.txt$/,
                type: 'asset/source'
            }
        ]
    }
}

第六章: 管理资源

6.1 什么是loader

实际上,webpack除了资源模块以外,还可以通过 loader,去引入其他类型的文件。

webpack 本身只能理解和打包js和 json文件,而loader可以让webpack去解析他的类型的文件,并且将这些文件转化为有效的模块,以供我们应用程序使用。

哪如何使用loaders呢?
loader定义有两个module.rules选项中重要的属性:
test属性:指定处理哪种类型的文件,通过正则表达式来匹配文件名。
use属性:指定使用哪个loader来处理。

6.2 加载CSS

css-loader

style-loader

less-loader、sass-loader、stylus-loader

为了在JavaScript模块中import一个CSS文件,你需要安装style-loadercss-loader,并在module配置中添加这些loader。

npm install --save-dev style-loader css-loader

修改配置文件。

module: {
    rules: [
        //处理css文件
        {
            test: /\.css$/,
            use: ['style-loader','css-loader']
        }
    ]
}

注意:
0、css-loader和style-loader的区别:
css-loader:将css文件转换为有效的模块,使得webpack能够识别和打包。
style-loader:会通过js代码,将css代码以style标签的形式插入到html中,使得其样式生效。
1、css-loader和style-loader的顺序不能颠倒,先使用css-loader,再使用style-loader。
2、webpack支持loaders的链式调用,上一个loader将转换后的代码传递给下一个loader继续进行处理,最后一个loader会转换为js代码,传递为webpack。
3、use数组 是从末尾到头部的顺序,依次使用loader进行处理的。

1、安装css-loader、style-loader。

image.png

2、修改配置文件。

...
module: {
    ...
    rules: [
        ...
        //处理css文件
        {
            test: /\.css$/,
            use: ['style-loader','css-loader']
        }
    ]
}

3、引入css文件。

//style.css

.hello{
    color: red;
    font-size: 40px;
}
//index.js

//导入css
import '../css/style.css'
...

4、打包。

image.png

image.png

5、浏览器执行效果。

image.png

less-loader

如何打包.sass、.less文件呢?使用less-loader。

1、安装less-loader。

npm install less less-loader --save-dev

image.png

2、修改配置文件。

module: {
    rules: [
        //处理css、less文件
        { 
            test: /\.(css|less)$/, 
            use: ['style-loader', 'css-loader', 'less-loader'] 
        }
    ]
}

3、引入less文件。

//style2.less

@backgroundColor: pink;
p{
    background-color: @backgroundColor;
}
//index.js

...
//导入less
import '../css/style2.less'

4、打包。

image.png

image.png

5、浏览器执行效果。

image.png

6.3 抽离和压缩CSS

通过css-loaderstyle-loaderless-loader,可以将样式文件(.css、.less)文件转化为js代码,这些js代码会被合并到bundle.js中,它们会在执行时,创建style标签,将样式字符串包裹进style标签中,最后将style标签添加到html文件中,使得样式代码生效。

缺点:css代码并没有单独输入成一个文件,容易导致bundle.js文件体积过大。

哪如何将css代码单独打包成一个文件呢?然后再通过link标签自动引入它呢?

mini-css-extract-plugin

在多数情况下,我们也可以进行压缩CSS,以便在生产环境中节省加载时间,同时还可以将CSS文件抽离成一个单独的文件。

实现这个功能,需要 mini-css-extract-plugin 这个插件来帮忙。

该插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。本插件基于 webpack v5 的新特性构建,并且需要 webpack 5 才能正常工作。

单独的 mini-css-extract-plugin 插件不会将这些 CSS 加载到页面中。 html-webpack-plugin 可以帮助我们自动生成 link 标签或者在创建 index.html 文件时使用 link 标签,自动引入打包输出的样式文件。

注意:style-loadermini-css-extract-plugin只能使用其中一个。

1、安装插件。

npm install mini-css-extract-plugin --save-dev

2、修改配置文件。

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

...
module: {
    rules: [
        ...
        //抽离css文件
        {
            test: /\.(css|less)$/,
            use: [MiniCssExtractPlugin.loader,'css-loader','less-loader']
        }
    ]
},

plugins: [
    ...
    //实例化MiniCssExtractPlugin
    new MiniCssExtractPlugin()
],

指定输出目录

如何指定css打包后的输出目录呢?实例化插件时,传入一个配置对象。


const MiniCssExtractPlugin = require("mini-css-extract-plugin")
...
plugins: [
    ...
    //指定css输出目录
    new MiniCssExtractPlugin({
        filename: 'css/[contenthash].css'
    })
],

css-minimizer-webpack-plugin

发现文件并没有压缩和优化,为了压缩输出文件,请使用类似于 css-minimizer-webpack-plugin 这样的插件。

注意:
1.css-minimizer-webpack-plugin插件 需要 optimization选项 中进行配置。
2.css-minimizer-webpack-plugin插件 一般在生产模式下使用。

1、安装插件。

npm install css-minimizer-webpack-plugin --save-dev

2、修改配置文件。

...
//引入插件
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')

...
//生产模式
mode: 'production'

...
//优化配置
optimization:{
    minimizer: [
        new CssMinimizerPlugin()
    ]
}

示例

1、安装mini-css-extract-plugin、css-minimizer-webpack-plugin。

image.png image.png

2、修改配置文件。

...
//引入插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')

...
//生产模式
mode: 'production',

...
plugins: [
    ..
    //实例化插件
    new MiniCssExtractPlugin({
         //指定css输出目录
        filename: 'css/[contenthash].css'
    })
],
module: {
    rules: [{
        ...
        //抽离css文件
        {
            test: /\.(css|less)$/,
            use: [MiniCssExtractPlugin.loader,'css-loader','less-loader']
        }
    ]
},
//优化配置
optimization:{
    minimizer: [
        new CssMinimizerPlugin()
    ]
}

4、引入样式文件。

5、打包。

image.png

image.png

image.png

6、浏览器执行效果。

image.png

6.4 加载images图像

css文件中,如果某些css属性引用了图片,则webpack要如何进行打包呢?

其实,在第5章 资源模块中,webpack通过内置的资源模块就可以打包js文件中引用的png、svg、jpg等图片。同样,也可以打包css文件中引用的图片。配置方法是一样的。

示例:
1、css文件引用图片。

//style.css

...
.bg-img{
    background-image: url(../assets/img-1.png);
}

2、入口文件。

//index.js

//导入css
import '../css/style.css'

const block = document.createElement('div')
block.style.cssText = 'width: 200px; height: 200px;'
block.classList.add('bg-img')
document.body.appendChild(block)

3、配置文件。

...
module: {
    rules: [
            {
                test: /\.png$/,
                //type: 'asset/resource',
                type: 'asset',
                generator: {
                    filename: 'images/[contenthash][ext]'
                },
                parser: {
                    dataUrlCondition: {
                        maxSize: 6 * 1024 // 6kb
                    }
                }
            },
            {
                test: /\.svg$/,
                type: 'asset/inline',
            },
    ]
}

4、打包。

image.png

image.png

5、浏览器执行效果。

image.png

6.5 加载fonts字体

那么,像字体这样的其他资源如何处理呢?使用 Asset Modules 可以接收并加载任何文件,然后将其输出到构建目录。这就是说,我们可以将它们用于任何类型的文件,也包括字体。

...
module: {
    rules: [{
        ...
        //处理字体文件
        {
            test: /\.(woff|woff2|eot|ttf|otf)$/,
            type: 'asset/resource',
        }
    ]
},

示例:
1、修改配置文件。

//处理字体文件
{
    test: /\.(woff|woff2|eot|ttf|otf)$/,
    type: 'asset/resource',
    //指定输出目录
    generator: {
        filename: 'fonts/[contenthash][ext]'
    },
}

2、引入字体。

//style.css

/* 声明字体 */
@font-face {
    font-family: 'webfont';
    font-display: swap;
    src: url('../font/webfont.eot');
    /* IE9 */
    src: url('../font/webfont.eot?#iefix') format('embedded-opentype'),
        /* IE6-IE8 */
        url('../font/webfont.woff2') format('woff2'),
        url('../font/webfont.woff') format('woff'),
        /* chrome、firefox */
        url('../font/webfont.ttf') format('truetype'),
        /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/
        url('../font/webfont.svg#webfont') format('svg');
    /* iOS 4.1- */
}

/* 使用字体 */
.fun {
    font-family: "webfont" !important;
    font-size: 16px;
    font-style: normal;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

3、打包。

image.png

image.png

4、浏览器执行效果。

image.png

6.6 加载数据

csv-loader

xml-loader

此外,可以加载的有用资源还有数据,如 JSON 文件,CSV、TSV 和 XML。类似于NodeJS,JSON 支持实际上是内置的,也就是说 import Data from './data.json' 默认将正常运行。要导入 CSV、TSV 和 XML,你可以使用 csv-loaderxml-loader 来加载这三类文件。

1、安装csv-loader、xml-loader。

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

image.png

2、修改配置文件。

{
    test: /\.(csv|tsv)$/,
    use: ['csv-loader']
},
{
    test: /\.xml$/,
    use: ['xml-loader']
}

3、现在,你可以 import 这四种类型的数据(JSON, CSV, TSV, XML)中的任何一种,所导入的 Data 变量,将包含可直接使用的已解析 JSON。.xml 文件转化为一个JS对象, .cvs 转化为一个数组。

示例:
1、引入xml、csv。

//test.xml

<?xml version="1.0" encoding="UTF-8"?> 
<note> 
    <to>Mary</to> 
    <from>John</from> 
    <heading>Reminder</heading> 
    <body>Call Cindy on Tuesday</body> 
</note>
//test2.csv

to,from,heading,body 
Mary,John,Reminder,Call Cindy on Tuesday 
Zoe,Bill,Reminder,Buy orange juice 
Autumn,Lindsey,Letter,I miss you
//index.js

//导入xml
import xmlData from '../data/test.xml'
//导入csv
import csvData from '../data/test2.csv'

...
console.log(xmlData)
console.log(csvData)

2、修改配置文件。 3、打包。

image.png

image.png

4、浏览器执行效果。

image.png

image.png

6.7 自定义JSON模块parser

yaml 文件我们在做项目过程中经常的看到,它有key、value,同时通过缩进来去完成子项的设置。注意:yaml文件只能用空格来缩进,而不能用Tab键。

toml 相比较yaml就会好一些,不是通过缩进而是通过一个key等于value的方式。如果有子项,则通过一个中括号来定义。

json5格式 是对json格式的一个升级。json格式不能使用注释,并且key必须使用双引号。json5中key可以不使用引号,value可以使用单引号,还可以使用/n、/r等等。

webpack中,如何处理yaml、toml、json5文件呢?

通过使用 自定义 parser 替代特定的 webpack loader,可以将任何 toml 、 yaml 或json5 文件作为 JSON 模块导入。

1、首先安装 toml、 yamljs、json5 的 packages。

npm install toml yamljs json5 --save-dev

image.png

2、配置。

const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5'); 

module: {
    rules: [
        //处理toml、yaml、json5
        {
            test:/\.toml$/,
            type: 'json',
            parser: {
                parse: toml.parse
            }
        },
        {
            test:/\.yaml$/,
            type: 'json',
            parser: {
                parse: yaml.parse
            }
        },
        {
            test:/\.json5$/,
            type: 'json',
            parser: {
                parse: json5.parse
            }
        }
    ]
},

3、引入toml、yaml、json5。

//data.toml

title = "TOML Example"

[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
dob = 1979-05-27T07:32:00Z
//data.yaml

title: YAML Example
owner:
	name: Tom Preston-Werner
	organization: GitHub
	bio: |-
		GitHub Cofounder & CEO
		Likes tater tots and beer.
	dob: 1979-05-27T07:32:00.000Z
//data.json5

{
    //comment
    title: 'JSON5 Example',
    owner: {
        name: 'Tom Preston-Werner',
        organization: 'GitHub',
        bio: 'GitHub Cofounder & CEO\n\
                                Likes tater tots and beer.',
        dob: '1979-05-27T07:32:00.000Z',
    },
}
//index.js

//导入toml、yaml、json5
import toml from '../json/data.toml'
import yaml from '../json/data.yaml'
import json5 from '../json/data.json5'


console.log(toml)
console.log(toml.title)
console.log(toml.owner.name)

console.log(yaml)
console.log(yaml.title)
console.log(yaml.owner.name)

console.log(json5)
console.log(json5.title)
console.log(json5.owner.name)

4、打包。

npx webpack

image.png

image.png

5、浏览器执行效果。

image.png

image.png

第七章: 使用babel-loader

7.1 为什么需要babel-loader

webpack 自身可以自动加载JS文件,就像加载JSON文件一样,无需任何 loader。可是,加载的JS文件会原样输出,即使你的JS文件里包含ES6+的代码,也不会做任何的转化。

低版本浏览器可能不支持最新的JS语法。该怎么办呢?

这时我们就需要Babel来帮忙。Babel 是一个 JavaScript 编译器,可以将ES6+转化成ES5。在Webpack里使用Babel,需要使用 babel-loader。

7.2 使用babel-loader

1、安装babel-loader@babel/core@babel/preset-env

npm install -D babel-loader @babel/core @babel/preset-env

babel-loader: 在webpack里应用 babel 解析ES6的桥梁。
@babel/core: babel核心模块。
@babel/preset-env: babel预设,一组 babel 插件的集合。

2、安装 @babel/runtime

npm install --save @babel/runtime

@babel/runtime 中包含了 regeneratorRuntime,运行时需要(运行时依赖 )。

3、安装 @babel/plugin-transform-runtime

npm install -D @babel/plugin-transform-runtime

作用:在需要regeneratorRuntime的地方,自动require导包。编译时需要(开发依赖)。
它是babel-loader的一个插件,需要在options.plugins选项中进行配置。

3、配置。

module: {
    rules: [ 
        {
            test: /\.js$/,
            exclude: /node_modules/,
            use: {
                loader: 'babel-loader',
                options: {
                    presets: ['@babel/preset-env'],
                    plugins: [
                        [
                            '@babel/plugin-transform-runtime'
                        ]
                    ]
               },
            }
        }
    ]
},

exclude选项 :node_modules目录中第三方库的js文件不需要babel来进行转换,使用exclude选项来排除。

options选项 :loader的配置对象。

7.3 regeneratorRuntime插件

示例:

1、入口文件。

//test.js

function getString(){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('hello,world!')
        }, 2000)
    })
}

async function helloWorld(){
    let string = await getString()
    console.log(string)
}

//导出函数
export default helloWorld
//index.js

import helloWorld from './test.js'
helloWorld()

2、打包。

3、浏览器执行效果。

image.png

第八章: 代码分离

image.png

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

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

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

8.2 入口起点

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

bundle:指打包输出的文件。

1、安装lodash。

npm install lodash 

2、在 src 目录下创建 another-module.js 文件。

这两个js文件都使用了lodash库,都作为入口文件。

//another-module.js

import _ from 'lodash'

console.log(_.join(['Another', 'module', 'loaded!'], ' '))
//index.js

import _ from 'lodash'

console.log(_.join(['index', 'module', 'loaded!'], ' '))

3、配置多入口

entry: {
    index: './src/js/index.js',
    another: './src/js/another-module.js'
},
output: {
    filename: '[name].bundle.js'
},

4、打包。

image.png

image.png

image.png

5、结论。
1、打包输出了两个bundle:index.bundle.js、another.bundle.js。它们都被自动引入html中,浏览器执行正常。说明多入口配置成功。
2、这种方式的确存在一些隐患
● 如果入口 chunk 之间包含一些重复的模块,那些重复模块都会被引入到各个bundle 中。
● 这种方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来。

8.3 防止重复

入口依赖

配置 dependOn option 选项,这样可以在多个 chunk 之间共享模块。

entry: {
    index: {
        import: './src/js/index.js',
        dependOn: 'shared'
    },
    another: {
        import: './src/js/another-module.js',
        dependOn: 'shared'
    },
    shared: 'lodash'
},
output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, './dist'),
    clean: true,
},

示例:
1、配置。
2、打包。
image.png

image.png

3、结论。
index.bundle.js 与 another.bundle.js 共享的模块 lodash.js 被打包到一个单独的文件 shared.bundle.js 中。

SplitChunksPlugin

SplitChunksPlugin是webpack内置的一个插件。

SplitChunksPlugin 插件可以自动地将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。

1、配置。

entry: {
    index: './src/js/index.js',
    another: './src/js/another-module.js'
},
output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, './dist'),
    clean: true,
},
optimization: {
    ...
    splitChunks: {
        chunks: 'all'
    }
}

2、打包。

image.png

image.png

3、结论。

使用 optimization.splitChunks 配置选项之后,现在应该可以看出,index.bundle.js 和 another.bundle.js 中已经移除了重复的依赖模块。需要注意的是,插件将 lodash 分离到单独的 chunk,并且将其从 main bundle中移除,减轻了大小。

8.4 动态导入

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

import()

静态导入: 全局作用域中执行import()。
动态导入: 函数作用域中执行import()。

1、创建 async-module.js 文件。

function getComponent(){
    return import('loadsh')
        .then(({default: _}) => {
            const element = document.createElement('div')
            element.innerHTML = _.join(['Hello', 'webpack'], ' ')
            return element
        })
        .catch((error) => {
            return 'An error occurred while loading the component'
        })
}

getComponent().then((component) => {
    document.body.appendChild(component)
})

2、配置。

optimization: {
    splitChunks: {
        chunks: 'all'
    }
}

3、在入口文件中导入。

//index.js

import './async-module.js'

4、打包、浏览器执行效果。

image.png

image.png

image.png

4、结论。
我们看到,静态和动态载入的模块都正常工作了。而且它们公共的lodash模块也被单独打包成一个文件。(another-module.js中静态导入lodash, async-module.js中动态导入lodash)

8.5 懒加载

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

懒加载 是动态导入的一种应用场景。

1、创建一个 math.js 文件,在主页面中通过点击按钮调用其中的函数。
我们希望math.js文件是懒加载的。

// math.js
export const add = (x, y) => {
    return x + y
}

export const minus = (x, y) => {
    return x - y
}

2、编辑 index.js 文件。

//index.js
const button = document.createElement('button') 
button.textContent = '点击执行加法运算'
button.addEventListener('click', ()=>{
    import(/* webpackChunkName: 'math' */ './math.js').then(({ add })=>{
        console.log(add(3, 4))
    })
})

document.body.appendChild(button)

这里有句注释,我们把它称为 webpack 魔法注释: webpackChunkName: 'math',告诉webpack打包生成的文件名为 math 。

3、打包。

image.png

image.png

4、启动服务,在浏览器上查看。

image.png

image.png

5、结论。

1、懒加载模块( math.js)被单独打包成一个文件。
2、页面第一次加载时,不会立即加载懒加载模块。点击按钮时,浏览器才会向服务器请求 懒加载模块( math.js)。可以节省用户的网络的流量。

8.6 预获取、预加载模块

预获取预加载动态导入 的第二个应用场景。

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

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

  • prefetch(预获取):将来某些导航下可能需要的资源
  • preload(预加载):当前导航下可能需要资源

prefetch 预获取

示例1:prefetch。

1、编辑 index.js 文件。

const button = document.createElement('button') 
button.textContent = '点击执行加法运算'
button.addEventListener('click', ()=>{
    import(/* webpackChunkName: 'math', webpackPrefetch: true */ './math.js').then(({ add })=>{
        console.log(add(3, 4))
    })
})

document.body.appendChild(button)

添加第二句魔法注释: webpackPrefetch: true 告诉 webpack 执行预获取。这会生成 <link rel="prefetch" href="math.js"> 并追加到页面头部,指示着浏览器在闲置时间预取 math.js 文件。

2、打包。

3、启动服务,在浏览器上查看。

image.png

image.png

4、结论。
我们发现,在还没有点击按钮时, math.bundle.js 就已经下载下来了。同时,在app.html 里webpack自动添加了一句:<link rel="prefetch" as="script" href="http://localhost:8080/math.bundle.js">。点击按钮,会立即调用已经下载好的 math.bundle.js 文件中的 add 方法。

preload 预加载

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

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

示例:preload。

1、创建一个 print.js 文件。

export const print = () => {
    console.log('preload chunk.')
}

2、修改 index.js 文件。

import(/* webpackChunkName: 'print', webpackPreload: true */ './print.js')
.then(({ print }) => {
	print()
})

3、打包,启动服务,打开浏览器。

npx webpack-dev-server --open

image.png

image.png

image.png

4、结论。
print.bundle.js 被加载下来,是和当前 index.bundle.js 并行加载的。

小结

代码分离 是webpack是一个非常好的一个功能,可以把多个模块共享的代码抽离出去,减少入口文件的大小,从而提高首屏的加载速度。

预获取prefetch:在网络空闲的时侯,把将来很可能会使用的代码请求下载下来。这样既不影响首屏的加载速度,又省去了将来模块加载的延迟。

预加载preload:实现多个模块的并行加载。

第九章: 缓存

以上,我们使用 webpack 来打包我们的模块化后的应用程序,webpack 会生成一个可部署的 /dist 目录,然后把打包后的内容放置在此目录中。只要 /dist 目录中的内容部署到 server 上,client(通常是浏览器)就能够访问此 server 的网站及其资源。而最后一步获取资源是比较耗费时间的,这就是为什么浏览器使用一种名为缓存 的技术。可以通过命中缓存,以降低网络流量,使网站加载速度更快,然而,如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就会使用它的缓存版本。由于缓存的存在,当你需要获取新的代码时,就会显得很棘手。

本节通过必要的配置,以确保 webpack 编译生成的文件能够被客户端缓存,而在文件内容变化后,能够请求到新的文件。

9.1 输出文件的文件名

原理:当我们修改自己的代码后,打包后的文件名(哈希值部分)就会发生变化,浏览器就会重新请求新的资源,而不是使用缓存的旧资源。

我们可以通过替换 output.filename 中的 substitutions 设置,来定义输出文件的名称。webpack 提供了一种使用称为 substitution(可替换模板字符串) 的方式,通过带括号字符串来模板化文件名。其中, [contenthash] substitution 将根据资源内容创建出唯一 hash。当资源内容发生变化时, [contenthash] 也会发生变化。

修改配置文件:

output: {
    filename: '[name].[contenthash].js',
    ...
},

执行打包编译:

image.png

可以看到,bundle 的名称是它内容(通过 hash)的映射。如果我们不做修改,然后再次运行构建,文件名会保持不变。

9.2 缓存第三方库

将第三方库(library)(例如 lodash )提取到单独的 vendor chunk 文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。因此通过实现以上步骤,利用 client 的长效缓存机制,命中缓存来消除请求,并减少向 server 获取资源,同时还能保证 client 代码和 server 代码版本一致。

我们在optimization.splitChunks 添加如下 cacheGroups 参数并构建:

optimization: {
    ...
    splitChunks: {
        cacheGroups: {
            vendor: {
                test: /[\\/]node_modules[\\/]/,
                name: 'vendors',
                chunks: 'all'
            }
        }
    }
}

执行编译:

image.png

9.3 将js文件放到一个文件夹中

目前,全部 js 文件都在 dist 文件夹根目录下,我们尝试把它们放到一个文件夹中,这个其实也简单,修改配置文件:

output: {
    filename: 'js/[name].[contenthash].js',
},

我们在输出配置中修改 filename ,在前面加上路径即可。执行编译:

npx webpack

image.png

截止目前,我们已经把 JS 文件、样式文件及图片等资源文件分别放到了 scripts、styles、 images 三个文件夹中。

...
output: {
    //指定js文件输出目录
    filename: 'js/[name].[contenthash].js',
},
module: {
    rules: [
       {
            test: /\.png$/,
            type: 'asset',
            generator: {
                //指定图片输出目录
                filename: 'images/[contenthash][ext]'
            }
        },
        {
            test: /\.(woff|woff2|eot|ttf|otf)$/,
            type: 'asset/resource',
            generator: {
                //指定字体文件输出目录
                filename: 'fonts/[contenthash][ext]'
            },
        },
        ...
    ]
}
plugins: [
    ...
    new MiniCssExtractPlugin({
        //指定css文件输出目录
        filename: 'css/[contenthash].css'
    })
],

第十章: 拆分开发环境和生产环境配

现在,我们只能手工的来调整 mode 选项,实现生产环境和开发环境的切换,且很多配置在生产环境和开发环境中存在不一致的情况,比如开发环境没有必要设置缓存,生产环境还需要设置公共路径等等。

本节介绍拆分开发环境和生产环境,让打包更灵活。

10.1 公共路径

在开发环境中,我们通常有一个 assets/ 文件夹,它与索引页面位于同一级别。这没太大问题,但是,如果我们将所有静态资源托管至 CDN,然后想在生产环境中使用呢?

publicPath 配置选项在各种场景中都非常有用。你可以通过它来指定应用程序中所有资源的基础路径。

示例:output.publicPath。

1、设置publicPath选项。

output: {
    publicPath: 'http://localhost:8080/'
},

2、打包效果。

image.png

3、结论。
通过 output.publicPath 选项可以在打包资源的引用路径前添加一个域名,这个域名可以指定为项目的前端域名,或者cdn 服务器的域名等等都可以的。

10.2 环境变量

想要消除 webpack.config.js 在 开发环境生产环境 之间的差异,你可能需要环境变量(environment variable)。

webpack 命令行 环境配置 的 --env 参数,可以允许你传入任意数量的环境变量。而在 webpack.config.js 中可以访问到这些环境变量。例如, --env production 或--env goal=local 。

对于我们的 webpack 配置,有一个必须要修改之处。通常, module.exports 指向配置对象。要使用 env 变量,你必须将 module.exports 转换成一个函数。函数会默认传入一个参数环境变量env,根据命令行参数 env 来设置不同环境的 mode。

示例:环境变量。

1、修改配置文件。
1.module.exports一个函数,函数中返回一个对象。
2.函数中可以使用环境变量。

module.exports = (env) => {
    console.log(env)
    console.log(env.goal)

    return {
        ...
        // 根据命令行参数 env 来设置不同环境的 mode
        mode: env.production ? 'production' : 'development',
        ...
    }
}

2、执行打包命令时,添加--env参数,设置环境变量。

npx webpack --env production --env goal=local

3、打包效果。

image.png

压缩js代码

在前面,我们使用了 mini-css-extract-plugincss-minimizer-webpack-plugin插件来抽离和压缩css代码,但是它们会导致webpack默认的压缩js的功能失效,所以需要手动进行配置 terser 插件。

1、安装 terser-webpack-plugin。

npm install terser-webpack-plugin --save-dev

2、修改配置。

const TerserPlugin = require('terser-webpack-plugin')
module.exports = (env) => {
    return {
        ...
        optimization: {
            minimizer: [
                ...
                //实例化Terser插件
                new TerserPlugin()
            ],
        }
    }
}

3、重新打包。

npx webpack --env production --env goal=local

4、打包效果。

image.png

5、结论。
打包输出的js文件成功压缩了。

注意: webpack只会在生产打包时,对js代码进行压缩。而开发打包时,不会压缩js代码。

10.3 拆分配置文件

在前面,我们可以通过 环境变量 和 三元运算符 来判断当前是开发打包还是生产打包,从而进行各自的配置,但是这种方式比较糟糕。推荐划分两个不同的配置文件的方式来执行不同环境的打包。

目前,生产环境和开发环境使用的是一个配置文件,我们需要将这两个文件单独放到不同的配置文件中。如 webpack.config.dev.js (开发环境配置)和webpack.config.prod.js (生产环境配置)。在项目根目录下创建一个配置文件夹 config 来存放他们。

注意:因为配置文件被放在 config/ 目录中了,所以需要将output.path中的'./dist'修改为'../dist'。

webpack.config.dev.js

webpack.config.dev.js 配置如下:

1.module.exports是一个对象。
2.不需要缓存:去掉output.filename中的contenthash。
3.不需要公共路径: 去掉publicPath。
4.mode选项指定为'development'。
5.需要调试代码 devtool。
6.需要开发服务器 devServer。
7.不需要css、js压缩:去掉css-minimizer-webpack-plugin、terser-webpack-plugin、optimization.minimizer。
8.其它项保留。

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
//const MiniCssExtractPlugin = require('mini-css-extract-plugin')
//const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const toml = require('toml')
const yaml = require('yamljs')
const json5 = require('json5')
//const TerserPlugin = require('terser-webpack-plugin')

module.exports =  {
	//entry: './src/js/index.js',
	
	entry: {
		index: './src/js/index.js',
		another: './src/js/another-module.js'
	},
	/* 
	entry: {
		index: {
			import: './src/js/index.js',
			dependOn: 'shared'
		},
		another: {
			import: './src/js/another-module.js',
			dependOn: 'shared'
		},
		shared: 'lodash'
	}, */
	output: {
		//filename: 'bundle.js',
		filename: '[name].bundle.js',
		//filename: 'js/[name].[contenthash].js',
		path: path.resolve(__dirname, './dist'),
		clean: true,
		assetModuleFilename: 'images/[contenthash][ext]', 
		//publicPath: 'http://localhost:8080/'
	},
	//生产模式
	//mode: 'production',
	//mode: 'development',
	mode: 'development',
	devtool: 'inline-source-map',
	
	//开发服务器
	devServer: {
		static: './dist'
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: './src/index.html',
			filename: 'app.html',
			inject: 'body'
		}),
		//指定css输出目录
		new MiniCssExtractPlugin({
			filename: 'css/[contenthash].css'
		})
	],
	module: {
		rules: [{
				test: /\.png$/,
				//type: 'asset/resource',
				type: 'asset',
				generator: {
					filename: 'images/[contenthash][ext]'
				},
				parser: {
					dataUrlCondition: {
						maxSize: 6 * 1024 // 6kb
					}
				}
			},
			{
				test: /\.svg$/,
				type: 'asset/inline',
			},
			{
				test: /\.txt$/,
				type: 'asset/source'
			},
			//处理css文件、less文件
			/* 
			{
				test: /\.(css|less)$/,
				use: ['style-loader','css-loader','less-loader']
			} */
			//抽离css文件
			{
				test: /\.(css|less)$/,
				use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
			},
			//处理字体文件
			{
				test: /\.(woff|woff2|eot|ttf|otf)$/,
				type: 'asset/resource',
				generator: {
					filename: 'fonts/[contenthash][ext]'
				},
			},
			{
				test: /\.(csv|tsv)$/,
				use: ['csv-loader']
			},
			{
				test: /\.xml$/,
				use: ['xml-loader']
			},
			//处理toml、yaml、json5
			{
				test:/\.toml$/,
				type: 'json',
				parser: {
					parse: toml.parse
				}
			},
			{
				test:/\.yaml$/,
				type: 'json',
				parser: {
					parse: yaml.parse
				}
			},
			{
				test:/\.json5$/,
				type: 'json',
				parser: {
					parse: json5.parse
				}
			},
			{
				test: /\.js$/,
				exclude: /node_modules/,
				use: {
					loader: 'babel-loader',
					options: {
						presets: ['@babel/preset-env'],
						plugins: [
							[
								'@babel/plugin-transform-runtime'
							]
						]
					}
				}
			}
		]
	},
	//优化配置
	optimization: {
		/* minimizer: [
			new CssMinimizerPlugin(),
			new TerserPlugin()
		], */
		/* 
		splitChunks: {
			chunks: 'all'
		}, */
		splitChunks: {
			cacheGroups: {
				vendor: {
					test: /[\\/]node_modules[\\/]/,
					name: 'vendors',
					chunks: 'all'
				}
			}
		}
	}
}

webpack.config.prod.js

webpack.config.prod.js 配置如下:

1.module.exports是一个对象。
2.需要缓存:output.filename中使用contenthash。
3.需要公共路径:output.publicPath。
4.mode指定为'production'。
5.不需要开发服务器:去掉devtool。
6.不需要调试代码:去掉devServer。
7.其它项保留。

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const toml = require('toml')
const yaml = require('yamljs')
const json5 = require('json5')
const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
	//entry: './src/js/index.js',
	
	entry: {
		index: './src/js/index.js',
		another: './src/js/another-module.js'
	},
	/* 
	entry: {
		index: {
			import: './src/js/index.js',
			dependOn: 'shared'
		},
		another: {
			import: './src/js/another-module.js',
			dependOn: 'shared'
		},
		shared: 'lodash'
	}, */
	output: {
		//filename: 'bundle.js',
		//filename: '[name].bundle.js',
		filename: 'js/[name].[contenthash].js',
		path: path.resolve(__dirname, '../dist'),
		clean: true,
		assetModuleFilename: 'images/[contenthash][ext]',
		publicPath: 'http://localhost:8080/'
	},
	//生产模式
	//mode: 'production',
	//mode: 'development',
	mode: 'production',
	//devtool: 'inline-source-map',
	
	//开发服务器
	/* 
	devServer: {
		static: './dist'
	}, */
	plugins: [
		new HtmlWebpackPlugin({
			template: './src/index.html',
			filename: 'app.html',
			inject: 'body'
		}),
		//指定css输出目录
		new MiniCssExtractPlugin({
			filename: 'css/[contenthash].css'
		})
	],
	module: {
		rules: [{
				test: /\.png$/,
				//type: 'asset/resource',
				type: 'asset',
				generator: {
					filename: 'images/[contenthash][ext]'
				},
				parser: {
					dataUrlCondition: {
						maxSize: 6 * 1024 // 6kb
					}
				}
			},
			{
				test: /\.svg$/,
				type: 'asset/inline',
			},
			{
				test: /\.txt$/,
				type: 'asset/source'
			},
			//处理css文件、less文件
			/* 
			{
				test: /\.(css|less)$/,
				use: ['style-loader','css-loader','less-loader']
			} */
			//抽离css文件
			{
				test: /\.(css|less)$/,
				use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
			},
			//处理字体文件
			{
				test: /\.(woff|woff2|eot|ttf|otf)$/,
				type: 'asset/resource',
				generator: {
					filename: 'fonts/[contenthash][ext]'
				},
			},
			{
				test: /\.(csv|tsv)$/,
				use: ['csv-loader']
			},
			{
				test: /\.xml$/,
				use: ['xml-loader']
			},
			//处理toml、yaml、json5
			{
				test:/\.toml$/,
				type: 'json',
				parser: {
					parse: toml.parse
				}
			},
			{
				test:/\.yaml$/,
				type: 'json',
				parser: {
					parse: yaml.parse
				}
			},
			{
				test:/\.json5$/,
				type: 'json',
				parser: {
					parse: json5.parse
				}
			},
			{
				test: /\.js$/,
				exclude: /node_modules/,
				use: {
					loader: 'babel-loader',
					options: {
						presets: ['@babel/preset-env'],
						plugins: [
							[
								'@babel/plugin-transform-runtime'
							]
						]
					}
				}
			}
		]
	},
	//优化配置
	optimization: {
		minimizer: [
			new CssMinimizerPlugin(),
			new TerserPlugin()
		],
		/* 
		splitChunks: {
			chunks: 'all'
		}, */
		splitChunks: {
			cacheGroups: {
				vendor: {
					test: /[\\/]node_modules[\\/]/,
					name: 'vendors',
					chunks: 'all'
				}
			}
		}
	}
}

拆分成两个配置文件后,分别运行这两个文件:

开发环境:

npx webpack -c ./config/webpack.config.dev.js 
或
npx webpack serve -c ./config/webpack.config.dev.js

生产环境:

npx webpack -c ./config/webpack.config.prod.js 
或
npx webpack -c ./config/webpack.config.prod.js

说明:
1、-c参数,--config,指定使用哪个配置文件。
2、serve参数 表示启动开发服务器。开发打包时,可以使用该参数启动一个开发服务器,方便查看页面效果和调试代码。

示例1:使用 开发配置文件 进行打包。

image.png

image.png

结论:开发打包 输出的文件没有被压缩,文件名中也不包含hash值。

示例2:使用 生产配置文件 进行打包。

image.png

image.png

结论:生产打包 输出的文件被压缩,文件名中包含hash值。

10.4 npm脚本

简化打包指令

每次打包或启动服务时,都需要在命令行里输入一长串的命令。能不能优化一下呢?

可以通过在package.json的scripts 选项中添加 自定义指令 的方式来简化打包指令。

1、我们将父目录的package.json 、 node_modules 与 package-lock.json 拷贝到当前目录下。

image.png

2、package.json / scripts选项中,添加自定义指令。

配置 npm 脚本来简化命令行的输入,这时可以省略 npx。

//package.json

...
"scripts": {
    "start": "npx webpack serve -c ./config/webpack.config.dev.js",
    "build": "npx webpack -c ./config/webpack.config.prod.js",
},

或

"scripts": {
    "start": "webpack serve -c ./config/webpack.config.dev.js",
    "build": "webpack -c ./config/webpack.config.prod.js",
},

3、执行打包指令。

//开发打包
npm run start

//生产打包
npm run build

关闭性能提示

生产打包时,会输出一些性能方面的警告信息。如果我们不需要这些提示信息,则可以: 在配置文件中添加一个选项performance来关闭它。

//webpack.config.prod.js

performance: {
    hints: false
}

10.5 提取公共配置

这时,我们发现这两个配置文件里存在大量的重复代码,可以手动的将这些重复的代码单独提取到一个文件里。

webpack.config.common.js

1、创建 webpack.config.common.js ,配置公共的内容。

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
//const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const toml = require('toml')
const yaml = require('yamljs')
const json5 = require('json5')
//const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
	//entry: './src/js/index.js',
	
	entry: {
		index: './src/js/index.js',
		another: './src/js/another-module.js'
	},
	/* 
	entry: {
		index: {
			import: './src/js/index.js',
			dependOn: 'shared'
		},
		another: {
			import: './src/js/another-module.js',
			dependOn: 'shared'
		},
		shared: 'lodash'
	}, */
	output: {
		//filename: 'bundle.js',
		//filename: '[name].bundle.js',
		//filename: 'js/[name].[contenthash].js',
		path: path.resolve(__dirname, '../dist'),
		clean: true,
		assetModuleFilename: 'imgs/[contenthash][ext]',
		//publicPath: 'http://localhost:8080/'
	},
	//生产模式
	//mode: 'production',
	//mode: 'development',
	//mode: env.production ? 'production' : 'development',
	//devtool: 'inline-source-map',
	
	//开发服务器
	/* 
	devServer: {
		static: './dist'
	}, */
	plugins: [
		new HtmlWebpackPlugin({
			template: './src/index.html',
			filename: 'app.html',
			inject: 'body'
		}),
		//指定css输出目录
		new MiniCssExtractPlugin({
			filename: 'css/[contenthash].css'
		})
	],
	module: {
		rules: [{
				test: /\.png$/,
				//type: 'asset/resource',
				type: 'asset',
				generator: {
					filename: 'images/[contenthash][ext]'
				},
				parser: {
					dataUrlCondition: {
						maxSize: 6 * 1024 // 6kb
					}
				}
			},
			{
				test: /\.svg$/,
				type: 'asset/inline',
			},
			{
				test: /\.txt$/,
				type: 'asset/source'
			},
			//处理css文件、less文件
			/* 
			{
				test: /\.(css|less)$/,
				use: ['style-loader','css-loader','less-loader']
			} */
			//抽离css文件
			{
				test: /\.(css|less)$/,
				use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
			},
			//处理字体文件
			{
				test: /\.(woff|woff2|eot|ttf|otf)$/,
				type: 'asset/resource',
				generator: {
					filename: 'fonts/[contenthash][ext]'
				},
			},
			{
				test: /\.(csv|tsv)$/,
				use: ['csv-loader']
			},
			{
				test: /\.xml$/,
				use: ['xml-loader']
			},
			//处理toml、yaml、json5
			{
				test:/\.toml$/,
				type: 'json',
				parser: {
					parse: toml.parse
				}
			},
			{
				test:/\.yaml$/,
				type: 'json',
				parser: {
					parse: yaml.parse
				}
			},
			{
				test:/\.json5$/,
				type: 'json',
				parser: {
					parse: json5.parse
				}
			},
			{
				test: /\.js$/,
				exclude: /node_modules/,
				use: {
					loader: 'babel-loader',
					options: {
						presets: ['@babel/preset-env'],
						plugins: [
							[
								'@babel/plugin-transform-runtime'
							]
						]
					}
				}
			}
		]
	},
	//优化配置
	optimization: {
		/* 
		minimizer: [
			new CssMinimizerPlugin(),
			new TerserPlugin()
		], */
		/* 
		splitChunks: {
			chunks: 'all'
		}, */
		splitChunks: {
			cacheGroups: {
				vendor: {
					test: /[\\/]node_modules[\\/]/,
					name: 'vendors',
					chunks: 'all'
				}
			}
		}
	}
}

webpack.config.dev.js

module.exports =  {
    output: {
        filename: '[name].js',
    },
    mode: 'development',
    devtool: 'inline-source-map',
    //开发服务器
    devServer: {
        static: './dist'
    }
}

webpack.config.prod.js

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
    output: {
        filename: 'js/[name].[contenthash].js',
        publicPath: 'http://localhost:8080/'
    },
    //生产模式
    mode: 'production',
    //优化配置
    optimization: {
        minimizer: [
            new CssMinimizerPlugin(),
            new TerserPlugin()
        ]
    }
}

10.6 合并配置文件

配置文件拆分好后,新的问题来了,如何保证配置合并没用问题呢?webpack-merge 这个工具可以完美解决这个问题。

1、安装 webpack-merge 。

npm install webpack-merge -D

2、创建 webpack.config.js ,合并代码。

在config目录中,新建一个webpack.config.js文件,对前面三个文件进行合并。写入以下内容:

const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.config.common.js')
const developmentConfig = require('./webpack.config.dev.js')
const productionConfig = require('./webpack.config.prod.js')

module.exports = (env) => {
    switch(true){
        case env.development:
            return merge(commonConfig, developmentConfig)
        case env.production:
            return merge(commonConfig, productionConfig)
        default:
            throw new Error('No matching configuration was found!')
    } 
}

3、修改自定义脚本。

//package.json

"scripts": {
    "start": "npx webpack serve -c ./config/webpack.config.js --env development",
    "build": "npx webpack -c ./config/webpack.config.js --env production",
},

image.png

4、打包。

开发打包:

npm run start

生产打包:

npm run build

小结

1、bundle、chunk、vendor
bundle:n.捆;(一)包;(一)扎;一批(同类事物或出售的货品);
chunk:n.厚块;厚片;大块;相当大的量;
vendor:n.小贩;摊贩;

官方链接

BootCDN

Node.js 中文网

百度脑图

webpack
webpack Plugins
css-loader
csv-loader
xml-loader
自定义 parser
entry
Entry dependencies
SplitChunksPlugin
dependOn option
SplitChunksPlugin
output.publicPath
开发环境
生产环境
环境配置
webpack-merge

视频教程

_ 千锋最新前端webpack5全套教程,全网最完整的webpack教程(基础+高级)

webpack5课程分为四大部分,分别是webpack基础应用篇,webpack高级应用篇,webpack项目实战篇以及webpack内部原理篇。在本课程中,我们将通过前后呼应的demo来一步步从0到1地进行webpack5教学,在课程后期我们也将学到更低层的原理知识。从而做到知其然并知其所以然的精熟掌握程度。

配套资料

资料目录:H:\学习课程\ediary日记\学习课程\webpack5_千峰\资料

练习代码目录:H:\学习课程\ediary日记\学习课程\webpack5_千峰\wepack5_千峰_练习代码目录

其它

进度:211228-220103