1. webpack介绍
本质上,webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具。当 webpack 处理应用程序时,它会在内部会以路口文件构建一个 依赖图(dependency graph),
此依赖图对应映射到项目所需的每个模块,并生成一个或多个 bundle
1)安装
//webpack是核心包,webpack-cli命令行工具包,通过命令行工具包执行核心包
npm install webpack webpack-cli --save-dev
2)核心概念
1.入口(entry)
入口起点指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的
默认值是 ./src/index.js(./相对的目录是process.cwd(),就是运行的项目),但你可以通过在 webpack configuration 中配置 entry 属性,来指定一个(或多个)不同的入口起点
2.输出(output)
output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件,主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中
output: {
path: path.resolve(__dirname, 'dist'),// 输出文件夹的绝对路径
//入口代码块的名称 main
filename: 'main.js' // 输出的文件名
// 当你把打包后文件插入到index.html文件里的时候 src如何写的?publicPath+filename
// /assets/main.js
// publicPath:'/assets',//开发中要配publicPath:'/',不然引用资源路径前面没有/就是相对路径,可能资源路径会有问题
//非入口代码块的名称配置项
//非入口代码块有两个来源1.代码分割 vendor(第三方模块) common(共享模块)
//懒加载 import方法加载模块
chunkFilename:'[name].[hash:10].js',
}
3.loader
webpack 只能理解 JavaScript 和 JSON 文件,loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中,
loader本质上是个转化器,可以把任意类型的模块转换成js模块,本质上是一个函数,接收源文件,返回一个JS模块代码
module: {
rules: [
//raw-loader解析纯文本,直接将文本拿过来,什么事都不做
{ test: /\.txt$/, use: 'raw-loader' }
]
}
4.插件(plugin)
loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量
plugins: [
//以./src/index.html为模板,将打包后的脚步插入到模板中
new HtmlWebpackPlugin({template: './src/index.html'})
]
5.模式mode
日常的前端开发工作中,一般都会有两套构建环境
一套开发时使用,构建结果用于本地开发调试,不进行代码压缩,打印 debug 信息,包含 sourcemap(方便代码调试)文件
一套构建后的结果是直接应用于线上的,即代码都是压缩后,运行时不打印 debug 信息,静态文件不包括 sourcemap
webpack 4.x 版本引入了 mode 的概念
当你指定使用 production mode 时,默认会启用各种性能优化的功能,包括构建结果优化以及 webpack 运行性能优化
而如果是 development mode 的话,则会开启 debug 工具,运行时打印详细的错误信息,以及更加快速的增量编译构建
// mode 当前的运行模式 development开发环境 production生产环境 其默认值为 production
mode: 'development',
6.webpack配置文件
webpack配置文件是webpack.config.js文件,我们可以通过package.json文件中的"build": "webpack --config webpack.config2.js",
来修改文件名,运行npm run build会自动去找webpack.config.js文件运行
2.开发环境配置
webpack-dev-sever就是一个express服务器
1)开发服务器webpack-dev-server的配置
1.安装
//可以帮我们预览我们的项目,可以支持热更新
npm install webpack-dev-server --save-dev
2.配置package.json文件指令
//webpack5指令,webpack4指令是webpack-dev-server
"start": "webpack serve"
3.配置
// devServer会启动一个HTTP开发服务器,把一个文件夹作为静态根目录,默认是output指定的文件夹,再是contentBase字段指定的文件夹
// 静态文件根目录是可以有多个的,就是页面中访问的资源,如图片,会先去output指定的文件夹找,再去contentBase字段指定的文件夹找
// 为了提高性能,使用的内存文件系统,不会再硬盘中输出dist目录,配置writeToDisk:true会在硬盘中输出一份
// 开发环境使用,线上环境没用
devServer: {
contentBase: resolve(__dirname, 'static'),//指定静态根目录
// writeToDisk:true,//如果你指定此选项,也会把打包后的文件写入硬盘一份
compress: true, /// 是否启动压缩 gzip
port: 8080, // 指定HTTP服务器的端口号
open: true, // 自动打开浏览器
},
2)支持CSS
1.安装模块
//css-loader用来翻译处理@import和url()
//style-loader可以把CSS生成style标签插入DOM中
npm i style-loader css-loader -D
2.配置
module: {
rules: [
//rules匹配规则从下到上,从右到左
{ test: /\.css$/, use: ['style-loader','css-loader'] }
]
},
3)支持less和sass
1.安装模块
npm i less less-loader -D
npm i node-sass sass-loader -D
2.配置
module: {
rules: [
//先将less或sass转成css,再去解析@import和url(),再去把CSS生成style标签插入DOM中
{ test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
]
},
4)支持图片
1.安装模块
//file-loader 解决CSS等文件中的引入图片路径问题
//url-loader 当图片小于limit的时候会把图片BASE64编码,大于limit参数的时候还是使用file-loader进行拷贝
//html-loader 把html文件中引入资源的相对路径改成绝对路径,确保资源引入的正确性
npm i file-loader url-loader html-loader -D
2.配置(一般四种都要配)
1)方式1:放在静态文件static目录里,通过index.html中的img标签(/logo.png,会去static目录里找)直接引用,需要配置`devServer的contentBase字段`
2)方式2:可以在CSS中通过 url 引入图片 css-loader来进行解析处理
3)方式3:图片是通过import或require的方式引入的
{ test: /\.(jpg|png|bmp|gif|svg)$/,
use: [{
//如果使用file-loader那么没有limit字段,url-loader就是file-loader的加强版
loader: 'url-loader',
options: {
//不开启es6模块,开启es6模块获取图片要加.default,而commonjs模块不需要
esModule: false,
//输出的图片名字,10位hash值加扩展名
name: '[hash:10].[ext]',
limit: 32 * 1024,// 如果文件的体积小于limit,小于8K的话,就转成BASE64字符串内嵌到HTML中,否则 行为和file-loader
}
}]
},
4)方式4:html文件中通过相对路径的方式引入图片或其他资源
//把html文件中引入资源的相对路径改成绝对路径
{ test: /\.html$/, use: ['html-loader'] },
5)JS的兼容性
Babel其实是一个编译JavaScript的平台,可以把ES6/ES7,React的JSX转义为ES5
1.babel-loader、babel-core、babel-preset-env之间的关系
let babelCore = require('@babel/core');
let presetEnv = require('@babel/preset-env');
//babel-loader的本质就是个转换器,就是loader函数, 作用是调用babelCore
//babelCore本身只是提供一个过程管理功能,把源代码转成抽象语法树,进行遍历和生成,它本身也并不知道 具体要转换什么语法,
//以及语法如何转换,babel-preset-env做的就是这件事
//1.先把ES6转成ES6语法树 babelCore
//2.然后调用预设preset-env把ES6语法树转成ES5语法树 preset-env
//3.再把ES5语法树重新生成es5代码 babelCore
function loader(source){
let es5 = babelCore.transform(source,
{
presets:['@babel/preset-env']
});
return es5;
}
module.exports = loader;
2.安装模块
//babel-loader 使用Babel和webpack转译JavaScript文件
//@babel/core Babel编译的核心包
//@babel-preset-env 预设可以转换js语法
//@babel/preset-react React插件的Babel预设,可以转换jsx语法
//@babel/plugin-proposal-decorators把类和对象装饰器编译成ES5
//@babel/plugin-proposal-class-properties转换静态类属性以及使用属性初始值化语法声明的属性
//@babel/polyfill可以处理所有ES6以上的语法JS兼容包(不包含基本ES6语法),但是没有按需加载功能,需要配合core-js才有按需加载功能
cnpm i babel-loader @babel/core @babel/preset-env @babel/preset-react -D
cnpm i @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D
cnpm i @babel/polyfill core-js -D
3.配置
{
test: /\.jsx?$/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
["@babel/preset-env",//@babel/preset-env只能将一些普通ES6语法转换ES5一下,一旦一些较复杂的语法,如Promise、Async等,就不会转化(此时在IE浏览器运行就报错)
//@babel/polyfill可以处理所有ES6以上的语法JS兼容包(不包含基本ES6语法),但是没有按需加载功能,需要配合core-js才有按需加载功能
/* {
useBuiltIns: 'usage', // 按需加载polyfill,用到哪些高级的语法,就引入对应的代码来解析
// debug: true,
corejs: { version: 3 }, // 指定corejs(会去按需引入polyfill)的版本号 2或者3
targets: { // 指定要兼容哪些浏览器
chrome: '60',
},
}, */
],
"@babel/preset-react", // 可以转换JSX语法
],
//loader中的插件也是从下往上的,webpack的插件是从上往下的
plugins: [
//legacy: true表示支持该插件,presets是plugins的集合
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
],
},
},
],
},
6)path的区别和联系
|output|path|指定输出到硬盘上的目录
|output|publicPath|表示的是打包生成的index.html文件里面引用资源的前缀
|devServer|contentBase|用于配置提供额外(输出到硬盘上的目录额外的静态文件内容的目录)静态文件内容的目录
|devServer|publicPath|表示的是打包生成的静态文件所在的位置,可以说是devServer设置了两个静态文件内容的目录,分别是contentBase和publicPath字段设置的,publicPath优先级更高
(若是devServer里面的publicPath没有设置,则会认为是output里面设置的publicPath的值,如果它也没有设置,就取默认值 `/`)
- publicPath可以看作是对生成目录`dist`设置的虚拟目录(可以当做就是dist目录),devServer首先从devServer.publicPath中取值,如果它没有设置,就取 `output.publicPath`的值作为虚拟目录,如果它也没有设置,就取默认值 `/`
- `output.publicPath`不仅可以影响虚拟目录的取值,也影响利用`html-webpack-plugin`插件生成的index.html中引用的js、css、img等资源的引用路径。会自动在资源路径前面追加设置的output.publicPath
- 一般情况下都要保证`devServer`中的`publicPath`与`output.publicPath`保持一致
6)ESLint代码校验
1.安装模块
//eslint核心包,babel-eslint转换一些高级语法让eslint可以识别
cnpm install eslint eslint-loader babel-eslint --D
2.创建配置文件.eslintrc.js
module.exports = {
root: true,
parser:"babel-eslint",
//指定解析器选项
parserOptions: {
sourceType: "module",
ecmaVersion: 2015
},
//指定脚本的运行环境
env: {
browser: true,
},
// 启用的规则及其各自的错误级别
rules: {
"indent": "off",//缩进风格
"quotes": "off",//引号类型
"no-console": "error",//禁止使用console
}
}
3.配置
{
test: /\.jsx?$/,
loader: 'eslint-loader',
enforce: 'pre', // pre强制指定顺序,先进行代码校验,然后再编译代码 之前 pre normal inline post
options: { fix: true }, // 启动自动修复
include: resolve(__dirname, 'src'), // 只检查src目录里面的文件 白名单
// exclude:/node_modules/ //不需要检查node_modules里面的代码 黑名单
},
4.配置保存代码自动修复eslint错误
1)安装vscode的eslint插件
2)src同级创建.vscode\settings.json文件
{
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
7)sourcemap(代码调试用的)
sourcemap可以解决在浏览器运行代码时报错,定位报错代码的源文件位置(生成的map文件是给浏览器使用的)
webpack通过配置可以自动给我们source maps文件,map文件是一种对应编译文件和源文件的方法
1.配置webpack的devtool字段
//false表示什么都没做
//source-map(使用它就行,包含最完整的信息)包含行和列信息,包含loader的sourcemap(包含loader可以定位到loader编译前的文件就是源文件,不包含就是定位到编译后的文件)
//inline-source-map包含行和列信息,包含loader的sourcemap,不会单独生成一个map文件,会内嵌到编译后的文件中
//cheap-source-map包含行信息不包含列信息,不包含loader的sourcemap
//cheap-module-source-map包含行信息不包含列信息,包含loader的sourcemap
//开发环境推荐使用cheap-module-eval-source-map,线上推荐使用hidden-source-map
devtool: 'false',
8)打包第三方类库
1.方式1:通过import或require直接引入
//只能在引入文件中使用
import _ from 'lodash';
2.方式2:插件引入
webpack配置ProvidePlugin后,在使用时将不再需要import和require进行引入,直接使用即可
_ 函数会自动添加到模块的上下文,无需显示声明(只能在模块中使用,不能在全局中使用,必如index.html文件中)
const webpack = require('webpack');
plugins: [
//这样就可以在所有模块中使用_,但是不能在全局中使用,必如index.html文件中
new webpack.ProvidePlugin({
_: 'lodash',
}),
],
3.expose-loader
expose-loader可以把模块添加到全局对象上,在调试的时候比较有用
1)在一个模块中引入第三方模块
import _ from 'lodash';
2)配置webpack,顺便把引入的模块添加到全局上
module: {
rules: [
{
test: require.resolve('lodash'),
loader: 'expose-loader',
options: {
exposes: {
//模块中使用的名字
globalName: '_',
override: true,
},
},
}
]
}
4.cdn方式引入
如果我们想引用一个库,但是又不想让webpack打包,并且又不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用,那就可以通过配置externals
1)配置webpack
npm i html-webpack-externals-plugin -D
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
new HtmlWebpackExternalsPlugin({
externals: [
{
module: 'lodash', // 模块名
//cdn地址
entry: "https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.20/lodash.js",
global: '_', // 全局变量名
},
],
}),
2)文件中使用
const _ = require("lodash");
9)设置环境变量
1.方式一
在package.json文件中设置
"scripts": {
"build": "webpack --env=production",
},
在webpack.config.js文件中获取
module.exports = (env) => {
console.log(env.production)//结果为true
}
2.方式二
npm i cross-env
在package.json文件中设置
"scripts": {
"build": "cross-env NODE_ENV=production webpack",
},
在webpack.config.js文件中获取
console.log(process.env.NODE_ENV);
10)定义全局变量
const webpack = require('webpack');
//可以在其它模块中直接使用process.env.NODE_ENV
new webpack.DefinePlugin({
"process.env.NODE_ENV": mode,
}),
11)环境变量的区别
三个变量 一个是在模块内部使用的变量默认production,一个是在node环境也就是webpack.config.js里面用的变量默认undefined,都是通过process.env.NODE_ENV来获取
一个是webpack配置文件中导出的函数参数env默认undefined,模块中获取的process.env.NODE_ENV就是webpack的mode字段值,默认是production
1.如何修改模块中的process.env.NODE_ENV(这个变量是webpack内部通过webpack.DefinePlugin插件设置全局变量process.env.NODE_ENV为mode的值)
//在package.json文件中配置
"build": "webpack --mode=development"
2.如何修改webpack配置文件中导出的函数参数env
//在package.json文件中配置
"build": "webpack --env=development"
3.如何让模块中process.env.NODE_ENV的值等于webpack配置文件中导出的函数参数env
const webpack = require('webpack');
//方式1
//定义全局process.env.NODE_ENV覆盖模块中的process.env.NODE_ENV
new webpack.DefinePlugin({
"process.env.NODE_ENV": env.development?JSON.stringfy('development'):JSON.stringfy('production'),
}),
//方式2
mode:env.development?development:production
4.如何修改webpack.config.js里面用的process.env.NODE_ENV
//cross-env可以保证在多个系统里面设置环境变量是一样的
npm i cross-env
//在package.json文件中设置
"scripts": {
"build": "cross-env NODE_ENV=production webpack",
},
12)调试代码
1.测试环境调试(这个时候要把webpack配置文件的devtool字段设为false)
1)下载模块
npm i filemanager-webpack-plugin -D
2)配置
new webpack.SourceMapDevToolPlugin({
filename:'[file].map',//定义打包生成的 source map 的名称 main.js.map
append:"\n//# sourceMappingURL=http://localhost:8081/[url]" //在源文件中增加sourcemap文件的映射
}),
new FileManagerPlugin({
events:{
onEnd:{
copy:[
{//source表示将打包后dist目录中的map文件放到destination指定的目录中,delete表示再把dist目录中的map文件删除
source:'./dist/*.map',
destination:'C:/aproject/zhufengwebpack202011/1.basic/sourcemap'
}
],
delete:['./dist/*.map']
}
}
})
2.生产环境调试
webpack打包仍然生成sourceMap,但是将map文件挑出放到本地服务器,将不含有map文件的部署到服务器,跟测试环境调试一样,就是不配置append就行
13)watch、clean、copy、proxy
1.watch
当代码发生修改后可以自动重新编译
1)配置
watch:true,//开启监听
watchOptions:{//监听选项
ignored:/node_modules/,//不监听哪些文件夹
aggregateTimeout:300,//监听到文件发生变化后延迟300毫秒才去重新编译(相当于防抖)
poll:1000//1秒扫描1000次文件,数字越大,越敏感,数字越小,越延迟
},
2.copy
打包时,将项目中的文件直接拷贝到dist目录中,不经过编译
1)安装模块
npm i copy-webpack-plugin -D
2)配置
const CopyWebpackPlugin = require('copy-webpack-plugin');
new CopyWebpackPlugin({
patterns:[
{//from来源,to拷贝去哪里
from:resolve(__dirname,'src/documents'),
to:resolve(__dirname,'dist/documents')
}
]
}),
3.clean
可以打包前先清空dist目录
1)安装模块
npm i clean-webpack-plugin -D
2)配置
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
new CleanWebpackPlugin({
// **/*代表清空所有文件
cleanOnceBeforeBuildPatterns:["**/*"]
}),、
4.proxy
//访问8080的请求并且以/api开头的,如http://localhost:8080/api/users
//会被转发到http://localhost:3000/users,重写路径去掉/api
devServer: {
port: 8080, // 指定HTTP服务器的端口号
proxy:{
'/api':{
target:'http://localhost:3000',
pathRewrite:{
"^/api":""
}
}
} */
},
14)chunk和bundle和module
打包前一个入口和它依赖的模块是一个chunk,打包后就变成一个bundle(一个是打包前叫法,一个是打包后叫法)
module就是一个模块文件,一个 chunk 应该包括多个 module
3.生产环境
1)提取CSS
因为CSS的下载和JS可以并行,当一个HTML文件很大的时候,我们可以把CSS单独提取出来使用link标签引入并行加载,提高加载效率,原来的css是通过style标签脚步插入到
html文件中的,style标签引入的css不能并行加载,加载效率低
1.下载模块
npm install --save-dev mini-css-extract-plugin
2.配置
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
//用MiniCssExtractPlugin.loader替换掉style-loader(这个loader会把css以style脚步的形式插入到页面)
//MiniCssExtractPlugin.loader会把所有的css样式先收集起来
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
//把收集到的所有的CSS样式都写入到css/main.css,然后HtmlWebpackPlugin插件把此资源插入到HTML里去
new MiniCssExtractPlugin({
filename:'css/[name].css'
})
2)如何打包后的文件分类放好,图片放images文件夹,css放css文件夹里面
1.图片
{
test: /\.(jpg|png|gif|bmp)$/,
use: [{
loader: 'url-loader',
options: {
name: 'images/[hash:10].[ext]',
esModule: false,
limit: 32 * 1024,
},
}],
},
或
{
test: /\.(jpg|png|gif|bmp)$/,
use: [{
loader: 'url-loader',
options: {
name: '[hash:10].[ext]',
esModule: false,
limit: 32 * 1024,
//html文件中的引入路径是output中的publicPath+这里的publicPath+/+name
outputPath:'images',//指定输出图片的目录images目录
publicPath:'/images'//访问图片的话也需要去images目录里找
},
}],
},
2.css
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
//用MiniCssExtractPlugin.loader替换掉style-loader(这个loader会把css以style脚步的形式插入到页面)
//把所有的css样式先收集起来
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
//把收集到的所有的CSS样式都写入到css/main.css,然后HtmlWebpackPlugin插件把此资源插入到HTML里去
new MiniCssExtractPlugin({
filename:'css/[name].css'
})
3)hash、chunkhash和contenthash(实战,打包出来的文件名会根据个人需求添加这三个hash,webpack再次打包时对比的hash值一样不在重新打包,直接走缓存)
文件指纹是指打包后输出的文件名和后缀,hash一般是结合CDN缓存来使用,通过webpack构建之后,生成对应文件名自动带上对应的MD5值。如果文件内容改变的话,那么对应文件哈希值也会改变,
对应的HTML引用的URL地址也会改变,触发CDN服务器从源服务器上拉取对应数据,进而更新本地缓存。
指纹占位符:
ext 资源后缀名
name 文件名称
path 文件的相对路径
folder 文件所在的文件夹
hash 每次webpack构建会根据整个项目的模块内容生成一个统一的hash值
chunkhash 每次webpack构建会根据同一个chunk的所有的模块生成一个chunkhash,来源于同一个chunk,则hash值就一样
contenthash 每次webpack构建会根据模块内容生成一个contenthash,文件内容相同hash值就相同
4)webpack打包文件的文件名的由来
1.对于入口来说,name就是entry的key,字符串就是main
2.对于非入口来说
import('./src/title.js'):是根据相对路径计算来的把/和.换成_,如src_title_js
代码分割 vendor common名字自己指定的
5)CSS兼容性
为了浏览器的兼容性,有时候我们必须加入-webkit,-ms,-o,-moz这些前缀
Trident内核:主要代表为IE浏览器, 前缀为-ms
Gecko内核:主要代表为Firefox, 前缀为-moz
Presto内核:主要代表为Opera, 前缀为-o
Webkit内核:产要代表为Chrome和Safari, 前缀为-webkit
1.安装模块
//postcss-loader可以使用PostCSS处理CSS
//postcss-preset-env把现代的CSS转换成大多数浏览器能理解的
npm i postcss-loader postcss-preset-env -D
2.方式一
配置postcss.config.js文件
let postcssPresetEnv = require('postcss-preset-env');
module.exports={
plugins:[postcssPresetEnv()]
}
配置webpack
{ test: /\.less$/, use: ['style-loader', 'css-loader','postcss-loader', 'less-loader'] },
3.方式二
配置webpack
{ test: /\.css$/, use: [
'style-loader',
'css-loader',
{
loader:'postcss-loader',
options:{
postcssOptions: {
plugins: [
"postcss-preset-env"
],
},
}
},
6)压缩JS、CSS和HTML
optimize-css-assets-webpack-plugin是一个优化和压缩CSS资源的插件
terser-webpack-plugin是一个优化和压缩JS资源的插件
html-webpack-plugin 压缩html
1.配置
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
optimization:{
minimize:process.env.NODE_ENV==='production',//如果是生产环境才开启压缩
//mode=production,就不需要再自己配置TerserPlugin,内部自己会启动terser-plugin进行压缩
//mode=development,还要想压缩,就得配置TerserPlugin
minimizer:(env&&env.production)?[
new TerserPlugin()//如果是生产环境才会配置js压缩器
]:[]//否则不配置任何压缩器
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify:{//启动HTML压缩
collapseWhitespace:true,//去掉空格
removeComments:true//去掉注释
}
}),
(env&&env.production)&&new OptimizeCssAssetsWebpackPlugin()
//上面可能得到false,去掉false,否则报错
].filter(Boolean)
7)px 自动转成rem
1.下载模块
//px2rem-loader自动将px转换为rem
npm i px2rem-loader lib-flexible -D
2.配置
webpack配置
{ test: /\.css$/, use: [
'style-loader',
'css-loader',
{
loader:'postcss-loader',
options:{
postcssOptions: {
plugins: [
"postcss-preset-env"
],
},
}
},
{
loader:'px2rem-loader',
options:{
//75px转成1rem
remUnit:75
}
}] },
index.html文件中
<script>
let docEle = document.documentElement;
function setRemUnit () {
//750/10=75 375/10=37.5
docEle.style.fontSize = docEle.clientWidth / 10 + 'px';
}
setRemUnit();
window.addEventListener('resize', setRemUnit);
</script>
8)多入口配置
1.一般会创建一个文件夹来放所有路口文件(本项目是pages文件夹)
2.动态配置入口和输出页面
//basename根据文件名取出后缀,拿到文件名前缀
const { resolve ,join,basename} = require('path');
const fs = require('fs');
const HtmlWebpackPlugin = require('html-webpack-plugin');
//动态生成多路口配置,和生成多个html文件配置
let pagesRoot = resolve(__dirname,'src','pages');
let pages = fs.readdirSync(pagesRoot);
let htmlWebpackPlugins = [];
let entry = pages.reduce((entry,fileName)=>{
//entryName='page1'
let entryName = basename(fileName,'.js');
entry[entryName] = join(pagesRoot,fileName);
htmlWebpackPlugins.push(new HtmlWebpackPlugin({
template: './src/index.html',
//输出的多个html文件,设置page1为默认入口页面
filename:`${entryName==='page1'?'index':entryName}.html`,
//将那个入口文件插入到html文件中
chunks:[entryName],
minify:{//启动HTML压缩
collapseWhitespace:true,
removeComments:true
}
}));
return entry;
},{});
plugins: [
...htmlWebpackPlugins]
9)合并多个webpack配置
1.安装模块
npm i webpack-merge -D
2.使用(我们一般会在config文件夹里面写所有的webpack配置)
webpack.dev.js文件中
const { merge } = require('webpack-merge');
const base = require('./webpack.base');
const devConfig = {
mode:'development',
};
module.exports = merge(base,devConfig);
webpack.prod.js文件中
webpack.dev.js文件中
const { merge } = require('webpack-merge');
const base = require('./webpack.base');
const prodConfig = {
mode:'production',
};
module.exports = merge(base,prodConfig);
4.原理
1)预备知识
1.Symbol.toStringTag
Symbol.toStringTag 是一个内置 symbol,它通常作为对象的属性键使用,对应的属性值应该为字符串类型,这个字符串用来表示该对象的自定义类型标签
通常只有内置的 Object.prototype.toString() 方法会去读取这个标签并把它包含在自己的返回值里
let myExports = {};
Object.defineProperty(myExports, Symbol.toStringTag, { value: "人" });
console.log(Object.prototype.toString.call(myExports)); //[object 人]
2.模块的ID
- 不管你是用什么样的路径来加载的,最终模块ID统一会变成相对根目录的相对路径
- index.js ./src/index.js
- title.js ./src/title.js
- jquery ./node_modules/jquery/dist/jquery.js
2)源码
1.同步加载打包文件
1.sync/main.js文件中将所有项目的依赖模块放在modules对象上,箭头函数自调用执行主入口文件代码,调用require()方法会先去cache对象中读取缓存
没有缓存就会去modules对象上兑取模块内容
2.模块兼容性实现实现
1)common.js 加载common.js
webpack自己实现了一个common.js,所有天生支持common.js 加载common.js
2)common.js 加载 ES6 modules
2.common-load-es/main.js文件中modules对象中,如果引入的模块是es6方式导出的会调用require.r()说明这个模块是es6模块
会调用require.d()方法将默认导出的模块放在exports.default属性上,分别导出的模块直接放在exports上
3)ES6 modules 加载 ES6 modules
3.es-load-es/main.js文件中使用require()方法加载主入口文件,在modules对象中增加主入口模块,对主入口模块做上面2)的处理
4)ES6 modules 加载 common.js
4.es-load-common/main.js文件中,./src/title.js模块是common.js模块不做处理,./src/index.js模块把import语法变成require语法
同个require.n()获取引入模块默认值
5)总结
1.把es6转成common,被引入的es6会给exports.default添加模块默认导出模块,会给exports添加分别默认导出模块
2.引入的es6模块的imort会变成require,使用的默认值会通过require.n()获取,使用的分别值会通过引入对象.的方式获取
3.异步加载代码块
1)0到4步做了通过jsonp异步加载导入的模块
2)5步收集hello.js模块里面的所有模块,6步将hello.js的所有模块挂在全局modules并让promise成功,接着走then(require.bind(require, "./src/hello.js"))
步骤加载hello模块内容
4.AST的生成和遍历
1)抽象语法树定义
webpack和Lint这些工具的原理都是通过JavaScript Parser把代码转化为一颗抽象语法树(AST),这颗树定义了代码的结构,通过操纵这颗树,我们可以精准的定位到声明语句、赋值语句、
运算语句等等,实现对代码的分析、优化、变更等操作;
2)JavaScript Parser
JavaScript Parser是把JavaScript源码转化为抽象语法树的解析器。
浏览器会把JavaScript源码通过解析器转为抽象语法树,再进一步转化为字节码或直接生成机器码。一般来说每个JavaScript引擎都会有自己的抽象语法树格式,
Chrome 的 v8 引擎,firefox 的 SpiderMonkey 引擎等等,MDN 提供了详细SpiderMonkey AST format 的详细说明,算是业界的标准。
3)常用的 JavaScript Parser
esprima
traceur
acorn
shift
4)下载模块
//esprima把JS源代码转成AST语法树
//estraverse遍历语法树,修改树上的节点
//escodegen把AST语法树重新转换成代码
cnpm i esprima estraverse escodegen -S
5.转换箭头函数babel插件
1)下载模块
//@babel/core Babel的编译器,用来生成语法树,遍历语法树调用预设插件修改语法上的节点,生成新的代码
//babel-types 用于 AST 节点的 Lodash 式工具库, 它包含了构造、验证以及变换 AST 节点的方法,对编写处理 AST 逻辑非常有用
npm i @babel/core babel-types -D
2)在arrow.js文件中调用core.transform()方法生成语法树,遍历语法树时调用预设插件修改语法上的节点,生成解析后的新代码,解析箭头函数
时会调用BabelPluginTransformEs2015ArrowFunctions插件的ArrowFunctionExpression()方法将箭头节点传给该方法,方法中
将节点类型变成es5函数类型
3)调用hoistFunctionEnvironment()方法处理this指针的问题,如果箭头函数内部有用到this,那么会在外层作用域定义let _this = this
然后把箭头函数里面的this变量换成 _this
4)总结
1.调用core.transform()方法解析代码生成ast语法树,遍历语法树调用预设的插件转换高级语法生成新的代码,插件实际上是个对象(plugins字段中是类),对象上的visitor属性
放在各种解析高级语法的方法
2.@babel/preset-env插件上就集合了一些转换es6语法的插件,一些高级es6语法需要预设其它插件来解析
6.类转换
npm i @babel/plugin-transform-classes
1)转换前
class Person {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
2)转换后
function Person(name){
this.name = name;
}
Person.prototype.getName = function(){
return this.name;
}
3)在class.js文件中ClassDeclaration()方法获取原来类的方法,包括构造方法和普通方法,这个遍历处理方法,如果处理构造方法,那么创建es5类
复用构造方法的参数和函数体,如果处理普通方法,那么给es5类原型上加上该方法即可
7.编写插件的一般步骤
1) 仔细观察转换前和转换后的语法树,找到它们的相同点和不同点
2) 想办法把转换前的转成转换后的,并且要尽可能和复用旧节点(老的没有,新的有,就得创建新节点了,可以通过babel-types可以创建新节点)
8.tree-shaking插件(babel-plugin-import这个插件实现了)
1)转换前
import {flatten,concat} from 'lodash';
2)转换后(转换前会把整个包引进来,转换后只会引入使用的包)
import flatten from 'lodash/flatten';
import concat from 'lodash/concat';
3)tapable
tapable 是一个类似于 Node.js 中的 EventEmitter的库,但更专注于自定义事件的触发和处理
webpack 通过 tapable 将实现与流程解耦,所有具体实现通过插件的形式存在
let {SyncHook} = require('tapable')
//不同的事件需要创建不同的hook
//优点就是结构会比较清晰
//webpack事件大概有四五百种,有几百个钩子,各干各的监听 和触发,互不干扰
//需要给构建函数传给一个形参数组,它将决定在call的时候要接收多少个参数
let aHook = new SyncHook(['a','age']);
let bHook = new SyncHook(['b','age']);
//tap类似于我们以前学的events库中的 on 监听事件
aHook.tap('这个名字没有什么用,只是给程序员看的',(name,age)=>{
console.log(name,age,'这是一个回调');
});
//call类似于我们以前学的events库中的 emit
aHook.call('zhufeng',10);
4)webpack 编译流程(debug.js文件中实现)
1.在webpack/index.js文件中的webpack()方法中初始化参数:从配置文件(webpack.config.js)和Shell语句(cmd窗口输入的命令)中读取并合并参数,得出最终的配置对象
用上一步得到的参数初始化Compiler对象,加载所有配置的插件,插件中的apply()方法的compiler.hooks.run.tap()订阅钩子,Compiler对象中的run()方法执行编译过程中
去执行对应的钩子来改变编译结果
2.在debug.js文件中执行compiler对象的run()方法开始执行编译(插件的执行顺序:不同的hook,触发的顺序就是webpack触发hooks的顺序
同一个hook,就是注册的顺序)
3.Compiler.js文件中的run()方法中根据配置中的entry找出入口文件entryFilePath
4.Compiler.js文件中的run()方法中的buildModule()从入口文件出发,调用所有配置的Loader对模块进行编译得到targetSourceCode
在通过parser.parse()方法得到ast树,在通过traverse()方法遍历语法树,并找出require节点,修改我们的ast树,同时module对象
搜集模块id和它依赖的模块数组,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
5.Compiler.js文件中的run()方法中根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,放到this.chunks上
再把每个Chunk转换成一个单独的文件加入到输出列表this.assets上
6.Compiler.js文件中的run()方法中在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
7.总结
1)初始化参数:从配置文件和Shell语句(sricpt脚步命令)中读取并合并参数,得出最终的配置对象
2)用上一步得到的参数初始化Compiler对象
3)加载所有配置的插件
4)执行对象的run方法开始执行编译
5)根据配置中的entry找出入口文件
6)从入口文件出发,调用所有配置的Loader对模块进行编译
7)再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
8)根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk
9)再把每个Chunk转换成一个单独的文件加入到输出列表
10)在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
5.loder
1)特点
1.所谓 loader 只是一个导出为函数的 JavaScript 模块。它接收上一个 loader 产生的结果或者资源文件(resource file)作为入参。
2.compiler(编译) 需要得到最后一个 loader 产生的处理结果。这个处理结果应该是 String 或者 Buffer,因为这样webpack才能将字符串js脚本转成ast语法树
3.loader 的执行顺序 = pre(前置)+normal(正常)+inline(内联)+post(后置) ,执行顺序从后往前,pre、normal、post在webpack.config.js文件中通过enforce字段配置
// inline在引入文件是配置let request = `inline-loader1!inline-loader2!${filePath}`;
// !号前面就是loader
{
test:/\.js$/,
enforce:'post', //默认normal
use:['post-loader1','post-loader2']
},
4.loader-runner是一个执行loader链条的的模块,webpack内置loader,先从前往后执行loader中的pitch方法,再去读取文件,再去
从后往前执行loder,如果pitch方法中有返回值,那么直接将返回值给前一个patch的loader
2.特殊符号配置
1)-!noPreAutoLoaders
// inline在引入文件是配置let request = `-!inline-loader1!inline-loader2!${filePath}`;
// 不要前置和普通 loader
// 在reqire方法前面使用
2)!noAutoLoaders
// 不要普通 loader
3)!!noPrePostAutoLoaders
// 不要前后置和普通 loader,只要内联 loader
3.babel-loader
在babel-loader.js文件中调用@babel/core和设置options中的presets将高级js代码转成es5 js代码
4.file-loader
负责把图片打包到dist目录中
5.url-loader
没有超出设置的limit将图片转成base64内嵌的html中,超出的话交给file-loader去处理
6.less-loader、css-loader、style-loader
less-loader把LESS编译成CSS字符串、css-loader的作用是处理css中的@import 和 url(./images/logo.png)
style-loader把CSS变成一个JS脚本,脚本就是动态创建一个style标签,并且把这个style标签插入到HTML里header
7.loader-runner
1)loader-runner.js文件中runLoaders()方法获取配置对象的资源路径、读取资源的方法、处理资源的loader、loader的上下文对象loaderContext
2)通过createLoaderObject()方法返回loader对象数组,给上下文对象loaderContext添加一些必要属性
3)调用iteratePitchingLoaders()方法循环执行loader的pitch方法,如果pitch方法中有返回值,那么直接调用iterateNormalLoaders()方法调用
前一个patch的loader,如果pitch没有返回值都执行完调用processResource()方法读取文件 ,文件读取完后调用 iterateNormalLoaders()方法循环执行loader
4)总结
先从前往后执行loader中的pitch方法,再去读取文件,再去从后往前执行loder,如果pitch方法中有返回值,那么直接将返回值给前一个patch的loader
8.css-loader
@import and url()翻译成import/require(),然后可以解析处理它们
1)css-loader处理过后的webpack
1.dist/main3.js文件中api.js模块返回了一个list数组,并且重写了数组的toSring()方法拿到css模块内容
2.css-loader.js!./src/global.css模块返回数组EXPORT包含所有的css模块id和css内容
3../src/global.css模块返回所有的css模块内容
4.在main4.js文件中css-loader.js!./src/index.css模块导出所有css模块数组,@import导入的其它css模块通过require()方法引入
在通过EXPORT.i(GLOBAL)将css模块添加到总模块当中
2)css-loader
1)PostCSS(相当于js中的babel)是一个将CSS代码转成语法树的工具,再去遍历操作css节点,px-to-rem的实现原理就是这样的
2)css-loader.js文件中pipeline.process()方法会将css代码转成语法树交给cssPlugin(),cssPlugin()会把@import干掉再将引入的css路径
放到options.imports里,删完@import的代码会在pipeline.process().then()的result中拿到
3)pipeline.process().then()返回js脚本,脚本中将所有的@import变成了require,本模块内容放在list中,在webpack解析require是会循环此
步骤,最终所有的css模块内容都会被整合到list中
4)通过importLoaders属性处理模块id,就是require()路径前缀,表示当前模块需要经过哪些loader处理
5)处理url(),cssPlugin会把url(xxx)变成url(require('xxx')),require会给webpack看和分析,webpack一看你引入了一张图片
webpack会使用file-loader去加载图片
3)style-loader
在style-loader的pitch()方法中创建style标签,并把css插入标签中插入html文件中,其它css loader会内联到require中
6.插件
插件的apply()方法会订阅回调在webpack的编译对象compiler.hooks上,在编译的过程中会去触发对应的回调去改变webpack编译结果
1)tapable 分类
Webpack 本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是 Tapable,webpack 中最核心的负责编译的 Compiler
和负责创建 bundle 的 Compilation 都是 Tapable 的实例
1.按同步异步分类
Hook 类型可以分为同步Sync和异步Async,异步又分为并行和串行
Sync:SyncHook,SyncBailHook,SyncWaterfallHook,SyncLoopHook
Async:AsyncParallelHook(并行),AsyncParallelBailHook,AsyncSeriesHook(串行),AsyncSeriesBailHook,AsyncSeriesWaterfallHook
2.按返回值分类
BasicL:执行每一个事件函数,不关心函数的返回值,有 SyncHook、AsyncParallelHook、AsyncSeriesHook
Bail:执行每一个事件函数,遇到第一个结果 result !== undefined 则返回,不再继续执行。有:SyncBailHook、AsyncSeriesBailHook, AsyncParallelBailHook
Waterfall:如果前一个事件函数的结果 result !== undefined,则 result 会作为后一个事件函数的第一个参数,有 SyncWaterfallHook,AsyncSeriesWaterfallHook
Loop:不停的循环执行事件函数,直到所有函数结果 result === undefined,有 SyncLoopHook 和 AsyncSeriesLoopHook
2)SyncHook
const {SyncHook} = require('tapable');
// 从上往下依次调用
// ['name','age']定义回调的参数个数,与变量名无关
const hook = new SyncHook(['name','age']);
// 订阅事件,参数1没有意义,给人看的
hook.tap('1',(name,age)=>{
console.log(1,name,age);
});
// 触发事件
hook.call('jiagou',20);
3)SyncBailHook
const {SyncBailHook} = require('tapable');
//当回调函数返回非undefined的值 的时候会停止后续调用
const clickHook = new SyncBailHook(['name','age']);
clickHook.tap('1',(name,age)=>{
console.log(1,name,age);
});
//后面再有clickHook的事件不执行
clickHook.tap('2',(name,age)=>{
console.log(2,name,age);
return null;
});
4)SyncWaterfallHook
表示如果上一个回调函数返回结果不为undefined,则会作为下面回调函数的第一个参数,否则按传的
const {SyncWaterfallHook} = require('tapable');
const clickHook = new SyncWaterfallHook(['name','age']);
clickHook.tap('1',(name,age)=>{
console.log(1,name,age);
return 'jiagou';
});
clickHook.tap('2',(name,age)=>{
console.log(2,name,age);
});
5)SyncLoopHook
不停的循环执行回调函数,直到所有函数的结果等于undefined,特别要注意是每次循环都是从头开始的
6)AsyncParallelHook
异步并行执行所有的订阅回调,都执行完后会执行callAsync的回调
const { AsyncParallelHook } = require('tapable');
const hook = new AsyncParallelHook(['name', 'age']);
1.方式1
hook.tapAsync('1', (name, age, callback) => {
setTimeout(() => {
console.log(1, name, age);
callback();
}, 1000);
});
hook.tapAsync('2', (name, age,callback) => {
setTimeout(() => {
console.log(2, name, age);
callback();
}, 2000);
});
hook.callAsync('zhufeng', 10, (err) => {});
2.方式2
hook.tapPromise('1', (name, age) => {
return new Promise(function(resolve){
setTimeout(() => {
console.log(1, name, age);
resolve();
}, 1000);
});
});
hook.tapPromise('2', (name, age) => {
return new Promise(function(resolve){
setTimeout(() => {
console.log(2, name, age);
resolve();
}, 2000);
});
});
hook.promise('zhufeng', 10).then((result) => {});
7)AsyncParallelBailHook
异步并行执行所有的订阅回调,有一个任务返回值不为空就直接结束,对于promise来说,就是resolve或reject的值不为空
对于callback来说,就是callback有参数
8)AsyncSeriesHook
异步串行执行所有的订阅回调,都执行完后会执行callAsync的回调
9)AsyncSeriesBailHook
异步串行执行所有的订阅回调,有一个任务返回值不为空就直接结束,对于promise来说,就是resolve或reject的值不为空
对于callback来说,就是callback有参数
10)AsyncSeriesWaterfallHook
异步串行执行所有的订阅回调,如果上一个回调函数返回结果不为undefined,则会作为下面回调函数的第一个参数,否则按传的
11)stage和before
let {SyncHook} = require('./tapable');
let hook = new SyncHook(["name"]);
// 默认回调从上到下执行,stage可以控制执行顺序,越小越先执行
hook.tap({name:'tap1',stage:2},(name)=>{
console.log('tap1',name);
});
hook.tap({name:'tap3',stage:1},(name)=>{
console.log('tap3',name);
});
// before可以控制回调在哪些回调之前执行,优先级比stage高
hook.tap({name:'tap1'},(name)=>{
console.log('tap1',name);
});
hook.tap({name:'tap2',before:['tap1']},(name)=>{
console.log('tap2',name);
});
hook.call('zhufeng');
12)自定义AutoExternalPlugin插件
通过cdn的方式引入第三方库,webpack中可以通过externals字段配置
7.hmr热更新
模块内容发生变化会重写打包调用方模块文件,页面只会刷新改变的区域
1. 启动一个HTTP服务器,会打包我们的项目,并且让我们可以预览我们的产出的文件,默认端口号8080
2. 还会启动一个websocket双向通信服务器,如果有新的模块发生变更的话,会通过消息的方式通知客户端,让客户端拉取最新代码,并且进行客户端的热更新
//webpack配置
devServer:{
hot:true,//支持热更新,webpack会自动帮你添加HotModuleReplacementPlugin插件
},
// 模块中./title.js模块发生变化会调用render()方法
let render = ()=>{
let title = require('./title.js');
document.getElementById('root').innerText = title;
}
render();
if(module.hot){
//可以接收并处理哪些模块的变更
module.hot.accept(["./title.js"],render);
}
8.模块联邦
Module Federation的动机是为了不同开发小组间共同开发一个或者多个应用,每个应用块由不同的组开发
应用或应用块共享其他其他组件或者库
// host主机,消费方
let ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
new ModuleFederationPlugin({
name:'host',
filename:'remoteEntry.js',
remotes:{ //消费的组件
remote:'remote@http://localhost:3000/remoteEntry.js'
}
})
import React from 'react';
const RemoteNewsList = React.lazy(()=>import('remote/NewsList'));
export default (props)=>{
return (
<div>
<h1>远程的NewsList</h1>
<React.Suspense fallback="loading RemoteNewsList">
<RemoteNewsList/>
</React.Suspense>
</div>
)
}
// remote远程,提供方
let ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
//如果这个组件想被别人引用,引用路径 ${name}/${expose} remote/NewsList
new ModuleFederationPlugin({
name:'remote',
filename:'remoteEntry.js',
exposes:{
'./NewsList':'./src/NewsList'
}
})
9.webpack5新特性介绍
1)安装
npm install webpack webpack-cli webpack-dev-server html-webpack-plugin babel-loader @babel/core @babel/preset-env @babel/preset-react style-loader css-loader --save-dev
npm install react react-dom --save
2)启动命令
"start": "webpack serve"
3)持久化缓存(内置了cache配置)
webpack会缓存生成的webpack模块和chunk,来改善构建速度
缓存在webpack5中默认开启,缓存默认是在内存里,但可以对cache进行设置
webpack 追踪了每个模块的依赖,并创建了文件系统快照。此快照会与真实文件系统进行比较,当检测到差异时,将触发对应模块的重新构建
cache: {
// 缓存地方 filesystem硬盘持久化缓存 memory内存
// 默认是memory
type: 'filesystem', //'memory' | 'filesystem'
// 缓存的文件放在那里,filesystem才要配置,下面是默认值
cacheDirectory: path.resolve(__dirname, 'node_modules/.cache/webpack'),
},
4)资源模块 (webpack5里file-loaer url-loader已经废弃 了,直接设置type:'asset')
资源模块是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader
raw-loader => asset/source 导出资源的源代码
file-loader => asset/resource 发送一个单独的文件并导出 URL
url-loader => asset/inline 导出一个资源的 data URI
asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现
{
test:/\.png$/,
type:'asset/resource'//对标file-loader
},
{
test:/\.ico$/,
type:'asset/inline'//对标url-loader 模块的大小<limit base64字符串
},
{
test:/\.txt$/,
type:'asset/source'//对标raw-loader
},
{
test:/\.jpg$/,
type:'asset',//对标raw-loader,大于4*1024走file-loader,小于4*1024走url-loader
parser:{
dataUrlCondition:{
maxSize:4*1024
}
}
},
5)moduleIds & chunkIds的优化
module: 每一个文件其实都可以看成一个 module
chunk: 每个入口对应一个chunk
在webpack5之前,没有从entry打包的chunk文件,都会以1、2、3...的文件命名方式输出,删除某些些文件可能会导致缓存失效
+ optimization:{
+ moduleIds:'deterministic',
+ chunkIds:'deterministic' // deterministic文件名以模块文件名为基础生成的hash值,文件名不变缓存就不会失效,因为是取hash值前三位,所以打包出来的文件超过999个会有命名冲突风险
+ }
6)移除Node.js的polyfill
webpack4带了许多Node.js核心模块的polyfill,一旦模块中使用了任何核心模块(如crypto),这些模块就会被自动启用
webpack5不再自动引入这些polyfill,需要自己配置
cnpm i crypto-js crypto-browserify stream-browserify buffer -D
resolve:{
/* fallback:{
"crypto": require.resolve("crypto-browserify"), // 模块的polyfill,不需要可以配置false
"buffer": require.resolve("buffer"),
"stream":require.resolve("stream-browserify")
}, */
},
7)更强大的tree-shaking
tree-shaking就在打包的时候剔除没有用到的代码
webpack4 本身的 tree shaking 比较简单,主要是找一个 import 进来的变量是否在这个模块内出现过
webpack5可以进行根据作用域之间的关系来进行优化
// webpack配置
optimization:{
usedExports:true //标使用到的导出,开发环境下会使用tree-shaking
}
// package.json文件配置
// tree-shaking可能会有副作用,所有的css文件不使用tree-shaking
"sideEffects":["*.css"],
8)模块联邦