都是B站千峰看的
一、基础
1.安装webpack
- 前提条件:在开始之前请确保安装了node.js,因为webpack是在node基础上运行了.
- webpack安装有两种:1.全局安装2.在本地工作目录下安装.
- 安装webpack需要安装两个包:1.webpack主包2.webpack-cli,这个安装好后可以在命令行里执行命令
1.1全局安装
global是指在全局的情况下安装
npm install webpack webpack-cli --global
查看
webpack -v
1.2本地安装
在本地安装webpack之前,需要安装个npm管理文件
npm init -y
安装本地webpack
npm install webpack webpack-cli --save-dev
2.运行webpack
先准备如下文件
- 左侧菜单栏找到本项目文件夹,右击点"在集成终端中打开",在终端中输入
webpack
(全局)或npx webpack
(本地),这时左侧菜单出现了dist文件夹,里面包含main文件,点击打开就是默认帮我们打包的代码.
- main是通过哪些途径帮我们打的包呢?可以在终端再次输入
webpack --stats detailed
(全局)或npx webpack
(本地),下图显示的表示从src文件夹打包出来的,而src里有index.js,默认就从这里作为打包入口.文件中加载了helloworld,helloworld中有一句console.log(“hello world”),于是webpack就帮我们打包成如上代码.
3.自定义webpack配置
webpack提供了丰富的终端命令行指令,可以通过
webpack --help
(全局)或npx webpack --help
(本地)查看
3.1根据webpack参数打包或者设置webpack
- 先手动删除dist文件夹
- 终端输入
npx webpack --entry
表示设置文件,这里设置成src 下面的index.js文件,所以下面加上./src/index.js
,然后指定mode(模式)为production
指生产环境,回车
npx webpack --entry ./src/index.js --mode production
dist又出来了
3.2配置文件更改main与dist的文件名
- 由于通过命令行来更改不直观也不能保存我们的一些配置,所以webpack给我们提供了一个配置文件来自定义配置参数这样的能力.
- 在文件根部创建webpack.config.js,这个文件名不能随便取,因为是webpack自动读取的,因为这个文件是使用node读取的,所以得使用nodejs的ComminJS模块,所以使用module.exports去定义,把它的值赋值为一个对象,这个是配置对象,并配置一个入口为
entry
,值为字符串,于是将入口于路径写上去- 设置出口为
output
,也是一个对象,包含两个属性,一个是filename,就是指定输出文件的文件名为bundle.js,再写个文件的输出路径为path
来指定,输出路径必须为绝对路径- 写完后在终端输入
npx webpack
- 尝试把新打的包在浏览器运行下
4.自动引入资源
4.1什么是插件?
webpack就像一条生产线,他需要经过一系列的流程处理后才能将源文件(入口文件)转换成输出的结果(出口),源文件可以依赖于其他的两个js模块,其中这两个js模块可能还依赖于其他的js模块,并且这个js可能还会引用css,这个css的引用需要使用webpack loaders.
webpack会把这些依赖的关系记录下来,然后交给webpack编译器,webpack经过加工以后会生成目标文件,比如css和js文件.
webpack编译的过程需要应用工具来帮忙,这些工具可以帮助webpack来执行一些特定的任务,例如打包优化,资源管理等,这些工具就是plugins插件,
- 打开webpack官方文档,里面分三部分,Community是社区插件,Webpack,是webpack内置的官方插件,Webpack Contrib是第三方插件
4.2使用HtmlWebpackPlugin自动引入
- 使用HtmlWebpackPlugin解决上面手工修改html.js去加载bundle.js的路径才能在浏览器正常显示的问题
- 先安装插件,根目录打开终端安装命令
npm install html-webpack-plugin -D
,-D参数是在本地开发目录安装.- 打开webpack.config.js,添加
plugins
属性,加s是表示去配置多个这样的插件,通过数组声明,数组里定义插件- 使用一个常量定义这个插件,可以叫HtmlWebpackPlugin,大写开头表示是一个类或者是构造函数,所以在plugins实例化,进行html自动化生成
- 在终端输入
npx webpack
可以看到dist有自动生成bundle.js和index.html,html里就自动生成包含了bundle路径的script标签
const path = require('path')
const HtnlWebpackPlugin = require('html-webpack-plugin')//新增
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist')
},
mode: 'none',
//新增
plugins: [
new HtnlWebpackPlugin()
]
}
- 该index.html文件与先前写的index.html没什么关系,是新生成的.如果想让两个有关系,或者新生的index.html与上层之前写的index.html有关系,就得需要修改配置,在new HtnlWebpackPlugin()的括号中配置花括号,配置几个option.
1.template
:模板,可以指定为目录下的index.html
2.filename
:输出的文件名
3.inject
:自动生成的script标签在什么位置,例如可以在body里生成
再执行npx webpack
const path = require('path')
const HtnlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist')
},
mode: 'none',
plugins: [
new HtnlWebpackPlugin({
template: './index.html',
filename: 'app.html',
inject: 'body'
})
]
}
4.3如何清理dist
- 如上代码在dist里面可以看到dist里还残留着最初留下的main.js与index.html文件,已经没有用了,webpack将生成的文件并放置在dist文件夹中,但是它不会追踪哪些文件是实际在项目中用到的.通常比较推荐的做法是,在每次构建前清理dist文件夹,这样只会生成用到的文件.
- 所以值需要在
output
里设置clean
为true
,再重新运行下npx webpack
就可以清理上次遗留的文件了
5.搭建开发环境
5.1设置开发环境
- 现在每次都需要手动复制dist目录下app.html的完整路径到浏览器刷新才能访问这个页面,每次更新代码又需要重新编译命令,为了提高工作效率,现在搭建个开发环境.
- 在原先
webpack.config.js
里写了一段代码叫mode:none,因为这段代码不写的话在编译时会给警告,现在将mode的值改成development
,指的是开发的模式.再次在终端输入npx webpack
编译下
5.2 使用source map追踪错误
- bundle.js文件里18行上面有一行代码为"./src/helloWorld.js",这个js文件做了转换,通过eval()传了一些字符串,这些字符串就是将来要解析的js代码.但这些代码不适合人看,可能会很难追踪到error和warning在源代码中的原始位置.
- 为了更容易最终报错,js提供了source maps 功能,可以将编译后的代码映射回原始源代码
例子
- 假设故意写错console.log代码,打开浏览器时,报错信息指向的并不是程序的源文件代码的位置
- 在
webpack.config.js
里配置devtool: 'inline-source-map'
,重新运行下,
5.3使用watch mode(观察模式)
- 每次编译需要手动运行
npx webpack
会显得很麻烦,我们可以在webpack启动时添加’watch’参数,如果其中一个文件被更新,代码将被重新编译- 终端输入
npx webpack --watch
,此时更改代码时,观察终端,它就自动帮我们编译好了,这时直接刷新浏览器就可以了.
5.4使用webpack-dev-server自动加载浏览器
webpack-dev-server
为你提供了一个基本的web server,并且具有live reloading(实时重新加载)功能- 先安装
npm install --save-dev webpack-dev-server -D
,-D是希望在本地开发环境安装- 修改配置文件,告知dev server,从什么位置查找文件
- 终端输入
npx webpack-dev-server
,就出现http的本地路径. 如果后面加--open
就会自动打开
//webpack.config.js
module.exports = {
//...
// 新增代码
devServer: {
static: './dist'
}
}
- 点击打开链接,浏览器就指向了dist根目录下,点击app.html,就出现了打印的信息,这时候修改代码也可以实时更新
- webpack-dev-server其实没有输出任何的物理文件,它把输出打包以后的bundle文件放到了内存里,
6.资源模块
- 除了引入js,还可以使用内置的资源模块,我们叫
asset modules
(资源模块)来引入任何的其他类型资源,asset modules是一种模块类型,它允许我们用webpack来打包其他的资源文件,比如说像字体文件、图标文件等,资源文件的类型称之为asset module type
,会用四种新的类型模块来替换所有的loader,asset/resource
:它会发送一个单独的文件并导出URLasset/inline
:它会导出一个资源的Data URLasset/source
:会导出资源的源代码asset
:通用资源类型,它会在导出Data URL和发送一个单独的文件之间自动做出选择
6.1Resource资源
- 在webpack.config.js里使用
module
来配置资源文件,用rules
来设置规则,rules是一个数组,可以通过配置多个对象来加载不同类型的文件,用test
属性写正则定义需要加载的文件类型,type
就是asset/resource
这个资源类型- 准备一个assets文件夹,里面有png/svg/jpg等一些做测试的资源,然后在index.js里加载这些文件,使用ESmodule来加载,加载进来的问价会生成一个URL,可以通过这个URL在页面上显示
npx webpack
运行打包,可以看到dist文件夹中有图片文件,最后加载浏览器npx webpack-dev-server --open
module.exports = {
...
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource'
}
]
}
}
6.1.1定义文件名
- 两种方法:
- 1.1:output定义新属性
assetModuleFilename
(资源模块的文件名),这里不仅可以设置资源名称,还可以设置路径.重新运行打包.- 1.2.使用webpack默认的自动生成文件名,就是将如上test写成
[]
里面写一个叫做contenthash
,表示根据文件的内容生成哈希的字符串,扩展名也可以使用原资源的扩展名,所以也可以使用[ext]
来表示扩展名.重新运行编译
- 第二种方法:module中定义rules的时候可以定义一个属性叫
generator
,在这里面可以定义文件名字和路径,可以通过一个filename
属性定义,并且该方法优先级高于第一种方法,会覆盖
- 依旧可以使用中括号
6.2inline资源
- dist没变化,只能去浏览器看效果
//webpack.config.js页
module.exports = {
...
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource',
generator: {
filename: 'images/[contenthash][ext]'
}
},
//新增部分
{
test: /\.svg$/,
type: 'asset/inline'
}
]
}
}
//index.js页
//导入函数模块
import helloWorld from "./helloWorld";
import imgSrc from './assets/test.png'
import logoSvg from './assets/webpack-logo.svg'//新增
helloWorld()
const img = document.createElement('img')
img.src = imgSrc
document.body.appendChild(img)
//新增
const img2 = document.createElement('img')
img2.src = logoSvg
document.body.appendChild(img2)
6.3source资源
现在webpack.config.js里配置文件
module.exports = {
...
module: {
rules: [
...
{
test: /\.txt$/,
type: 'asset/source'
},
]
}
}
其次在assets文件夹下创建一个文本
index.js里将他导入并放在body上
npx webpack-dev-server --open打开浏览器就可以在页面看到效果
6.4通用资源类型
- 在导出一个data URI(inline)和发送一个单独的文件(resource)之间自动选择
- 照旧先在webpack.config.js里配置文件,assets里配置一张jpg格式的图片,index引入,npx webpack打包后发现dist里images中多了一个图片文件
module.exports = {
...
module: {
rules: [
...
{
test: /\.jpg$/,
type: 'asset'
},
]
}
}
- 但为什么会在images文件夹里?回看webpack.config.js里我们并没有像resource一样设置generator,它实际上是继承使用了output里面assetModuleFilename.
- 运行npx webpack-dev-server --open浏览器上打开也是正常的
- 上面说它会在两种资源中做选择,很显然现在选择了resource,那它是根据什么来进行选择的呢?
- 默认情况下webpack会去判断加载的资源大小,默认大于8k就会创建一个资源,小于8k就会作为inline生成base64的链接.但可以手动调整临界值.
- 在webpack.config.js添加
parser
解析器,默认maxSize为4*1024,现在可以调整为4兆试试4 * 1024 * 1024
module.exports = {
...
module: {
rules: [
...
{
test: /\.jpg$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 * 1024
}
}
},
]
}
}
- npx webpack打包后可以发现dist已经没有这个文件了,运行浏览器后打开后图片是正常显示的,但是打开控制台检查,这张图片的来源变成了base64
7.管理资源
- webpack除了上述资源模块以外还可以通过loader去引入其他类型文件,webpack只能理解js和json这样的文件,这是webpack开箱可用自带的能力,而loader可以让webpack去解析其他的类型的文件,并将这些文件转化为有效的模块以供程序使用.
- loader定义有两个重要的属性,如下
module.rules
下面定一个test
,这个属性识别出哪些文件被转换,还有一个属性叫use
,它在定义转换的时候应该使用哪个loader来进行转话.
7.1加载css
- 先在根目录下安装css,-D是为了在本地开发环境安装
npm install css-loader -D
这个loader会把我们的css放置到页面上
npm install style-loader -D
- 在webpack.config.js配置刚下载好的css,注意:use数组中的顺序不能颠倒,需要先加载
'css-loader'
再加载'style-loader'
,也就是执行顺序是从后往前加载的,也就是webpack支持loader的链式调用,链式的每一个loader都可以对源进行转换,而且转换是逆序的,第一个loader转换的源会传给第二个loader,最后webpack希望styleloader会返回一个js
module.exports = {
...
devServer: {
static: './dist'
},
module: {
rules: [
...
{
test: /\.css$/,
use: ['style-loader','css-loader']
}
]
}
}
- src文件夹下定义个style.css文件,里面随便写个颜色
- 然后在index.js像加载模块一样加载这个css,并添加到body上,npx webpack运行打包,npx webpack-dev-server --open在浏览器看效果,山竹绿了
- 解析less/scss这样的预解析文件
npm install less-loader less -D
- 并在webpack.config.js中将这个文件配置上去,这里需要注意
less-loader
一定要放到最后,因为需要用loader解析css文件,然后才将解析好的文件交给css-loader,最后将结果交给style-loader放置在页面标签上- 然后将扩展名再加个less
module.exports = {
...
module: {
rules: [
...
{
test: /\.(css|less)$/,
use: ['style-loader', 'css-loader', 'less-loader']
}
]
}
}
- 而后在src文件夹中创建一个新的名为less后缀的文件,随便写点颜色,重新运行打包到浏览器查看
7.2 抽离和压缩css
- 多数情况下,也可以进行压缩css,以便在生产环境中节省加载时间,同时还可以将css文件抽离成一个单独的文件,通过link来加载.
- 可以使用一个插件来帮忙,该插件必须在webpack5才能使用
npm install mini-css-extract-plugin -D
- 在webpack.config.js引用插件
const MiniCssExtranstPlugin = require('mini-css-extract-plugin')
module.exports = {
...
plugins: [
...
new MiniCssExtranstPlugin()
],
}
- 之前我们是通过
style-loader
放置到页面的,现在我们是单独抽离出一个文件,所以就无效了,现在把它干掉用新的loader来代替,MiniCssExtranstPlugin.loader
- 运行打包下可以看到dist里面多了个
main.css
module.exports = {
...
module: {
rules: [
...
{
test: /\.(css|less)$/,
use: [MiniCssExtranstPlugin.loader, 'css-loader', 'less-loader']
}
]
}
}
- 该文件将两个文件的css代码都合并到一个css里面了,并且在app.html看到没有style标签了,但是多了link
- 运行到浏览器查看也是一样的
指定文件夹与文件名
- 在实例化插件时传入
filename
自定义文件名,运行后发现重新生成了styles文件
压缩css
npm install css-minimizer-webpack-plugin -D
webpack.config.js引入,在优化的配置
optimization
里面做配置,并将mode改为production
,运行打包
7.3加载images图像
- 加入,现在我们正在下载css,但是像background和icon这样的图像要如何处理呢?可以使用内置的assets Modules资源模块
7.4 加载font字体
- 在css3中新增了一个功能叫webfont,在css中可以加载一个font字库,然后就可以在代码中定义icon图标
- 像字体这样的其他资源如何处理呢?使用assets Modules可以接收并加载任何文件,然后将其输出到构建目录,这就是说,我们可以将它们用于任何类型的文件,也包括字体
- 先在webpack.config.js配置,准备一份ttf文件(我没有,看老师操作截图一份),在style.css中用css3的
@fontface
来定义字体,font-family
是自己定义的字体文字,src
指向文件的路径,format
定义格式- 然后打算在项目中使用这个定义好的字体文件,
module.exports = {
...
module: {
rules: [
...
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource'
}
]
},
}
加入页面中运行打开浏览器多了红心的icon
7.5 加载数据
可以加载的有用的资源还有数据,如JSON文件,CSV、TSV和XML。类似于Node.js,JSON支持实际上是内置的,也就是说
import Data from ./data.json
默认将正常运行.要导入CSV、TSV和XML,可以使用csv-loader
和xml-loader
npm install csv-loader xml-loader -D
- 在assets里配置xml文件与csv文件
data.xml
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Mary</to>
<from>John</from>
<heading>Reminder</heading>
<body>Call Cindy on Tueaday</body>
</note>
data.csv
to,from,heading,body
Mary,John,Reminder,Call Cindy on Tuesday
Zoe,Bill,Reminder,Buy orange juice
Autumn,Lindsey,Letter, I miss you
- 在webpack.config.js配置两个
module.exports = {
...
module: {
rules: [
...
{
test: /\.(csv|tsv)$/,
use: 'csv-loader'
},
{
test: /\.xml$/,
use: 'xml-loader'
},
]
},
}
index.js导入文件并打印,运行看浏览器
8.如何使用babel-loader
- 将ES6转换成低版本的浏览器能够识别的ES代码,
- webpack自带可以加载js模块的功能,但是它只能做js模块化的打包,并不能转化js里的代码,比如将ES6转化为ES5,那有时候我们代码能够正常运行,得靠浏览器来解析,当浏览器版本过低时,那运行时可能会导致报错.
因此写代码是需要转化的,这个转化的工具就是babel,babel与webpack结合就需要babel-loader- 安装:
babel-loader:
在webpack里应用babel解析ES6的桥梁
@babel/core:
babel核心模块
@babel/preset-env:
babel预设,一组babel插件的集合
在本地开发后面可以加-D
npm install babel-loader @babel/core @babel/preset-env -D
- 因为业务代码里既可以加载自己写的js,也可以加载全局里的node modules里面的js,但是node里的js不需要babel-loader编译,所以需要exclude排除node modules里的代码.运行
npx webpack
//webpack.config.js
module.exports = {
...
module: {
rules: [
...
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
}
regeneratorRuntime插件
- regeneratorRuntime是webpack打包生成的全局辅助函数,由babel生成,用于兼容async/await语法
# 这个包中包含了regeneratorRuntime,运行时需要
npm install @babel/runtime -D
# 这个插件会在需要regeneratorRuntime的地方自动require导包,编译时需要
npm install @babel/plugin-transform-runtime
- 在上面的代码中新增plugins,
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
[
'@babel/plugin-transform-runtime'
]
]
}
}
}
9.代码分离
代码分离是webpack最引人注目的特性之一,这个特性能够把代码分离到不同的bundle中,bundle就是我们打包分离出来的文件,然后我们把这些分拣按需加载或者是并行加载.
代码分离可以用于获取更小的bundle,以及控制资源加载的优先级,如果使用合理,会极大地影响加载时间.常用的代码分离有三种:
1.
配置入口起点
:使用entry配置手动地分离代码,这个方法有个问题,如果是多个入口,那么这些多个入口共享的文件会分别在每个包里边去重复打包2.
防止重复
:使用Entry dependencies
或者SplitChunksPlugin
去重和分离代码3.
动态导入
:通过模块的内嵌函数(import)调用来分离代码
9.1入口起点
- 这是迄今为止最简单直观的分离代码的方式.不过,这种方式手动配置较多,并且有一些隐患,我们将会解决这些问题,先来看看如何从
main bundle
中分离another module
(另一模块)- 在src下创建another-module.js,在这个js中去加载第三方的模块,试用下这个方法,比如说用join方法拼接下,这个方法后面可以拼接参数,比如传一个数组,数组里面放三个元素,然后用空格进行分割.
配置入口
- 打开webpack.config.js,原来entry中我们使用的是字符串只配置了一个路径,现在打算用对象配置多个入口,
module.exports = {
...
entry: {
index: './src/index.js',
anoyher: './src/another-module.js'
},
}
配置完编译就会产生报错,说是冲突了,表示多个入口使用的是一个出口文件名叫bundle
- 所以需要两个入口文件设置两个出口,在
output.filename
中设置[name]
方法,这个方法可以拿到入口里chunk里key,再次执行npx webpack
运行下
module.exports = {
...
output: {
filename: '[name].bundle.js',
...
},
}
这是因为之前引入了文件所以这么大
观察app.html中已经将这两个文件引入进来了,再运行npx webpack-dev-server --open
,控制台也顺利打印出another,module,loaded!
9.2防止重复
- 基于9.1中的代码做些调整,设置entry中index为对象,设置dependOn值为shared,这样可以把共享的文件给抽出来,然后在文件同级的那列设置shared值为lodash,意思是这两个文件有lodash代码就会抽离出来,并且取名为shared这样的一个chunk,
npx webpack
运行,观察控制台文件大小已经发生变化,app.html中也引入了第三个文件
module.exports = {
...
entry: {
index: { import: './src/index.js', dependOn: 'shared' },
anoyher: { import: './src/another-module.js', dependOn: 'shared' },
shared: 'lodash'
},}
- 除了上面的方法以外,还可以使用webpack内置的插件
split-chunks-plugin
,这个插件也可以将我们模块依赖的公共部分抽离成单独的chunk.- 先把之前entry中的内容注释,恢复成原来多个chunk的样子,然后配置新的选项,以前写过一个optimization,在里面添置新的配置项,叫做splitChunk对象,里面写一个chunk:all,这样就实现了一个代码分隔,并且把公共代码抽离至单独文件.
- npx webpack执行编译
module.exports = {
entry: {
// index: { import: './src/index.js', dependOn: 'shared' },
// anoyher: { import: './src/another-module.js', dependOn: 'shared' },
// shared: 'lodash'
index: './src/index.js',
anoyher: './src/another-module.js'
},
...
optimization: {
minimizer: [
new CssMinimizerPlugin()
],
splitChunks: {
chunks: 'all'
}
}
}
9.3动态导入
- 当涉及到动态代码拆分时,webpack提供了两个类似的技术.第一种,也是推荐选择的方式是,使用符合ECMAScript提案的import()语法来实现动态导入.第二种,则是webpack的遗留功能,使用webpack特定的require.ensure.
- 先注释掉以下
module.exports = {
entry: {
// index: { import: './src/index.js', dependOn: 'shared' },
// anoyher: { import: './src/another-module.js', dependOn: 'shared' },
// shared: 'lodash'
index: './src/index.js',
// anoyher: './src/another-module.js'
},
...
optimization: {
minimizer: [
new CssMinimizerPlugin()
],
//splitChunks: {
// chunks: 'all'
//}
}
}
- 在src里创建async-module.js的文件,定义一个getComponent的函数,使用import方法去加载模块,import函数返回的是promise,所以可以使用then成功以后的回调函数,这个函数就是载入的loadsh的一个引用,得需要从这里面解构出default,然后在里面就可以写业务代码了.
编译npx webpack
//try-webpack\src\async-module.js
function getComponent() {
return import('lodash').then(({ default: _ }) => {
const element = document.createElement('div')
element.innerHTML = _.join(['hello', 'webpack'], " ")
return element
})
}
getComponent().then((element) => {
document.body.appendChild(element)
})
//try-webpack\src\index.js
import './async-module'
9.4懒加载
懒加载或者按需加载,是一种很好的优化网页或应用的方式.这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块.这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载.
**创建一个math.js
**文件,在主页面中部分通过点击按钮调用其中的函数;
//try-webpack\src\math.js
export const add = (x, y) => {
return x + y
}
export const minus = (x, y) => {
return x - y
}
编辑index.js文件
//这里有句注释,我们把它称为webpack魔法注释:webpackChunkName:'math',告诉webpack打包生成的文件名为math
const button = document.createElement('button')
button.textContent = '点击执行加法运算'
button.addEventListener('click', () => {
import(/* webpackChunkName:'math'*/ './math.js').then(({ add }) => {
console.log(add(4, 5));
})
})
document.body.appendChild(button)
npx webpack
编译
可以看dist里面就有math.bundle.js这个文件,点击打开就是刚刚写好的两个函数
npx webpack-dev-server到浏览器上看下
- 一开始并没有加载math文件,只有当用户点击按钮时才加载下来,如果用户永远不点击,那就永远不加载,这样可以节省很多网络流量
9.5 预获取/预加载模块
webpack v4.6.0+ 增加了对预获取和预加载的支持.
在声明import时,使用下面这些内置指令,可以让webpack输出’resource hint(资源提示)’,来告知浏览器:
prefetch(预获取):将来某些导航下可能需要的资源
preload(预加载):当前导航下可能需要资源
如上面的步骤,找到index.js,在魔法注释的部分加上webpackPrefetch:true
,意思为预获取,npx webpack编译下,npx webpack-dev-server打开浏览器,可以看到math文件已经被加载下来了
当点击按钮时又加载了一次
这样看起来还不如刚刚的懒加载,它的意义是如何呢?
先打开element,可以看到多了一个新的这样的link,它把math的js文件通过prefetch预加载了下来,它的意义就在于当页面加载完毕,在网络空闲的时候再去加载我们打包好的math,这种方式比刚刚的懒加载还要优秀,这就是所谓的prefetch
除了prefetch以外,还有一个功能叫做preload,修改下一下代码,npx webpack运行编译,npx webpack-dev-server打开浏览器
一开始并没有加载math文件,点击后才加载,preload方法与上面懒加载效果类似
10.缓存
- 在如上的知识点中,我们是用webpack来打包我们模块化以后的应用程序,webpack会生成一个可以部署的dist目录,然后我们把打包好的内容放置在这个目录里,将来我们把这个目录的内容部署到server上,也就是服务器上,那么client通常是浏览器就能够访问我们这个服务器上的网站和资源了,而最后一步获取资源是比较耗时间的,这就是为什么我们浏览器会使用一个名为缓存的技术,我们可以通过命中缓存以降低网络流量,使网站加载速度更快.
- 然而我们在部署新版本的时候,不更改资源的文件名,浏览器可能会认为你没有更新,那就会使用他的缓存版本,由于缓存的存在,我们需要获取新的代码的时候就会显得很棘手,这里通过一些设置来确保webpack编译生成的文件,能够被客户端缓存,而在文件内容发生变化的时候又能够请求到新的文件.
10.1输出文件的文件名
打开webpack.config.js,output中filename里配置了一个中括号name,它的作用就是可以读取到我们每个chunk的名字,后边我们又冠以bundle.js,那么我们在dist文件夹里看到打包好以后的文件叫index,bundle.js,那么浏览器有缓存特性,它会把我们的index,bundle.js给缓存下来,但是如果我们修改了index.bundle.js里边文件的内容,那么文件名没有变,浏览器会认为我们没有修改这个文件,所以我们必须在打包的时候把这个文件的名字也一同给重新打包.
那我们可以在这里面重新的去修改一下这个filename,像这种中括号name的格式我们称之为substitution可替换的模板字符串,
现在把bundle修改成中括号contenthash,这样的话可以根据我们文件的内容来形成一个哈希的字符串,从而我们的文件名会随着我们文件内容的变化而变化,这样就不怕浏览器缓存了,
npx webpack看下dist里已经是哈希值了
10.2缓存第三方库
将第三方库(library)(例如lodash)提取到单独的vendor chunk文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改,因此通过实现以上步骤,利用client的长效缓存机制,命中缓存来消除请求,并减少像server获取资源,同时还能保证client代码和server代码版本一致.optimization.splitChunks添加如下cacheGroups参数并构建
例如这两个内容是始终不变的,那文件名也应该始终不变
打开webpack.config.js,第三方文件都放在node_modules文件夹里,所以匹配node_modules,另起名字为vendors,执行npx webpack
10.3将js文件放到一个文件夹中
目前,全部js文件都在dist文件夹根目录下,我们尝试把它们放到一个文件夹中,这个其实也简单,修改配置文件:
11.拆分开发环境和生产环境配置
现在我们只能手工地来天正mode选项,实现了生产环境和开发环境的切换,并且很多配置在生产环境和开发环境中是存在不一致的情况的,比如开发环境没有必要设置缓存,生产环境还需要设置公共路径等
11.1公共路径
publicPath配置选项在各种场景中都非常有用,你可以通过它来指定应用程序中所有资源的基础路径.
基于环境设置
在开发环境中,我们通常有一个assets/文件夹,它与索引页面位于同一级别.这没太大问题,但是,如果我们将所有静态资源托管值CDN,然后想在生产环境中使用呢?
想要解决这个问题,可以直接使用一个environment variable(环境变量),假设我们有一个变量ASSET_PATH:
打开webpack.config.js,找到output,创建publicPath,这个路径我们可以指定一个域名,比如:http://localhost:8080,后面别忘了一定价格斜线,npx webpack 编译下,app.html下的资源路径已经由相对路径转为域名,这个域名可以指定为前端的域名或者cdn服务器的域名等待都可以的
11.2环境变量
想要消除webpack.config.js在开发环境和生产环境之间的差异,你可能需要环境变量(environment variable)
webpack命令行环境配置的–env参数,可以允许你传入任意数量的环境变量.而在webpack.config.js中可以访问到这些环境变量,例如–env production或–env goal=local
npx webpack --env goal=local --env production --progress
对于我们的webpack配置,有一个必须要修改之处,通常,module.exports指向配置对象,要使用env变量,你必须将module.exports转换成一个函数,函数的里头返回一个对象
可以console.log(env)打印下里面的内容,运行 npx webpack --env produrction,显示我们用户已经设置了一个env production
其实我们也可以去携带一个key和value
npx webpack --env production --env goal=local
这就说明我们可以利用用户再去编译的时候传入的这个参数来去对当前的配置进行修改
我们可以把mode的这个development这个值给修改一下,比如可以看看用户是否有production,如果有的话,那么这里边就会把它变成一个true的值给收回来.
我们用户现在很显然已经传入了production,
npx webpack --dev production --env goal=local
编译完了以后可以看到有两个警告语,表示当前是在生产环境下编译的,点击dist的script文件,可以看到已经被打包了,虽然代码是已经打包了,但是有个问题,这些代码没有压缩
我们可以使用webpack开箱即用的terser插件,但是这是没有生效的原因是因为我们在下面配了一个minimizer做了一个css压缩,如果这里边我们做了配置以后,那么terser需要我们单独的配置一下才可以.
现在打开根目录终端,新增一个终端窗口,
npm install terser-webpack-plugin -D
下载好以后就可以引入刚刚下载好的terser-webpack-plugin
运行下生产环境,就可以看到打包好的文件,如果是在开发环境,比如将–env development,它就不执行压缩
npx webpack --env production
11.3拆分配置文件
目前,生产环境和开发环境使用的是一个配置文件,我们需要将这两个文件单独放到不用的配置文件中.如webpack.config.dev.js(开发环境配置)和webpack.config.prod.js(生产环境配置).在项目根目录下创建一个配置文件夹config来存放他们
webpack.config.dev.js配置如下,将webpack.config.js复制粘贴过去,将函数的配置删掉,恢复成扁平的对象
现在准备做开发环境的改造,
- 1.将output关于文件名的这个缓存删掉
- 2.开发环境也没必要做publicPath,也删了
- 3.move改为development开发环境
- 4.开发环境不需要压缩,所以minimizer删了
运行需要使用我们定制的文件,可以用-c指定文件,
打包后在config文件夹中生成了dist,打开看看,script的哈希没了,代码也没压缩,HTML里文件缓存也没了
npx webpack -c ./config/webpack.config.dev.js
//try-webpack\config\webpack.config.dev.js
//更改后代码如下
const path = require('path')
const HtnlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtranstPlugin = require('mini-css-extract-plugin')
module.exports = {
entry: {
// index: { import: './src/index.js', dependOn: 'shared' },
// anoyher: { import: './src/another-module.js', dependOn: 'shared' },
// shared: 'lodash'
index: './src/index.js',
//anoyher: './src/another-module.js'
},
output: {
filename: 'scripts/[name].js',
path: path.resolve(__dirname, '../dist'),
clean: true,
assetModuleFilename: "images/[contenthash][ext]"
},
mode: 'development',
devtool: 'inline-source-map',
plugins: [
new HtnlWebpackPlugin({
template: './index.html',
filename: 'app.html',
inject: 'body'
}),
new MiniCssExtranstPlugin({
filename: 'styles/[contenthash].css'
})
],
devServer: {
static: './dist'
},
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource',
generator: {
filename: 'images/[contenthash][ext]'
}
},
{
test: /\.svg$/,
type: 'asset/inline'
},
{
test: /\.txt$/,
type: 'asset/source'
},
{
test: /\.jpg$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 * 1024
}
}
},
{
test: /\.(css|less)$/,
use: [MiniCssExtranstPlugin.loader, 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/,
use: 'csv-loader'
},
{
test: /\.xml$/,
use: 'xml-loader'
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
[
'@babel/plugin-transform-runtime'
]
]
}
}
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
}
现在做生产环境的配置,在config文件夹里创建webpack.config.prod.js,再次复制粘贴webpack.config.js的内容进来,
1.如上需要将函数改为对象
2.move改成生产环境
3.开发环境不需要devtool,删掉
4.devServer不需要,删掉
运行
npx webpack -c ./config/webpack.config.prod.js
,看dist,它又压缩了,又有哈希了
//try-webpack\config\webpack.config.prod.js
//更改后代码如下
const path = require('path')
const HtnlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtranstPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
entry: {
// index: { import: './src/index.js', dependOn: 'shared' },
// anoyher: { import: './src/another-module.js', dependOn: 'shared' },
// shared: 'lodash'
index: './src/index.js',
//anoyher: './src/another-module.js'
},
output: {
filename: 'scripts/[name].[contenthash].js',
path: path.resolve(__dirname, '../dist'),
clean: true,
assetModuleFilename: "images/[contenthash][ext]",
publicPath: 'http://localhost:8080/'
},
mode: 'production',
devtool: 'inline-source-map',
plugins: [
new HtnlWebpackPlugin({
template: './index.html',
filename: 'app.html',
inject: 'body'
}),
new MiniCssExtranstPlugin({
filename: 'styles/[contenthash].css'
})
],
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource',
generator: {
filename: 'images/[contenthash][ext]'
}
},
{
test: /\.svg$/,
type: 'asset/inline'
},
{
test: /\.txt$/,
type: 'asset/source'
},
{
test: /\.jpg$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 * 1024
}
}
},
{
test: /\.(css|less)$/,
use: [MiniCssExtranstPlugin.loader, 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/,
use: 'csv-loader'
},
{
test: /\.xml$/,
use: 'xml-loader'
},
{
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: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
}
运行
npx webpack serve -c ./config/webpack.config.dev.js
,这里npx webpack serve相当于npx webpack -dev -server,可以看到正常运行
11.4npm脚本
每次打包或启动服务时,都需要在命令行里输入一长串的命令,我们将父目录的package.json、node_modules与package-lock.json拷贝到当前目录下
- 1.根目录创建package.json
- 2.对象里写script,就是脚本,在脚本里面我们可以把刚刚在命令行里面输的那两个特别复杂的命令给定义好,例如start,就是所谓的启动一个开发环境的命令,值写成npx webpack serve -c ./config/webpack.config.dev.js,
- 3.运行npm run start,就跟平时运行Vue那样
//try-webpack\package.json
{
"scripts": {
"start": "npx webpack serve -c ./config/webpack.config.dev.js",
"build": "npx webpack -c ./config/webpack.config.prod.js"
}
}
把这个复制粘贴到最外层的package.json,然后把刚刚那个文件删掉,再把package-lock.json,package.json,和node_modules文件夹复制粘贴到根目录
现在可以把script里面的npx删掉,也是可以运行的
11.5提取公共配置
这时,我们发现这两个配置里存在大量的重复代码,可以手动的将这些重复的代码提取到一个文件里,
创建webpack.config.common.js,配置公共的内容
//config\webpack.config.prod.js
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
output: {
filename: 'scripts/[name].[contenthash].js',
publicPath: 'http://localhost:8080/'
},
mode: 'production',
devtool: 'inline-source-map',
optimization: {
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin()
]
}
}
//config\webpack.config.dev.js
module.exports = {
output: {
filename: 'scripts/[name].js'
},
mode: 'development',
devtool: 'inline-source-map',
devServer: {
static: './dist'
},
}
//config\webpack.config.common.js
const path = require('path')
const HtnlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtranstPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
entry: {
// index: { import: './src/index.js', dependOn: 'shared' },
// anoyher: { import: './src/another-module.js', dependOn: 'shared' },
// shared: 'lodash'
index: './src/index.js',
//anoyher: './src/another-module.js'
},
output: {
path: path.resolve(__dirname, '../dist'),
clean: true,
assetModuleFilename: "images/[contenthash][ext]",
},
plugins: [
new HtnlWebpackPlugin({
template: './index.html',
filename: 'app.html',
inject: 'body'
}),
new MiniCssExtranstPlugin({
filename: 'styles/[contenthash].css'
})
],
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource',
generator: {
filename: 'images/[contenthash][ext]'
}
},
{
test: /\.svg$/,
type: 'asset/inline'
},
{
test: /\.txt$/,
type: 'asset/source'
},
{
test: /\.jpg$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 * 1024
}
}
},
{
test: /\.(css|less)$/,
use: [MiniCssExtranstPlugin.loader, 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/,
use: 'csv-loader'
},
{
test: /\.xml$/,
use: 'xml-loader'
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
[
'@babel/plugin-transform-runtime'
]
]
}
}
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
}
11.6合并配置文件
配置文件拆分好后,如何保证配置合并没有问题呢?webpack-merge这个工具可以完美解决这个问题
npm install webpack-merge -D
在config创建新的文件,叫webpack.config.js
抽离merge方法
定义变量引入公共/生产/开发环境的文件路径
通过module.exports导出一个函数,函数体里可以返回我们相关的一些合并好的对象
判断用户输入的关键字决定合并的文件
//config\webpack.config.js
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.config.common')
const productionConfig = require('./webpack.config.prod')
const developmentConfig = require('./webpack.config.dev')
module.exports = (env) => {
switch (true) {
case env.development:
return merge(commonConfig, developmentConfig)
case env.production:
return merge(commonConfig, productionConfig)
default:
return new Error('No matching cofiguration was found')
}
}
外面的package.json也修改下
运行下npm run start 或npm run build 试下有没有问题