1.前文
最近在项目中遇到许多关于项目的打包问题,关于webpack遇到许多坑,通过空闲的时间系统的学习一下webpack,虽然尤大最近又开始推崇vite,但是据目前来看还是webpack生态更加完善,更加稳定,废话不多说,下面是我系统学习webpack的过程,在这里共享一下,菜鸟一枚,希望能与大佬们一起交流学习。
2.webpack的简介
webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。 在 webpack 看来, 前端的所有资源文件(js/json/css/img/less/...)都会作为模块处理。 它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle.js)
3.webpack的核心概念
3.1 Entry
入口(Entry)指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。
3.2 Output 输出(Output)
指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名。
3.3 Loader
Loader 让 webpack 能 够 去 处 理 那 些 非 JavaScript 文 件 (webpack 自 身 只 理 解 JavaScript)
3.4 Plugins
插件(Plugins)可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩, 一直到重新定义环境中的变量等。
3.5 Mode
4.webpack基本使用
4.1初始化
npm init
这里的项目文件名不能是中文
4.2下载webpack包
全局安装:npm install webpack webpack-cli -g
局部安装:npm install webpack webpack-cli -D
4.3 打包
开发环境
webpack ./src/index.js -o ./dist/buddle.js --mode=development
生产环境
webpack ./src/index.js -o ./dist/buddle.js --mode=production
打包成功
- webpack默认可以处理js文件和json文件,生产环境下多了代码混淆和压缩了代码
4.4 配置文件
- .在根路径下新建一个webpack.config.js文件
- .在webpack.config.js中配置
const path=require("path")
module.exports={
//入口文件,入口文件的路径
entry:"./src/index.js",
//输出
output:{
//输出文件名称
filename:"bundle.js",
//输出路径,这里需要绝对路径
//这里相当于 D:\下载\webpacjks\webpack与\dist拼接
path:path.resolve(__dirname,'dist'),
//这里设置,可以清除上一次打包的内容,
clean:true
},
//开发模式,这里还可以时production生产模式
mode:'development'
}
4.5输入打包命令
webpack
5.使用loader
5.1 css打包
样式的打包可以用:cssloader,styleloader
下载依赖
npm i less -D
npm i style-loader css-loader less-loader -D
{
//匹配规则,得用正则表达式,这里是匹配后缀名
test: /(\.css|\.less)$/,
use: [
//先将css文本的格式用style标签插进html中,在进行css渲染
//将js的样式插入style标签中
//数组中解析的顺序是从下到上的顺序,逆序执行
"style-loader",
//将css转化为js
"css-loader",
//将less转化为css
"less-loader"
],
},
5.2 css兼容性
下载两个包postcss-loader postcss-preset-env
npm i postcss-loader postcss-preset-env
//package.json
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
}
//webpack.config.js
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//设置node环境变量,默认是生产环境production
process.env.NODE_ENV = "development";
module.exports = {
entry: "./src/js/index.js",
output: {
filename: "js/bundle.js",
path: resolve(__dirname, "dist"),
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
//将css转化为js
"css-loader",
//使用postcss-loader
{
loader: "postcss-loader",
options: {
postcssOptions: {
ident: "postcss",
plugins: () => [
//帮助css找到package.json中的browserslist里面的配置,通过加载指定的css样式
require("postcss-preset-env")(),
],
},
},
},
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
new MiniCssExtractPlugin({
//对打包后css重命名,并将之放入一个文件
filename: "css/built.css",
}),
],
mode: "development",
};
5.3 加载资源
加载csv,xml这种资源,使用csv-loader,xml-loader
npm i csv-loader xml-loader -D
{
//匹配规则,得用正则表达式,这里是匹配后缀名
test: /(\.csv|\.tsv)$/,
use: ["csv-loader"],
},
{
//匹配规则,得用正则表达式,这里是匹配后缀名
test: /\.xml$/,
use: "xml-loader",
},
//使用就是引入文件main.js中引入
import xml from "./assets/index.xml"
import csv from "./assets/inde.csv"
let box2 = document.createElement("div");
box2.innerHTML=csv
document.body.appendChild(box2);
let box3 = document.createElement("div");
box3.innerHTML=xml
document.body.appendChild(box3);
5.4 加载数据
加载json5,yaml,toml这种文件,需要yaml-loader,toml-loader,json5-loader
npm i yaml toml json5-D
//1.在webpack.config.js配置
{
test: /\.yaml$/,
type: "json",
parser:{
parse: yaml.parse
}
},
{
test: /\.toml$/,
type: "json",
parser: {
parse:toml.parse
}
},
//2.main.js使用
import yaml from "./assets/index.yaml"
import toml from "./assets/index.toml"
console.log(yaml.languages);
console.log(toml.data);
5.5 babel-loader
将js的es6语法转成es5的语法
npm i babel-loader @babel/core @babel/preset-env @babel/polyfill core-js -D
npm i @babel/runtime -D
npm i @babel/plugin-transform-runtime -D
//webpack.config.js下的module->rules下面配置
{
test: /\.js$/,
exclude: /node_modules/,
use:{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
plugins:[
[
'@babel/plugin-transform-runtime'
]
]
},
}
},
6.使用plugins
6.1 html-webpack-plugin
安装
npm i html-webpack-plugin -D
//webapck.config.js
const {resolve}=require('path')
//引入html plugins
const HtmlWebpackPlugin=require('html-webpack-plugin')
module.exports={
entry:'./src/main.js',
output:{
filename:'bundle.js',
path:resolve(__dirname,'./dist')
},
mode:'production',
plugins:[
new HtmlWebpackPlugin({
//这里是需要打包html文件的路径
template:'./index.html',
//打包后html文件的名称
filename:'bundle.html',
//将引入的js放在哪个位置,默认放在head里面
inject:'body'
})
]
}
6.2 mini-css-extract-plugin
抽离css
npm i mini-css-extract-plugin -D
//首先引入插件
const MiniCssExtarctPlugin = require("mini-css-extract-plugin");
//实例化插件
plugins: [
new MiniCssExtarctPlugin({
//打包后的名字
filename: "style/[contenthash].css",
}),
],
//使用插件
module: {
//这里进行配置模块资源,但是确保图片都在assets下
rules: [
{
//匹配规则,得用正则表达式,这里是匹配后缀名
test: /(\.css|\.less)$/,
use: [
//先将css文本的格式用style标签插进html中,在进行css渲染
//将js的样式插入style标签中
//使用插件
MiniCssExtarctPlugin.loader,
//将css转化为js
"css-loader",
//将less转化为css
"less-loader",
],
},
},
6.3 css-minimizer-webpack-plugin
压缩混淆css
npm i css-minimizer-webpack-plugin -D
//引入插件
const CssminimizerWebpackPlugin=require("css-minimizer-webpack-plugin")
//注册插件,这个optimization跟module,plugins是同一级,这里的插件不是放在plugins中
optimization:{
minimizer:[
new CssminimizerWebpackPlugin()
]
}
//改变开发环境
mode: "production"
6.4 terser-webpack-plugin
默认webpack是可以混淆js的,但是如果使用了css-minimizer-webpack-plugin就不可以了,需要使用另外一个插件terser-webpack-plugin
npm i terser-webpack-plugin -D
optimization: {
minimizer: [new CssminimizerWebpackPlugin(), new TerserWebpackPlugin()],
},
6.5 webpack-bundle-analyzer
可以分析webpack依赖模块
> npm i webpack-bundle-analyzer -D
const{ BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')
plugins: [
new BundleAnalyzerPlugin()
],
7.webpack性能优化
7.1代码分离
7.1.1 code-spliting
这种方式是配置多入口,修改output打包文件,这种方式可以打包成多入口,分离代码,但是如果项目中引入像lodash,jquery这种非常大的包,只要使用一次,每个入口包中都会将其打包进去,造成重复打包,代码臃肿
module.exports = {
entry: {
//多入口打包
index:"./src/main.js",
another:"./src/js/load.js"
},
output: {
//这里的名字得是动态的
filename: "[name].bundle.js",
path: resolve(__dirname, "./dist"),
clean: true,
},
mode: "production",
}
7.1.2 抽离公共chunk
将每个入口包中得公用包抽离出来形成一个新的chunk,减少资源浪费 方式一:配置entry
entry: {
index:{
import:"./src/main.js",
dependOn:'shared'
},
//公共的包
another:{
import:"./src/js/load.js",
dependOn:'shared'
},
shared:'lodash'
},
方式二:配置optimization
optimization: {
minimizer: [new CssminimizerWebpackPlugin()],
//将公共的代码放在一个chunk中,例如lodash,jquery单独打包
splitChunks:{
chunks:'all'
}
},
7.1.3 动态导入
//asyncCpmponent.js
function asyncCpmponent() {
//动态导入
return import("lodash").then(({ default: _ }) => {
const element = document.createElement("div");
element.innerHTML = _.join(["wo", "shi", "ws"], " ");
return element;
});
}
asyncCpmponent().then((element)=>{
document.body.appendChild(element)
})
//在入口文件导入
import "./js/asyncCpmponent.js"
7.2 source map
可以在发生错误时,开发者可以准确定位到错误代码的位置
7.3 watch mode
每次运行代码,可以检测到js发生变化webpck会重新启动
//输入
webpack --watch
//代替
webpack
7.3 webpack-dev-server
当我们的文件发生改变浏览器可以自动更新,webpack-dev-server没有真正的输出物理文件,它是将所有的文件都放在了内存中然后执行
npm i webpack-dev-server -D
启动用npx webpack serve,如果使用webpack serve启动需要保证本地全局webpack版本为最新版本
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./main.js",
output: {
filename: "buddle.js",
path: resolve(__dirname, "./dist"),
clean: true,
},
mode: "development",
devtool: "source-map",
plugins: [
new HtmlWebpackPlugin({
template: "./index.html",
filename: "index.html",
}),
],
devServer:{
static:resolve(__dirname,"./dist"),
//设置是否在服务器端进行代码压缩,减少传输的数据大小,gzip压缩
compress:true,
//修改端口号
port:3000,
//配置请求头文件
headers:{
"wx-token":'fvfet4htghrjt123'
},
//本地服务代理
proxy:{
'/api':'http://localhost:8080',
},
//配置https
// https:true,
//http2
https:true,
//当浏览器访问没有路由地址时,或者没配置代理时,重定向
historyApiFallback:true
}
};
7.4懒加载
当页面加载时模块不加载,当操作到当前模块在进行加载
//math.js
export const minus=(a,b)=>a-b
export const add=(a,b)=>a+b
//main.js使用
let btn = document.createElement("button");
btn.innerHTML = "点击触发加法事件";
btn.addEventListener("click", () => {
//注释部分可以修改打包后的包名字
import(/*webpackChunkName:'math*/"./js/math.js").then(({ add }) => {
console.log(add(6, 7));
});
});
document.body.appendChild(btn);
7.5 预加载
在浏览器空闲的时候再去打包该模块
7.6缓存
通过命中缓存索引来优化,使得webpack生成的文件被客服端缓存,获得更新时则更新
7.6.1 babel缓存
第二次构建时,会读取之前的缓存
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage",
corejs: { version: 3 },
targets: { chrome: "60", firefox: "50" ,ie:'9'},
},
],
],
//这里打包时设置js得babel为true
cacheDirectory:true
},
},
7.6.2文件资源缓存
给打包后得js加上一个10位得hash值
重点
●hash: 每次wepack构建时会生成一个唯一的hash值。
问题: 因为js和css同时使用一个hash值。如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件 )
●chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
问题: js和css的hash值还是一样的,因为css是在js中被引入的,所以同属于一个chunk
●contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
/*
缓存:
babel缓存
cacheDirectory: true
--> 让第二次打包构建速度更快
文件资源缓存
hash: 每次wepack构建时会生成一个唯一的hash值。
问题: 因为js和css同时使用一个hash值。
如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)
chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
问题: js和css的hash值还是一样的
因为css是在js中被引入的,所以同属于一个chunk
contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样
--> 让代码上线运行缓存更好使用
*/
// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';
// 复用loader
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
{
// 还需要在package.json中定义browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [require('postcss-preset-env')()]
}
}
];
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.[contenthash:10].js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
// 在package.json中eslintConfig --> airbnb
test: /\.js$/,
exclude: /node_modules/,
// 优先执行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
// 以下loader只会匹配一个
// 注意:不能有两个配置处理同一种类型文件
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
/*
正常来讲,一个文件只能被一个loader处理。
当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
先执行eslint 在执行babel
*/
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
// 开启babel缓存
// 第二次构建时,会读取之前的缓存
cacheDirectory: true
}
},
{
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: 'file-loader',
options: {
outputPath: 'media'
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.[contenthash:10].css'
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
mode: 'production',
devtool: 'source-map'
};
7.6.3对第三方库的缓存
缓存node_modules文件夹
optimization: {
minimizer: [new CssminimizerWebpackPlugin(), new TerserWebpackPlugin()],
splitChunks:{
cacheGroups:{
vendor:{
//这里是需要缓存第三方包的路径
test:/[\\/]node_modules[\\/]/,
//打包chunk后的文件名
name:'vendors',
//所有的chunks
chunks:'all'
}
}
}
},
生成以下文件
7.7 HMR
一个模块发生变化,只会打包当前模块,而不是所有模块,这时就需要热模块替换,让代码与页面保持一致,修改了代码,页面就会及时发生变化
devServer:{
contentBase:resolve(__dirname,'build'),
//启动GZIP压缩
compress:true,
//本地启动端口号
port:3000,
//自动打开浏览器
open:true,
//开启hmr
hot:true
}
-
样式文件css/less文件:默认可以使用HMR功能,因为style-loader内部实现了
-
html文件:默认可以不使用HMR功能,同时导致html文件无法热更新
-
js文件:默认可以不使用HMR功能 js的HMR:
//index.js
if (module.hot) {
// 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效
module.hot.accept('./print.js', function() {
// 方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建。
// 会执行后面的回调函数
print();
});
}
7.8 Web Workers
JavaScript 语言采用的是单线程模型,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。随着电脑计算能力的增强,尤其是多核 CPU 的出现,单线程带来很大的不便,无法充分发挥计算机的计算能力。
Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。
Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。
基础使用
//app.js
const worker=new Worker(new URL("./work.js",import.meta.url))
worker.postMessage({
question:'hello ws'
})
worker.onmessage=message=>{
console.log(message.data.name);
}
//work.js
self.onmessage=message=>{
//这里可以接受到来自app.js里post出来的数据
console.log(message)
self.postMessage({
name:'ws'
})
}
7.9 tree-shaking
树摇在Webapck中是一个对性能优化非常好的操作,它将项目中没有引入的包以及引入了而没有使用的包全都会打包到打包后的文件中,对性能是一个很好的优化
//在webpack.connfig.js配置
optimization:{
usedExports:true
}
7.10 sideEffects
告诉webpack,当前写的代码是没有副作用的,有副作用的文件,webpack是无法进行tree-shaking删除的
//package.json
//所有文件都有副作用,不删除文件
sideEffect:true
//所有文件都没副作用,删除文件
sideEffect:false
//指定文件有副作用,不删除指定文件文件,下面遇到css文件不删除
sideEffect:['*.css]
7.11 PWA
当网络断了,页面还是可以加载,相当于将页面缓存下来,对页面进行优化
7.11.1 添加serviceWork
//安装workbox-webpack-plugin
npm i workbox-webpack-plugin -D
//webpack.config.js
const { resolve } = require("path");
const WorkboxPlugin=require("workbox-webpack-plugin")
module.exports = {
entry: "./main.js",
output: {
filename: "buddle.js",
path: resolve(__dirname, "./dist"),
clean: true,
},
mode: "development",
devtool: "source-map",
plugins: [
new WorkboxPlugin.GenerateSW({
//启用serviceWorks
clientsClaim:true,
//跳出当前serviceWorks,不允许旧的serviceWorks
skipWaiting:true
})
],
};
7.11.2 注册serviceWork
//main.js
if('serviceWorker' in navigator){
window.addEventListener('load',()=>{
//这里的service-worker.js是打包后的文件,所以先得运行webapck打包一下
navigator.serviceWorker.register('/service-worker.js')
.then(res=>{
console.log('注册成功'+res);
}).catch(err=>{
console.log('注册失败'+err);
})
})
}
npx webpack serve
关闭服务还是可以打印,因此页面内容被浏览器缓存了
7.12 dll
使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包,当你运行 webpack 时,默认查找 webpack.config.js 配置文件
*需要运行 webpack.dll.js 文件
//在src文件夹下新建webpack.dll.js
const { resolve } = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
// 最终打包生成的[name] --> jquery
// ['jquery'] --> 要打包的库是jquery
jquery: ['jquery'],
},
output: {
filename: '[name].js',
path: resolve(__dirname, 'dll'),
library: '[name]_[hash]' // 打包的库里面向外暴露出去的内容叫什么名字
},
plugins: [
// 打包生成一个 manifest.json --> 提供和jquery映射
new webpack.DllPlugin({
name: '[name]_[hash]', // 映射库的暴露的内容名称
path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
})
],
mode: 'production'
};
运行
webpack --config webpack.dill.js
//下载
npm i add-asset-html-webpack-plugin -D
//webpack.config.js
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
// 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 将某个文件打包输出去,并在html中自动引入该资源
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
],
mode: 'production'
};
目录
7.13 shimming
shimming可以全局引入第三方包,使得无需在文件中引入包便可以使用,并且在有些模块中this未必指向window,在commonJs模块中this并不指向window,因此需要改变this的指向,才能引入有些第三方的包
7.13.1预设全局变量
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
//这里引入webpack
const webpack=require('webpack')
module.exports = {
entry: "./src/main.js",
output: {
filename: "scripts/[name].[contenthash].js",
path: resolve(__dirname, "./dist"),
},
mode: "development",
plugins: [
new HtmlWebpackPlugin({
//这里是需要打包html文件的路径
template: "./index.html",
//打包后html文件的名称
filename: "bundle.html",
//将引入的js放在哪个位置,默认放在head里面
inject: "body",
}),
new webpack.ProvidePlugin({
_:'lodash'
})
],
};
//src/main.js
//这里没有引入lodash就可以使用
console.log(_.join(['ws','love','qy'],'_'))
7.13.2细颗粒度
如果引入的第三方包中有this,可能它不指向window,因此需要改变this指向
使用imports-loader
npm i imports-loader -D
module:{
rules:[
{
test: require.resolve('./src/main.js'),
use: 'imports-loader?wrapper=window',
}
]
}
7.13.3全局暴露
使用exports-loader
npm i exports-loader -D
module:{
rules:[
{
test: require.resolve('./src/main.js'),
use: 'imports-loader?wrapper=window',
},
{
test: require.resolve('./src/golobalWays.js'),
//使用exports-loader,类型是commonJs类型,首先暴露出一个一个属性,
//通过multiple暴露一个对象,管道符第二个参数是对象的属性值/方法,第三个参数是对象的属性名/方法名
use: 'exports-loader?type=commonjs&exports=ws,multiple|files.output|output',
}
]
}
8.资源模块
文件目录
resource用于打包导出图片,png,jpg等格式,inline用于导出svg等base64格式,source用于导出资源源代码
//webpack.config.js
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/main.js",
output: {
filename: "bundle.js",
path: resolve(__dirname, "./dist"),
clean: true,
//这里也可以输入assets中文件打包后的文件名
assetModuleFilename: "images/[contenthash][ext]",
},
mode: "production",
plugins: [
new HtmlWebpackPlugin({
//这里是需要打包html文件的路径
template: "./index.html",
//打包后html文件的名称
filename: "bundle.html",
//将引入的js放在哪个位置,默认放在head里面
inject: "body",
}),
],
devServer: {
contentBase: resolve(__dirname, "dist"),
//启动GZIP压缩
compress: true,
//本地启动端口号
port: 3000,
//自动打开浏览器
open: true,
},
module: {
//这里进行配置模块资源,但是确保图片都在assets下
rules: [
{
test: /\.png$/,
type: "asset/resource",
generator: {
filename: "images/[contenthash][ext]",
},
},
{
//inline不会打包进入dist目录下
test: /\.svg$/,
type: "asset/inline",
},
{
//source用于读取数据
test: /\.txt$/,
type: "asset/source",
},
{
//source用于读取数据
test: /\.jpg$/,
type: "asset",
parser: {
//超过这个最大值就会转成base64
dataUrlCondition: {
maxSize: 25 * 1024,
},
},
},
],
},
};
配置完成后将资源当成模块导入
//main.js
const { add } = require("./js/add.js");
import img from "./assets/line.png";
import svg from "./assets/egg.svg";
import txt from "./assets/index.txt"
console.log(add(1, 6));
//resource
let img1 = document.createElement("img");
img1.src = img;
document.body.appendChild(img1);
//inline
let img2 = document.createElement("img");
img2.src = svg;
document.body.appendChild(img2);
//source
let box = document.createElement("h1");
box.textContent=txt
document.body.appendChild(box);
如果是通用数据类型,它会在导出dataurl和资源源代码中进行选择,默认情况webpack会加载资源大小,当资源小于8k,就会生成一个base64的链接,这种就是inline模式,如果大于8k就会切换到resource这种模式,这个8k是可以调整的,需要在webpack.config.js-module-rules下面配置一个parser
如何加载iconfont字体
在webpack可以使用type:resource这种配置加载字体
//1.在module->rules下面配置
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: "asset/resource"
}
//2.在css中引入
@font-face {
font-family: 'iconfont';
src: url(../assets/iconfont.ttf) format('truetype');
}
.icon{
font-family: 'iconfont';
font-size: 30px;
}
//3.使用字体,在main.js中
//引入
import './style/index.css'
let box1 = document.createElement("h1");
//使用
box1.className='icon'
box1.innerHTML=''
document.body.appendChild(box1);
9.拆分生产环境和开发环境
9.1共同路径
output: {
filename: "scripts/[name].[contenthash].js",
path: resolve(__dirname, "./dist"),
clean: true,
//这里也可以输入assets中文件打包后的文件名
assetModuleFilename: "images/[contenthash][ext]",
//配置公共路径
publicPath:'http://localhost:8080/'
},
打包后路径会加上这个路径
9.2 环境变量
需要知道用户是在生产环境还是开发环境中使用
module.exports = (env) => {
return {
entry: {
index: "./src/main.js",
another: "./src/js/load.js",
},
output: {
filename: "scripts/[name].[contenthash].js",
path: resolve(__dirname, "./dist"),
clean: true,
//这里也可以输入assets中文件打包后的文件名
assetModuleFilename: "images/[contenthash][ext]",
publicPath: "http://localhost:8080/",
},
//判断用户输入什么环境
mode: env.production ? "production" : "development",
}
}
9.3webpack文件拆分
//公共模块webpack.common.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtarctPlugin = require("mini-css-extract-plugin");
const toml = require("toml");
const yaml = require("yaml");
module.exports = {
entry: {
index: "./src/main.js",
another: "./src/js/load.js",
},
output: {
path: path.resolve(__dirname, "../dist"),
clean: true,
//这里也可以输入assets中文件打包后的文件名
assetModuleFilename: "images/[contenthash][ext]",
},
plugins: [
new HtmlWebpackPlugin({
//这里是需要打包html文件的路径
template: "./index.html",
//打包后html文件的名称
filename: "bundle.html",
//将引入的js放在哪个位置,默认放在head里面
inject: "body",
}),
new MiniCssExtarctPlugin({
//打包后的名字
filename: "style/[contenthash].css",
}),
],
module: {
//这里进行配置模块资源,但是确保图片都在assets下
rules: [
{
//匹配规则,得用正则表达式,这里是匹配后缀名
test: /(\.css|\.less)$/,
use: [
//先将css文本的格式用style标签插进html中,在进行css渲染
//将js的样式插入style标签中
//数组中解析的顺序是从下到上的顺序,逆序执行
MiniCssExtarctPlugin.loader,
//将css转化为js
"css-loader",
//将less转化为css
"less-loader",
],
},
{
//匹配规则,得用正则表达式,这里是匹配后缀名
test: /(\.csv|\.tsv)$/,
use: ["csv-loader"],
},
{
//匹配规则,得用正则表达式,这里是匹配后缀名
test: /\.xml$/,
use: "xml-loader",
},
{
test: /\.png$/,
type: "asset/resource",
generator: {
filename: "images/[contenthash][ext]",
},
},
{
//inline不会打包进入dist目录下
test: /\.svg$/,
type: "asset/inline",
},
{
//source用于读取数据
test: /\.txt$/,
type: "asset/source",
},
{
//source用于读取数据
test: /\.jpg$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 25 * 1024,
},
},
},
{
test: /\.yaml$/,
type: "json",
parser: {
parse: yaml.parse,
},
},
{
test: /\.toml$/,
type: "json",
parser: {
parse: toml.parse,
},
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: "asset/resource",
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: "asset/resource",
},
{
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",
},
},
},
},
};
开发环境配置
//webpack.config.dev.js
const { resolve } = require("path");
module.exports = {
output: {
filename: "scripts/[name].js",
},
mode: "development",
devServer: {
contentBase: resolve(__dirname, "dist"),
//启动GZIP压缩
compress: true,
//本地启动端口号
port: 3000,
//自动打开浏览器
open: true,
},
devtool: "inline-source-map",
};
生产环境配置
//webpack.config.prod.js
const CssminimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
module.exports = {
output: {
filename: "scripts/[name].[contenthash].js",
publicPath: "http://localhost:3000/",
},
mode: "production",
optimization: {
minimizer: [new CssminimizerWebpackPlugin(), new TerserWebpackPlugin()],
},
performance: {
hints: false,
},
};
组合公共环境,生产环境和开发环境,使用webpack-merage
npm i webpack-merage -D
const { merge } = require("webpack-merge");
const commonfig = require("./webpack.config.common");
const devfig = require("./webpack.config.dev");
const prodconfig = require("./webpack.config.prod");
module.exports = (env) => {
switch (true) {
case env.development:
return merge(commonfig, devfig);
case env.production:
return merge(commonfig, prodconfig);
default:
return new Error("error");
}
};
改变package.json运行路径
"scripts": {
"start": "webpack serve --config ./config/webpack.config.js --env development",
"build": "webpack -c ./config/webpack.config.js --env production"
},
10.webpack配置
10.1wepack文件路径起别名
resolve: {
//配置路径
alias: {
"@": resolve(__dirname, "./serve"),
},
//配置扩展名,在项目中就可以省略扩展名了
extensions: [".json", ".js", ".vue"],
},
10.2 webpack外部扩展
当我们需要减小bundle的体积,需要用cdn的方式去引入文件,例如jquery。
//webpack.config.js
//表示是以script标签的方式进行插入
externalsType: "script",
externals: {
//第一个元素是需要放在html里面的链接
//第二个元素是script在浏览器上暴露的一个对象,这里是$
jquery: ["https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js", "$"],
},
11.搭建多页面应用
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: {
//多入口打包,lodash为公共的包,需要抽离出来形成一个chunk
main: {
import: "./src/main.js",
dependOn: "lodash",
filename: "files1/[name].[contenthash].js",
},
main1: {
import: "./src/main1.js",
dependOn: "lodash",
filename: "files2/[name].[contenthash].js",
},
lodash: {
import: "lodash",
filename: "commonFile/[name].[contenthash].js",
},
},
output: {
//输入文件夹目录
path: resolve(__dirname, "./buddle"),
//再次打包后可以清理上一次打包的目录
clean: true,
},
mode: "development",
plugins: [
new HtmlWebpackPlugin({
template: "./src/index1.html",
//这里的title在html中可以直接取到通过ejs模板的方式
title: "多页面的应用",
//定义的script放在哪个位置,body还是head中
inject: "body",
//当前页面加载哪些js包
chunks: ["main", "lodash"],
//设置打包后文件名
filename: "files1/index1.html",
}),
new HtmlWebpackPlugin({
template: "./src/index2.html",
//这里的title在html中可以直接取到通过ejs模板的方式
title: "多页面的应用",
//定义的script放在哪个位置,body还是head中
inject: "body",
//当前页面加载哪些js包
chunks: ["main1", "lodash"],
//设置打包后文件名
filename: "files2/index2.html",
}),
],
};
//这里的title使用ejs可以引入webpack.config.js的option中的变量
//这是html-webpack-plugin这个插件独有的
<!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>
</body>
</html>
12.模块联邦
12.1 基本介绍
微前端将微服务的理念引入前端开发。与其将应用程序或站点开发为一个整体,重点是将其拆分为单独编程的较小部分,然后在运行时绑定在一起。
通过这种方法,可以使用不同的技术来开发应用程序的其他部分,并让单独的团队进行开发。,以这种方式拆分开发可以避免与传统单体相关的维护成本。
作为一个副作用,它支持后端和前端开发人员之间的新型协作,因为他们可以作为一个有凝聚力的团队专注于应用程序的特定部分。例如,您可以让一个团队只关注搜索功能或围绕核心功能的其他关键业务部分。
从 webpack 5 开始,有用于开发微前端的内置功能。模块联合并为您提供足够的功能来处理微前端方法所需的工作流。 模块共享的方式:
12.2 基本模型
传统的共享代码的方式
微前端
模块联邦
模块联邦可以让我在a项目中引入b项目的组件,是webpack5的一大新特性
12.3具体使用
在这里定义三个项目,分别是home,nav,search项目,在home项目中存在一个HomeList组件,nav项目中存在一个header组件,需求是在home项目中引入nav项目的header组件,serach项目引入home项目中的Homelist组件以及nav项目中的header组件
1.在nav项目暴露header组件
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const {ModuleFederationPlugin}=require('webpack').container
module.exports = {
entry: "./index.js",
output: {
filename: "scripts/[name].[contenthash].js",
path: resolve(__dirname, "./dist"),
},
mode: "development",
plugins: [
new HtmlWebpackPlugin(),
new ModuleFederationPlugin({
//标识模块联邦的名字,如果外部需要引入该部分组件就需要这个标识
name:'nav',
//远端的entry,此时当前项目位于线上
filename:'remoteEntry.js',
//引用其他应用暴露的组件
remotes:{
},
//暴露出的组件给别的应用使用
exposes:{
//key表示将来别的应用使用到这个组件用来拼接的路径url
//后面值是当前应用需要暴露的组件路径
//这里的./Header,在其他模块用到时候可以拼接上去,找到当前模块
'./Header':'./src/header.js'
},
//第三方共享模块,例如lodash,jquery
shared:{
}
})
],
};
2.使用webpack-dev-serve运行home项目
npx webpack serve --port 3002
3.在home项目中引入nav项目中的header组件,并将自己的Homelist组件暴露
const HtmlWebpackPlugin = require("html-webpack-plugin");
const {ModuleFederationPlugin}=require('webpack').container
module.exports = {
entry: "./src/index.js",
mode: "development",
plugins: [
new HtmlWebpackPlugin(),
new ModuleFederationPlugin({
//标识模块联邦的名字,如果外部需要引入该部分组件就需要这个标识
name:'home',
//远端的entry,此时当前项目位于线上
filename:'remoteEntry.js',
//引用其他应用暴露的组件
remotes:{
//另外一个应用中模块联邦定义的name为nav后面
//nav后面是需要引入应用的地址
//remoteEntry.js是在引入应用的filename
nav:'nav@http://localhost:3003/remoteEntry.js'
},
//暴露出的组件给别的应用使用
exposes:{
//key表示将来别的应用使用到这个组件用来拼接的路径url
//后面值是当前应用需要暴露的组件路径
'./Homelist':'./src/HomeList.js'
},
//第三方共享模块,例如lodash,jquery
shared:{
}
})
],
};
//home/src/index.js
//这里使用异步引入
import HomeList from "./HomeList.js";
import("nav/Header").then((Header) => {
let box = document.createElement("div");
box.appendChild(Header.default());
document.body.appendChild(box);
box.innerHTML += HomeList(4);
});
4.使用webpack-dev-serve运行nav项目,这里的端口号要与引入项目中webpack.config.js的remote保持一致
npx webpack serve --port 3003
5.search项目引入上面两个项目的配置
const HtmlWebpackPlugin = require("html-webpack-plugin");
const {ModuleFederationPlugin}=require('webpack').container
module.exports = {
entry: "./src/index.js",
mode: "development",
plugins: [
new HtmlWebpackPlugin(),
new ModuleFederationPlugin({
//标识模块联邦的名字,如果外部需要引入该部分组件就需要这个标识
name:'search',
//远端的entry,此时当前项目位于线上
filename:'remoteEntry.js',
//引用其他应用暴露的组件
remotes:{
//另外一个应用中模块联邦定义的name为nav后面
//nav后面是需要引入应用的地址
//remoteEntry.js是在引入应用的filename
nav:'nav@http://localhost:3003/remoteEntry.js',
home:'home@http://localhost:3002/remoteEntry.js'
},
//暴露出的组件给别的应用使用
exposes:{
},
//第三方共享模块,例如lodash,jquery
shared:{
}
})
],
};
//引用并使用组件,这里引入两个组件,采用Promise.all的方式
//这里使用promise.all的方式分别加载两个异步组件
Promise.all([import('nav/Header'), import('home/Homelist')]).then(([
//导出组件中default选项
{
default: Header
},
{
default: Homelist
}
]) => {
document.body.appendChild(Header())
document.body.innerHTML+=Homelist(6)
})
6.使用webpack-dev-serve运行search项目
npx webpack serve --port 3001
13.总结
通过系统性的学习webpack5,掌握了许多对前端性能的提高,本篇文章是根据官网系统学习的一篇文章,如有不足,请多指教