webpack学习
webpack学习笔记
- 参考文档:
Webpack官网中文文档 - 学习视频:
Webpack原理与实践
温故webpack打包知识,忘记了就回头看看~ 根据学习视频学习整理,不为一一对应笔记,主要是根据对学习视频知识的梳理~
PS:上学习视频是基于webpack4的,我这边运行时基于webpack5的,不对的地方已经按照官方文档进行定义配置
初始化项目
环境要求:node+npm+webpack
- 初始化 npm 项目
npm init -y
- 安装 webpack 和 webpack-cli:
npm install --save-dev webpack webpack-cli
- 创建项目
- 建立webpack.config.js文件
- 新建src文件夹,新建打包入口文件main.js
webpack.config.js
const path = require('path');
module.exports = {//定义入口文件entry: './src/main.js',// 定义输出文件名output:{//定义输出名称filename:'bundle.js',//定义输出路径path: path.join(__dirname, "/dist"),}
}
main.js
alert('Hello world!')
- 定义打包命令package.json中script
- 运行npm run build 则打包文件,默认根据webpack.config.js配置打包
"scripts": {"build": "webpack"}
- 在根目录建立index.html文件,通过script标签引入打包的文件(后面通过插件htlm-webpack-plugin自动生成,则删除此文件,直接自动打包到dist文件夹中,并会自动引入打包.js文件)
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Webpack Plugin Demo</title><meta name="viewport" content="width=device-width, initial-scale=1"><script src="dist/bundle.js"></script></head><body></body>
</html>
- 安装server运行静态服务器
运行serve则打开当前目录,若有index.html默认运行index.html文件
npm install -g serve
运行
- 当前开始时运行的自定义的index.html看效果,
serve .
- 后续运行dist文件夹的自动生成的index.html看效果
serve dist
模块化标准规范
- 浏览器:ES Module (主流的打包方案)
- Nodejs: Commonjs(内置的环境系统)
Entry(入口):
Webpack 的打包过程从入口文件开始。入口文件可以是一个或多个,指定了应用程序的起点。
// webpack.config.js
module.exports = {
entry: './src/index.js'
};
Output(输出):
配置打包后的文件输出位置和文件名。
// webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: __dirname + '/dist'
}
};
Loaders(加载器):
Loaders 让 Webpack 能够处理非 JavaScript 文件!!! 如 CSS、图片、字体等。通过配置 loaders,Webpack 可以将这些文件转换为可以被应用程序使用的模块。
具体每个loader作用以及更多loader查看官网,在这里配置最常见的loader
常见loader如下:
- style-loader & css-loader
- file-loader & url-loader
- html-loader
- babel-loader
- 自制loader
- …
- 安装依赖
npm i style-loader css-loader file-loader url-loader html-loader babel-loader
- 配置应用
const webpack = require('webpack');
const path = require("path");
module.exports = {mode: "none",entry: "./src/main.js",output: {filename: "[name]-[chunkhash:8]-bundle.js",path: path.join(__dirname, "/dist"),},module: {rules: [{test: /\.m?js$/,exclude: /(node_modules|bower_components)/,use: {loader: "babel-loader",options: {//将ES Modules转换为CommonJSpresets: [['@babel/preset-env', {//默认是auto自动,写了commonjs是强制转换为commonjs,此时usedExports无法生效,即tree shaking无法生效modules: 'commonjs'}]],}}},{test: /\.html$/,use: 'html-loader',},{test: /\.css$/,use: [MiniCssExtractPlugin.loader,'css-loader']},{test: /\.png$/,use: {loader: "url-loader",options: {name: '[name].[hash:8].[ext]',outputPath: 'images/',publicPath: 'images/',limit: 10 * 1024,}}}]}
}
自制loader详解
自制markdown-loader为了解析引入.md文件
- 安装markdown-it(webpack5用这个解析.md文件)
npm i markdown-it
- 新建markdown-loader.js
const markdownIt = require('markdown-it')();module.exports = function markdownLoader(source) {const html = markdownIt.render(source);return `module.exports = ${JSON.stringify(html)}`;
};
- 配置使用
const webpack = require('webpack');
const path = require("path");
module.exports = {mode: "none",entry: "./src/main.js",output: {filename: "[name]-[chunkhash:8]-bundle.js",path: path.join(__dirname, "/dist"),},module: {rules: [{test: /\.md$/,use: './markdown-loader'}]}
}
style-loader 和 css-loader
这两个 Loaders 通常一起使用,用于处理 CSS 文件并将其引入到 JavaScript 模块中。
- css-loader:
解析 CSS 文件中的 @import 和 url() 语法,并将 CSS 转换为 JavaScript 模块。
允许你在 JavaScript 文件中通过 import 或 require 引入 CSS 文件。
- style-loader:
将 CSS 以
示例配置:
module.exports = {module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']}]}
};
file-loader 和 url-loader
这两个 Loaders 用于处理文件资源,如图片、字体等。
- file-loader:
将文件解析为 import 或 require 语句,并返回一个相应的 URL。
将文件复制到输出目录,并根据配置返回相对 URL 或绝对 URL。
- url-loader:
功能类似于 file-loader,但如果文件小于设定的阈值(以字节为单位),它会将文件内容转换为 Base64 编码的 Data URL,嵌入到生成的 JavaScript 文件中。
超过阈值的文件仍然会使用 file-loader 处理
示例配置:
module.exports = {module: {rules: [{test: /\.(png|jpg|gif)$/,use: [{loader: 'url-loader',options: {limit: 8192, // 8KB 以下的文件会被转为 Base64name: '[name].[hash:8].[ext]'}}]}]}
};
html-loader
- html-loader:
解析 HTML 文件中的 标签、 标签和其他资源引用,将它们转换为 import 或 require 语句,从而使得这些资源能够被 Webpack 打包。
处理 HTML 文件中的资源依赖,生成正确的资源路径。
示例配置
module.exports = {module: {rules: [{test: /\.html$/,use: 'html-loader'}]}
};
babel-loader
babel-loader:
使用 Babel 将 ES6/ES7/ES8 等现代 JavaScript 语法转换为 ES5,从而兼容更多的浏览器环境。
支持 Babel 插件和预设(presets),如 @babel/preset-env,以便使用最新的 JavaScript 特性和语法。
示例配置:
module.exports = {module: {rules: [{test: /\.js$/,exclude: /node_modules/,use: {loader: 'babel-loader',options: {presets: ['@babel/preset-env']}}}]}
};
总结
- style-loader & css-loader:处理 CSS 文件,解析并注入到 HTML 中。
- file-loader & url-loader:处理文件资源(如图片、字体),将文件复制到输出目录并生成相应的 URL,url-loader 可以将小文件内联到 JavaScript 中。
- html-loader:解析 HTML 文件中的资源引用,生成正确的资源路径。
- babel-loader:使用 Babel 将现代 JavaScript 语法转换为 ES5,以兼容更多浏览器环境。
通过使用这些 Loaders,Webpack 可以处理多种类型的文件资源,并将它们整合到打包输出中,提高开发效率和代码兼容性。
Plugins(插件):
插件用于执行更广泛的任务,如优化打包结果、资源管理和环境变量注入。插件比 loaders 更强大,提供了更多功能。
具体每个plugins作用以及更多plugins查看官网,在这里配置最常见的plugins
常见plugins如下:
- clean-webpack-plugin
- html-webpack-plugin
- copy-webpack-plugin
- mini-css-extract-plugin
- css-minimizer-webpack-plugin
- terser-webpack-plugin
- 自制plugins
- …更多plugins详见官网
- 安装依赖
npm i --save-dev clean-webpack-plugin html-webpack-plugin copy-webpack-plugin mini-css-extract-plugin css-minimizer-webpack-plugin terser-webpack-plugin
- 配置应用
// clean-webpack-plugin 清除上次生成的dist目录 避免遗留文件
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
// html-webpack-plugin 通过webpack输出html文件 index.html中的script路径引用 是否正常
const HTMLWebpackPlugin = require("html-webpack-plugin");
// copy-webpack-plugin 拷贝不需要打包的目录
const CopyWebpackPlugin = require("copy-webpack-plugin");
//提取css文件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 压缩css
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
//js压缩插件
const TerserPlugin = require('terser-webpack-plugin');
const webpack = require('webpack');
const path = require("path");module.exports = {mode: "none",entry: "./src/main.js",output: {filename: "[name]-[chunkhash:8]-bundle.js",path: path.join(__dirname, "/dist"),},//相当于tree shaking:集中配置webpack当中优化功能optimization: {// 压缩代码 负责【摇掉】他们 开发环境一般不压缩minimize: false,//minimizer默认压缩,配置为数组方式则为自定义配置打包压缩,CssMinimizerPlugin压缩css,TerserPlugin压缩jsminimizer: [new CssMinimizerPlugin(),new TerserPlugin()]},plugins: [new CleanWebpackPlugin(),// 用于生成index.htmlnew HTMLWebpackPlugin({title: "Webpack Plugin Demo",meta: {viewport: "width=device-width, initial-scale=1"},// 按照模版输出// template: "./src/index.html",}),//用于热更新new webpack.HotModuleReplacementPlugin(),//按需加载css文件new MiniCssExtractPlugin({// :8指定长度 推荐使用chunkhash// * hash:项目中任何一个地方改动,打包都会造成全部文件变化// * chunkhash推荐: 根据修改代码处,打包只变化修改的代码处文件名// * contenthash:根据代码修改代码处,修改只变化内容的文件名// filename:'[name]-[hash:8].bundle.css',filename: '[name]-[chunkhash:8].bundle.css',// filename:'[name]-[contenthash].bundle.css',}),// 压缩cssnew CssMinimizerPlugin()]
}
自制plugins详解
自制my-plugins插件,用于清除js文件中的注释!
- 通在webpack生命周期的钩子中挂载函数实现扩展 !!! 类似事件 webpack给每个环节埋下钩子 挂载不同任务 就可扩展webpack能力。
- webpack要求我们钩子必须是一个函数或者是一个包含apply方法的对象
配置如下:
const path = require("path");
// 自制插件 plugin 通在webpack生命周期的钩子中挂载函数实现扩展 !!! 类似事件 webpack给每个环节埋下钩子 挂载不同任务 就可扩展webpack能力
//webpack要求我们钩子必须是一个函数或者是一个包含apply方法的对象
//编写一个插件 去除js文件中的注释
class MyPlugin {apply(compiler) {compiler.hooks.emit.tap('MyPlugin', compilation => {//compilation 理解为此次打包的上下文for (const name in compilation.assets) {console.log(`Processing asset: ${name}`);if (name.endsWith(".js")) {const content = compilation.assets[name].source();console.log(`Original content: \n${content}`);const withoutComments = content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');console.log(`Content without comments: \n${withoutComments}`);compilation.assets[name] = {source: () => withoutComments,//必须的方法size: () => withoutComments.length,};}}});}
}module.exports = {mode: "none",entry: "./src/main.js",output: {filename: "[name]-[chunkhash:8]-bundle.js",path: path.join(__dirname, "/dist"),},plugins: [// 自制插件 用于清除js文件中的注释new MyPlugin(),]
}
- 配置使用
const webpack = require('webpack');
const path = require("path");
module.exports = {mode: "none",entry: "./src/main.js",output: {filename: "[name]-[chunkhash:8]-bundle.js",path: path.join(__dirname, "/dist"),},module: {rules: [{test: /\.md$/,use: './markdown-loader'}]}
}
clean-webpack-plugin
- 作用:在每次构建之前清理 /dist 文件夹,确保输出目录中只有构建过程中生成的文件。
- 主要功能:
- 删除旧的文件,防止冗余文件堆积,保持输出目录整洁。
安装依赖
- 删除旧的文件,防止冗余文件堆积,保持输出目录整洁。
npm i --save-dev clean-webpack-plugin
配置
const { CleanWebpackPlugin } = require('clean-webpack-plugin');module.exports = {plugins: [new CleanWebpackPlugin()]
};
html-webpack-plugin
-
作用:生成 HTML 文件,并自动注入打包生成的 JavaScript 和 CSS 文件。
-
主要功能:
- 根据模板或默认生成 HTML 文件。
- 自动注入所有打包生成的资源(例如 JS、CSS 文件)。
- 支持设置模板、文件名、标题等选项。
安装依赖
npm i --save-dev html-webpack-plugin
配置
const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = {plugins: [new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html',title: 'My App'})]
};
copy-webpack-plugin
-
作用:将文件或文件夹从一个位置复制到另一个位置(通常是复制静态资源到输出目录)。
-
主要功能:
- 复制不需要进行编译处理的文件,如图片、字体、静态 HTML 等。
安装依赖
npm i --save-dev copy-webpack-plugin
配置
const CopyWebpackPlugin = require('copy-webpack-plugin');module.exports = {plugins: [new CopyWebpackPlugin({patterns: [{ from: 'public', to: 'dist' }]})]
};
mini-css-extract-plugin
通过MiniCssExtractPlugin插件实现css文件的按需加载。建议css文件超过150kb才考虑是否提取到文件当中,会单独生成一个文件。
-
作用:将 CSS 提取到单独的文件,而不是嵌入到 JavaScript 中。
-
主要功能:
- 提高 CSS 加载速度,减少 JavaScript 文件的体积。
- 支持按需加载 CSS。
安装依赖
npm i --save-dev mini-css-extract-plugin
配置
const MiniCssExtractPlugin = require('mini-css-extract-plugin');module.exports = {module: {rules: [{test: /\.css$/,use: [MiniCssExtractPlugin.loader, 'css-loader']}]},plugins: [new MiniCssExtractPlugin({filename: '[name].[contenthash].css'})]
};
css-minimizer-webpack-plugin
- optimize-css-assets-webpack-plugin
- css-minimizer-webpack-plugin
PS:由于 optimize-css-assets-webpack-plugin 版本与 webpack 版本之间的兼容性问题。
具体来说,optimize-css-assets-webpack-plugin@6.0.1 需要 webpack@^4.0.0,而我当前的项目中使用的是 webpack@5.91.0。
则不适用安装此,我将安装css-minimizer-webpack-plugin代替。
当然,你也可以使用 --legacy-peer-deps 参数来忽略兼容性检查,但这可能会导致其他问题。
- 作用:压缩和优化 CSS 文件,减少文件体积,提高加载速度。
- 主要功能:
- 使用 cssnano 库进行 CSS 的压缩优化。
- 通常与 mini-css-extract-plugin 配合使用。
- 通常与 terser-webpack-plugin配合使用,压缩js文件。
- 安装依赖
- 压缩css
npm i --save-dev css-minimizer-webpack-plugin
- 压缩js
为了区分环境,使用optimization中minimizer进行,一般默认压缩,配置为数组方式则为自定义配置打包压缩,CssMinimizerPlugin压缩css,则需要安装TerserPlugin压缩js)
npm install --save-dev terser-webpack-plugin
- 引入并使用
// 引入依赖压缩css
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
//js压缩插件
const TerserPlugin = require('terser-webpack-plugin');module.exports={// 压缩建议在生产环境才输出,建议放在optimization中minimize统一配置optimization:{//可以由这里控制是否压缩,开发环境设置为false则不压缩,生产环境设置为true开启压缩minimize:true,//minimizer默认压缩,配置为数组方式则为自定义配置打包压缩,CssMinimizerPlugin压缩css,TerserPlugin压缩jsminimizer: [new CssMinimizerPlugin(),new TerserPlugin()]},//这里配置直接压缩不区分环境// plugins:[// // 压缩css// new CssMinimizerPlugin()// ]
}
terser-webpack-plugin
- 作用:压缩和优化 JavaScript 文件,减少文件体积,提高加载速度。
- 主要功能:
- 使用 Terser 库进行 JavaScript 的压缩优化。
- 替代 Webpack 4 之前的 UglifyJS 插件。
安装依赖
npm i --save-dev terser-webpack-plugin
配置
const TerserPlugin = require('terser-webpack-plugin');module.exports = {optimization: {minimize: true,minimizer: [new TerserPlugin()]}
};
总结
- clean-webpack-plugin:在每次构建之前清理输出目录,保持目录整洁。
- html-webpack-plugin:生成 HTML 文件并自动注入打包生成的资源。
- copy-webpack-plugin:复制文件或文件夹到输出目录。
- mini-css-extract-plugin:将 CSS 提取到单独的文件,提高加载速度。
- css-minimizer-webpack-plugin:压缩和优化 CSS 文件,减少文件体积。
- terser-webpack-plugin:压缩和优化 JavaScript 文件,减少文件体积。
通过使用这些插件,Webpack 可以更高效地处理和优化各种资源,提高构建和加载性能。
webpack-dev-server
自动刷新功能:将打包结果暂时存放在内存当中,没有进入磁盘读写,webpack-dev-server从内存当中将变化读取出来,发送到浏览器,添加–open默认自动打开我们的浏览器,这里添加到script命令里,我配置到dev,运行一次就可以看到一边编码一边预览的环境
- 安装依赖
npm install --save-dev webpack-dev-server
- 配置运行命令在package.json中
"scripts": {"dev": "webpack-dev-server --open"}
运行
npm run dev
运行之后,在文件中修改浏览器内容在保存后便会直接变化,无需刷新方可更新
devtools(source-map)
设置属性 devtool: ‘source-map’ 一般效果最好的生成的最慢,约为12种,每种方式效率和效果不同
- 开发环境偏向选择cheap-module-source-map 转换过后差异过大 首次打包慢无所谓 重写打包就快了
- 生产环境 选择none 为了不暴露源代码 隐患 调试是开发阶段的事情 或者nosources-source-map
module.exports={devtool: 'cheap-module-source-map'
}
点击查看更多source-map
HMR热更新
Hot Module Replacement 模块热替换或者叫模块热更新。
Webpack 的热更新(Hot Module Replacement, HMR)是一种允许在运行时替换、添加或删除模块,而无需重新加载整个页面的技术。HMR 的核心原理包括模块热替换、依赖管理以及状态保持。以下是 Webpack 热更新的详细实现原理
Webpack 热更新的工作流程:
- 检测变更:
- Webpack 使用文件系统监视工具(如 chokidar)监视源代码文件的变更。当开发者修改文件并保存时,Webpack 侦测到这些变化。
- 编译模块:
- 一旦检测到变更,Webpack 重新编译受影响的模块(模块包括 JavaScript、CSS、模板文件等)。这一步只重新编译变更的部分,而不是整个应用。
- 生成补丁:
- 编译完成后,Webpack 生成更新的模块,并将这些模块打包成补丁(Hot Update Chunk),包含变更的模块和相应的依赖关系。
- 通知客户端:
Webpack 开发服务器(Webpack Dev Server)通过 WebSocket 向客户端(浏览器)发送更新通知。通知内容包括更新的模块 ID 和哈希值等信息。 - 应用更新:
- 客户端收到更新通知后,通过 WebSocket 从开发服务器请求补丁数据。
- 客户端运行 HMR 运行时逻辑,动态地将新的模块替换进运行中的应用。
Webpack HMR 关键组件
-
Webpack Dev Server
Webpack Dev Server 是一个开发服务器,提供静态文件服务、实时重载以及 HMR 支持。它在内存中保存最新的编译结果,减少文件系统 I/O 操作,提高性能。 -
HMR Runtime
HMR 运行时是注入到打包输出中的一段 JavaScript 代码,负责处理模块更新。它通过 WebSocket 与 Webpack Dev Server 通信,接收更新通知并应用补丁。
热更新的应用过程:
- 更新检查:
- HMR Runtime 通过 WebSocket 接收来自 Webpack Dev Server 的更新通知。
- 请求更新模块:
- HMR Runtime 发送请求获取更新模块的补丁数据(包含新的模块代码和更新的哈希值)。
- 模块热替换:
- HMR Runtime 加载新的模块代码,调用模块的 accept 或 dispose 钩子函数,执行模块热替换逻辑。
accept 钩子函数允许模块在更新时执行自定义逻辑,例如重新渲染组件。
dispose 钩子函数允许模块在被替换前执行清理工作,例如移除事件监听器或保存状态。
例子:当然也可在本main.js中体会
if (module.hot) {
module.hot.accept('./moduleA.js', function() {
console.log('moduleA updated');
// 处理更新后的逻辑
render();
});module.hot.dispose(function() {
console.log('Cleaning up before module is replaced');
// 清理逻辑,例如移除事件监听器
});
}function render() {
const content = require('./moduleA.js');
document.getElementById('app').innerHTML = content;
}render();
在这个示例中,当 moduleA.js 被更新时,HMR Runtime 会调用 accept 钩子函数重新渲染内容,并在模块被替换前调用 dispose 钩子函数进行清理工作。
总结
Webpack 的热更新机制通过监听文件变化、重新编译受影响模块、生成并应用补丁来实现模块的热替换。
关键组件包括 Webpack Dev Server 和 HMR Runtime,它们共同配合,使得开发者可以在不刷新整个页面的情况下高效地进行模块更新。
通过使用 HMR,开发者可以显著提升开发体验和效率。
开启热更新,配置package.json文件 配置–hot
"scripts": {"dev": "webpack-dev-server --open --hot"
}
不同环境中的配置
通过运行命令env区别:
打包命令 配置在script上
webpack --env production
实际操作 webpack.common.js 根据env判断环境做相应处理
const config = {
}
//生产环境下的配置if(env==='production'){config.mode = 'prodution'config.devtool = falseconfig.plugin =[...config.plugins,new CleanWebpackPlugin(),new CopyWebpackPlugin(['public'])]}
}
大型项目建议
不同环境对应不同配置文件
- webpack.common.js 通用配置
- webpack.dev.js 开发环境配置
- webpack.prod.js 生产环境配置
webpack-merge合并逻辑
- 安装依赖
npm i --save-dev webpack-merge
- 配置使用,比如在生产环境配置 webpack.prod.js中:
//通用配置
const common = require('./webpack.common');
//合并配置
const merge = require('webpack-merge');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = merge(common,{mode:'production',plugins: [new CleanWebpackPlugin(),new CopyWebpackPlugin(['public'])]
})
打包命令构建,配置在package.json中的script命令
webpack --config webpack.prod.js
definePlugin注入变量
通常用于不同环境的区别
- 在webpack.prod.js定义
const webpack = require('webpack');
module.exports={mode:'none',entry: "./src/main.js",output:{filename: 'bundle.js',},plugins:[new webpack.DefinePlugin({API_BASE_URL:`"https:''api/example.com"`})]
}
- 在main.js测试
//可以看到结果https:''api/example.com
console.log(API_BASE_URL);
验证:执行打包npm run build:prod可以看到bundle.js有https://api.example.com
Tree Shaking 摇树
tree shaking:将枯树叶摇落:即意思为自动检测出代码中未引用的代码,移除未用的多余代码。
生产环境默认开启!生产模式默认开启tree shaking功能!
运用
自动开启
这里我在editor.js定义了一个未被引用的console.log
- 开发环境:运行npm run build:dev打包就打印出来了,未开启tree shaking功能
- 生产环境:运行npm run build:prod打包就没有打印出来,生产模式默认开启tree shaking功能
1-指定生产环境
或打包时候指定为生产环境:
- package.json script配置命令
webpack --mode prodution
或者配置中指定mode
module.exports = {mode:'prodution'
}
运行命令
npm run build:prod
2-手动开启tree shaking
主要通过配置 optimization 比如在开发环境下配置
// 开发环境打包配置
const common = require('./webpack.common');
// v5之前
// const merge = require('webpack-merge');
//v5版本之后 webpack-merge v5 之后的导入方式有所不同,你需要从 webpack-merge 包中导入 merge 函数。
const {merge} = require('webpack-merge');
module.exports = merge(common, {mode: 'development',devtool: 'cheap-module-source-map',//相当于tree shaking:集中配置webpack当中优化功能,下面二者缺一不可optimization: {//只导出使用的模块 负责标记【枯树叶】usedExports: true,//压缩代码 负责【摇掉】他们minimize: true,}
})
运行命令
npm run build:dev
合并模块ConcatenateModules
主要通过在optimization配置concatenateModules:true,同时为了更好地看到效果,关闭掉minimizee:true
作用:尽可能将所有模块合并输出到一个函数中,就不是一个模块对应一个函数了。又名 Scope Hoisting 作用域提升
// 开发环境打包配置
const common = require('./webpack.common');
// v5之前
// const merge = require('webpack-merge');
//v5版本之后 webpack-merge v5 之后的导入方式有所不同,你需要从 webpack-merge 包中导入 merge 函数。
const {merge} = require('webpack-merge');
module.exports = merge(common, {mode: 'development',devtool: 'cheap-module-source-map',//相当于tree shaking:集中配置webpack当中优化功能optimization: {//只导出使用的模块 负责标记【枯树叶】usedExports: true,//压缩代码 负责【摇掉】他们// minimize: true,//这里:合并模块!!concatenateModules: true,}
})
运行查看
npm run buil
Tree Shaking & Babel
webpack发展非常快,有人提出使用了babel-loader,tree shaking就会失效。
原因是 tree shaking使用的前提是ES Modules.交给Webpack打包的代码必须使用ES Modules.而babel-loader是将ES Modules转换成CommonJS。
- 情况一:babel-loader 会判断是否usedExport,如果有,就禁用ES Module的转换。tree shaking生效
- 情况二:babel-loader在presets里面配置了modules为commonjs则为强制转换,tree shaking不生效
具体如下代码:
const webpack = require('webpack');
const path = require("path");
module.exports = {mode: "none",entry: "./src/main.js",output: {filename: "bundle.js",path: path.join(__dirname, "/dist"),},//生产环境 选择none 为了不暴露源代码 隐患 调试是开发阶段的事情 或者nosources-source-mapdevtool: 'cheap-module-source-map',optimization: {//!!!只导出使用的模块 负责标记【枯树叶】 但是使用了babel-loader就看具体情况,强制转换commonjs这次就失效usedExports: true,//压缩代码 负责【摇掉】他们minimize: true,//这里:合并模块!!concatenateModules: true,},module: {rules: [{test: /\.m?js$/,exclude: /(node_modules|bower_components)/,use: {loader: "babel-loader",options: {//将ES Modules转换为CommonJSpresets: [['@babel/preset-env', {//默认是auto自动,写了commonjs是强制转换为commonjs,此时usedExports无法生效,即tree shaking无法生效modules: 'commonjs'}]],}}}]}
}
SideEffects副作用特性
适用前提:确保你的代码没有副作用!! sideEffects一般用于NPM包标记是否有副作用,生产环境下默认开启,开启了之后没有用到的模块就不再会打包。
在optimization中配置
标识是否有副作用
在 Webpack 中,“副作用”(side effects)这个术语也有特定的意义,通常用于描述模块是否对导入它的代码产生额外影响。具体来说,Webpack 会使用 sideEffects 字段来标记一个模块或包是否包含副作用,以便进行更好的优化,例如 Tree Shaking(移除未使用的代码)。
package.json中
{
"name": "my-library",
"version": "1.0.0",
"sideEffects": false
}
在这个例子中,设置 “sideEffects”: false 表示模块没有副作用,Webpack 可以安全地移除未使用的代码部分。如果某些文件确实有副作用,可以指定它们:
{
"name": "my-library",
"version": "1.0.0",
"sideEffects": ["*.css", "*.scss"]
}
这告诉 Webpack 除了 .css 和 .scss 文件外,其他文件都没有副作用。
开启副作用功能
在编程中,“副作用”(side effect)指的是函数或表达式在计算结果之外,还会对程序的其他部分产生影响的情况。通俗一点说,副作用是指除了返回一个值以外,函数或表达式还做了其他事情,这些事情会影响到外部的状态或环境。比如说有个方法Number.propotype.pad = function(){}通过原型改变Number方法这也是副作用,开启了副作用将不再打包这段函数
webpack.dev.js:
optimization:{//开启副作用,生产环境默认开启,没用到的模块不再打包sideEffects: true
}
多入门打包
entry定义为一个对象,一个属性就是打包的一个路径,多个入口打包出多个结果,filename不直接定义为一个固定名称。
但是若是希望打包出来也为多个index.html就需要独立配置,都则将会打包到一个index.html中引入。则需在HtmlWebpackPlugin中定义chunks:[‘index’]
实现步骤
- 定义entry多个入口 对象方式
- 修改输出多个文件名称区分
- 定义输出多个不用名称index.html,多个引入代替一个index.html引入多个
// 开发环境打包配置
const common = require('./webpack.common');
const HTMLWebpackPlugin = require('html-webpack-plugin');
// v5之前
// const merge = require('webpack-merge');
//v5版本之后 webpack-merge v5 之后的导入方式有所不同,你需要从 webpack-merge 包中导入 merge 函数。
const {merge} = require('webpack-merge');
const path = require("node:path");
module.exports = merge(common, {// 1-定义多个入口entry:{main: './src/main.js',hello:'./src/hello.js'},//2-修改输出名称output: {filename: '[name].bundle.js',path: path.join(__dirname, "/dist"),},//3-定义输出多个不同名称index.htmlplugins: [new HTMLWebpackPlugin({title: 'webpack main',filename: 'main.html',//定义chunks连接chunks:['main']}),new HTMLWebpackPlugin({title: 'webpack hello',filename: 'hello.html',//定义chunks连接chunks:['hello']})],mode: 'development',devtool: 'cheap-module-source-map',//相当于tree shaking:集中配置webpack当中优化功能optimization: {//只导出使用的模块 负责标记【枯树叶】usedExports: true,//压缩代码 负责【摇掉】他们 仅仅对于js文件生效 css不生效minimize: true,//这里:合并模块!!concatenateModules: true,//副作用,开启了之后没有用到的模块就不再会打包。sideEffects: true,}
})
不同打包之提取公共模块
代码中会有相同的共用部分造成代码复用率低。
利用optimization中配置splitChunks代码分割为chunks:'all’开启提取打包公用模块
//相当于tree shaking:集中配置webpack当中优化功能optimization: {//提取公共部分splitChunks:{chunks: 'all'}}
打包查看
npm run build:dev
可看到dist文件中生成了多余的js文件名为二者合一公用模块
动态导入
动态导入按需加载极大节省我们的带宽以及流量,提高响应速度。
事例情况:就是比如点击菜单A显示A内容,菜单B显示B内容。而不是一开始就加载全部AB内容,而是做到按需加载。
具体原理:按照监听菜单瞄点,是什么瞄点加载什么内容
具体原理实现代码:
const render = ()=>{const hash = window.location.hash || "#posts";const mainElements = document.querySelector('.main');mainElements.innerHTML = '';if(hash==='#posts'){import('./posts/post').then(({default:posts})=>{mainElements.appendChild(posts());})}else if(hash==='#album'){import('./posts/album').then(({default:album})=>{mainElements.appendChild(album());})}
}
render();
//监听改变
window.addEventListener('hashchange', render);
魔法注释
通过分包时候添加注释,相同的chunks name就会打包到一起
实现如下:
const render = () => {const hash = window.location.hash || "#posts";const mainElements = document.querySelector('.main');mainElements.innerHTML = '';if (hash === '#posts') {//魔法注释import(/* webpackChunkname:'posts' */ './posts/post').then(({default: posts}) => {mainElements.appendChild(posts());})} else if (hash === '#album') {//魔法注释import(/* webpackChunkname:'album' */ './posts/album').then(({default: album}) => {mainElements.appendChild(album());})}
}
render();
//监听改变
window.addEventListener('hashchange', render);
输出文件名 Hash
启用静态资源,用户则不需要重复请求加载得到这些资源,整体响应速度就会有一个大幅度提升。
为了解决:
- 缓存失效时间设置的过短,效果则不明显
- 缓存失效时间设置的过长,没办法及时更新到客户端
生产模式下,文件名使用Hash哈希值,一旦资源文件发生改变,则会一起改变。
对于客户端而言,全新的文件名就是全新的请求,则没有缓存的问题。那缓存时间设置的非常长,也不担心更新问题。
hash模式
为了减少静态资源请求,设置缓存,则减少服务器的请求。
- 缓存时间设置的短,请求频繁。
- 缓存时间设置的长,用户不能及时更新
解决方式:设置hash哈希值
- hash:项目中任何一个地方改动,打包都会造成全部文件变化
- chunkhash推荐: 根据修改代码处,打包只变化修改的代码处文件名(推荐使用)
- contenthash:根据代码修改代码处,修改只变化内容的文件名
通过:length指定哈希长度,默认是20位
//提取css文件
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports={output:{filename:'[name]-[chunkhash:8].bundle.js',path: path.join(__dirname, "/dist"),},plugins:[new MiniCssExtractPlugin({filename:'[name]-[chunkhash:8].bundle.css'})]
}