5 大核心概念
1、entry(入口)
指示 Webpack 从哪个文件开始打包
2、output(输出)
指示 Webpack 打包完的文件输出到哪里去,如何命名等
3、loader(加载器)
webpack 本身只能处理 js、json 等资源,其他资源需要借助 loader,Webpack 才能解析
4、plugins(插件)
扩展 Webpack 的功能
5、mode(模式)
主要由两种模式:
开发模式:development
生产模式:production
webpack默认开启tree shaking功能
开发服务器安装
// 运行指定目录 serve dist
npm i serve -g
// 运行当前项目目录
npm i live-server -g
安装webpack
npm install webpack@5.72.0 webpack-cli@4.9.2 -D
// 命令行打包
// npx webpack ./src/main.js --mode=development
// npx webpack ./src/main.js --mode=production
加载css
npm install css-loader@6.7.1 style-loader@3.3.1 -D
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
兼容css
npm install postcss@8.4.13 postcss-loader@6.2.1 postcss-preset-env@7.5.0 -D
{
// 处理css兼容性问题
// 配合package.json中browserslist来指定兼容性
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: ["postcss-preset-env"],
},
},
},
编译less
npm install less@4.1.2 less-loader@10.2.0 -D
{
test: /\.less$/i,
use: [
// 使用多个loader
"style-loader",
"css-loader",
"less-loader", // 将less编译成css文件
],
},
编译scss
npm install sass@1.51.0 sass-loader@12.6.0 -D
{
test: /\.s[ac]ss$/,
use: [
"style-loader",
"css-loader",
"sass-loader", // 将sass编译成css文件
],
},
整理图片
// 图片整理 webpack内置
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
// 小于10kb的图片转base64
// 优点:减少请求数量 缺点:体积会更大
maxSize: 10 * 1024, // 10kb
},
},
generator: {
// 输出图片名称
// [hash:10] hash值取前10位
filename: "static/images/[hash:10][ext][query]",
},
},
验证js和jsx的代码
npm install eslint@8.14.0 eslint-webpack-plugin@3.1.1 -D
npm install thread-loader@3.0.4 -D
// 检查js和jsx的代码,需要配置 .eslintrc.js 也可以直接在package.json中配置eslintConfig
const os = require("os");
const ESLintPlugin = require("eslint-webpack-plugin");
const threads = os.cpus().length;
new ESLintPlugin({
// 检测哪些文件
context: path.resolve(__dirname, "../src"),
exclude: "node_modules", // 默认值
cache: true, // 开启缓存
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/eslintcache"
),
threads, // 开启多进程和设置进程数量
}),
加载js,默认包含css
npm install babel-loader@8.2.5 @babel/core@7.17.10 @babel/preset-env@7.17.10 -D
npm install thread-loader@3.0.4 -D
// 处理es代码 转成js,把高级语法转成低级语法来兼容低级环境,可设置预设 babel.config.js
// @babel/preset-env 允许使用最新的js
// @babel/preset-react react jsx的预设
// @babel/preset-typescript typescript的预设
npm install thread-loader@3.0.4 -D
npm install @babel/plugin-transform-runtime@7.17.10 -D
// 禁用了babel自动对每个文件的runtime注入,而是引入
// 并且使所有辅助代码从这里引用
// 生产开发环境都可以用
const os = require("os");
const threads = os.cpus().length;
// 默认情况下
{
test: /\.js$/,
exclude: /node_modules/, // 排除node_modules下的文件,其他文件都处理
include: path.resolve(__dirname, "../src"), // 只处理src下的文件,其他文件不处理
use: [
{
loader: "thread-loader", // 开启多进程
options: {
works: threads, // 进程数量
},
},
{
loader: "babel-loader",
options: {
// presets: ["@babel/preset-env"],
cacheDirectory: true, // 开启babel缓存
cacheCompression: false, // 关闭缓存文件压缩
plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
},
},
],
},
// react情况下
{
test: /\.jsx?$/,
include: path.resolve(__dirname, "../src"),
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: [
"react-refresh/babel", // 激活js的HMR
],
},
},
// vue情况下
{
test: /\.js$/,
include: path.resolve(__dirname, "../src"),
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
},
},
{
test: /\.vue$/,
loader: "vue-loader",
options: {
// 开启缓存
cacheDirectory: path.resolve(__dirname, "../node_modules/.cache/vue-loader"),
},
},
打包html
npm install html-webpack-plugin@5.5.0 -D
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 自动打包 自动引入
new HtmlWebpackPlugin({
// 模板:以public/index.html文件创建新的html文件
// 新的html文件特点:1. 结构和原来一致 2. 自动引入打包输出的资源
template: path.resolve(__dirname, "public/index.html"),
}),
搭起服务,自动化刷新前端
npm install webpack-dev-server@4.8.1 -D
// 自动化 npx webpack serve
devServer: {
host: "localhost", // 启动服务器域名
port: "3000", // 启动服务器端口号
open: true, // 是否自动打开浏览器
hot: true, // 开启HMR(默认值)
},
把css单独抽离
npm install mini-css-extract-plugin@2.6.0 -D
npm install postcss-loader@6.2.1 postcss@8.4.13 postcss-preset-env@7.5.0 -D
// MiniCssExtractPlugin.loader, // 使用loader,提取css成单独文件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
function getStyleLoader(pre) {
return [
MiniCssExtractPlugin.loader, // 提取css成单独文件
"css-loader", // 将css资源编译成commonjs的模块到js中
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"postcss-preset-env", // 能解决大多数样式兼容性问题
],
},
},
},
pre,
].filter(Boolean);// 过滤undefined null的值
}
new MiniCssExtractPlugin({
filename: "static/css/main.css",
}),
// 兼容什么浏览器
"browserslist": [
"last 2 version",
"> 1%",
"not dead"
]
压缩css
npm install css-minimizer-webpack-plugin@3.4.1 -D
// 压缩css
new CssMinimizerPlugin(),
多线程
// npm install terser-webpack-plugin -D // 不需安装
npm install thread-loader@3.0.4 -D
// webpack自带,正式环境打包 自动开启压缩html js
const os = require("os");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const threads = os.cpus().length;
new TerserWebpackPlugin({
parallel: threads, // 开启多进程和设置进程数量
}),
压缩图片
npm install image-minimizer-webpack-plugin@3.2.3 imagemin@8.0.1 -D
// 无损压缩
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D
// 有损压缩
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
// 压缩图片
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",
},
},
],
},
],
],
},
},
}),
代码分割
optimization: {
// 代码分割配置
splitChunks: {
chunks: "all", // 对所有模块都进行分割
// 以下是默认值
// minSize: 20000, // 分割代码最小的大小
// minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
// minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
// maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
// maxInitialRequests: 30, // 入口js文件最大并行请求数量
// enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
// cacheGroups: { // 组,哪些模块要打包到一个组
// defaultVendors: { // 组名
// test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
// priority: -10, // 权重(越大越高)
// reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
// },
// default: { // 其他没有写的配置会使用上面的默认值
// minChunks: 2, // 这里的minChunks权重更大
// priority: -20,
// reuseExistingChunk: true,
// },
// },
},
},
runtime
// 用于保留没修改的js文件
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
加载顺序
npm install @vue/preload-webpack-plugin@2.0.0 -D
// 加载顺序,
new PreloadWebpackPlugin({
// rel: "preload", // 推荐
// as: "script",
rel: "prefetch", // 让浏览器空闲加载,增加用户体验
}),
离线可访问
npm install workbox-webpack-plugin@6.5.3 -D
const WorkboxPlugin = require("workbox-webpack-plugin");
new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何“旧的” ServiceWorkers
clientsClaim: true,
skipWaiting: true,
}),
// 代码中
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 cross-env -D
"scripts": {
"start": "npm run dev",
"dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.config.js",
"build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js"
},
兼容es6+
npm i core-js
"dependencies": {
"core-js": "^3.22.5"
}
// 代码中
// 完整引入
// import 'core-js'
// 手动按需加载
// import "core-js/es/promise";
// 自动按需加载 babel.config.js
module.exports = {
// 智能预设:能够编译ES6语法
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage", // 按需加载自动引入
corejs: 3,
},
],
],
};
react的js热更新 HMR
npm i @pmmmwh/react-refresh-webpack-plugin@0.5.5 react-refresh@0.13.0 -D
// 已经包含 plugin-transform-runtime
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
{
test: /\.jsx?$/,
include: path.resolve(__dirname, "../src"),
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: [
"react-refresh/babel", // 激活js的HMR
],
},
},
new ReactRefreshWebpackPlugin(), // 激活js的HMR
devServer: {
host: "localhost",
port: 3000,
open: true,
hot: true, // 开启HMR 但没有针对react的js进行热更新,需要自己处理
historyApiFallback: true, // 解决前端路由刷新404问题
},
复制文件
npm i copy-webpack-plugin@10.2.4 -D
const CopyPlugin = require("copy-webpack-plugin");
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, "../public"),
to: path.resolve(__dirname, "../dist"),
globOptions: {
// 忽略index.html文件
ignore: ["**/index.html"],
},
},
],
}),
使用vue来加载css
npm install css-loader@6.7.1 vue-style-loader@4.1.3 -D
{
test: /\.css$/i,
use: ["vue-style-loader", "css-loader"],
},
编译vue文件
npm install vue-loader vue-template-compiler -D
const { VueLoaderPlugin } = require("vue-loader");
{
test: /\.vue$/,
loader: "vue-loader",
},
new VueLoaderPlugin(),
定义环境变量
const { DefinePlugin } = require("webpack");
// cross-env定义的环境变量给打包工具使用 webpack
// DefinePlugin定义环境变量给源代码使用,从而解决vue3页面警告的问题
new DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
}),
自动按需引用vue组件
npm i unplugin-auto-import@0.7.1 -D
const AutoImport = require("unplugin-auto-import/webpack");
const Components = require("unplugin-vue-components/webpack");
const { ElementPlusResolver } = require("unplugin-vue-components/resolvers");
// 按需加载element-plus
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [
ElementPlusResolver({
// 自定义主题,引入sass
importStyle: "sass",
}),
],
}),
配置路径别名
// webpack解析模块加载选项
resolve: {
// 路径别名
alias: {
"@": path.resolve(__dirname, "../src"),
},
},
热加载 适用于(开发模式)
if (module.hot) {
// 判断是否支持热模块替换功能
module.hot.accept("./js/count", function(){
// 代码已修改回调
});
module.hot.accept("./js/sum");
}
// devServer中添加hot: true
// 实际项目开发会使用其他loader进行解决
// vue-loader react-hot-loader
oneOf 适用于(开发模式 正式模式)
// 每个文件只能被其中一个loader配置处理
onrOf:[{test:...}]
解析模块加载配置
// webpack解析模块加载选项
resolve: {
// 自动补全文件扩展名
extensions: [".jsx", ".js", ".json"],
},
.eslintrc.js
module.exports = {
// 继承 Eslint 规则
extends: ["eslint:recommended"],
env: {
node: true, // 启用node中全局变量
browser: true, // 启用浏览器中全局变量
},
parserOptions: {
ecmaVersion: 6, // es6
sourceType: "module", // es module
},
rules: {
"no-var": 2, // 不能使用 var 定义变量
},
plugins: ["import"], // 解决动态导入语法报错
};
babel.config.js
module.exports = {
// 智能预设:能够编译ES6语法
presets: ["@babel/preset-env"],
};
// devtool: "cheap-module-source-map",
// 编译的时候生成源代码映射,浏览器自动通过映射查找源代码的位置,开发环境的时候使用
// cheap-module-source-map 只映射行
// source-map 映射行和列
“资源模块”类型有四种
- asset/resource: 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
- asset/inline: 导出一个资源的 data URI。之前通过使用 url-loader 实现。
- asset/source: 导出资源的源代码。之前通过使用 raw-loader 实现。
- asset: 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。
4 个角度对 webpack 和代码进行了优化
- 提升开发体验
- 使用 Source Map 让开发或上线时代码报错能有更加准确的错误提示。
- 提升 webpack 提升打包构建速度
- 使用 HotModuleReplacement 让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。
- 使用 OneOf 让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。
- 使用 Include/Exclude 排除或只检测某些文件,处理的文件更少,速度更快。
- 使用 Cache 对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。
- 使用 Thead 多进程处理 eslint 和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效果)
- 减少代码体积
- 使用 Tree Shaking 剔除了没有使用的多余代码,让代码体积更小。
- 使用 @babel/plugin-transform-runtime 插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。
- 使用 Image Minimizer 对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)
- 优化代码运行性能
- 使用 Code Split 对代码进行分割成多个 js 文件,从而使单个文件体积更小,并行加载 js 速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。
- 使用 Preload / Prefetch 对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。
- 使用 Network Cache 能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。
- 使用 Core-js 对 js 进行兼容性处理,让我们代码能运行在低版本浏览器。
- 使用 PWA 能让代码离线也能访问,从而提升用户体验。
webpack.prod.js
const path = require("path"); // nodejs核心模块,专门用来处理路径问题
const ESLintPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
// 用来获取处理样式的loader
function getStyleLoader(pre) {
return [
MiniCssExtractPlugin.loader, // 提取css成单独文件
"css-loader", // 将css资源编译成commonjs的模块到js中
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"postcss-preset-env", // 能解决大多数样式兼容性问题
],
},
},
},
pre,
].filter(Boolean);
}
module.exports = {
// 入口
entry: "./src/main.js", // 相对路径
// 输出
output: {
// 所有文件的输出路径
// __dirname nodejs的变量,代表当前文件的文件夹目录
path: path.resolve(__dirname, "../dist"), // 绝对路径
// 入口文件打包输出文件名
filename: "static/js/main.js",
clean: true,
},
// 加载器
module: {
rules: [
// loader的配置
{
oneOf: [
{
test: /\.css$/, // 只检测.css文件
use: getStyleLoader(), // 执行顺序:从右到左(从下到上)
},
{
test: /\.less$/,
// loader: 'xxx', // 只能使用1个loader
use: getStyleLoader("less-loader"),
},
{
test: /\.s[ac]ss$/,
use: getStyleLoader("sass-loader"),
},
{
test: /\.styl$/,
use: getStyleLoader("stylus-loader"),
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
// 小于10kb的图片转base64
// 优点:减少请求数量 缺点:体积会更大
maxSize: 10 * 1024, // 10kb
},
},
generator: {
// 输出图片名称
// [hash:10] hash值取前10位
filename: "static/images/[hash:10][ext][query]",
},
},
{
test: /\.(ttf|woff2?|map3|map4|avi)$/,
type: "asset/resource",
generator: {
// 输出名称
filename: "static/media/[hash:10][ext][query]",
},
},
{
test: /\.js$/,
exclude: /node_modules/, // 排除node_modules下的文件,其他文件都处理
loader: "babel-loader",
},
],
},
],
},
// 插件
plugins: [
// plugin的配置
new ESLintPlugin({
// 检测哪些文件
context: path.resolve(__dirname, "../src"),
}),
new HtmlWebpackPlugin({
// 模板:以public/index.html文件创建新的html文件
// 新的html文件特点:1. 结构和原来一致 2. 自动引入打包输出的资源
template: path.resolve(__dirname, "../public/index.html"),
}),
new MiniCssExtractPlugin({
filename: "static/css/main.css",
}),
new CssMinimizerPlugin(),
],
// 模式
mode: "production",
devtool: "source-map",
};
webpack.dev.js
const path = require("path") // nodejs核心模块,专门用来处理路径问题
const ESLintPlugin = require("eslint-webpack-plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
// 入口
entry: "./src/main.js", // 相对路径
// 输出
output: {
// 所有文件的输出路径
// 开发模式没有输出
// path: undefined,
path: path.resolve(__dirname, "../dist"), // 绝对路径
// 入口文件打包输出文件名
filename: "static/js/main.js",
},
// 加载器
module: {
rules: [
// loader的配置
{
// 每个文件只能被其中一个loader配置处理
oneOf: [
{
test: /\.css$/, // 只检测.css文件
use: [
// 执行顺序:从右到左(从下到上)
"style-loader", // 将js中css通过创建style标签添加html文件中生效
"css-loader", // 将css资源编译成commonjs的模块到js中
],
},
{
test: /\.less$/,
// loader: 'xxx', // 只能使用1个loader
use: [
// 使用多个loader
"style-loader",
"css-loader",
"less-loader", // 将less编译成css文件
],
},
{
test: /\.s[ac]ss$/,
use: [
"style-loader",
"css-loader",
"sass-loader", // 将sass编译成css文件
],
},
{
test: /\.styl$/,
use: [
"style-loader",
"css-loader",
"stylus-loader", // 将stylus编译成css文件
],
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
// 小于10kb的图片转base64
// 优点:减少请求数量 缺点:体积会更大
maxSize: 10 * 1024, // 10kb
},
},
generator: {
// 输出图片名称
// [hash:10] hash值取前10位
filename: "static/images/[hash:10][ext][query]",
},
},
{
test: /\.(ttf|woff2?|map3|map4|avi)$/,
type: "asset/resource",
generator: {
// 输出名称
filename: "static/media/[hash:10][ext][query]",
},
},
{
test: /\.js$/,
exclude: /node_modules/, // 排除node_modules下的文件,其他文件都处理
loader: "babel-loader",
},
],
},
],
},
// 插件
plugins: [
// plugin的配置
new ESLintPlugin({
// 检测哪些文件
context: path.resolve(__dirname, "../src"),
}),
new HtmlWebpackPlugin({
// 模板:以public/index.html文件创建新的html文件
// 新的html文件特点:1. 结构和原来一致 2. 自动引入打包输出的资源
template: path.resolve(__dirname, "../public/index.html"),
}),
],
// 开发服务器: 不会输出资源,在内存中编译打包的
devServer: {
host: "localhost", // 启动服务器域名
port: "3000", // 启动服务器端口号
open: true, // 是否自动打开浏览器
},
// 模式
mode: "development",
// devtool: "cheap-module-source-map",
}