一、 webpack 简介
1.1 webpack 是什么
webpack
是一种前端资源构建
工具,一个静态模块打包器
(module bundler)。
在 webpack 看来, 前端的所有资源文件(js/json/css/img/less/...
)都会作为模块
处理。
它将根据模块
的依赖关系
进行静态分析,打包生成对应的静态资源(bundle)
。
webpack会从打包入口开始,找到所有的依赖(js/json/css/img/less/…),webpack会将这些依赖作为 chunk
块(代码块)。然后将 chunk 块中的各种文件内容转变为浏览器可以理解的内容:less -> css、es6语法 -> 浏览器可以理解的语法 … 这些转变后的打包产物
称为bundle
。
Chunk是Webpack打包过程中,一堆module的集合。我们知道Webpack的打包是从一个入口文件开始,也可以说是入口模块,入口模块引用这其他模块,模块再引用模块。Webpack通过引用关系逐个打包模块,这些module就形成了一个Chunk。
如果我们有多个入口文件,可能会产出多条打包路径,一条路径就会形成一个Chunk。
1.2 webpack 五个核心概念
1.2.1 Entry
入口(Entry)
指示 webpack 以哪个文件为入口起点
开始打包,分析构建内部依赖图
。
1.2.2 Output
输出(Output)
指示 webpack 打包后的资源 bundles
输出
到哪里去,以及如何命名
。
1.2.3 Loader
Loader
让 webpack 能够去处理那些非 JavaScript 文件 (webpack 自身只理解 JavaScript)。
loader 将 webpack 不识别的资源翻译
为 webpck 识别的资源。
1.2.4 Plugins
插件(Plugins)
可以用于执行范围更广的任务。插件的范围包括,从打包优化
和压缩
,一直到重新定义环境中的变量
等。
1.2.5 Mode
模式(Mode)
指示 webpack 使用相应模式的配置
选项 | 描述 | 特点 |
---|---|---|
development | 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。 | 能让代码本地调试运行的环境 |
production | 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin, UglifyJsPlugin 和 TerserPlugin。 | 能让代码优化上线运行环境。 |
二、 webpack 的初体验
2.1 初始化配置
- 初始化 package.json
输入指令:
npm init
- 下载并安装 webpack
输入指令:
npm install webpack webpack-cli -g
npm install webpack webpack-cli -D
webpack-cli
: webpack命令行,支持使用指令的方式使用webpack的功能。-g
: 全局安装-D
: 本地安装
2.2 编译打包应用
- 创建文件
// index.js
import data from './data.json';
console.log(data);
function add(x, y) {
return x + y;
}
console.log(add(1, 2));
// package.json
{
"name": "webpack_test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack src/index.js -o build/built.js --mode=development",
"pro": "webpack src/index.js -o build/built.js --mode=production"
},
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11"
}
}
- 运行指令
- 开发环境指令:
webpack src/index.js -o build/built.js --mode=development
webpack会以 ./src/index.js
为入口文件开始打包,打包后输出到 ./build/built.js
。整体打包环境,是开发
环境。
- 生产环境指令:
webpack src/index.js -o build/built.js --mode=production
webpack会以 ./src/index.js
为入口文件开始打包,打包后输出到 ./build/built.js
。 整体打包环境,是生产
环境。
- 结论
- webpack 能够编译打包
js 和 json
文件。 - 能将 es6 的模块化语法转换成
浏览器能识别
的语法。 - 生产环境比开发环境多一个
压缩js
代码。
- 问题
- 不能编译打包
css、img
等文件。 - 不能将
js 的 es6
基本语法转化为es5 以下语法
。
三、 webpack 开发环境的基本配
3.1 创建配置文件
- 创建文件
webpack.config.js
: webpack的配置文件- 作用: 指示 webpack 干哪些活(当你运行
webpack
指令时,会加载
里面的配置) - 所有构建工具都是基于
nodejs
平台运行的,模块化默认
采用commonjs
。
- 配置内容如下
const { resolve } = require("path"); // node 内置核心模块,用来处理路径问题。
module.exports = {
entry: "./src/index.js", // 入口文件
output: {
// 输出配置
filename: "built.js", // 输出文件名
path: resolve(__dirname, "build"), // 输出文件路径配置
},
mode: "development", //开发环境
};
- 运行指令:
webpack
3.2 打包样式资源
- 创建文件
/* index.css */
html, body{
margin: 0;
padding: 0;
height: 100%;
background-color: pink;
}
// index.js
// 引入样式资源
import './index.css';
import './index.less';
// index.less
#title {
color: #fff;
}
- 下载安装 loader 包
npm i css-loader style-loader less-loader less -D
- 修改配置文件
// resolve用来拼接绝对路径的方法
const { resolve } = require("path");
module.exports = {
// webpack配置
// 入口起点
entry: "./src/index.js",
// 输出
output: {
// 输出文件名
filename: "built.js",
// 输出路径
// __dirname nodejs的变量,代表当前文件的目录绝对路径
path: resolve(__dirname, "build"),
},
// loader的配置
module: {
rules: [
// 详细loader配置
// 不同文件必须配置不同loader处理
{
// 匹配哪些文件
test: /\.css$/,
// 使用哪些loader进行处理
use: [
// use数组中loader执行顺序:从右到左,从下到上 依次执行
// 创建style标签,将js中的样式资源插入进行,添加到head中生效
"style-loader",
// 将css文件变成commonjs模块加载js中,里面内容是样式字符串
"css-loader",
],
},
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
// 将less文件编译成css文件
// 需要下载 less-loader和less
"less-loader",
],
},
],
},
// plugins的配置
plugins: [
// 详细plugins的配置
],
// 模式
mode: "development", // 开发模式
// mode: 'production'
};
- 运行指令: webpack
3.3 打包 HTML 资源
- 创建文件
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webpack</title>
</head>
<body>
<h1 id="title">hello html</h1>
</body>
</html>
function add(x, y) {
return x + y;
}
console.log(add(2, 3));
- 下载安装 plugin 包
npm install --save-dev html-webpack-plugin
- 修改配置文件
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
output: {
filename: "built.js",
path: resolve(__dirname, "build"),
},
module: {
rules: [
// loader的配置
],
},
plugins: [
// plugins的配置
// html-webpack-plugin
// 功能:默认会创建一个空的HTML,自动引入打包输出的所有资源(JS/CSS)
// 需求:需要有结构的HTML文件
new HtmlWebpackPlugin({
// 复制 './src/index.html' 文件,并自动引入打包输出的所有资源(JS/CSS)
template: "./src/index.html",
}),
],
mode: "development",
};
3.4 打包图片资源
- 创建文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webpack</title>
</head>
<body>
<div id="box1"></div>
<div id="box2"></div>
<div id="box3"></div>
<img src="./angular.jpg" alt="angular">
</body>
</html>
import './index.less';
#box1{
width: 100px;
height: 100px;
background-image: url('./vue.jpg');
background-repeat: no-repeat;
background-size: 100% 100%;
}
#box2{
width: 200px;
height: 200px;
background-image: url('./react.png');
background-repeat: no-repeat;
background-size: 100% 100%;
}
#box3{
width: 300px;
height: 300px;
background-image: url('./angular.jpg');
background-repeat: no-repeat;
background-size: 100% 100%;
}
- 下载安装 loader 包
npm install --save-dev html-loader url-loader file-loader
- 修改配置文件
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
output: {
filename: "built.js",
path: resolve(__dirname, "build"),
},
module: {
rules: [
{
test: /\.less$/,
// 要使用多个loader处理用use
use: ["style-loader", "css-loader", "less-loader"],
},
{
// 问题:默认处理不了html中img图片
// 处理图片资源
test: /\.(jpg|png|gif)$/,
// 使用一个loader
// 下载 url-loader file-loader,url-loader 依赖 file-loader
loader: "url-loader",
options: {
// 图片大小小于8kb,就会被base64处理
// 优点: 减少请求数量(减轻服务器压力)
// 缺点:图片体积会更大(文件请求速度更慢)
limit: 8 * 1024,
// 问题:因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
// 解析时会出问题:[object Module]
// 解决:关闭url-loader的es6模块化,使用commonjs解析
esModule: false,
// 给图片进行重命名
// [hash:10]取图片的hash的前10位
// [ext]取文件原来扩展名
name: "[hash:10].[ext]",
},
},
{
test: /\.html$/,
// 处理html文件的img图片(负责引入img,从而能被url-loader进行处理)
loader: "html-loader",
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
mode: "development",
};
3.5 打包其他资源
- 使用范围
不需要做任何处理,只需要原封不动输出即可,比如字体图标。
- 配置文件
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
output: {
filename: "built.js",
path: resolve(__dirname, "build"),
},
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
// 打包其他资源(除了html/js/css资源以外的资源)
{
// 排除css/js/html资源
exclude: /\.(css|js|html|less)$/,
loader: "file-loader",
options: {
name: "[hash:10].[ext]",
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
mode: "development",
};
3.6 devServer
- 作用
开发服务器 devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器),开发者只需要关注编码
- 配置文件
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
output: {
filename: "built.js",
path: resolve(__dirname, "build"),
},
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
// 打包其他资源(除了html/js/css资源以外的资源)
{
// 排除css/js/html资源
exclude: /\.(css|js|html|less)$/,
loader: "file-loader",
options: {
name: "[hash:10].[ext]",
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
mode: "development",
// 开发服务器 devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器~~)
// 特点:只会在内存中编译打包,不会有任何输出
// 启动devServer指令为:npx webpack-dev-server
devServer: {
// 项目构建后路径
contentBase: resolve(__dirname, "build"),
// 启动gzip压缩
compress: true,
// 端口号
port: 3000,
// 自动打开浏览器(默认浏览器)
open: true,
},
};
- npx是执行Node软件包的工具,它从 npm5.2版本开始,就与npm捆绑在一起。
- 默认情况下,首先检查路径中是否存在要执行的包(即在项目中);如果存在,它将执行;若不存在,意味着尚未安装该软件包,npx将安装其最新版本,然后执行它;
- 运行指令
npx webpack-dev-server
3.7 开发环境配置
- 目的
开发环境配置:能让代码运行(代码运行即可)
运行项目指令:
webpack
: 会将打包结果输出出去npx webpack-dev-server
: 只会在内存中编译打包
,没有输出
- 配置文件
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/js/index.js",
output: {
filename: "js/built.js",
path: resolve(__dirname, "build"),
},
module: {
rules: [
// loader的配置
{
// 处理less资源
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
{
// 处理css资源
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
// 处理图片资源
test: /\.(jpg|png|gif)$/,
loader: "url-loader",
options: {
limit: 8 * 1024,
name: "[hash:10].[ext]",
// 关闭es6模块化
esModule: false,
outputPath: "imgs",
},
},
{
// 处理html中img资源
test: /\.html$/,
loader: "html-loader",
},
{
// 处理其他资源
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: "file-loader",
options: {
name: "[hash:10].[ext]",
outputPath: "media",
},
},
],
},
plugins: [
// plugins的配置
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
mode: "development",
devServer: {
contentBase: resolve(__dirname, "build"),
compress: true,
port: 3000,
open: true,
},
};
- 运行指令
npx webpack-dev-server
四、 webpack 生产环境的基本配置
为什么要单独配置生产环境
- 开发环境中将css放在了js中,导致js文件的体积变大
- 因为将css放在了js中,所以只有加载完js,css才会生效,会导致出现闪现情况
- 开发环境的目的是使得代码跑起来,能够调测;生产环境的目的是让代码跑的更快,更平稳。生产环境会使用代码压缩,css兼容性保障等手段
- 压缩,兼容性等耗时较长,放在开发环境会使得构建较慢,不利于开发调测
4.1 提取 css 成单独文
- 下载插件
npm install --save-dev mini-css-extract-plugin
- 修改配置文件
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [
// 创建style标签,将样式放入
// 'style-loader',
// 这个loader取代style-loader。作用:提取js中的css成单独文件
MiniCssExtractPlugin.loader,
// 将css文件整合到js文件中
'css-loader'
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new MiniCssExtractPlugin({
// 对输出的css文件进行重命名,默认名称是main.css
filename: 'css/built.css'
})
],
mode: 'development'
};
style-loader
: 创建style标签,将样式放入MiniCssExtractPlugin.loader
: 提取js中的css成单独文件
4.2 css 兼容性处理
- 下载 loader
npm install --save-dev postcss-loader postcss-preset-env
- 修改配置文件
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 设置nodejs环境变量
// process.env.NODE_ENV = 'development';
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
/*
css兼容性处理:postcss --> postcss-loader postcss-preset-env
帮postcss找到package.json中browserslist里面的配置,通过配置加载指定的css兼容性样式
"browserslist": {
// 开发环境 --> 设置node环境变量:process.env.NODE_ENV = development
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
// 生产环境:默认是看生产环境
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
}
*/
// 使用loader的默认配置
// 'postcss-loader',
// 修改loader的配置
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
// postcss的插件
require('postcss-preset-env')()
]
}
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new MiniCssExtractPlugin({
filename: 'css/built.css'
})
],
mode: 'development'
};
- 修改 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"
]
}
}
4.3 压缩 css
- 下载安装包
npm install --save-dev optimize-css-assets-webpack-plugin
- 修改配置文件
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
// 设置nodejs环境变量
// process.env.NODE_ENV = 'development';
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
// postcss的插件
require('postcss-preset-env')()
]
}
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new MiniCssExtractPlugin({
filename: 'css/built.css'
}),
// 压缩css
new OptimizeCssAssetsWebpackPlugin()
],
// optimization: {
// minimize: true,
// minimizer: [
// // 压缩 js
// new TerserWebpackPlugin(),
// // 压缩css
// new OptimizeCssAssetsWebpackPlugin(),
// ],
// },
mode: 'development'
};
- webpack官方文档介绍时并不是将
OptimizeCssAssetsWebpackPlugin
插件配置在plugins
数组中。而是配置在optimization.minimizer
数组中。 - 原因是: 配置在
plugins
中,webpack就会在启动时使用这个插件。而配置在optimization.minimizer
中,就只会在optimization.minimize
这个特性开启时使用。所以webpack推荐,像压缩类的插件,应该配置在optimization.minimizer
数组中。以便于通过optimization.minimize
统一控制。(生产环境会默认开启minimize) - 需要注意的是:如果配置了
minimizer
,就表示开发者在自定义
压缩插件。内部的JS压缩器就会被覆盖掉。因此需要手动配置js压缩。
4.4 js 语法检查
- 下载安装包
npm install --save-dev eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import
- 修改配置文件
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/js/index.js",
output: {
filename: "js/built.js",
path: resolve(__dirname, "build"),
},
module: {
rules: [
/*
语法检查: eslint-loader 依赖 eslint 库
注意:只检查自己写的源代码,第三方的库是不用检查的
设置检查规则:
package.json中eslintConfig中设置~
"eslintConfig": {
// 继承 airbnb-base 的规则检查
"extends": "airbnb-base"
}
airbnb(检查规则) --> eslint-config-airbnb-base eslint-plugin-import eslint
*/
{
test: /\.js$/,
exclude: /node_modules/,
loader: "eslint-loader",
options: {
// 自动修复eslint的错误
fix: true,
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
mode: "development",
};
- 配置 package.json
配置检查规则
{
"eslintConfig": {
"extends": "airbnb-base",
"env": {
// eslint不认识 window、navigator全局变量,支持浏览器端全局变量
"browser": true
}
}
}
4.5 js 兼容性处理&es6语法转化
- 作用
将 es6 的语法转化为 es5 的语法
- 下载安装包
npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/polyfill core-js
- 修改配置文件
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/js/index.js",
output: {
filename: "js/built.js",
path: resolve(__dirname, "build"),
},
module: {
rules: [
/*
js兼容性处理:babel-loader @babel/core (babel的核心库)
1. 基本js兼容性处理 --> @babel/preset-env
问题:只能转换基本语法,如promise高级语法不能转换
2. 全部js兼容性处理 --> @babel/polyfill
问题:我只要解决部分兼容性问题,但是将所有兼容性代码全部引入,体积太大了~
使用方法:
引入即可,在index.js中 import '@babel/polyfill ';
3. 需要做兼容性处理的就做:按需加载 --> core-js
*/
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
// 预设:指示babel做怎么样的兼容性处理
presets: [
[
"@babel/preset-env",
{
// 按需加载
useBuiltIns: "usage",
// 指定core-js版本
corejs: {
version: 3,
},
// 指定兼容性做到哪个版本浏览器
targets: {
chrome: "60",
firefox: "60",
ie: "9",
safari: "10",
edge: "17",
},
},
],
],
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
mode: "development",
};
4.6 js 压缩
- 产环境下会自动压缩js代码
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
// 生产环境下会自动压缩js代码
mode: 'production'
};
生产环境下会启用 UglifyJsPlugin 插件,进行js压缩。
- 手动配置
webpack认为,如果配置了minimizer,就表示开发者在自定以压缩插件。内部的JS压缩器就会被覆盖掉。
需要修改配置如下:
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
entry: "./src/js/index.js",
output: {
filename: "js/built.js",
path: resolve(__dirname, "build"),
},
optimization: {
minimize: true,
minimizer: [
// 压缩 js
new TerserWebpackPlugin(),
// 压缩css
new OptimizeCssAssetsWebpackPlugin(),
],
},
mode: "development",
};
4.7 HTML 压缩
- 修改配置文件
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
// 压缩html代码
minify: {
// 移除空格
collapseWhitespace: true,
// 移除注释
removeComments: true
}
})
],
mode: 'production'
};
4.8 生产环境配置
- 修改配置文件
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');
// 定义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.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
/*
正常来讲,一个文件只能被一个loader处理。
当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
先执行eslint 再执行babel
*/
{
// 在package.json中eslintConfig --> airbnb
test: /\.js$/,
exclude: /node_modules/,
// 优先执行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {version: 3},
targets: {
chrome: '60',
firefox: '50'
}
}
]
]
}
},
{
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.css'
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
mode: 'production'
};
五、 webpack 优化配置
5.1 性能优化总览
- 性能优化分类
- 开发环境性能优化
- 生产环境性能优化
- 开发环境性能优化
- 优化打包构建速度
- HMR
- 优化代码调试
- source-map
- 生产环境性能优化
- 优化打包构建速度
- oneOf
- babel缓存
- 多进程打包
- externals
- dll
- 优化代码运行的性能
- 缓存(hash-chunkhash-contenthash)
- tree shaking
- code split
- 懒加载/预加载
- pwa
5.2 HMR
- 介绍
- HMR:
hot module replacement
热模块替换 / 模块热替换 - 作用:一个模块发生变化,
只
会重新打包这一个
模块(而不是打包所有模块
) 极大提升构建速度 - 样式文件:可以使用HMR功能: 因为
style-loader
内部实现了 - js文件:默认
不能
使用HMR功能: 需要修改
js代码,添加支持HMR功能的代码- 注意:HMR功能对js的处理,只能处理
非入口js文件
的其他文件。
- 注意:HMR功能对js的处理,只能处理
- html文件: 默认
不能
使用HMR功能,同时会导致问题:html文件不能热更新了(本地写的html代码没有更新浏览器) (不用做HMR功能)- 解决:修改entry入口,将html文件引入
entry: ['./src/js/index.js', './src/index.html']
- 解决:修改entry入口,将html文件引入
- js文件开启HMR功能
// index.js
// 引入
import print from './print';
import '../css/iconfont.css';
import '../css/index.less';
console.log('index.js文件被加载了~');
print();
function add(x, y) {
return x + y;
}
console.log(add(1, 3));
if (module.hot) {
// 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效
module.hot.accept('./print.js', function() {
// 方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建。
// 会执行后面的回调函数
print();
});
}
// print.js
console.log('print.js被加载了~');
function print() {
const content = 'hello print';
console.log(content);
}
export default print;
- 配置文件
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: ['./src/js/index.js', './src/index.html'],
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
// loader的配置
{
// 处理less资源
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
// 处理css资源
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
// 处理图片资源
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
// 关闭es6模块化
esModule: false,
outputPath: 'imgs'
}
},
{
// 处理html中img资源
test: /\.html$/,
loader: 'html-loader'
},
{
// 处理其他资源
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media'
}
}
]
},
plugins: [
// plugins的配置
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
// 开启HMR功能
// 当修改了webpack配置,新配置要想生效,必须重新webpack服务
hot: true
}
};
5.3 source-map
- 简介
source-map: 一种提供源代码到构建后代码映射技术
(如果构建后代码出错了,通过映射
可以追踪源代码
错误)
- 类别
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
- source-map:外部
- 错误代码准确信息 和 源代码的错误位置
- inline-source-map:内联
- 只生成一个内联source-map
- 错误代码准确信息 和 源代码的错误位置
- hidden-source-map:外部
- 错误代码错误原因,但是没有错误位置
- 不能追踪源代码错误,只能提示到构建后代码的错误位置
- 可以隐藏源代码,不隐藏构建代码
- eval-source-map:内联
- 每一个文件都生成对应的source-map,都在eval
- 错误代码准确信息 和 源代码的错误位置
- nosources-source-map:外部
- 错误代码准确信息, 但是没有任何源代码信息
- 可以隐藏源代码,也隐藏构建代码
- cheap-source-map:外部
- 错误代码准确信息 和 源代码的错误位置
- 只能精确到行
- cheap-module-source-map:外部
- 错误代码准确信息 和 源代码的错误位置
- module 会将 loader 的 source map 加入
- 内联和外部的区别
- 外部生成了文件,内联没有(内联直接将source-map信息放在js里面了)
- 内联构建速度更快
- source-map 的选择
- 开发环境:速度快,调试更友好
- 速度快(eval>inline>cheap>…) eval-cheap-souce-map > eval-source-map
- 调试更友好: souce-map > cheap-module-souce-map > cheap-souce-map
- 建议: eval-source-map(vue,react脚手架) / eval-cheap-module-souce-map
- 生产环境:源代码要不要隐藏? 调试要不要更友好
- 内联会让代码体积变大,所以在生产环境不用内联
- nosources-source-map 全部隐藏
- hidden-source-map 只隐藏源代码,会提示构建后代码错误信息
- source-map(调试友好) / cheap-module-souce-map(速度快些)
- 配置信息
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: ['./src/js/index.js', './src/index.html'],
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
// loader的配置
{
// 处理less资源
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
// 处理css资源
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
// 处理图片资源
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
// 关闭es6模块化
esModule: false,
outputPath: 'imgs'
}
},
{
// 处理html中img资源
test: /\.html$/,
loader: 'html-loader'
},
{
// 处理其他资源
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media'
}
}
]
},
plugins: [
// plugins的配置
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
hot: true
},
devtool: 'eval-source-map'
};
5.4 oneOf
- 解决问题
- 不使用 oneOf 的情况下,所有的 rules 都会匹配一次,有的可以命中,有的无法命中。
- 使用 oneOf 的情况下,oneOf 中的 loader 只会匹配一个
- 优化生产环境打包构建速度
- 配置文件
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");
// 定义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.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"],
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage",
corejs: { version: 3 },
targets: {
chrome: "60",
firefox: "50",
},
},
],
],
},
},
{
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.css",
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: "./src/index.html",
minify: {
collapseWhitespace: true,
removeComments: true,
},
}),
],
mode: "production",
};
5.5 缓存
- 缓存分类
- babel缓存(让第二次打包构建速度更快)
- cacheDirectory: true
- 文件资源缓存(让代码上线运行缓存更好使用)
- 使用场景: 服务器配置了缓存,用户会使用缓存文件,此时出了个bugfix版本,将变更的文件替换,如何使用户放弃缓存,使用新的文件
- 文件资源缓存方法
- hash: 每次wepack构建时会生成一个唯一的hash值。
- 问题: 因为js和css同时使用一个hash值。 如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)
- chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
- 问题: js和css的hash值还是一样的因,为css是在js中被引入的,所以同属于一个chunk
- contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样
- hash: 每次wepack构建时会生成一个唯一的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");
// 定义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",
};
5.6 tree shaking&树摇
- 目的
tree shaking:去除无用代码,减少代码体积
- 前提
- 必须使用ES6模块化
- 开启production环境
- 问题
可能会把 css
、 @babel/polyfill
(副作用)文件干掉
在package.json中配置:
- “sideEffects”: false 所有代码都没有副作用(都可以进行tree shaking)
- “sideEffects”: [“.css", ".less”] 不要进行 tree shaking
- 配置文件
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");
// 定义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"],
},
{
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",
};
5.7 code split
- 目的
- 代码分割,将一份大的代码分割为多个代码,并行加载,提高加载速度
- 实现按需加载
- 方式一: 多入口
通过多入口的方式拆分文件
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// 单页面应用 单入口最终输出一个bundle
// entry: './src/js/index.js',
entry: {
// 多页面应用 多入口:每个入口输出一个bundle
index: "./src/js/index.js",
test: "./src/js/test.js",
},
output: {
// [name]:取文件名
filename: "js/[name].[contenthash:10].js",
path: resolve(__dirname, "build"),
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
minify: {
collapseWhitespace: true,
removeComments: true,
},
}),
],
mode: "production",
};
- 方式二: splitChunks
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 单入口
// entry: './src/js/index.js',
entry: {
index: './src/js/index.js',
test: './src/js/test.js'
},
output: {
// [name]:取文件名
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
/*
1. 可以将node_modules中代码单独打包一个chunk最终输出
2. 自动分析多入口chunk中,有没有公共的文件(如果公共文件太小,也不会单独生成chunk)。如果有会打包成单独一个chunk
*/
optimization: {
splitChunks: {
chunks: 'all'
}
},
mode: 'production'
};
- 方式三: 通过js代码,让某个文件被单独打包成一个chunk
import
动态导入语法:能将某个文件单独打包- webpackChunkName: 配置打包文件的名字,若不配置则为 0,1,2,3…
function sum(...args) {
return args.reduce((p, c) => p + c, 0);
}
import(/* webpackChunkName: 'test' */ "./test")
.then(({ mul, count }) => {
// 文件加载成功~
// eslint-disable-next-line
console.log(mul(2, 5));
})
.catch(() => {
// eslint-disable-next-line
console.log("文件加载失败~");
});
// eslint-disable-next-line
console.log(sum(1, 2, 3, 4));
5.8 lazy loading&懒加载
console.log('index.js文件被加载了~');
// import { mul } from './test';
document.getElementById('btn').onclick = function() {
// 懒加载~:当文件需要使用时才加载~
// 预加载 prefetch:会在使用之前,提前加载js文件
// 正常加载可以认为是并行加载(同一时间加载多个文件)
// 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
};
预加载慎用,原因是兼容性问题比较大。
5.9 pwa & 渐进式网络开发应用程序(离线可访问)
- 现状
由于兼容性问题,应用并不广泛,有些大厂在用(淘宝)
- 下载安装包
通过 workbox(谷歌开源插件) 使用, webpack 需要安装 workbox-webpack-plugin
npm install --save-dev workbox-webpack-plugin
- 配置文件
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');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
// 定义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']
},
{
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
}
}),
new WorkboxWebpackPlugin.GenerateSW({
/*
1. 帮助serviceworker快速启动
2. 删除旧的 serviceworker
生成一个 serviceworker 配置文件(service-worker.js)~
*/
clientsClaim: true,
skipWaiting: true
})
],
mode: 'production',
devtool: 'source-map'
};
import { mul } from "./test";
import "../css/index.css";
function sum(...args) {
return args.reduce((p, c) => p + c, 0);
}
// eslint-disable-next-line
console.log(mul(2, 3));
// eslint-disable-next-line
console.log(sum(1, 2, 3, 4));
/*
1. eslint不认识 window、navigator全局变量
解决:需要修改package.json中eslintConfig配置
"env": {
"browser": true // 支持浏览器端全局变量
}
2. sw代码必须运行在服务器上
方式一:直接使用 nodejs
方式二:使用 serve 包
npm i serve -g
serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去
*/
// 注册serviceWorker
// 处理兼容性问题
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker
.register("/service-worker.js")
.then(() => {
console.log("sw注册成功了~");
})
.catch(() => {
console.log("sw注册失败了~");
});
});
}
5.10 多进程打包
- 下载安装包
npm install --save-dev thread-loade
- 配置文件
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");
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");
// 定义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"],
},
{
test: /\.js$/,
exclude: /node_modules/,
use: [
/*
放在某一个loader的后面,会对该loader开启多进程打包。
进程启动大概为600ms,进程通信也有开销。
只有工作消耗时间比较长,才需要多进程打包
*/
{
loader: "thread-loader",
options: {
workers: 2, // 进程2个
},
},
{
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,
},
}),
new WorkboxWebpackPlugin.GenerateSW({
/*
1. 帮助serviceworker快速启动
2. 删除旧的 serviceworker
生成一个 serviceworker 配置文件~
*/
clientsClaim: true,
skipWaiting: true,
}),
],
mode: "production",
devtool: "source-map",
};
5.11 externals
- 作用
防止将某些包输出到某些依赖中,比如jq我们希望从cdn链接中获取,不希望打包进来。
- 配置文件
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'production',
externals: {
// 拒绝jQuery被打包进来
jquery: 'jQuery'
}
};
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webpack</title>
</head>
<body>
<h1 id="title">hello html</h1>
<!-- 需要手动引入进来 -->
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
</body>
</html>
5.12 dll & 动态连接库
- 目的
使用dll技术,对某些库(第三方库:jquery、react、vue…)进行单独打包,这部操作是预先操作的。等到真正业务打包的时候就不需要打包这样文件了,只需要打包业务代码,引入预先打包好的第三方库。
- 配置文件
// webpack.dll.js
/*
使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包
当你运行 webpack 时,默认查找 webpack.config.js 配置文件
需求:需要运行 webpack.dll.js 文件
--> webpack --config 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.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/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'
};
六、 webpack 配置详情
6.1 entry
入口起点
- 字符串
entry: './src/index.js'
- 单入口
- 打包形成
一个chunk
- 输出
一个bundle
文件 - 此时
chunk
的名称默认是main
- 数组
entry: ['./src/index.js', './src/add.js']
- 多入口
- 所有入口文件最终只会形成
一个chunk
, 输出出去只有一个bundle
文件。 - 此时
chunk
的名称默认是main
- 只有在HMR功能中让html热更新生效
- 数组的文件一般是没有互相依赖关系的,但是又处于某些原因要将它们打包在一起
- 对象
entry: {
index: "./src/index.js",
add: "./src/add.js"
}
- 多入口
- 有几个入口文件就形成几个
chunk
,输出几个bundle
文件 - 此时
chunk
的名称是key
- 特殊用法
entry: {
// 所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。
index: ["./src/index.js", "./src/count.js"],
// 形成一个chunk,输出一个bundle文件。
add: "./src/add.js"
}
6.2 output
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
output: {
// 文件名称(指定名称+目录)
filename: "js/[name].js",
// 输出文件目录(将来所有资源输出的公共目录)
path: resolve(__dirname, "build"),
// 所有资源引入公共路径前缀,例如:'imgs/a.jpg' --> '/imgs/a.jpg'
publicPath: "/",
chunkFilename: "js/[name]_chunk.js", // 非入口chunk的名称
// library: '[name]', // 整个库向外暴露的变量名
// libraryTarget: 'window' // 变量名添加到哪个上 browser
// libraryTarget: 'global' // 变量名添加到哪个上 node
// libraryTarget: 'commonjs'
},
plugins: [new HtmlWebpackPlugin()],
mode: "development",
};
6.3 module
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'js/[name].js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
// loader的配置
{
test: /\.css$/,
// 多个loader用use
use: ['style-loader', 'css-loader']
},
{
test: /\.js$/,
// 排除node_modules下的js文件
exclude: /node_modules/,
// 只检查 src 下的js文件
include: resolve(__dirname, 'src'),
// 优先执行
enforce: 'pre',
// 延后执行
// enforce: 'post',
// 单个loader用loader
loader: 'eslint-loader',
options: {}
},
{
// 以下配置只会生效一个
oneOf: []
}
]
},
plugins: [new HtmlWebpackPlugin()],
mode: 'development'
};
6.4 resolve
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/[name].js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [new HtmlWebpackPlugin()],
mode: 'development',
// 解析模块的规则
resolve: {
// 配置解析模块路径别名: 优点简写路径 缺点路径没有提示
alias: {
$css: resolve(__dirname, 'src/css')
},
// 配置省略文件路径的后缀名
extensions: ['.js', '.json', '.jsx', '.css'],
// 告诉 webpack 解析模块是去找哪个目录
modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
}
};
6.5 devServer
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/[name].js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [new HtmlWebpackPlugin()],
mode: 'development',
resolve: {
alias: {
$css: resolve(__dirname, 'src/css')
},
extensions: ['.js', '.json', '.jsx', '.css'],
modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
},
devServer: {
// 运行代码的目录
contentBase: resolve(__dirname, 'build'),
// 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload
watchContentBase: true,
watchOptions: {
// 忽略文件
ignored: /node_modules/
},
// 启动gzip压缩
compress: true,
// 端口号
port: 5000,
// 域名
host: 'localhost',
// 自动打开浏览器
open: true,
// 开启HMR功能
hot: true,
// 不要显示启动服务器日志信息
clientLogLevel: 'none',
// 除了一些基本启动信息以外,其他内容都不要显示
quiet: true,
// 如果出错了,不要全屏提示~
overlay: false,
// 服务器代理 --> 解决开发环境跨域问题
proxy: {
// 一旦devServer(5000)服务器接受到 /api/xxx 的请求,就会把请求转发到另外一个服务器(3000)
'/api': {
target: 'http://localhost:3000',
// 发送请求时,请求路径重写:将 /api/xxx --> /xxx (去掉/api)
pathRewrite: {
'^/api': ''
}
}
}
}
};
6.6 optimization
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin')
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build'),
chunkFilename: 'js/[name].[contenthash:10]_chunk.js'
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [new HtmlWebpackPlugin()],
mode: 'production',
resolve: {
alias: {
$css: resolve(__dirname, 'src/css')
},
extensions: ['.js', '.json', '.jsx', '.css'],
modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
},
optimization: {
splitChunks: {
chunks: 'all'
// 默认值,可以不写~
/* minSize: 30 * 1024, // 分割的chunk最小为30kb
maxSiza: 0, // 最大没有限制
minChunks: 1, // 要提取的chunk最少被引用1次
maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量
maxInitialRequests: 3, // 入口js文件最大并行请求数量
automaticNameDelimiter: '~', // 名称连接符
name: true, // 可以使用命名规则
cacheGroups: {
// 分割chunk的组
// node_modules文件会被打包到 vendors 组的chunk中。--> vendors~xxx.js
// 满足上面的公共规则,如:大小超过30kb,至少被引用一次。
vendors: {
test: /[\\/]node_modules[\\/]/,
// 优先级
priority: -10
},
default: {
// 要提取的chunk最少被引用2次
minChunks: 2,
// 优先级
priority: -20,
// 如果当前要打包的模块,和之前已经被提取的模块是同一个,就会复用,而不是重新打包模块
reuseExistingChunk: true
}
}*/
},
// 将当前模块的记录其他模块的hash单独打包为一个文件 runtime
// 解决:修改a文件导致b文件的contenthash变化,在b文件中引入a文件,打包后b文件通过引用a文件的contenthash来引入a文件,修改a文件,a文件的contenthash变化自然会导致b文件发生变化
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
},
minimizer: [
// 配置生产环境的压缩方案:js和css
new TerserWebpackPlugin({
// 开启缓存
cache: true,
// 开启多进程打包
parallel: true,
// 启动source-map
sourceMap: true
})
]
}
};
参考
- webpack中文文档
- webpack二刷之五、生产环境优化(5.提取压缩CSS)
附录一 package.json
{
"name": "webpack_code",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.8.4",
"@babel/polyfill": "^7.8.3",
"@babel/preset-env": "^7.8.4",
"add-asset-html-webpack-plugin": "^3.1.3",
"babel": "^6.23.0",
"babel-loader": "^8.0.6",
"core-js": "^3.6.4",
"css-loader": "^3.4.2",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-loader": "^3.0.3",
"eslint-plugin-import": "^2.20.1",
"file-loader": "^5.0.2",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"less": "^3.11.1",
"less-loader": "^5.0.0",
"mini-css-extract-plugin": "^0.9.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0",
"style-loader": "^1.1.3",
"terser-webpack-plugin": "^2.3.5",
"thread-loader": "^2.1.3",
"url-loader": "^3.0.0",
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"workbox-webpack-plugin": "^5.0.0"
},
"dependencies": {
"jquery": "^3.4.1"
},
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
},
"eslintConfig": {
"extends": "airbnb-base",
"env": {
"browser": true
}
},
"sideEffects": [
"*.css"
]
}