目录
1、Webpack五个核心概念
-
Entry
入口(Entry)指示Webpack以哪个文件为入口起点开始打包,分析构建内部依赖图。
-
Output
输出(Output)指示Webpack打包后的资源bundles输出到哪里去,以及如何名。
-
Loader
Loader让WebPack能够去处理那些非JavaScript文件(webpack 自身只理解JavaScript)
-
Plugins
插件(Plugins)可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量等。
-
Mode
2、使用source map
作用:精准定位代码行数
devtool: 'inline-source-map',
3、处理图片资源
module:{
rules:[
{
// 问题:默认处理不了html中img图片
// 处理图片资源
test: /\.(png|jpg|gif|svg)$/i,
type: 'asset',
//指定图片的打包位置
generator: {
filename: 'images/[contenthash][ext]'
},
parser: {
// 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。其中这里是4M
dataUrlCondition: {
maxSize: 4 * 1024
}
}
},
]
}
4、加载css并抽离和压缩css
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 给每个css文件单独抽调出来放进html的link上引入
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//这个插件使用 cssnano 优化和压缩 CSS。
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
mode: 'production',
entry: {
index: './src/index.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
devtool: 'inline-source-map',
// loader的配置
module: {
rules: [
{
test: /\.(css|less)$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
}),
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
optimization: {
minimizer: [
// 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
// `...`,
new CssMinimizerPlugin(),
],
}
};
5、使用babel-loader
目的:为了使浏览器兼容es6以上等的写法转化为熟悉浏览器认识的es5写法。
1、安装
npm install -D babel-loader @babel/core @babel/preset-env
- babel-loader:在webpack里应用babel解析ES6的桥梁
- @babel/core:babel核心模块
- @babel/preset-env:babel预设,一组babel插件的集合
配置如下:
module:{
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
2、regeneratorRuntime插件
此时执行编译,在浏览器里打开项目发现报了一个致命错误:
regeneratorRuntime是webpack打包生成的全局辅助函数,由babel生成,用于兼容async/await的语法。
regeneratorRuntime is not defined这个错误显然是未能正确配置babel。
正确的做法需要添加一下的插件和配置:
npm install @babel/runtime
npm install -D @babel/plugin-transform-runtime
//配置
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-runtime']
}
}
}
6、代码分离
-
入口起点-防止重复
根据不同的入口打包,将公用的代码分离出来
第一种
webpack.config.js
entry:{
index: {
import: './src/index.js',
dependOn: 'shared'
},
another: {
import: './src/another-module.js',
dependOn: 'shared'
},
shared: 'lodash'
}
第二种
使用SplitChunksPlugin插件
modules.exports={
entry: {
index: './src/index.js',
another: './src/another-module.js',
},
optimization: {
splitChunks: {
// 包含所有类型的块
chunks: 'all',
},
}
}
-
动态导入
使用符合ECMAScript提案的import()语法实现动态导入
新建async-module.js
function getComponent () {
return import('lodash')
.then(({ default: _ }) => {
const element = document.createElement('div')
element.innerHTML = _.join(['Hello', 'webpack'], '')
return element
})
}
getComponent().then((ele) => {
// 为了单独处理lodash这个文件
document.body.appendChild(ele)
})
在所需的模块中引入
import './async-module'
- 懒加载
好处:减少网络流量请求
//math.js webpackChunkName为设置打包的后的名字
const button = document.createElement('button')
button.textContent = '点击执行加法运算'
button.addEventListener('click', () => {
import(/* webpackChunkName:'print'*/'./print.js').then(({ add }) => {
console.log(add(4, 5))
})
})
document.body.appendChild(button)
- 预加载
preFetch(预获取):将来某些导航下可能需要的资源,比懒加载更优的选择。
preload(预加载):当前导航下可能需要资源,和懒加载类似
const button = document.createElement('button')
button.textContent = '点击执行加法运算'
button.addEventListener('click', () => {
import(/* webpackChunkName:'print',webpackPrefetch:true*/'./print.js').then(({ add }) => {
console.log(add(4, 5))
})
})
document.body.appendChild(button)
7、缓存
当项目部署到服务器时,浏览器加载完服务器上的文件,会缓存我们打包好的模块,如果我们修改了自己的业务代码,而文件名没有变,浏览器就会获取本地缓存的内容,从而获取不到新内容,所以通过下面的方式解决。
7.1、输出文件的文件名
我们可以通过替换output.filename中的substitutions设置,来定义输出文件的名称。webpack提供了一种使用称为substitution(可替换模板字符串)的方式,通过带括号字符串来模板化文件名。其中,[contenthas] substitution将根据资源内容创建出唯一的hash。当资源内容发生变化时,[contenthash]也会发生变化。
修改配置文件
module.exports = {
output: {
filename: '[name].[contenthash].js',
},
}
7.2、缓存第三方库
将第三方库(library)(例如 lodash )提取到单独的 vendor chunk 文件中,是比较推荐的做法,这是因为,他们很少像本地的源代码那样频繁修改,因此通过实现以上步骤,利用client的长效缓存机制,命中缓存来消除请求,并减少向server获取资源,同时还能保证client代码和server代码版本一致。我们在 optimization.splitChunks 添加如下 cacheGroups 参数并构建:
//webpack.config.js
optimization:{
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
},
}
8、拆分开发环境和生产环境配置
使用不同的命令做开发环境或生产环境的打包
module.exports = (env) => {
return{
mode: env.production ? 'production' : 'development',
}
}
- 开发环境打包命令
npx webpack --env development
- 生产环境打包命令
npx webpack --env production
-
terser-webpack-plugin
// 该插件使用 terser 来压缩 JavaScript。
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimizer: [new TerserPlugin()],
},
};
8.1公共路径
publicPath 配置可以用来指定应用程序中所有资源的基础路径
output:{
publicPath: 'http://localhost:8080/'
}
8.2环境变量
可使用--env 的方式配置打包成开发环境或是生产环境
//package.json
"scripts":{
"start": "webpack --config webpack.config.js --env development",
"build": "webpack --config webpack.config.js --env production",
}
8.3拆分配置文件
webpack.config.js (使用webpack-merage对文件进行合并)
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.common')
const productionConfig = require('./webpack.prod')
const developmentConfig = require('./webpack.dev')
module.exports = (env) => {
switch (true) {
case env.development:
return merge(commonConfig, developmentConfig)
case env.production:
return merge(commonConfig, productionConfig)
default:
return Error('No matching configuration was found')
}
}
webpack.common.js (提取公共的部分)
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 给每个css文件单独抽调出来放进html的link上引入
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
clean: true,
},
// loader的配置
module: {
rules: [
{
test: /\.(css|less)$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
},
{
// 问题:默认处理不了html中img图片
// 处理图片资源
test: /\.(png|jpg|gif|svg)$/i,
type: 'asset',
generator: {
filename: 'images/[contenthash][ext]'
},
parser: {// 如果一个模块源码大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。
// 这里是4M
dataUrlCondition: {
maxSize: 4 * 1024 * 1024
}
}
},
{
test: /\.html$/,
// 处理html文件的img图片(负责引入img,从而能被url-loader进行处理)
use: 'html-loader'
},
{
test: /\.ttf$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[contenthash][ext]'
}
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-runtime']
}
}
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
}),
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
},
}
};
webpack.dev.js (开发环境配置,不需要缓存和压缩)
module.exports = {
mode: 'development',
output: {
filename: 'scripts/[name].[contenthash].js'
},
devtool: 'inline-source-map',
devServer: {
static: './dist'
}
};
webpack.prod.js (生产环境配置,加入缓存和压缩)
//这个插件使用 cssnano 优化和压缩 CSS。
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
// 该插件使用 terser 来压缩 JavaScript。
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
mode: 'production',
output: {
filename: 'scripts/[name].[contenthash].js',
publicPath: 'http://localhost:8080/'
},
devtool: 'inline-source-map',
optimization: {
minimizer: [
// 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
// `...`,
new CssMinimizerPlugin(),
new TerserPlugin()
]
},
performance: {
hints: false
}
};