一、前端构建工具简介
1.1 什么是构建工具
- 定义:自动化处理代码的开发工具
- 主要功能:
- 打包:合并多个模块为少量文件(减少请求)
- 编译:转换新语法(eg:es6->es5)(解决兼容性问题)
- 优化:压缩代码,删除未用代码
- 拓展:支持sass/less,ts等
1.2 常见的构建工具
- 常见工具:
- webpack:静态模块打包工具(主流)
- vite:基于ESM的轻量开发服务器
- rollup:库项目打包(适合sdk开发)
- parcel:零配置打包(适合简单项目)
二、Webpack基础内容
2.1 webpack介绍
- 定义:静态资源打包工具(将浏览器不识别的语法转换为浏览器识别的语法)
- webpack本身功能有限
- 开发模式:仅能编译js中的ES Module语法
- 生产模式:编译js中的ES Module语法 + js代码压缩
2.2 开始使用
# 初始化项目
npm init -y
# webpack:核心语法,webpack-cli:命令行
npm install webpack webpack-cli --save-dev
// index.js - 浏览器之前不支持es module
import count from './js/count'
import sum from './js/sum'
console.log(count(8,1))
console.log(sum(8,1))
npx webpack ./src/index.js --mode=development # 开发模式打包
npx webpack ./src/index.js --mode=production # 生产模式打包
2.3 基本配置
const path = require('path');
module.exports = {
// 1.打包入口文件
entry: './src/index.js', // 相对路径
// 2.文件的输出目录
output: {
path: path.resolve(__dirname, 'dist'), // 绝对路径。__dirname:当前文件夹的文件目录
filename: 'main.js', // 文件名
},
// 3.loader加载器(处理非JS资源,如:css,media媒体资源等)
module: {
rules:[
// loader的配置
]
},
// 4.插件(拓展功能,如:优化,资源管理)
plugins: [],
// 5.模式
mode: 'development'
}
2.4 处理样式,图片等资源
- 前提:浏览器不支持直接在JS模块中使用import来加载CSS
import './style/index.css'
// import './style/index.less'
// import './style/index.scss'
// 报错:Uncaught SyntaxError: Cannot use import statement outside a module
- 解决:使用loader解决(css文件,图片,字体图标库等)
// 安装:npm install --save-dev css-loader style-loader
// 按需安装:npm install --save-dev css-loader style-loader less less-loader
// 按需安装:npm install --save-dev css-loader style-loader sass-loader sass
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.less$/i,
use: [
'style-loader',
'css-loader',
'less-loader',
],
},
{
test: /\.s[ac]ss$/i,
// 执行顺序从下到上
use: [
'style-loader', // 将JS字符串生成为style节点
'css-loader', // 将CSS转化成CommonJS模块
'sass-loader', // 将Sass编译成CSS
],
},
{
test: /\.(png|jpe?g|gif|webp|svg)/,
type: 'asset',
parser: {
dataUrlCondition: {
// 小于10kb的图片转base64
// 优点:减少请求数量。缺点:体积会更大
maxSize: 10 * 1024 // 10kb
}
},
generator: {
// [hash:10]取hash前10位 ext:后缀 query:参数
filename: "static/images/[hash:10][ext][query]"
}
},
{
test: /\.(ttf|woff2?|mp3|mp4|avi)$/i,
type: 'asset/resource',
generator: {
filename: 'static/media/[hash:10][ext][query]'
},
},
]
},
2.5 处理js资源(Eslint,Babel)
2.5.1 介绍
- 前提:webpack只能编译js中es模块化语法,不能编译其他语法
- 方法:
- Eslint:检测代码格式
- Babel:针对js兼容性处理(es6 -> es5)
2.5.2 Eslint 官网
# 注意后面版本写法有所不同,最新版本只支持eslint.config.js
npm install eslint@7.32.0 eslint-webpack-plugin@3.1.1 --save-dev
npx eslint . # 检测ESLint是否正确加载配置文件
// 1.webpack.config.js
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
plugins: [new ESLintPlugin({
// 指定检测文件
context: path.resolve(__dirname, 'src')
})],
};
// 2.新建.eslintrc.js文件(新版本只支持eslintrc.config.js文件,需重新配置)
module.exports = {
// 继承Eslint官方的规则(vue/react有各自的规则)
extends: ["eslint:recommended"],
env: {
node: true, // 启动node中全局变量(console)
browser: true // 启动浏览器中全局变量(window)
},
// 语法环境
parserOptions: {
ecmaVersion: 6, // es6
sourceType: 'module', // es module
},
rules: {
"no-var": 2, // 禁止使用var定义变量
},
}
// 3.配置.eslintignore
dist
2.5.3 Babel
npm install -D babel-loader @babel/core @babel/preset-env
// webpack.config.js
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/, // 排除node_module中的js
use: {
loader: 'babel-loader',
// options: {
// presets: ['@babel/preset-env'],
// },
},
},
]
}
// 新建babel.config.js
module.exports = {
// 只能预设:能编译es6语法
presets: ['@babel/preset-env'],
}
2.6 处理html资源(HtmlWebpackPlugin)
- 作用:使用HtmlWebpackPlugin生成HTML文件
- 使用:
// 1.安装
npm install --save-dev html-webpack-plugin
// 2.webpack.config.js中使用
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
// 以public/index.html为模板
// 结构和模板一致,自动引入打包输出的资源(自动引入入口文件)
template: path.resolve(__dirname, 'public/index.html')
})
],
}
2.7 开发服务器及自动化
// 1.安装
npm install --save-dev webpack-dev-server
// 2.配置开发服务器
devServer: {
host: 'localhost', // 启动服务器域名
port: '3000', // 端口号
open: true, // 是否自动打开浏览器
},
// 3.启动
npx webpack serve
2.8 区分模式
- 开发模式
- 编译代码,使浏览器能识别运行
- 代码质量检查,树立代码规范
- 生产模式
- 新建config文件夹
- webpack.dev.js
- 修改文件中绝对路径(相对路径不用修改)
- 启动:npx webpack serve –config ./config/webpack.dev.js
- webpack.prod.js
- 修改文件中绝对路径(相对路径不用修改)
- 打包:npx webpack –config ./config/webpack.prod.js
- 配置package.json
"scripts": {
"start": "npm run dev",
"dev": "webpack serve --config ./config/webpack.dev.js",
"build": "webpack --config ./config/webpack.prod.js"
},
2.9 css处理 - 生产配置
2.9.1 抽离css为单独文件(阻止闪屏)
- 前提:css文件会被打包到js中,当js文件加载时,会创建一个style标签生成样式
- css-loader:把css打包到js中
- style-loader:创建style标签
- 缺点:会出现闪屏现象,用户体验不好
- 解决:提取单独css文件,通过link标签加载
- 使用
// 1.安装
npm install --save-dev mini-css-extract-plugin
// 2.引入
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 3.使用
plugins: [
new MiniCssExtractPlugin({
filename: "static/css/main.css"
})
],
rules: [
{
test: /\.css$/i,
use: [
MiniCssExtractPlugin.loader, // 抽离css为单独文件
'css-loader'
],
},
]
2.9.2 css兼容性处理(如:flex)
// 1.安装
npm i postcss-loader postcss postcss-preset-env -D
// 2.配置
{
test: /\.s[ac]ss$/i,
// 执行顺序从下到上
use: [
MiniCssExtractPlugin.loader, // 抽离css为单独文件
'css-loader', // 将CSS转化成CommonJS模块
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
"postcss-preset-env" // 解决大多数样式兼容性问题
]
}
}
},
'sass-loader', // 将Sass编译成CSS
],
},
// 3.测试(package.json)
"browserslist": [
"ie >= 8"
]
// 注意:正常浏览器兼容性
"browserslist": [
"last 2 version",
"> 1%",
"not dead"
]
2.9.3 css压缩(生产环境js和html默认压缩)
// 1.安装
npm install css-minimizer-webpack-plugin --save-dev
// 2.引入
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
// 3.使用
optimization: {
minimizer: [
new CssMinimizerPlugin(),
],
},
三、Webpack高级内容
3.1 提升开发体验
- 前提:开发环境js文件打包后错误信息不明显
- SourceMap:源代码与构建代码的映射
devtool: "cheap-module-source-map" // 开发环境-只包含行映射
devtool: "source-map" // 生产环境-包含行列映射,打包速度会变慢
3.2 提升打包速度
- HMR/热模块替换 - 开发环境下打包编译速度更快
// css支持热更新
devServer: {
host: 'localhost', // 启动服务器域名
port: '3000', // 端口号
open: true, // 是否自动打开浏览器
hot: true // 热更新
},
// js不支持热更新,自行搭建需写以西代码(vue-loader支持)
if(module.hot) {
// 判断是否支持热模块替换功能
module.hot.accept('./js/sum')
// ...有多少js文件写多少
}
- OneOf - 让文件只被一个loader配置处理
module: {
rules: [
{
oneOf: [
// ...
]
}
]
}
- Include/Exclude(一般处理eslint和babel)
{
test: /\.js$/,
exclude: /node_modules/, // 排除node_module中的js
use: {
loader: 'babel-loader',
// options: {
// presets: ['@babel/preset-env'],
// },
},
},
- Cache
{
test: /\.js$/,
exclude: /node_modules/, // 排除node_module中的js
use: {
loader: 'babel-loader',
options: {
// presets: ['@babel/preset-env'],
cacheDirectory: true, // 开启babel缓存
cacheCompression: false // 关闭缓存压缩
},
},
},
new ESLintPlugin({
// 指定检测文件
context: path.resolve(__dirname, '../src'),
exclude: "node_modules", // 排除node_module中的js
cache: true, // 开启缓存
cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache')
}),
- Thead - 多线程打包 - 每个进程的启动需要600ms左右开销
// 1.下载
npm i thread-loader -D
// 2.引入
const os = require('os');
const threads = os.cpus().length; // 获取cpu核数
// 3.使用
// babel
{
test: /\.js$/,
exclude: /node_modules/, // 排除node_module中的js
use: [
{
loader: 'thread-loader', // 开启多进程
options: {
works: threads, // 进程数量
}
},
{
loader: 'babel-loader',
options: {
// presets: ['@babel/preset-env'],
cacheDirectory: true, // 开启babel缓存
cacheCompression: false // 关闭缓存压缩
},
}
],
},
// eslint
new ESLintPlugin({
// 指定检测文件
context: path.resolve(__dirname, '../src'),
exclude: "node_modules", // 排除node_module中的js
cache: true, // 开启缓存
cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'),
threads, // 开启多进程和进程数量
}),
// js(开发环境不需要)
const TerserWebpackPlugin = require('terser-webpack-plugin');
optimization: {
minimizer: [
new CssMinimizerPlugin(),
// 压缩js,生产环境使用,添加配置需要重新引入
new TerserWebpackPlugin({
parallel: threads, // 开启多进程和进程数量
})
],
},
3.3 减少代码体积
- Tree Shaking - 没使用的功能不会打包
- 减少Babel代码体积 - 复用Babel辅助代码
// 1.安装
npm i @babel/plugin-transform-runtime -D
// 2.使用
{
loader: 'babel-loader',
options: {
// presets: ['@babel/preset-env'],
cacheDirectory: true, // 开启babel缓存
cacheCompression: false, // 关闭缓存压缩
plugins: ['@babel/plugin-transform-runtime'], // 减少代码体积
},
}
// 1.安装
npm install image-minimizer-webpack-plugin imagemin -D
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --save-dev // 无损压缩
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo --save-dev // 有损压缩
// 2.引入
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
// 3.无损压缩使用(注意:jpegtran.exe的安装及使用环境)
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
['gifsicle', { interlaced: true }],
['jpegtran', { progressive: true }],
['optipng', { optimizationLevel: 5 }],
[
'svgo',
{
plugins: [
"preset-default",
"prefixIds",
{
name: "sortAttrs",
params: {
xmlnsOrder: "alphabetical",
}
}
]
},
],
],
}
}
}),
// 4.有损压缩 - 安装固定版本
npm i image-minimizer-webpack-plugin@3.8.3 imagemin@8.0.1
imagemin-mozjpeg@9.0.0 imagemin-pngquant@9.0.0
imagemin-gifsicle@7.0.0 imagemin-svgo@10.0.1 --save-dev
// 使用
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminMinify,
options: {
plugins: [
// JPEG 压缩(有损)
['imagemin-mozjpeg', {
quality: 85,
progressive: true
}],
// PNG 压缩(有损)
['imagemin-pngquant', {
quality: [0.65, 0.9],
speed: 4
}],
// GIF 压缩
['imagemin-gifsicle', {
interlaced: true
}],
// SVG 压缩
['imagemin-svgo', {
plugins: [
{ name: 'removeViewBox', active: false },
{ name: 'cleanupIDs', active: false }
]
}]
]
}
},
severityError: 'warning' // 错误降级为警告
})
3.4 优化代码运行性能
3.4.1 Code Split (提升首屏加载速度)
- 作用:
- 分割文件:将打包生成的文件进行分割,生成多个js文件
- 按需加载:需要哪个文件就加载哪个文件
- 多入口,多输出
module.exports = {
entry: {
app: './src/index.js',
main: './src/main.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js', // [name]以文件名命名
clean: true, // 打包前清空path目录下文件
},
mode: 'production',
}
- 提取公共引用模块
- 场景
- 多个js文件同时使用import引入同一模块
- npm安装到node_modules中的文件的使用
- 使用
optimization: {
splitChunks: {
chunks: 'all', // 对所有模块进行分割
// cacheGroups: { // 修改配置
// default: {
// minSize: 20000, // 打包最小体积文件
// minChunks: 2, // 至少被引用的次数
// }
// }
}
}
* 3.4.3 按需引入(动态导入)
```js
// utils/sum.js
export function sum(...args) {
return args.reduce((acc, cur, index, arr) => acc + cur, 0)
}
// index.js 在需要使用的时候加载
// 注意:上方export default 下方需 { default }
document.getElementById('btn').onclick = function() {
import(/* webpackChunkName: "sum" */ './utils/sum').then(({ sum }) => {
alert(sum(1,2,3,4));
})
}
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js', // [name]以文件名命名 // 入口chunk
chunkFilename: '[name].[contenthash:8].chunk.js', // 非入口chunk
clean: true, // 打包前清空path目录下文件
},
3.4.2 Preload / Prefetch(提前加载资源,不执行)
- Preload:告诉浏览器立即加载资源
- Prefetch:告诉浏览器空闲时才开始加载资源
- 使用
// 1.安装
npm install --save-dev @vue/preload-webpack-plugin
// 2.引入
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin');
// 3.使用
new PreloadWebpackPlugin({
rel: 'preload', // 'prefetch'
as: 'script'
})
// 4.结果 - 动态引入的文件会缓存加载
<script defer="defer" src="static/js/main.js"></script>
<link href="static/js/sum.9eef1040.chunk.js" rel="preload" as="script">
3.4.3 Network Cache
- 作用:通过文件名哈希管理缓存,确保内容变更时浏览器获取新资源,同时最大化利用未变更资源的缓存
- 问题:若模块A被模块B引用,A的哈希变化会导致B的哈希也变化
- 解决:runtimeChunk - 将模块映射关系(文件名 → 哈希)提取到独立的 runtime 文件中
optimization: {
runtimeChunk: { name: (entry) => `runtime-${entry.name}.js` }
}
- 总结:合理组合 contenthash 与 runtimeChunk,可显著提升应用加载速度
3.4.4 Core.js
- 作用:专门用于解决ES6以及以上API的兼容性问题(如:async函数,promise对象等)
- 步骤:
// 1.下载
npm i core=js
// 2.main.js 引入
import 'core-js'; // 全部引入
import 'core-js/es/promise' // 手动按需引入
// babel 按需引入 babel.config.js
module.exports = {
// 只能预设:能编译es6语法
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage', // 按需加载自动引入
corejs: 3
}
]
],
}
3.4.5 PWA(离线访问体验-兼容性问题)
- 原理:内部通过Service Workers技术实现
- 使用
// 1.安装
npm install workbox-webpack-plugin --save-dev
// 2.引入
const WorkboxPlugin = require('workbox-webpack-plugin');
// 3.webpack.config.js使用
new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何“旧的” ServiceWorkers
clientsClaim: true,
skipWaiting: true,
}),
// 4.main.js中注册 Service Worker(兼容性问题)
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
console.log('SW registered: ', registration);
}).catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}
npm i serve -g // 可部署静态资源服务器
serve dist
四、webpack总结
- 核心:模块打包和资源整合。
- 优化策略:
- 构建速度优化
- 缓存(babel缓存,eslint缓存)
- 多线程
- 范围限制(exclude: /node_modules/)
- 产出性能优化
- 用Tree Shaking删未用代码(生产模式 + ES Module + 无副作用声明)
- 代码分割
- 按需加载:将代码拆分为多个chunk,减少首屏加载资源体积
- 并行加载:浏览器可同时加载多个chunk,加速资源获取
- 缓存优化:分离高频变更的业务代码与稳定的第三方库
- 资源压缩(js/css/图片)
- 优化缓存(contenthash + runtimeChunk/抽离运行时代码,防止contenthash变化)
- 注意:runtimeChunk:抽离Webpack的运行时代码,避免业务代码改动导致所有文件缓存(contenthash)失效
五、相关概念解读
5.1 webpack 核心命令汇总
npx webpack # 默认打包 - 入口src/index.js,输出dist/main.js
npx webpack --watch # 监听文件变化自动重新打包
npx webpack serve # 启动开发服务器
npx webpack ./src/index.js --mode=development # 开发模式打包
npx webpack ./src/index.js --mode=production # 生产模式打包
5.2 npx 和 npm run区别
* npx
* 定义:运行本地或全局安装的命令行工具
* 示例:npx webpack (执行本地 node_modules/.bin/webpack)
* npm run
* 定义:执行package.json中定义的脚本
* 示例:npm run serve
5.3 现代浏览器已经支持ES模块
// 加上type="module"才能支持ES Modules
// 注意:加上type="module",如果以`file://`打开的方式会报跨域
<script type="module" src="../src/index.js"></script>
5.4 [hash:10] 与 [contenthash:10] 的区别与作用
- [hash:10](项目级哈希)
- 所有文件共享同一哈希值 - 截取前10位
- 修改一个文件会导致所有缓存失效
- [contenthash:10](内容级哈希)
- 文件间哈希独立(截取前10位)
- 根据单个文件内容计算哈希,仅当文件内容变化时哈希值才改变
5.5 字体图标库的使用
- 下载
- 使用:
- 只需要.css .ttf woff woff2四个文件
- 修改.css文件路径
- 使用
<span class="iconfont icon-xx"></span>