「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。
入门webpack
webpack是一个静态的模块化打包工具。从入口文件开始,解析代码,生成一个依赖关系图。然后遍历关系图,不同的类型的文件使用相应的loader进行解析,最后打包成一个个模块。
安装
npm install webpack webpack-cli -g // 全局安装
npm install webpack webpack-cli -d // 局部安装
基本配置
// 默认入口:
根路径下的src/index.js
// 默认出口:
根路径下的dist文件夹
// 指定入口,出口:
npx webpack --entry ./src/main,js --output-path ./build
// 默认配置文件路径:
根路径下的webpack.config.js
// 自定义配置文件路径
npx webpack --config ./cyj.config.js
module.exports = {
entry: "./src/main.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "./dist")
}
}
// 在文档- api - 命令行接口(cli) 中查看更多命令
执行
- npx命令会执行当前路径下的node_module/.bin路径下的命令。也可以在package.json里配置script命令简化命令。
- 如果不使用npx,使用的是全局的webpack。
npm init -y
npm install webpack webpack-cli -d // 局部安装
npx webpack // 开始打包
常见loader
loader用于解析不同类型文件的工具,webpack默认只能解析js文件,解析css文件需要css-loader,解析图片需要file-loader。
css-loader
npm install -d css-loader // 解析css文件
npm install -d style-loader // 将解析的css生成sryle标签添加到html页面中
npm install -d less
npm install -d less-loader
let path = require("path")
module.exports = {
entry: "./src/entry",
output: {
filename: "my-bundle.js",
path: path.resolve(__dirname, "./my-dist")
},
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"], // 倒序执行
},
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
],
},
}
borwserslist
- 官方说明:在不同前端工具之间共享目标浏览器和 Node.js 版本的配置。其实是:根据配置文件,查询包含的浏览器列表,为其他插件提供需要兼容的浏览器列表。
- 安装webpack时自动安装了,无需手动安装
// 新建.browserslistrc文件:
>1%
last 2 version
not dead
PostCSS
- 获取browserslist提供的浏览器列表,配合插件完成一系列工作
- PostCSS是一个平台,通过其生态里的插件,可以实现兼容css等问题
- 例如使用autoprefixer插件,可以为css添加浏览器前缀,实现浏览器兼容
postcss-loader
- 下面案例中style-loader的importLoaders属性表示:当style-loader处理的css文件中含有
@import "a.less"时,会将该css交给前面第几个loader来处理。案例中为2,所以会将该less文件交给less-loader处理,并按顺序执行。
// 安装依赖
npm install postcss-loader -d
npm install autoprefixer -d
// 新建:postcss.config.js
// postcss-loader执行时会加载该配置文件
module.exports = {
plugins: [
require('autoprefixer')
]
}
// webpack.config.js
module: {
rules: [
{
test: /\.less$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
importLoaders: 2
}
},
"postcss-loader", // here
"less-loader"
],
},
],
},
postcss-preset-env
- 属于PostCSS的插件,是多个插件的集合,包含autoprefixer。
- 具有:处理兼容css,根据浏览器列表添加所需的polyfill,将16进制颜色转换为浏览器兼容的rgba等特点
// install
npm install postcss-preset-env -d
// postcss.config.js
module.exports = {
plugins: [
"postcss-preset-env"
]
}
// webpack.config.js
添加loader: "postcss-loader"
file-loader
- 将通过import、require方式引入的jpg,png等格式的图片输出到目标文件夹中
- 也可以加载字体文件,例如eot,ttf等格式
- css中的的background-img不生效
// install
npm install file-loader -d
// webpack.config.js
{
test: /\.(png|jpe?g|gif|svg)$/i,
use: [
{
loader: "file-loader",
options: {
name: "[name][hash:8].[ext]",
outputPath: "images"
},
},
]
}
url-loader
- 也是处理图片资源的,它会将limit大小内的图片转换为base64格式
- 如果图片超过大小,那就只做file-loader相同的操作
- 也是只处理导入的图片,而不会处理css里的背景图片
{
test: /\.(png|jpe?g|gif|svg)$/i,
use: [
{
// loader: "file-loader",
loader: "url-loader",
options: {
name: "[name][hash:8].[ext]",
outputPath: "images",
limit: 10 * 1024 // 10kb以内的图片转换为base64格式
},
},
]
}
asset module type
- webpack5资源文件处理方式,用于替代:file-loader, url-loader, raw-loader
- 具有四种模块类型
- asset/resource // 替代file-loader
- asset/inline // 替代url-loader,但是无limit选项,会将资源转换为dataUrl,嵌入到html中
- asset/source // 导出资源的源代码,替代raw-loader
- 使用场景:导入一个txt文本模块,获取文本内容
- asset // 根据配置的文件体积大小限制,在单独导出文件和转换为base64格式之间选择
// asset/resource
{
test: /\.(png|jpe?g|gif|svg)$/i,
type: "asset/resource",
generator: {
filename: "img/[name].[hash].[ext]"
}
}
// 打包字体文件
{
test: /\.(woff2?|eot|ttf)$/i,
type: "asset/resource",
generator: {
filename: "font/[name].[hash:6].[ext]"
}
}
// 特点:可以实现css里的背景图片的加载
// asset
{
test: /\.(png|jpe?g|gif|svg)$/i,
type: "asset",
generator: {
filename: "img/[name].[hash:6].[ext]"
},
parser: {
dataUrlCondition: {
maxSize: 100 * 1024 // 100k以内b的图片会被压缩为base64格式
}
}
}
// 特点:可以实现css里的背景图片的加载
// asset/source
{
test: /\.txt/,
type: "asset/source"
}
// main.js
import exampleText from './assets/example.txt'
console.log("文本内容:", exampleText);
常见plugin
- loader是用于特定模块类型进行转换
- plugin有更广泛的任务,比如打包优化,资源管理,环境变量注入等
clean-webpack-plugin
- 打包前删除dist文件夹
// install
npm install clean-webpack-plugin -d
// webpack.config.js
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
plugins: [
new CleanWebpackPlugin()
]
html-webpack-plugin
- 用于在dist文件夹中生成html模板
npm install html-webpack-plugin -d
// webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin")
plugins: [
new HtmlWebpackPlugin()
]
- 自定义html模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<p>
<%= htmlWebpackPlugin.options.abc %>
</p>
<div>这是一段<span>文字</span></div>
</body>
<script src="./my-dist/my-bundle.js"></script>
</html>
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: "自定义模板",
template: "./index.html",
abc: "ddd"
// 在这里定义的变量会挂载到htmlWebpackPlugin.options上
// 可以在自定义模板中使用自定义的变量
})
]
DefinePlugin
- 在编译时,创建全局变量
- webpack内置的插件,不需要单独安装
// 观察其他脚手架创建的自定义模板发现:
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
// webpack.config.js
const { DefinePlugin } = require("webpack");
plugins: [
new DefinePlugin({
BASE_URL: '"./"'
// 这里定义的变量也可以在自定义模板中使用
})
]
copy-webpack-plugin
- 复制文件
- 将指定文件夹里的文件复制到dist文件夹中
npm install copy-webpack-plugin -d
const copyWebpackPlugin = require("copy-webpack-plugin")
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: "./public",
globOptions: {
ignore: [
'**/index.html',
'**/.DS_Store',
'**/test02.js',
]
}
}
]
})
]
babel
- 一个工具链,主要用于将es6+的代码转换为es5格式的代码
- 包含语法转换、源代码转换、polyfill实现目标浏览器缺少的功能
命令行使用
// 安装babel核心库和babel命令行工具
npm install @babel/core -d
npm install @babel/cli -d
// 安装插件
// 安装箭头函数转换插件
npm install @babel/plugin-transform-arrow-functions -d
// 安装const,let变量转换工具(块作用域插件)
npm install @babel/plugin-transform-block-scoping -d
// 使用:
npx babel 入口文件/文件夹 --out-dir 输出文件夹 --plugins=插件名,插件名
npx babel src/entry/btest1.js --out-dir my-dist --plugins=@babel/plugin-transform-block-scoping,@babel/plugin-transform-arrow-functions
使用预设
- 如果需要安装的插件非常多,一个个安装是非常慢的。使用该预设,可以根据需要兼容的浏览器列表,进行安装相应的插件。
npm install @babel/preset-env -d
npx babel 入口文件/文件夹 --out-dir 输出文件夹 --presets=@babel/preset-env
babel-loader
npm install @babel/core -d
npm install babel-loader -d
npm install @babel/plugin-transform-block-scoping -d
npm install @babel/plugin-transform-arrow-functions -d
// webpack.config.js
{
test: /\.m?js$/,
use: {
loader: "babel-loader",
options: {
plugins: [
"babel/plugin-transform-babel-scoping",
"babel/plugin-transform-form-arrow-function"
]
}
}
}
使用预设
- 预设:解决某个问题,所需插件的集合
npm install @babel/core -d
npm install babel-loader -d
npm install @babel/preset-env -d
// webpack.config.js
{
test: /\.m?js$/,
// 第三方库都是已经编译好了的,避免重复编译,提高编译速度
exclude: /node_modules/,
use: {
loader: "babel-loader",
}
}
// babel.config.js
module.exports = {
presets: [
["@babel/preset-env"]
]
}
polyfill
- 当我们使用了一些新的语法特性(如promise,generator,symbol等),而浏览器不支持这些功能,就会报错。使用polfill来为浏览器打个补丁,浏览器就会支持该特性了。
- polyfill默认是将所有的特性添加到全局 使用
npm install core-js regenerator-runtime --save
{
test: /\.m?js$/,
// 第三方库都是已经编译好了的,避免重复编译,提高编译速度
exclude: /node_modules/,
use: "babel-loader"
}
// babel.config.js
module.exports = {
presets: [
["@babel/preset-env", {
// false(默认): 打包后的文件不使用polfill
// usage: 根据源代码中使用到的新特性,自动引入对应的polfill
// entry:导入所有的polfill,需要在入口文件添加import 'core-js/stable'和import 'regenerator-runtime/runtime'
useBuiltIns: "usage",
corejs: 3.8 // 根据安装decore-js依赖版本决定
}]
]
}
@babel/plugin-transform-runtime
在局部作用域中添加新特性(polyfill),防止污染全局。常用于开发第三方库。
打包react
- 使用
@babel/preset-react预设 - webpack加载入口文件,发现是js格式,交给babel-loader处理,根据react的预设,添加相关的插件(解析jsx代码等),最后将react代码进行打包。
使用
npm install ract react-dom -s
npm install @babel-core -d
npm install babel-loader -d
npm install @babel/preset-react -d
// webpack.config.js
{
test: /\.m?js$/,
// 第三方库都是已经编译好了的,避免重复编译,提高编译速度
exclude: /node_modules/,
use: {
loader: "babel-loader",
}
}
// babel.config.js
module.exports = {
presets: [
["@babel/preset-env"],
["@babel/preset-react"]
]
}
// react-test.js
import React from "react";
import ReactDom from "react-dom";
const app = (props) => {
return (
<div>{props.children}</div>
)
}
ReactDom.render(<app><h1>hello react</h1></app>, document.getElementById("app"))
打包ts
- 使用typescript包自带的compile
npm install typesctipt -g
cmd: tsc index.ts
- ts-loader
tsconfig.js文件包含转换后的js文件使用es几语法的配置,使用哪种模块化方式等。 这时你可能会问,这些事不是应该由babel来处理嘛?但是要知道babel是处理js文件的,需要先将ts转为js才能交给bebel处理。
npm install ts-loader -d
npm install typescript -d
// 项目根目录下创建tsconfig.js
tsc --init
npx webpack
- babel-loader
除了可以使用typescript compiler来编译typescript之外,还可以使用babel
npm install @babel/preset-typescript -d
- ts-loader与babel-loader如何选择
- ts-loader
- 只能将ts转换为js,如果这个过程中需要polyfill,ts-loader无能为力,需要借助babel
- 对错误类型进行检测,即遇到类型错误会报错
- babel-loader
- 可以实现polyfill
- 无法在babel-loader编译过程中,不会对错误类型进行检测,即遇到类型错误不会报错
- 最佳实践
- ts官网推荐使用babel来进行代码转换,使用tsc来进行类型检查
- npx tsc --noEmit --watch // 在打包前先进行类型检测,watch会实时监听(可选)
- ts-loader
打包vue
npm install vue vue-loader vue-template-compiler -d
// webpack.config.js
import { VueLoaderPlugin } from "vue-loader";
{
test: /\.vue$/,
use: "vue-loader",
}
plugins: [
new VueLoaderPlugin()
]
// test.vue
// index.js
import Vue from "vue";
import myComponent from "./test.vue";
new Vue({
render: h => h(myComponent)
}).$mount("#app")
// 具体配置可以查看vue-loader官方文档
vscode插件:eslint和prettier
- eslint插件会读取根目录的.eslintrc文件的里的规则,进行语法错误提示
- prettier插件会根据自定义的.prettierrc文件,通过快捷键,可以格式化代码为所需格式
webpack-dev-server
前面开发存在的问题:每次修改完代码都需要重新打包
解决方案:
- npx webpack --watch ,或者在在配置文件里添加watch:true
- 依赖图中的任何一个文件改变了,都会重新打包。
- 会在磁盘中生成新的文件
- 使用vscode的live server实现热更新
- webpack-dev-server
- 打包后的文件都存放在内存中
- 不会输出文件到dist文件夹中
npm install webpack-dev-server -s
npx webpack serve
具体使用参考官方文档指南部分
// 开启模块热替换(hmr)
{
devServer: {
hot: true,
}
}
// 当在模块a里导入b模块时,需要在a文件的最后添加:
// 指定该模块在更新时,进行 HMR
import component from "./b.js";
let demoComponent = component();
document.body.appendChild(demoComponent);
// HMR interface
if (module.hot) {
// Capture hot update
module.hot.accept("./b.js", () => {
const nextComponent = component();
// Replace old content with the hot loaded one
document.body.replaceChild(nextComponent, demoComponent);
demoComponent = nextComponent;
});
}
配置react热更新
- 如果需要对每个文件都进行module.hot.accept,会非常麻烦
- vue、react脚手架内置了hmr,不用手动去添加accept。vue使用vue-loader来支持hmr,react使用react-refresh来支持hmr
npm install @pmmmwh/react-refresh-webpack-plugin react-refresh -d
// webpack.config.js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
new ReactRefreshWebpackPlugin()
// 注意这里没有设置mode的话,报错信息显示不完整
devServer: {mode:"development", hot:true}
// babel.config.js
plugins: [
['react-refresh/babel']
]
配置vue热更新
vue-loader默认就配置了
HMR原理图
- webpack-dev-server插件开启了两个服务(http服务,socket服务)。
- http服务用于存放打包后的静态资源,供浏览器访问。
- 另一个是socket服务,当源代码发生修改时,HMR Server会生成两个文件(json文件和js文件),json文件记录着修改的内容,js文件为被修改的源文件(不是重新打包后的bundle.js)。将这两个文件发送给浏览器HMR runtime,浏览器这边的socket服务根据这两个文件更新浏览器中的代码。
webpack.config.js配置
source-map
- 本质是一个信息文件,存储着代码转换前后的对应位置信息,因为代码经过打包后,生产环境与开发环境的代码有差异,source-map文件解决了难以定位报错位置的问题
- 通过设置devtools的属性,决定webpack打包生成source-map的格式,一共有26个选项。大概分为:
- 1,source-map与bundle.js文件合并
- 2,分离
- 3,不生成source-map文件
- 最佳实践:
- 开发阶段:source-map或者cheap-module-source-map,这里分别是vue,react脚手架的配置
- 测试阶段:同开发阶段
- 生产环境:false,不写
publicPath
- 用于对打包后dist文件夹下的文件的路径的拼接
output中有两个很重要的属性:path和publicPathpath:用于指定文件的输出路径(比如打包的html、css、js等),是一个绝对路径,通常使用path.resolve()进行拼接。publicPath:默认是一个空字符串,它是我们项目资源指定的一个公共的路径,其实就是为我们打包的资源添加一个路径:资源的路径 = output.publicPath + 打包资源的路径(比如"js/[name].bundle.js")- 比较常设置的是两个值:
"./":本地环境下可以使用这个相对路径;"/":服务器部署时使用,服务器地址 + /js/[name].bundle.js;- webpack默认为:
"",vue脚手架默认设置为:"/" - 当在本地使用file协议打开时,需要改为:
"./"
优化
模块解析
用于寻找模块的绝对路径
// webpack.config.js
resolve: {
// 常用的两个配置
alias: // 别名
extensiobs: [] // 用于省略文件后缀名
}
// 在文档的 配置-> 解析 里查看
环境分离
npx webpack --config webpack.dev.js
npx webpack --config webpack.prod.js
或者:npx webpack --config webpack.config.js --env product
module.exports = function(env) {
const isProduction = env.profuction;
}
代码分离
- 多入口文件
- 每个入口文件都会生成一个js文件,每个js文件都会生成
script:src标签添加到html中
- 每个入口文件都会生成一个js文件,每个js文件都会生成
- 防止重复
- 每个入口文件都会将自己依赖的包,打包到一个bundle文件中。如果多个入口文件都依赖某个库,会造成重复文件。
- 解决:
- 1,可以通过配置entry解决
- 2,使用SplitCnunksPlugin插件
// entry
module.exports = {
mode: "development",
entry: {
main: { import: "./src/main.js", dependOn: 'utils' },
index: { import: "./src/index.js", dependOn: 'utils' },
utils: ["./src/components/common.js"],
},
output: {
filename: "[name].[hash:8].js",
chunkFilename: "[name].[hash:8].js"
},
}
// 最终dist文件夹下会有三个文件:main,index,utils.js
optimization: {
splitChunks: {
chunks: 'all', // 不管是异步还是啥情况导入的包,都进行拆分
},
},
// 更多配置查看Plugin -> SplitCnunksPlugin
模块懒加载
- 当点击按钮时,才加载该模块
- 以下案例中魔法注释的意思
- webpackChunkName:打包后的文件名: cus-b-module.bundle.js
- webpackPrefetch:是否开启预加载:会在所有js加载完后加载该文件,等点击按钮时,会从缓存中提取出来执行,可以观察chrom network
document.querySelector("#btn").addEventListener("click", function () {
import(
/* webpackChunkName: "cus-b-module" */
/* webpackPrefetch: true */
"./b").then(module => {
console.log(module)
})
})
打包运行时加载的模块
配置optimization.runtimeChunk属性,是否将运行时动态导入的模块打包到单独的文件(例如以上使用import()函数导入的文件),可以设置为true或者single。
- true:为每一个入口文件创建一个运行时bundle
- single:将所有入口的的动态导入的包打包到一个bundle
- 也可以使用对象
配置CDN
webpack打包默认会将依赖图中的所有库都打包,如何将第三方库配置cdn呢?
// 1,webpack.config.js
externals: {
// 排除使用cdn的包,包名: 全局对象
jquery: "jQuery",
lodash: "_"
}
// 2,模板html的底部添加scrpit:src标签
-
环境分离,开发环境不使用cdn,生产环境使用cdn
- 1,将externals移到webpack.dev.js中
- 2,使用ejs语法,因为DefinePlugin插件提供功能的环境变量
拆分css
MiniCssExtractPlugin插件
// webpack.config.js
import MiniCssExtractPlugin from "mini-css-extract-plugin"
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name].[contenthash:8].css",
chunkFilename: "css/[name].[contenthash:8].css"
})
]
// 将style-babel替换为:MiniCssExtractPlugin.loader
hash,contentHash,ChunkHash
压缩js
- terserWebpackPlugin插件
- webpack5内置了该插件,无需手动安装
- 特点:用于压缩代码,丑化代码,将变量名改短
- 具体配置查看webpack官网->Plugin -> TerserWebpackPlugin
- 也可以看terser的github首页
压缩css
css-minimizer-webpack-plugin插件,用于去除多余空格
npm install css-minimizer-webpack-plugin
// webpack.config.js
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
plugins: [
new CssMinimizerPlugin()
]
scoping hoisting
-
webpack打包后的文件是包含在自执行函数里面或者闭包中,当某个包需要调用另一个包里的函数,就需要先执行该闭包才能拿到变量或者函数。很繁琐。使用该插件将提升作用域,即去除闭包和自执行函数。
-
在mode:production模式下已经添加了该插件。
-
webpack官网->Plugin->ModuleConcatenationPlugin
DLL
某个包不想每次都进行打包,可以先将该包打包,然后在新项目中直接引用,而不需要重新打包。
Tree Shaking
- 消除项目中未使用的代码
- webpack实现
tree shaking的两种方式:usedExports和sideEffects
usedExports
- 设置为true后,会将导入的文件中没有被使用的函数、变量通过魔法注释进行标记:
/* unused hormony export sum */ - 再使用terser插件,打包时会将标记的内容进行删除。
optimzation: {
usedExports: true, // 使用usedExports
minimize: true, // 开启优化
minimizer: [
// 使用terser
new TerserPlugin({})
]
}
sideEffects
- 用于告知webpack compiler哪些文件有副作用
- 副作用是指会影响倒其他模块的内容,例如一个函数里面会修改其他对象/变量的值。
package.json根路径下添加:
sideEffects: true; // 代表所有文件都是有副作用的,告知webpack不可以删除没有使用到的代码
sideEffects: false; // 代表所有文件都是没有副作用的,告知webpack可以安全删除没有使用到的代码
如果有某个文件是有副作用的怎么办?
sideEffects: [
"./src/util/Format.js" // 表示该文件有副作用,会删除没有使用的代码,保留有副作用的代码
]
如果在js中导入css,会被当做没有使用到的代码,怎么处理?
方法一
sideEffects: [
"**.css"
]
方法二
在css、less的rule里面添加sideEffects: true,
设置为false后
// index.js
import {sum} from "math.js"
如果没有对math.js中的sum进行使用,那么math.js会在打包时被删除,无论该math.js中是否有副作用的代码。
如果设置上terser,也不会保留有副作用的代码
设置为true后
无论有没有使用math.js的sum,都会保留math代码中所有内容,
如果设置上terser,将会保留该被导入文件中含有副作用的代码
实现css的tree shaking
删除没有用到过的css,webpack项目里的所有代码html、css等代码都会由document.createElement,和dom.style.xxx来实现,所以webpack可以在打包的过程中判断哪些css没有用到
npm install purgecss-webpack-plugin -d
// webpack.config.js
const glob = require("glob")
const PurgecssPlugin = require("purgecss-webpack-plugin")
new PurgecssPlugin({
path:glob.sync(`${resolveApp('./src')}/**/*`,{nodir: true}),
safelist: function(){
return {
standard: ["html","body"] // 保留css里的html。body选择器
}
}
})
const path = require('path');
// node中的api
const appDir = process.cwd();
const resolveApp = (relativePath) => path.resolve(appDir, relativePath);
module.exports = resolveApp;
http压缩
是一种内置在浏览器和服务器端的,以改进传输速率利用率的方式
流程,当请求头包含:accept-Encoding: gzip时,表明客户端可以接受gizp压缩后的格式文件。服务器端会将请求的资源通过gzip格式压缩文件,并返回。
webpack可以实现压缩功能,节约服务器端压缩的时间。
npm install compression-webpack-plugin -d
// webpack.config.js
new CpmpressionPlugin({
// 更多属性查看文档
test: /\.(css|js)$/,
algorithm: "gizp",
})
优化html模板中的代码
前面使用了HtmlWebpackPlugin插件来生成html模板,它还有一些其他的配置:
template: "./index.html",
// inject: "body"
cache: true, // 当文件没有发生任何改变时, 直接使用之前的缓存
minify: isProduction ? {
removeComments: false, // 是否要移除注释
removeRedundantAttributes: false, // 是否移除多余的属性
removeEmptyAttributes: true, // 是否移除一些空属性
collapseWhitespace: false, // 折叠空格
removeStyleLinkTypeAttributes: true, // 比如link中的type="text/css"
minifyCSS: true, // 是否压缩style里的css
minifyJS: {
mangle: {
toplevel: true
}
}
}: false
优化runtime代码
runtime代码量不大,但是必须加载,可以内联到html中。可以使用react-dev-utils插件
npm install react-dev-utils -d
// webpack.config.js
const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin")
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
plugins: [
new InlineChunkHtmlPlugin(htmlWepackPlugin, [/runtime.+\.js/])
]
}
打包自定义的库文件
我们自定义的一个库文件,提供多个方法,为了想在nodejs/commjs/浏览器等环境下都可以使用,就需要做如下配置:
// webpack.config.js
module.exports = {
output: {
// 需要支持的环境,umd:amd/commonks/浏览器
libraryTarget: "umd",
// 该库暴露的全局变量名,myUtils.sum(1, 2)
library: "myUtil",
// 默认this就行
globalObject: "this"
}
}
// 经过这样打包后的文件,在头部会进行当前运行环境的判断,然后将变量暴露出去。
// lib/format.js
export function dateFormat() {
return "2020-10-20";
}
// lib/math.js
export function sum(num1, num2) {
return num1 + num2;
}
export function mul(num1, num2) {
return num1 * num2;
}
// index.js
import * as math from "./lib/math.js"
import * as format from "./lib/format.js"
export {
math,
format
}
npx webpack流程
1, 执行: npx webpack
2, 来到:node_modules/bin/webpack shell文件
3, 来到:node_modules/webpack/bin/webpack.js
1, 找到cli对象
2,执行if(!cli.installed),如果webpack-cli未安装,则进行一些报错。如果已经安装,执行runCli()
3, 最后require("webpack-cli/bin/cli.js"),即执行该文件
4,来到node_module/webpack-cli/bin/cli.js
如果webpack已安装,执行:runCLI(process.argv, orinimalModuleComiple)
5,来到node_module/webpack-cli/lib/bootstrap.js
1, const cli = new WebpackCLI(); // 创建webpackCli的实例对象
观察webpack-cli的构造函数
1.1, this.webpack = require("webpack");
1.2, 在后面会进行compiler = this.webpack(config, callback)
config是合并后的配置文件,包含webpack.config.js以及npx webpck命令后面跟的参数
webpack本质是一个函数
callback如果传递了,webpack会自动调用run方法,如果没有则需要使用webpack实例对象手动调用run(callback)
2,await cli.run(args); //
2.1,来到run方法里面,将配置配置文件合并成option对象
2.2,执行this.makeCommand(),
2.2,再执行buildCommand(),创建webpack对象,开始打包
总结:
webpack-cli做的工作其实就是合并配置文件
// 简易版webpack-cli
const config = require("./webapck.config.js")
const compiler = webpack(config);
compiler.run((err, stats) => {
if(err) console.error(err);
else
console.log(stats)
})
plugin在哪个阶段执行
在webpack生命周期的任意阶段都可能执行,具体取决于该plugin监听的是哪个hook,当hook执行时,会调用注册在该hook上的监听的回调函数。
webpack配置文件里的很多属性都是一个plugin来实现的,比如入口属性就是通过EntryPlugin来实现。
webpack会有很多hook,每一个hook相当于一个生命周期,在入口插件执行的时候会将各个插件进行注册,注册其实是对某个hook进行某个动作的监听,例如:compiler.hooks.make.tapAsync("EntryPlugin", callback),对make hook监听entryplugin事件
自定义loader
loader本质是一个函数,参数为匹配的文件的内容的字符串,返回值是处理后的字符串
配置loader
1,默认loader是在node_module文件夹下的寻找的,自定义的话需要使用路径:"./myloader/yj-loader01",并配置context路径:"."
2,如何只写yj-loader01,配置resolveLoader: {modules: ["node_modules", "./yj-loaders"]}
loader执行顺序:
loader文件中除了默认导出的函数,还可以导出一个名为pitch的函数,当一个rule需要使用多个loader时,会先从前往后依次执行loader的pitch函数,然后从后往前执行默认函数
// 异步loader
// 当我们需要在异步操作之后返回结果,则需要:
module.exports = function(content) {
const callback = this.async();
setTimeout(() => {
console.log("loader01", content);
callback(null, content)
}, 1000)
}
// 传递参数
// webpack.config.js
{
test: /\.js$/i,
use: {
loader: "yj-loader01",
options: {
name: "why",
age: 18
}
}
}
// yj-loader01.js
const { getOptions } = require("loader-utils"); // npm install loader-utils
module.exports = function(content) {
// 设置为异步loader
const callback = this.async();
// 获取参数
const options = getOptions(this);
setTimeout(() => {
console.log("loader01", content, options);
callback(null, content)
}, 1000)
}
// 校验参数
npm install schema-utils -d
// loader01_schema.json
{
"type": "object",
"properties": {
"name": {
"type": "string",
"descriptioon": "name属性接收string类型"
},
"age": {
"type": "number",
"description": "age 属性为number类型"
}
}
}
// yj-loader.js
在上方案例的settimeout函数上方添加:
const {validate} = require("schema-utils")
const loader01Schema = require("./schema/loader01_schema.json")
validate(options) // 检验
自定义md-loader
加载markdown文件
md-loader.js
使用marked库加载md文件,得到html字符串,拼接字符串:
module.export function() {
return `var code = ${htmlStr}; export default code`;
}
main.js
import md from "./webpack-note.md"
document.body.innerHTML = md;