说说你对前端工程化的理解?为什么要工程化
前端工程化是将软件开发工程学的理念引入前端开发,通过规范化流程、工具链集成、自动化手段,解决复杂项目中的协作、维护、性能等问题,最终实现高效、稳定、可扩展的研发体系。其核心包括:
维度 | 具体表现 |
---|---|
模块化 | 代码按功能拆分(如 ES Module、组件库),降低耦合度 |
规范化 | 统一代码风格(ESLint)、提交规范(Commitizen)、目录结构 |
自动化 | 构建(Webpack)、测试(Jest)、部署(CI/CD)等流程无需手动干预 |
可维护性 | 通过类型系统(TypeScript)、文档生成(Storybook)提升长期迭代效率 |
原因
指标 | 工程化影响 |
---|---|
开发效率 | 减少重复劳动(如热更新代替手动刷新),工具链集成节省配置时间 |
代码质量 | 静态检查 + 自动化测试提前拦截 Bug,类型系统减少运行时错误 |
性能表现 | 构建优化(Chunk 拆分、按需加载)、资源压缩(CSS/JS minify)、缓存策略 |
团队协作 | 统一开发环境、规范提交日志,降低新人上手成本 |
总结
前端工程化不是单纯引入工具,而是通过标准化、自动化、数据化的手段,将“手工作坊”升级为“现代化工厂”。其本质是用工具约束人,用流程保证质量,最终实现可预测、可度量、可持续的交付能力。
与webpack类似的工具还有哪些?区别?
场景 | 推荐工具 | 理由 |
---|---|---|
企业级复杂应用 | Webpack | 大而全 ,丰富的生态,loader,plugin可以满足各种使用场景的 |
偏向JS库/框架打包 | Rollup | 输出代码精简,Tree-shaking ,可选的输出格式 |
零配置简单项目 | Parcel | 资源编译流程优化,无需复杂配置 (简称0配置) 不支持tree shaking |
追求极限构建速度 | esbuild | Go 语言实现,线程间可以共享内存,性能碾压传统工具 |
快速原型/现代 SPA | Vite | 取长补短(用Rollup pro输入输出,用esbuild dev快速构建),按需打包,未来趋势 |
webpack的配置有哪些
Webpack的配置主要包括以下几个方面:
-
入口(Entry) :Webpack通过入口点开始打包过程。可以配置单个入口点或多个入口点。例如,可以指定一个或多个JavaScript文件作为入口点。
-
输出(Output) :配置打包后的文件输出位置和文件名。通常在
webpack.config.js
文件中设置output
对象,包括filename
和path
等属性。 -
加载器(Loaders) :Webpack本身只能理解JavaScript和JSON,加载器允许webpack处理其他类型的文件,如CSS、Images等。例如,使用
style-loader
和css-loader
来处理CSS文件。 -
插件(Plugins) :插件用于执行范围更广的任务,如bundle优化、资源管理和环境变量注入等。常见的插件包括
HtmlWebpackPlugin
和MiniCssExtractPlugin
等。 -
开发服务器(DevServer) :配置开发服务器以提供实时重新加载功能。可以通过
webpack-dev-server
来实现。 -
模式(Mode) :Webpack支持两种模式——开发模式(development)和生产模式(production)。不同模式下,Webpack会应用不同的优化策略。
-
拆分代码(Code Splitting) :通过入口起点、入口起点依赖和运行时依赖等方式拆分代码,以优化加载时间15。
-
性能优化:包括压缩代码、减少文件大小、缓存处理等,以提高应用性能。
-
跨域请求(CORS) :在开发服务器中配置代理解决跨域问题,通常使用
devServer.proxy
选项。 -
自定义配置:根据项目需求自定义配置项,如环境变量、别名等。
-
optimization :可以使用optimization.splitChunks和optimization.runtimeChunk配置代码拆分和运行时代码提取等优化策略。
-
externals:用于配置排除打包的模块,例如,可以将jQuery作为外置扩展,避免将其打包到应用程序中。
-
devtool:配置source-map类型。
-
context:webpack使用的根目录,string类型必须是绝对路径。
-
target:指定Webpack编译的目标环境。
-
performance:输出文件的性能检查配置。
-
noParse:不用解析和处理的模块。
-
stats:控制台输出日志控制。
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
entry: './src/index.js', // 打包的入口文件
// 指定打包后文件的输出位置和文件名
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
// 指定 Webpack 模式,可以是 development、production 或 none。
mode: 'development',
// 配置 Loader,用于处理不同类型的文件
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
{
test: /.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /.(png|svg|jpg|gif)$/,
use: ['file-loader'],
},
],
},
// 配置插件,用于执行各种任务,如打包优化、资源管理等
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
// 使用 DefinePlugin 插件定义环境变量
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
new WebpackManifestPlugin({
fileName: 'manifest.json', // 生成的 Manifest 文件名
publicPath: '/', // 公共路径
}),
],
// 配置开发服务器,用于本地开发和热更新
devServer: {
contentBase: './dist',
hot: true,
proxy: {
'/api': 'http://localhost:3000',
}, // 配置代理,用于将特定 URL 路径代理到另一个服务器
},
// 配置模块解析选项
resolve: {
// 自动补全文件扩展名,这样在导入模块时,可以省略这些扩展名
extensions: ['.js', '.jsx', '.json'],
// 创建模块别名,以便更方便地导入模块
alias: {
'@components': path.resolve(__dirname, 'src/components/'),
'@utils': path.resolve(__dirname, 'src/utils/'),
},
},
// 配置优化选项,如代码分割和压缩
optimization: {
splitChunks: {
chunks: 'all',
},
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
},
},
}),
],
},
// 配置 SourceMap 选项,用于调试,默认没 SourceMap
devtool: 'source-map',
};
webpack核心原理(也就是webpack做了什么)(webpack介绍)
Webpack是一个开源的JavaScript静态模块打包工具(Module Bundler),其最核心的功能是解决模块之间的依赖(模块打包),把各个模块按照特定的规则和顺序组织在一起,最终合并为一个JS文件(有时会有多个)使其打包后的结果能运行在浏览器上。这个过程就叫作模块打包。其核心目标是解决前端开发中的依赖管理和资源优化问题。
为了什么需要webpack? 当应用的规模大了之后,就必须借助一定的工具,否则人工维护代码的成本将逐渐变得难以承受。使用工具可以让开发效率成倍地提升,所谓“工欲善其事,必先利其器”就是这个意思。
为何模块? 在设计程序结构时,把所有代码都堆到一起是非常糟糕的做法。更好的组织方式是按照特定的功能将其拆分为多个代码段,每个代码段实现一个特定的目的。你可以对其进行独立的设计、开发和测试,最终通过接口来将它们组合在一起。这就是基本的模块化思想。
1. 通过导入和导出语句我们可以清晰地看到模块间的依赖关系
2. 模块可以借助工具来进行打包,所以在页面中只需要加载合并后的资源文件,减少了网络开销。
3. 多个模块之间的作用域是隔离的,彼此不会有命名冲突。
核心原理/为什么选择webpack
-
模块化支持
- 统一处理 ES Module、CommonJS AMD等模块化规范 。
-
依赖管理
- 自动解析模块间的依赖关系,避免手动维护加载顺序。
-
代码转换
- 通过 Loader 处理非 JS 文件(如 SASS → CSS → JS 内联)。
-
扩展功能
- 通过插件(Plugin)实现环境变量注入、HTML 生成等高级功能。 生态成熟
-
打包优化
- 代码分割(Code Splitting)、按需加载(Lazy Loading)优化性能。
-
开发支持
- 提供热更新(HMR)、Source Map 等提升开发效率。
webpack5 的新特性
Webpack 5 是前端构建工具的重要升级,引入了多项新特性以优化性能、简化配置并增强模块化能力。以下是其核心新特性及实现原理的总结:
1. 持久化缓存(Persistent Caching)
Webpack 5 通过文件系统缓存显著提升构建速度。默认启用内存缓存,但可配置为 filesystem
类型,将缓存写入磁盘。这减少了重复构建时的编译时间,尤其适合大型项目。
- 配置示例:
module.exports = { cache: { type: 'filesystem', cacheDirectory: path.resolve(__dirname, '.cache/webpack'), // 缓存存储路径 } };
- 原理:基于文件内容哈希生成缓存文件名,未修改的模块直接从缓存加载,无需重新编译。
2. 模块联邦(Module Federation)
这一革命性功能支持跨应用动态共享代码,尤其适用于微前端架构。
- 核心概念:
- Remote(远程模块)暴露可共享的模块;
- Host(宿主应用)动态加载远程模块。
- 配置示例:
new ModuleFederationPlugin({ name: 'app1', remotes: { app2: 'app2@http://remote-host/app2-entry.js' }, shared: ['react', 'react-dom'] // 共享依赖 });
- 优势:避免重复打包公共依赖,实现运行时按需加载,降低应用耦合度。
3. 增强的 Tree Shaking
Webpack 5 通过更精细的静态分析优化未使用代码的剔除:
- 作用域分析:支持嵌套模块和作用域链追踪,例如仅打包被引用的嵌套导出变量。
- 配置要求:
- 使用 ES Module 语法;
- 在
package.json
中标记"sideEffects": false
; - 避免全局变量污染和副作用代码。
4. 资源模块(Asset Modules)
无需额外 Loader 即可处理静态资源,简化配置:
- 类型:
asset/resource
:生成独立文件(替代file-loader
);asset/inline
:生成 Data URL(替代url-loader
);asset/source
:导出源码(替代raw-loader
)。
- 示例:
通过module: { rules: [{ test: /\.png$/, type: 'asset/resource' }] }
parser.maxSize
可配置内联阈值。
5. 移除 Node.js Polyfill
Webpack 5 不再自动注入 Node.js 核心模块的 Polyfill,需手动处理兼容性:
- 解决方案:
- 使用
core-js
或babel-polyfill
; - 在
resolve.fallback
中配置替代库(如crypto-browserify
)。
- 使用
6. 模块与 Chunk ID 优化
默认采用 deterministic
模式生成短哈希 ID,避免因模块顺序变化导致缓存失效:
- 优势:长期缓存更稳定,文件名仅随内容变化而改变。
7. 其他重要改进
- URIs 支持:可直接导入
data:
或http(s):
协议的资源。 - Top-Level Await:入口文件支持顶层
await
,简化异步加载逻辑。 - 开发体验优化:
- 启动命令改为
webpack serve
; - 默认启用 HMR(热模块替换)。
- 启动命令改为
总结
Webpack 5 通过持久化缓存、模块联邦、更智能的 Tree Shaking 等特性,显著提升了构建性能和开发效率。其设计方向强调 模块化、缓存友好 和 跨应用协作,尤其适合现代微前端和大型单页应用的开发需求。升级时需注意 Polyfill 兼容性和配置调整,以充分利用新特性优势。
webpack的生命周期/构建流程/webpack打包的整个过程(高频)
Webpack 的构建/打包流程是一个模块化代码的静态分析和依赖处理过程,其核心目标是将多个模块及其依赖打包成浏览器可执行的静态资源。以下是其详细流程和原理:
1. 初始化阶段
- 读取配置:解析
webpack.config.js
或命令行参数,确定入口(entry
)、输出(output
)、加载器(loaders
)、插件(plugins
)等配置。 - 创建编译实例:初始化
Compiler
对象(核心调度器),并加载所有插件。
2. 解析入口文件
- 入口起点:根据配置中的
entry
找到入口文件(如src/index.js
)。 - 构建依赖关系树:从入口文件开始,递归解析其依赖的模块(通过
import
、require
等语法),生成模块依赖图(Module Graph)。// 依赖解析示例: // index.js import a from './a.js'; import('./b.js'); // 动态导入(代码分割点)
3. 加载与转换模块
- 应用 Loaders:根据
module.rules
配置,使用对应的 Loader 处理模块:- 文件类型转换:例如用
babel-loader
转换 ES6+ 代码为 ES5。 - 资源处理:例如
css-loader
处理 CSS 依赖,file-loader
处理图片。
// webpack.config.js module: { rules: [ { test: /\.js$/, use: 'babel-loader' }, { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] }
- 文件类型转换:例如用
4. 生成 Chunk(代码块)
- 代码分割(Code Splitting):根据入口文件、动态导入(
import()
)或配置的optimization.splitChunks
规则,将模块划分为多个 Chunk。- Entry Chunk:每个入口生成一个主 Chunk。
- Async Chunk:动态导入的模块生成异步 Chunk。
- Vendor Chunk:第三方库分离为独立 Chunk(通过
SplitChunksPlugin
)。
5. 优化阶段
- Tree Shaking:删除未使用的代码(需 ES Module 语法)。
- 作用域提升(Scope Hoisting):将模块合并到单一函数作用域,减少闭包数量(通过
ModuleConcatenationPlugin
)。 - 代码压缩:使用
TerserWebpackPlugin
压缩 JS,CssMinimizerWebpackPlugin
压缩 CSS。 - 资源优化:例如 Base64 内联小图片(通过
url-loader
的limit
配置)。
6. 生成最终资源(Assets)
- 生成 Chunk 资源:将 Chunk 转换为浏览器可执行的代码块(如 JS、CSS 文件)。
- 应用 Plugins:在关键生命周期钩子(如
emit
)中执行插件逻辑:- 生成 HTML:
HtmlWebpackPlugin
将 Chunk 注入 HTML 模板。 - 文件指纹(Hash):为输出文件添加哈希(如
bundle.[contenthash].js
)以利用缓存。 - 拷贝静态资源:
CopyWebpackPlugin
复制无需处理的文件(如图片、字体)。
- 生成 HTML:
7. 输出到文件系统
- 写入磁盘:根据
output.path
配置,将最终资源写入指定目录。 - 输出结构示例: dist/ index.html main.[hash].js vendors.[hash].js async~b.[hash].js assets/logo.png
核心流程图解
初始化配置 → 解析入口 → 递归构建依赖图 → 转换模块 → 生成 Chunk → 优化代码 → 生成 Assets → 输出文件
关键角色解析
角色 | 作用 |
---|---|
Compiler | 调度整个流程,管理插件生命周期(如 compile 、emit )。 |
Compilation | 单次构建过程的上下文,包含模块、Chunk、依赖等详细信息。 |
Loader | 转换非 JS 模块(如 CSS、图片)为 Webpack 可处理的 JS 模块。 |
Plugin | 通过钩子函数干预构建流程(如优化、资源生成)。 |
Chunk | 代码块单位,最终会生成一个或多个文件(Bundle)。 |
构建流程示例
以入口文件 src/index.js
导入 src/utils.js
为例:
- 解析入口:找到
index.js
。 - 分析依赖:发现
import utils from './utils.js'
,将utils.js
加入依赖图。 - 转换代码:通过 Babel 转换 ES6 语法。
- 生成 Chunk:
index.js
和utils.js
合并为main
Chunk。 - 优化:删除未使用的函数,压缩代码。
- 输出:生成
main.[hash].js
和index.html
。
性能优化方向
- 加速构建:
- 使用
cache
(Webpack 5 持久化缓存)。 - 减少 Loader 处理范围(如
exclude: /node_modules/
)。
- 使用
- 减小体积:
- Code Splitting 按需加载。
- Tree Shaking 删除无用代码。
- 提升加载速度:
- 文件哈希 + CDN 缓存。
- 预加载关键资源(
<link rel="preload">
)。
Webpack 的构建流程通过模块化依赖分析和代码转换,将现代前端开发的复杂模块关系转化为高效、可部署的静态资源,是前端工程化的核心基石。
webpack如何优化编译速度/webpack打包优化
webpack如何减少打包后的体积代码?(高频)
以下是减少 Webpack 打包体积的优化方案,按优先级排序:
一、代码压缩
-
JS 压缩
使用TerserPlugin
压缩 JS 代码:optimization: { minimize: true, minimizer: [new TerserPlugin()] }
-
CSS 压缩
使用css-minimizer-webpack-plugin
压缩 CSS:const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); optimization: { minimizer: [new CssMinimizerPlugin()] }
二、Tree Shaking
-
启用 ES 模块
确保使用import/export
语法,并在package.json
中设置"sideEffects": false
。 -
标记未使用代码
使用/*#__PURE__*/
标记无副作用的函数调用,帮助 Tree Shaking 识别。
三、代码分割
-
提取公共代码
使用SplitChunksPlugin
提取公共模块:optimization: { splitChunks: { chunks: 'all', cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, name: 'vendors' } } } }
-
动态加载
使用import()
实现按需加载:import('path/to/module').then(module => { module.default(); });
四、优化依赖
-
按需引入
使用babel-plugin-import
按需加载组件库(如 Ant Design):plugins: [ ['import', { libraryName: 'antd', style: 'css' }] ]
-
移除无用依赖
使用webpack-bundle-analyzer
分析并移除未使用的依赖。
五、图片优化
-
压缩图片
使用image-webpack-loader
压缩图片:{ test: /\.(png|jpe?g|gif|svg)$/, use: [ { loader: 'file-loader', options: { name: 'images/[name].[hash:8].[ext]' } }, { loader: 'image-webpack-loader' } ] }
-
Base64 内联
小图片转为 Base64:{ test: /\.(png|jpe?g|gif)$/, use: [ { loader: 'url-loader', options: { limit: 8192 } // 小于 8KB 的图片转为 Base64 } ] }
六、Gzip 压缩
-
启用 Gzip
使用compression-webpack-plugin
生成.gz
文件:const CompressionPlugin = require('compression-webpack-plugin'); plugins: [ new CompressionPlugin({ algorithm: 'gzip', test: /\.(js|css)$/ }) ]
-
服务器配置
确保服务器支持 Gzip 压缩(如 Nginx 配置gzip on;
)。
七、其他优化
-
Scope Hoisting
启用作用域提升,减少闭包:optimization: { concatenateModules: true }
-
移除调试代码
使用DefinePlugin
移除开发环境代码:new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') })
总结
通过 代码压缩、Tree Shaking、代码分割、依赖优化 等策略,可显著减少 Webpack 打包体积,提升应用性能。
webpack proxy工作原理?为什么能解决跨域
一、Webpack Proxy 的工作原理
Webpack Dev Server 的代理功能 是通过在本地开发服务器和实际后端服务器之间增加一个中间层实现的。其核心流程如下:
- 拦截请求:开发服务器监听浏览器发出的请求。
- 转发请求:将匹配的请求转发到目标服务器。
- 返回响应:将目标服务器的响应返回给浏览器。
二、解决跨域的原理
跨域问题是由浏览器的 同源策略 引起的,而 Webpack Proxy 通过以下方式绕过该限制:
- 服务器间通信无跨域限制
- 代理服务器(Webpack Dev Server)和目标服务器(如 API 服务器)之间的通信不受同源策略限制。
- 浏览器与代理同源
- 浏览器访问的是本地开发服务器(如
http://localhost:8080
),与代理服务器同源,因此不会触发跨域限制。
- 浏览器访问的是本地开发服务器(如
三、配置示例
module.exports = {
devServer: {
proxy: {
'/api': { // 匹配所有以 /api 开头的请求
target: 'http://api.example.com', // 目标服务器地址
changeOrigin: true, // 修改请求头中的 Origin
pathRewrite: { '^/api': '' } // 重写路径,移除 /api 前缀
}
}
}
};
四、关键配置项
配置项 | 作用 |
---|---|
target | 目标服务器地址(如 http://api.example.com ) |
changeOrigin | 修改请求头中的 Origin ,伪装成目标服务器的同源请求(解决 CORS 问题) |
pathRewrite | 重写请求路径(如移除 /api 前缀) |
secure | 是否验证目标服务器的 SSL 证书(默认 true ,开发环境可设为 false ) |
五、工作流程
-
浏览器请求
浏览器发送请求到本地开发服务器: GET http://localhost:8080/api/data -
代理转发
开发服务器将请求转发到目标服务器: GET api.example.com/data -
返回响应
目标服务器返回响应,开发服务器将其返回给浏览器。
六、注意事项
-
仅限开发环境
- Webpack Proxy 仅在开发模式下生效,生产环境需通过 Nginx 或后端服务解决跨域。
-
路径匹配规则
- 使用通配符或正则表达式精确匹配需要代理的请求。
-
HTTPS 支持
- 如果目标服务器使用 HTTPS,需配置
secure: false
。
- 如果目标服务器使用 HTTPS,需配置
总结
Webpack Proxy 通过 中间层转发请求 的方式,巧妙地绕过了浏览器的同源策略限制,解决了开发环境下的跨域问题。
多页面打包是什么,如何实现 / webpack怎么实现多入口分模块打包
SPA打包:只有一个 HTML 页面和一个 JS 入口文件
MPA打包:是指在一个项目中,通过配置,构建多个独立的 HTML 页面,每个页面有自己的 JS 入口和依赖。更适合页面间相互独立。实现步骤如下:
- 定义入口配置:为每个页面配置一个入口文件,例如 page1 和 page2;
- 定义出口配置:使用
[name].bundle.js
模板字符串,为每个入口文件生成独立的输出文件。 - HTML插件配置:
HtmlWebpackPlugin
插件能为每个页面生成一个 HTML 文件,并将构建后的资源自动注入到这个 HTML 文件中。 - 优化配置(可选) :根据需要配置代码分割、压缩等优化功能。
- 输出结果:dist 目录中将包含 page1.html、page2.html 以及对应的 page1.bundle.js 和 page2.bundle.js 文件。
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production', // 或 'development'
entry: {
page1: './src/page1/index.js',
page2: './src/page2/index.js',
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
// 为每个页面生成独立的 HTML 文件
plugins: [
new HtmlWebpackPlugin({
filename: 'page1.html',
template: './src/page1/index.html',
chunks: ['page1'],
}),
new HtmlWebpackPlugin({
filename: 'page2.html',
template: './src/page2/index.html',
chunks: ['page2'],
}),
],
};
Chunkhash和Contenthash区别
Chunkhash:基于整个代码块(chunk)内容生成哈希值,只要该chunk内的任一模块发生变动,哈希值就会改变
*Contenthash:基于单个文件内容生成哈希值,仅当文件内容变化时,哈希值才会更新
打包时Hash码是怎么生成的
一、Hash 码的作用
在文件名中插入 Hash 码,用于标识文件内容。当文件内容变化时,Hash 值改变,触发浏览器缓存失效,确保用户获取最新资源。
二、Hash 码生成规则
Webpack 通过 内容摘要算法(如 MD4)生成哈希值,具体规则由配置的哈希类型决定。
三、三种哈希类型
哈希类型 | 作用范围 | 触发变化的因素 | 适用场景 |
---|---|---|---|
[hash] | 整个项目构建 | 任何文件内容或配置变化 | 不推荐使用(全局影响) |
[chunkhash] | 单个代码块(Chunk) | 当前 Chunk 内容或其依赖变化 | JS 文件 |
[contenthash] | 单个文件内容 | 仅文件自身内容变化 | CSS/图片/字体等静态资源 |
四、配置示例
// webpack.config.js
module.exports = {
output: {
// JS 文件使用 chunkhash
filename: '[name].[chunkhash:8].js',
// 图片使用 contenthash
assetModuleFilename: 'images/[name].[contenthash:8][ext]'
},
plugins: [
// CSS 文件使用 contenthash
new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash:8].css' })
]
};
五、哈希生成流程图
graph LR
A[文件/Chunk 内容] --> B[哈希算法处理] --> C[生成完整哈希] --> D[截断为指定长度] --> E[插入文件名]
总结
[hash]
:全局哈希,任何改动都会变化(慎用)。[chunkhash]
:基于 Chunk 内容,适合 JS 文件。[contenthash]
:基于文件内容,适合静态资源。
合理选择哈希类型可精准控制缓存策略,平衡构建性能和用户体验。
随机值存在一样的情况,如何避免
1. webpack.HashedModuleIdsPlugin
该插件可根据模块路径生成稳定的哈希 ID,避免因模块顺序变化而导致 ID 改变,确保每次构建时模块 ID 一致
2. webpack.NamedModulesPlugin
(Webpack 4 及以下)或 webpack.NamedChunksPlugin
(Webpack 5 及以上)
这两个插件可以使用模块或代码块的路径作为 ID,使 ID 具有可读性和稳定性。
3. terser-webpack-plugin
该插件用于压缩 JavaScript 代码,在压缩过程中可以确保生成的代码具有稳定的哈希值。
总结:通过使用这些插件,可以确保 Webpack 构建过程中生成的随机值(如模块 ID、哈希值等)具有稳定性和唯一性,避免因随机值重复导致的缓存失效等问题。
验证方法
- 修改文件内容后重新构建,观察哈希值是否变化
- 使用
webpack-bundle-analyzer
检查重复模块 - 运行
npx webpack --stats=verbose
查看详细构建日志
通过以上措施可有效避免哈希冲突,确保构建产物的唯一性。
webpack如何对相对路径引用进行优化
以下是 Webpack 对相对路径引用进行优化的核心方案及配置方法:
一、问题背景
项目中常见的复杂相对路径:
import Button from '../../../components/Button'; // 可读性差、维护成本高
二、优化方案
1. 配置路径别名(Alias)
通过 resolve.alias
将长路径映射为短别名,简化导入语句。
配置示例(webpack.config.js):
const path = require('path');
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src/'), // 根路径别名
'@components': path.resolve('src/components'), // 组件路径别名
'@utils': path.resolve('src/utils') // 工具类路径别名
}
}
};
使用效果:
import Button from '@/components/Button'; // 替代 ../../../components/Button
import { format } from '@utils/date'; // 替代 ../../utils/date
2. 自动解析目录层级(resolve.modules)
设置 resolve.modules
直接定位到项目根目录,减少 ../
层级。
配置示例:
resolve: {
modules: [
path.resolve(__dirname, 'src'), // 优先从 src 目录查找
'node_modules' // 其次从 node_modules 查找
]
}
使用效果:
import config from 'config'; // 自动查找 src/config.js → node_modules/config
3. 自动补全文件扩展名(resolve.extensions) 省略导入时的文件后缀名,自动匹配文件。
配置示例:
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'] // 按顺序匹配扩展名
}
使用效果:
import App from './App'; // 自动查找 App.jsx → App.tsx → App.json
import utils from '@utils'; // 自动补全 @utils/index.js
4. 集成 TypeScript/Javascript 路径映射(tsconfig/jsconfig) 配合开发工具(如 VS Code)实现编码时的路径智能提示。
配置示例(tsconfig.json/jsconfig.json):
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"]
}
}
}
三、效果对比
优化前 | 优化后 |
---|---|
import util from '../../../utils' | import util from '@utils' |
路径易错、重构困难 | 语义清晰、维护方便 |
依赖 IDE 手动补全 | 智能提示、跳转准确 |
通过以上配置,可大幅提升代码的可读性和可维护性,减少路径错误导致的构建问题。
webpack和vite的区别都有哪些, 分别适用于什么样的情形
Webpack 和 Vite 是两种主流的前端构建工具,它们在设计理念、构建方式、开发体验等方面有显著区别。以下是两者的核心区别及适用场景分析:
一、核心区别
特性 | Webpack | Vite |
---|---|---|
构建方式 | 基于打包构建,将所有模块打包成一个或多个 bundle 文件,启动时需全量打包。 | 基于原生 ES 模块(ESM),开发时按需加载模块,无需预打包,启动速度快。 |
生态系统 | 插件生态丰富,支持多种资源处理和优化。 | 插件生态较新,但基于 Rollup 插件体系,扩展性强。 |
配置复杂度 | 配置灵活但复杂,需手动配置 loader 和 plugin。 | 配置简单,开箱即用,默认配置已满足大部分需求。 |
兼容性 | 支持多种浏览器,包括老旧浏览器(如 IE)。 | 仅支持现代浏览器,依赖原生 ESM。 |
开发体验 | 热更新(HMR)较慢,需重新构建依赖图。 | 热更新(HMR)近乎实时,仅更新修改的模块。 |
生产构建 | 使用 Webpack 打包,适合复杂项目的深度优化。 | 使用 Rollup 打包,适合现代前端项目的快速构建。 |
二、适用场景
1. Webpack 适用场景
- 大型复杂项目:需要高度定制化配置和深度优化。
- 多浏览器兼容:需支持老旧浏览器(如 IE)。
- 复杂资源处理:需处理多种资源类型(如 CSS、图片、字体等)并进行复杂优化。
- 成熟生态系统:依赖丰富的插件和 loader 生态。
2. Vite 适用场景
- 中小型项目:注重开发体验和构建速度。
- 现代浏览器项目:仅需支持现代浏览器。
- 快速原型开发:需要快速启动和实时热更新。
- 现代框架支持:如 Vue、React、Svelte 等。
为什么 Vite 速度比 Webpack 快?
一、开发模式的差异
- 当使用 Webpack 时,所有的模块都需要在开发前进行打包 ,会增加启动时间和构建时间。
- Vite 则是直接启动,它会在请求模块时再进行实时编译,这种按需动态编译的模式极大地缩短了编译时间,特别是在大型项目中,文件数量众多,Vite 的优势更为明显。
二、底层语言的差异
- Webpack 是基于 Node.js 构建的,毫秒级别的
- Vite 则是基于 esbuild 进行预构建依赖。esbuild 是采用 Go 语言编写的,纳秒级别的
因此,Vite 在打包速度上相比Webpack 有 10-100
倍的提升。
三、热更新的处理
-
Webpack 中,当一个模块或其依赖的模块内容改变时,需要重新编译这些模块。
-
Vite 中,当某个模块内容改变时,只需要让浏览器重新请求该模块即可,这大大减少了热更新的时间。
使用webpack构建时有无做一些自定义操作
在使用 Webpack 构建时,你可以通过编写自定义 Loader、自定义插件或者使用 Webpack 的配置选项来实现一些自定义操作。下面为你详细介绍具体的实现方式。
编写自定义 Loader
Loader 用于对模块的源代码进行转换,你可以编写自己的 Loader 来处理特定类型的文件。
编写自定义插件
插件可以在 Webpack 构建过程的不同阶段执行自定义操作,例如在构建完成后生成一个文件清单。
使用 Webpack 配置选项
Webpack 提供了丰富的配置选项,你可以通过配置这些选项来实现自定义操作,例如修改输出文件名、添加环境变量等。
通过以上方法,你可以在 Webpack 构建过程中实现各种自定义操作,满足不同的项目需求。
什么是长缓存?在webpack中如何做到长缓存优化?
一、什么是长缓存?
长缓存(Long-term Caching) 是一种通过优化资源文件名和缓存策略,使浏览器能够长期缓存静态资源(如 JS、CSS 文件)的技术。其核心目标是:
- 减少重复加载:用户再次访问时直接从缓存加载资源,提升页面加载速度。
- 降低服务器压力:减少不必要的资源请求,节省带宽。
二、长缓存的核心原理
- 内容哈希:根据文件内容生成唯一哈希值,内容不变则文件名不变。
- 分离稳定代码:将频繁变动的代码与稳定代码分离,避免缓存失效。
三、Webpack 长缓存优化方案
1. 使用 [contenthash]
在文件名中添加内容哈希,确保内容变化时文件名更新:
output: {
filename: '[name].[contenthash:8].js', // JS 文件名
chunkFilename: '[name].[contenthash:8].chunk.js' // 异步 chunk 文件名
}
2. 提取第三方库
将 node_modules
中的依赖单独打包,避免业务代码更新导致缓存失效:
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
3. 提取 Webpack Runtime
将 Webpack 的运行时代码单独打包,避免因模块 ID 变化导致缓存失效:
optimization: {
runtimeChunk: 'single'
}
4. 模块 ID 固化
使用 HashedModuleIdsPlugin
或 moduleIds: 'deterministic'
固定模块 ID,避免因模块顺序变化导致缓存失效:
optimization: {
moduleIds: 'deterministic' // Webpack 5+ 推荐
}
5. 提取 CSS 文件
将 CSS 提取为独立文件,并添加内容哈希:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css', // CSS 文件名
chunkFilename: '[name].[contenthash:8].chunk.css'
})
]
};
四、验证长缓存效果
-
构建产物分析
使用webpack-bundle-analyzer
检查打包结果,确保代码分割合理。 -
缓存命中率测试
修改业务代码后重新构建,观察vendors
和runtime
文件是否未变化。 -
浏览器缓存验证
通过开发者工具的 Network 面板,检查资源是否从缓存加载。
五、注意事项
- 避免过度分割
过多的 chunk 文件会增加 HTTP 请求数,影响性能。 - CDN 缓存策略
确保 CDN 配置支持长缓存(如设置Cache-Control: max-age=31536000
)。 - 版本管理
使用版本号或时间戳管理 HTML 文件,确保用户获取最新资源。
通过以上优化,可实现高效的长缓存策略,显著提升用户体验和服务器性能。
什么是bundle,是什么chunk,什么是module?
-
Module:webpack 里一个概念性内容,每个文件都可以看为一个 module。 js、css、图片等都可以看作 module。
-
Chunk:代码块,webpack 处理代码时候的一个中间态,它表示有一组功能相关的模块的集合。一个 Chunk 可以由多个模块(module)组成
-
Bundle:是 Webpack 构建结果的输出,由一个或多个 Chunk 的合并优化后的结果,最终以文件形式输出,用于在浏览器中加载和执行。
webpack externals
一、什么是 Webpack Externals?
Externals 是 Webpack 提供的一种配置选项,用于将某些依赖从打包结果中排除,改为通过外部环境(如全局变量、CDN)引入。其核心作用是:
- 减少打包体积:避免将大型库(如 React、Lodash)打包到最终产物中。
- 优化加载性能:通过 CDN 加载外部依赖,利用浏览器缓存加速页面加载。
二、注意事项
-
确保外部依赖可用
- 使用 Externals 时,需确保外部依赖(如 CDN 或全局变量)在运行时可用。
-
避免过度使用
- 过度使用 Externals 可能导致依赖管理混乱,建议仅用于大型库或特殊场景。
-
兼容性处理
- 在多种模块化规范(如 CommonJS、AMD、ESM)下使用时,需确保 Externals 配置兼容。
总结:合理使用 Externals 可以显著提升项目的性能和可维护性。
文件监听是什么,怎么用,原理是什么
文件监听是在源代码发生变化时,自动重新编译代码的功能。
开启文件监听后,webpack会轮询访问文件的最后修改时间,当发现文件修改时间发生变化后,会先缓存起来等到aggregateTimeout
再统一执行。开启文件监听方式:可以在构建时带上--watch
参数或者设置watch:true
,而watchOptions
则可以对监听的细节进行定制
一、如何使用
- 命令行启动:
webpack --watch
- 或者,配置文件设置
module.exports = {
watch: true,
};
二、配置优化
功能很有用,但是有些优化手段也应该了解
- 排除不需要监听的文件:
watchOptions.ignored
- 设置轮训间隔:
watchOptions.poll
module.exports = {
watch: true,
watchOptions: {
ignored: /node_modules/,
poll: 1000, // 每 1 秒检查一次变化
},
};
三、原理
基于 文件系统事件 或 轮询 实现的,具体方式取决于操作系统和配置
- 文件系统事件:在支持文件系统事件的操作系统上(Linux、macOS,Windows),Webpack 会注册这些事件来直接获取文件变化通知。
- 轮询:在不支持文件系统事件或文件系统事件不可靠的环境中,Webpack 可能会退回到轮询模式。在轮询模式下,Webpack 定期检查文件的最后修改时间来判断文件是否发生变化。
四、跟热更新的区别
- 文件监听:监视文件变化,自动重新编译代码,会重新加载整个页面,导致应用状态丢失。实现简单。
- 热更新(HMR):在应用程序运行时替换、添加或删除模块,无需重新加载整个页面,保留应用状态。实现相对复杂,但显著提高开发效率。
webpack 能动态加载 require 引入的模块吗?
可以,虽然动态加载模块的主要方式是使用 import()
语法,Webpack 会将这种动态导入转换为代码分割,从而实现按需加载模块。但require
引入的模块也能动态加载
动态加载单个模块,
require.ensure(dependencies, callback, chunkName);
- 适用于 Webpack 2 及更高版本。
dependencies
:包含所有需要加载的模块的数组。通常可以传递一个空数组[]
。callback
:在所有依赖模块加载完成后执行的函数。require
动态加载在这实现chunkName
(可选):一个字符串,用于指定生成的代码块的名称。这有助于调试和缓存。
动态加载一组模块
const context = require.context(directory, useSubdirectories, regExp);
- 适用于需要在运行时动态引入多个模块的场景。
directory
:要搜索的目录路径。useSubdirectories
:一个布尔值,表示是否搜索子目录。regExp
:一个正则表达式,用于匹配文件名。
// require.ensure()
function loadModuleA() {
require.ensure([], function(require) {
const moduleA = require('./moduleA');
moduleA.greet();
}, 'moduleA');
}
const context = require.context('./modules', false, /.js$/);
function loadModule(moduleName) {
const module = context(`./${moduleName}.js`);
module.greet();
}
// 在某个条件下调用 loadModuleA or loadModule
if (someCondition) {
loadModuleA();
loadModule('moduleA');
}
按需加载如何实现,原理是什么(高频)
一、什么是按需加载?
按需加载(Lazy Loading) 是一种将代码分割为多个小块,在需要时动态加载的技术。其核心目标是:
- 减少首屏加载时间:只加载当前页面所需的代码。
- 优化资源利用率:按需加载非关键资源,减少带宽消耗。
二、实现方式
1. 使用 import()
动态导入
Webpack 会将 import()
语法自动转换为动态加载的代码块。
示例:
// 动态加载模块
button.addEventListener('click', () => {
import('./module.js').then(module => {
module.default(); // 调用模块的默认导出
});
});
打包结果:
- 生成独立的 chunk 文件(如
1.bundle.js
)。 - 在用户点击按钮时动态加载该文件。
2. Vue 中的按需加载
使用 defineAsyncComponent
实现组件的动态加载。
示例:
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
);
三、核心原理
-
代码分割
- Webpack 将
import()
语法标记为动态加载点,生成独立的 chunk 文件。
- Webpack 将
-
运行时加载
- 在浏览器中通过
JSONP
或fetch
动态加载 chunk 文件。
- 在浏览器中通过
-
缓存管理
- 使用
webpackJsonp
或import()
的缓存机制,避免重复加载。
- 使用
四、Webpack 配置
-
默认配置
Webpack 5+ 默认支持动态加载,无需额外配置。 -
自定义 chunk 名称
使用魔法注释指定 chunk 名称:import(/* webpackChunkName: "my-chunk" */ './module.js');
-
预加载与预获取
使用魔法注释优化加载优先级:import(/* webpackPrefetch: true */ './module.js'); // 空闲时预加载 import(/* webpackPreload: true */ './module.js'); // 高优先级预加载
五、优化建议
-
合理分割代码
- 按路由或功能模块分割代码,避免过度拆分。
-
预加载关键资源
- 对非首屏但关键的资源(如首页轮播图)启用
webpackPreload
。
- 对非首屏但关键的资源(如首页轮播图)启用
-
错误处理
- 捕获动态加载失败的情况:
import('./module.js') .then(module => module.default()) .catch(err => console.error('加载失败', err));
- 捕获动态加载失败的情况:
六、注意事项
-
兼容性
import()
语法需要支持 Promise 的浏览器(可通过@babel/plugin-syntax-dynamic-import
转译)。
-
性能监控
- 使用
webpack-bundle-analyzer
分析 chunk 文件大小,确保合理分割。
- 使用
-
SEO 影响
- 动态加载的内容可能不被搜索引擎抓取,需结合服务端渲染(SSR)优化。
通过按需加载,可显著提升应用性能,优化用户体验。
AMD和CMD
AMD(Asynchronous Module Definition)和CMD (Common Module Definition)的主要区别在于加载方式、依赖声明和适用环境
加载方式
- AMD:支持异步加载,模块的加载不会阻塞页面其他内容的加载和执行。这种异步加载方式使得AMD尤其适合在浏览器环境中使用,尤其是在Web应用**中12。
- CMD:支持同步加载,模块会在遇到时立即执行,不管它是否已经加载完成。这种同步加载方式使得CMD更适合在服务器端环境或需要同步加载的场景中使用
依赖声明
- AMD:在定义模块时需要明确列出所有依赖的模块,并将它们作为参数传递给模块的回调函数。这种“依赖前置”的方式使得代码结构清晰,但可能会增加模块间的耦合度
- CMD:不需要在定义模块时明确列出所有依赖的模块,而是在需要使用模块时再引入它们。这种“依赖就近”的方式使得代码更加灵活,适合复杂的模块化需求。
适用环境
模块规范 | 适用场景 |
---|---|
AMD | 浏览器端,适合依赖关系明确、模块数量较少的场景。 |
CMD | 浏览器端和 Node.js,适合依赖关系复杂、模块数量较多的场景。 |
介绍模块化发展历程
模块化主要作用:抽离公共代码,隔离作用域,避免代码冲突
- 无模块化 → 2. IIFE → 3. CommonJS → 4. AMD → 5. UMD → 6. ES6 Module
最终趋势是 ES6 Module,成为现代 JavaScript 开发的标准。
常用的loader/用过哪些loader
-
babel-loader:将ES6+的代码转换成ES5的代码。
-
css-loader:解析CSS文件,并处理CSS中的依赖关系。
-
style-loader:将CSS代码注入到HTML文档中。
-
sass-loader:将Sass文件编译成CSS文件。
-
less-loader:将Less文件编译成CSS文件。
-
postcss-loader:自动添加CSS前缀,优化CSS代码等。
-
file-loader:用于打包文件类型的资源,并返回其publicPath。
-
url-loader:类似于file-loader,但是可以将小于指定大小的文件转成base64编码的Data URL格式
-
vue-loader:主要工作就是将SFC(
Single-File Component,单文件组件
)中不同类型的代码块分割开来,并交给对应的loader来处理。比如script
代码块可能会交给babel-loader来处理,style
可能会交给css-loader来处理。 -
source-map-loader: 加载额外的
Source Map
文件 -
eslint-loader: 通过ESlint 检查js代码
-
cache-loader: 可以在一些开销较大的
Loader
之前添加可以将结果缓存到磁盘中,提高构建的效率 -
thread-loader: 多线程打包,加快打包速度
loader的原理,如何编写loaders/自定义loader?
一、Loader 的核心原理
Loader 是用于将非 JavaScript 文件(如 CSS、图片、字体)转换为 JavaScript 模块。Loader 本质上是一个函数,作用是将某个源码字符串转换成另一个源码字符串返回。接收源文件代码字符串为参数,经过处理转换,然后 return
目标代码字符串。其工作原理如下:
-
链式调用: Loader 按照配置顺序从右到左依次执行,前一个 Loader 的输出作为后一个 Loader 的输入。
-
模块转换:Loader 接收文件内容,处理后返回 JavaScript 代码或资源路径。
-
上下文传递:Loader 通过
this
上下文访问 Webpack 的 API(如this.async
、this.emitFile
)。
二、自定义loader
1. 接收输入:获取源码内容
loader支持链式调用,上一个loader的执行结果会作为下一个loader的入参。 根据这个特性,我们知道我们的loader想要有返回值,并且这个返回值必须是标准的JavaScript字符串或者AST
代码结构,这样才能保证下一个loader的正常调用。
// 基础结构
module.exports = function(source, map, meta) {
// source: 输入内容
// map: SourceMap 开启source-map可以便于我们在浏览器的开发者工具中查看源码
// meta: 其他元数据
return transformedSource; // 返回处理后的字符串
}
2. 转换处理:按需求修改源码
3. 返回输出:返回字符串或调用回调
module.exports = function(source) {
// 替换操作
const result = source.replace(/world/g, 'loader');
// 返回处理后的 JS 代码
return `export default ${JSON.stringify(result)}`;
}
异步 Loader 写法
处理需要异步操作时(如文件读取):
module.exports = function(source) {
const callback = this.async(); // 获取异步回调
setTimeout(() => {
const result = source.replace(/world/g, 'loader');
callback(null, result); // 参数:错误, 处理结果
}, 100);
}
注意
:如果 Loader 有异步操作需要通过 this.async()
处理,不然可能会出现 Loader 函数在异步操作完成前返回,导致转换结果不正确。this.async()
方法返回一个回调函数,你将通过这个回调函数来返回处理结果或错误。回调接收三个参数
- 错误:Loader 执行过程中出错,则返回给这个参数。没错就传
null/undefined
- 结果:Loader 执行成功后的结果
- SourceMap(可选):如果转换过程中能产生 SourceMap 可以通过这个传参帮助定位错误位置
module.exports = function(source) {
const callback = this.async();
someAsyncOperation(source, (err, transformedSource, sourceMap) => {
if (err) {
// 如果有错误发生,传递错误对象
callback(err);
return;
}
// 成功处理,传递处理后的结果和source map(如果有的话)
callback(null, transformedSource, sourceMap);
});
};
本地测试方法
在 webpack.config.js
中直接引用本地 Loader:
module.exports = {
module: {
rules: [
{
test: /\.txt$/,
use: [
{
loader: path.resolve(__dirname, 'loaders/my-loader.js'),
options: { /* 传参 */ }
}
]
}
]
}
}
完整示例(带参数传递)
// my-loader.js
module.exports = function(source) {
const options = this.getOptions(); // 获取配置参数
return source.replace(new RegExp(options.target, 'g'), options.replaceWith);
}
// webpack.config.js
{
loader: path.resolve(__dirname, 'loaders/my-loader.js'),
options: {
target: 'foo',
replaceWith: 'bar'
}
}
总结
编写 Loader 只需三步:
- 接收输入:获取源码内容
- 转换处理:按需求修改源码
- 返回输出:返回字符串或调用回调
实际开发中可结合 loader-utils
等工具库处理参数和复杂场景,它主要用于提供一些帮助函数。如果想发布 NPM 就走发布流程,然后写份清晰的文档
webpack 如何确定依赖引用顺序
依赖图的构建过程
- 入口点:Webpack 从配置的入口点
entry
开始,从入口文件开始解析。 - 递归解析:递归解析每个模块的依赖,找到所有被引用的模块。
- 构建依赖图:根据模块之间的依赖关系构建一个依赖图。
- 确定顺序:根据依赖图确定模块的引用顺序,确保被依赖的模块先于依赖它们的模块打包。
如何保证众多Loader按照想要的顺序执行?
可以通过enforce
来强制控制Loader的执行顺序 (pre
表示在所有正常的loader执行之前执行,post
则表示在之后执行)
loader的执行顺序为什么是后写的先执行
Webpack 中 Loader 的执行顺序是「从右到左」(配置数组靠后的 Loader 先执行),原因如下:
核心原理:
-
链式管道模型:
Loader 处理资源的过程类似管道传输,后一个 Loader 的输入是前一个 Loader 的输出。配置顺序需要直观反映资源逐步加工的逻辑。
示例:javascriptCopy Code use: ['style-loader', 'css-loader', 'sass-loader']
实际流程:
SCSS →sass-loader
(编译为 CSS) →css-loader
(解析依赖) →style-loader
(注入 DOM)。
一句话总结:
后写的 Loader 先执行,是为了让配置顺序与资源处理的实际步骤一致(如先编译 SCSS → 再处理 CSS → 最后插入 DOM)。
webpack如何配sass,需要配哪些loader
所需的 loader
sass-loader
:该 loader 依赖 Sass 编译器(如 Dart Sass),它能把 Sass(.sass
)或 SCSS(.scss
)文件编译成 CSS 文件。css-loader
:负责解析 CSS 文件里的@import
和url()
等语句,处理 CSS 模块和 CSS 中的依赖关系,将 CSS 文件转换为 CommonJS 模块。style-loader
:会把编译后的 CSS 以<style>
标签的形式插入到 DOM 中,让样式在页面上生效。mini-css-extract-plugin
(可选) :在生产环境中,通常使用该插件将 CSS 提取到单独的文件中,而不是将其内嵌到 JavaScript 代码里,这样有助于提升性能和缓存效率。
配置解释
entry
和output
:指定入口文件和输出文件的路径。module.rules
:定义了处理不同类型文件的规则。对于.scss
或.sass
文件,使用style-loader
(开发环境)或MiniCssExtractPlugin.loader
(生产环境)、css-loader
和sass-loader
来处理。MiniCssExtractPlugin
:在生产环境下,使用该插件将 CSS 提取到单独的文件中。
开发环境与生产环境的区别
- 开发环境:使用
style-loader
将 CSS 内联到 JavaScript 中,这样可以实现热更新,方便开发调试。 - 生产环境:使用
MiniCssExtractPlugin.loader
将 CSS 提取到单独的文件中,以提高性能和缓存效率。
postcss配置
PostCSS 是一个用 JavaScript 编写的工具,用于将 CSS 转换为另一种 CSS。它可以处理诸如添加浏览器前缀、压缩 CSS、使用未来的 CSS 特性等任务。以下为你详细介绍在不同场景下如何配置 PostCSS。
安装依赖
首先,确保你已经安装了 PostCSS 及其相关插件,在项目根目录下的终端中执行以下命令: bash
npm install postcss postcss-loader autoprefixer cssnano --save-dev
postcss
:核心库。postcss-loader
:用于在 Webpack 中使用 PostCSS。autoprefixer
:自动添加浏览器前缀。cssnano
:压缩和优化 CSS。
在 package.json
中配置浏览器列表
为了让 autoprefixer
知道要为哪些浏览器添加前缀,需要在 package.json
中配置 browserslist
字段:
json
{
"browserslist": [
"last 2 versions",
"> 1%",
"not dead"
]
}
这个配置表示为最近两个版本的浏览器、市场占有率大于 1% 的浏览器以及未停止维护的浏览器添加前缀。
如何配置把js、css、html单独打包成一个文件
在项目根目录下的终端中执行以下命令来安装所需的依赖:
bash
npm install webpack webpack-cli html-webpack-plugin mini-css-extract-plugin --save-dev
webpack
和webpack-cli
:Webpack 的核心库和命令行工具。html-webpack-plugin
:用于生成 HTML 文件,并自动注入打包后的 JS 和 CSS 文件。mini-css-extract-plugin
:用于将 CSS 提取到单独的文件中。
loader和plugin有什么区别(高频)
功能不同:
Loader本质是一个函数,它是一个转换器。webpack只能解析原生js文件,对于其他类型文件就需要loade进行转换
Plugin它是一个插件,用于扩展webpack功能,解决loader无法做的事。webpack在运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 API 改变输出结果
用法不同
Loader的配置是在module.rules下进行。类型为数组,每⼀项都是⼀个 Object ,⾥⾯描述了对于什么类型的⽂件( test ),使⽤什么加载( loader )和使⽤的参数( options ) 。
Plugin的配置在plugins下。类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。
如何选择 Loader 还是 Plugin?
- Loader:用于处理特定类型的文件(如 CSS、图片)。css-loader,file-loader
- Plugin:用于扩展 Webpack 功能(如打包优化、资源管理)。
是否可以同时使用 Loader 和 Plugin?
-
可以,Loader 和 Plugin 通常配合使用。例如:
- 使用
css-loader
处理 CSS 文件。 - 使用
MiniCssExtractPlugin
将 CSS 提取为独立文件。
- 使用
-
Loader:专注于文件转换,处理模块级别的任务。
-
Plugin:专注于构建流程,处理项目级别的任务。
webpack的plugins和loaders的实现原理( webpack里面的插件是怎么实现的) x4(高频)
见单个题目解答
常用的plugins
-
define-plugin: 定义环境变量(webpack4之后可以通过指定
mode:production/development
实现同样效果),这是webpack的内置插件,不需要安装 -
HtmlWebpackPlugin:自动生成 HTML 文件,并自动引入打包后的 JS 文件和CSS文件,web-webpack-plugin优于它。
-
ExtractTextWebpackPlugin:将CSS代码提取到单独的CSS文件中。不支持按需加载
-
MiniCssExtractPlugin:将 CSS 提取为独立的文件,支持按需加载和缓存。
css文件的压缩需要mini-css-extract-plugin和css-minimize-webpack-plugin 的配合使用 即先使用mini-css-extract-plugin将css代码抽离成单独文件,之后使用 css-minimize-webpack-plugin对css代码进行压缩。
-
CssMinimizeWebpackPlugin:优化和压缩 CSS 资产,Webpack4+,性能更好,OptimizeCSSAssetsPlugin不维护了
-
uglifyjs-webpack-plugin: 压缩js代码
-
CommonsChunkPlugin:将多个Chunk中公共的部分提取出来。Webpack4之前内部自带的插件(Webpack 4之后替换为SplitChunks)
-
TerserWebpackPlugin:压缩 JavaScript,Webpack 4+ 默认内置。(tree-shaking)
-
DllPlugin:DllPlugin和代码分片有点类似,都可以用来提取公共模块
-
HotModuleReplacementPlugin:模块热替换(HMR),实现页面实时预览更新。
-
compression-webpack-plugin: 生产环境采用
gzip
压缩JS和CSS -
ParalleUglifyPlugin: 多进程并行压缩js -
speed-measure-webpack-plugin
: 用于分析各个loader和plugin的耗时,可用于性能分析 -
CleanWebpackPlugin:每次打包时删除上次打包的产物, 保证打包目录下的文件都是最新的
-
webpack-merge: 用来合并公共配置文件,常用(例如分别配置
webpack.common.config.js/ webpack.dev.config.js/webpack.production.config.js
并将其合并) -
ignore-plugin: 忽略指定的文件,引用了也不会被打包进资源文件中,可以加快构建速度
-
BundleAnalyzerPlugin:可视化 Webpack 输出文件的大小,帮助分析和优化。
-
webpack-dashboard: 可以更友好地展示打包相关信息
plugins的实现原理,以及如何编写plugins
初步理解,后续加深
Webpack 是一个强大的模块打包工具,而 Plugin(插件)是 Webpack 的核心特性之一,它可以在 Webpack 构建过程的不同阶段执行自定义操作,从而扩展 Webpack 的功能。
核心概念:钩子(Hooks)机制
Webpack 的插件系统基于钩子(Hooks)机制。钩子是 Webpack 在构建过程中特定阶段触发的事件,插件可以通过监听这些钩子来执行自定义逻辑。Webpack 的钩子分为同步钩子和异步钩子,同步钩子会依次执行注册的插件逻辑,而异步钩子则允许插件进行异步操作,在操作完成后通知 Webpack 继续执行后续流程。
实现一个简单的 Webpack Plugin
一个 Webpack 插件本质上是一个具有 apply
方法的 JavaScript 对象,apply
方法会在 Webpack 编译器(compiler
)实例化时被调用,并传入 compiler
对象作为参数。compiler
对象代表了整个 Webpack 编译过程,包含了所有的配置信息和钩子,插件可以通过 compiler
对象监听不同的钩子。
- 编写插件类:创建一个类,实现
apply
方法。Webpack 在启动编译过程时,会调用每个插件实例的apply
方法 - 注册钩子回调:在
apply
方法中,使用编译器 Compiler 对象注册你需要的钩子回调。 - 实现功能逻辑:在回调函数中实现具体的插件逻辑。
// 通过 tap 方法注册钩子,第一个参数是插件名称,第二个参数是回调函数
module.exports = class MyPlugin {
apply(compiler) {
// 注册事件,类似于window.onload = function() {}
compiler.hooks.done.tap('MyPlugin', (Compilation) => {
console.log('MyPlugin: Compilation finished!');
});
}
}
在这个例子中,MyPlugin 类定义了一个 apply 方法,这个方法接收一个 compiler 参数。
我们在 compiler.hooks.done
上注册了一个回调,这个回调会在编译完成后执行,输出一条消息。要将插件应用到 webpack,需要把插件对象配置到 webpack 的 plugins 数组中,如下:
const MyPlugin = require('./MyPlugin');
module.exports = {
plugins: [
new MyPlugin(),
]
}
插件执行流程
- 初始化:Webpack 在启动时会实例化
compiler
对象,并调用插件的apply
方法,将compiler
对象作为参数传递给插件。 - 注册钩子:插件在
apply
方法中通过compiler.hooks
监听特定的钩子,并使用tap
(同步钩子)或tapAsync
、tapPromise
(异步钩子)方法注册回调函数。 - 触发钩子:Webpack 在构建过程中会按照特定的顺序触发不同的钩子,当某个钩子被触发时,注册在该钩子上的所有插件回调函数会依次执行。
- 执行自定义逻辑:插件的回调函数中可以执行自定义的逻辑,例如修改编译结果、生成额外的文件、输出日志等。
常见的钩子类型
- Compiler 钩子:与整个编译过程相关,如
compile
、compilation
、make
、emit
、done
等。 - Compilation 钩子:与单个编译实例相关,如
buildModule
、optimize
、seal
等。
通过监听不同的钩子,插件可以在 Webpack 构建过程的不同阶段执行各种自定义操作,从而实现丰富的功能扩展。
webpack热更新(HMR)原理(高频)
热替换可以让我们不用刷新浏览器,通过增删改将新代码替换掉旧代码。HMR 的实现依赖于 Webpack Dev Server 启动一个 WebSocket 服务器,跟浏览器进行全双工通信。客户端与服务器建立实时通信
1. 建立通信通道
- WebSocket 连接:当使用
webpack-dev-server
或中间件(如webpack-hot-middleware
)时,客户端(浏览器)与服务器会建立一个 WebSocket 长连接,用于实时传输更新事件。 - EventSource(可选):部分场景下可能使用 Server-Sent Events(SSE)作为备选通信方案。
2. 文件监听与编译
- 文件系统监听:Webpack 通过
watch
模式监听文件系统的变化。当文件被修改时,触发重新编译。 - 内存编译:编译结果不会写入磁盘,而是保存在内存中(通过
memfs
等库),提升编译速度。
3. 生成更新补丁
- 增量构建:Webpack 重新编译修改的模块,生成新的代码块(Chunk)和对应的
[hash]
(唯一标识本次编译)。 - 生成 Manifest:服务器生成一个 JSON 文件(称为
manifest
),记录本次更新的模块信息(如chunk ID
和hash
)。
4. 通知客户端更新
- 推送消息:服务器通过 WebSocket 向客户端发送
hash
和ok
事件,表明有新版本可用。 // 服务器推送的消息示例 { type: 'hash', data: 'a1b2c3' } { type: 'ok' }
5. 客户端拉取更新
- 请求更新资源:客户端(HMR Runtime)通过
JSONP
或fetch
请求以下资源:- Manifest 文件:通过
[hash].hot-update.json
获取更新的模块列表。 - 代码块文件:通过
[chunkID].[hash].hot-update.js
获取新模块代码。
- Manifest 文件:通过
6. 应用热更新
- 模块替换:HMR Runtime 将新模块代码替换旧模块,具体流程如下:
- 检查模块依赖:通过
module.hot
API 检查哪些模块支持 HMR(是否有accept
处理函数)。 - 冒泡更新:从被修改的模块向上遍历依赖树,直到找到能处理更新的模块(或根模块)。
- 执行替换:
- 若模块定义了
module.hot.accept
,则执行回调函数处理新逻辑。 - 若无法处理更新,则触发页面刷新(Fallback to Reload)。
- 若模块定义了
- 检查模块依赖:通过
7. 异常处理
- 更新失败回退:如果热更新过程中发生错误(如模块未处理更新),客户端会降级为刷新整个页面(
window.location.reload()
)。
关键角色与工具
- HotModuleReplacementPlugin:
- 向打包后的代码注入 HMR Runtime(客户端热更新逻辑)。
- 在模块中生成
module.hot
API。
- HMR Runtime:
- 处理通信、拉取更新、模块替换等核心逻辑。
- Webpack Dev Server:
- 提供静态资源托管服务、WebSocket 通信、文件监听与编译。
代码示例
// 客户端代码中通过 module.hot 定义更新逻辑
if (module.hot) {
module.hot.accept('./module.js', () => {
// 当 module.js 更新时,执行此回调
const newModule = require('./module.js');
newModule.doSomething();
});
}
总结流程图
文件修改 → 触发编译 → 生成新 Hash 和 Chunk
↓
WebSocket 推送消息 → 客户端拉取 Manifest 和 Chunk
↓
HMR Runtime 替换模块 → 成功则局部更新,失败则刷新页面
HMR 通过高效的增量更新机制,显著提升了开发体验,尤其适用于复杂应用的状态保留需求(如 React/Vue 组件状态)。
用法
-
通过配置项
devServer.hot: true
,启用 HMR 功能。 -
或者使用
HotModuleReplacementPlugin
HMR 插件。
什么是code spliting?原理
Code Splitting代码分割,是一种优化技术。它允许将一个大的chunk拆分成多个小的chunk,从而实现按需加载或并行加载,减少初始加载时间,并提高应用程序的性能 。在Webpack中通过optimization.splitChunks配置项来开启代码分割。
- 解决的问题:
- 减少首次加载时的文件体积(降低白屏时间)。
- 避免加载用户当前不需要的代码(如未访问的路由、未触发的功能)。
- 并行加载资源,利用浏览器并发能力。
核心原理
1. 静态分析依赖关系
- 打包工具(如 Webpack、Rollup)会分析代码中的模块依赖关系,构建依赖图(Dependency Graph)。
- 通过特定语法(如动态导入
import()
)标记代码分割点。
2. 生成独立代码块(Chunks)
- 根据分割点将代码拆分为多个独立的文件(如
chunk.js
)。 - 常见分割策略:
- 入口点分割:不同入口生成不同 Chunk。
- 动态导入:通过
import()
语法按需加载模块。 - 公共代码提取:将公共依赖(如第三方库)提取为独立 Chunk(通过
SplitChunksPlugin
)。
3. 运行时动态加载
- 浏览器在需要时动态请求并执行代码块:
- JSONP 或 Fetch:Webpack 使用类似 JSONP 的机制加载 Chunk。
- 懒加载(Lazy Loading):例如在路由切换或用户交互时触发加载。
// 动态导入语法(触发代码分割) button.addEventListener('click', async () => { const module = await import('./module.js'); module.doSomething(); });
4. 缓存优化
- 将不常变动的代码(如第三方库)分离为独立 Chunk,利用浏览器缓存机制提升加载速度。
Webpack 的实现示例
以 Webpack 为例,代码分割的实现流程如下:
-
配置分割规则:
// webpack.config.js module.exports = { optimization: { splitChunks: { chunks: 'all', // 提取公共模块 cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, // 分离第三方库 name: 'vendors', }, }, }, }, };
-
动态导入触发分割:
// 使用 import() 语法 import(/* webpackChunkName: "my-chunk" */ './myModule.js');
-
生成结果:
- 主文件
main.js
(包含 Webpack 运行时代码)。 - 分割后的 Chunk 文件(如
my-chunk.js
和vendors.js
)。
- 主文件
-
运行时加载:
- Webpack 注入的代码会管理 Chunk 的加载和执行,确保依赖顺序正确。
代码分割的常见场景
- 路由懒加载:SPA 中按路由加载组件。
- 功能按需加载:如弹窗、复杂图表等用户可能不触发的功能。
- 第三方库分离:将 React、Lodash 等库单独打包。
- CSS 分割:通过
mini-css-extract-plugin
分离 CSS 文件。
优势与权衡
优势 | 权衡 |
---|---|
减少首次加载时间 | 增加 HTTP 请求数量 |
提升缓存利用率 | 需要配置合理的分割策略 |
按需加载节省带宽 | 动态加载可能引入延迟 |
总结
Code Splitting 的本质是通过静态分析和动态加载技术,将代码拆分为更小的单元,在运行时按需加载。它结合了打包工具的自动化能力(如 Webpack 的 SplitChunksPlugin
)和开发者手动标记分割点(如 import()
),是优化现代前端应用性能的核心手段。
什么是tree shaking?原理?如何实现
一、什么是 Tree Shaking?
Tree Shaking 是一种通过静态分析移除 JavaScript 中未使用代码的优化技术。其名称源自“摇树”动作——摇掉树上未成熟的果实(未使用的代码)。可以减小打包文件的体积,提高加载性能
二、核心原理
-
静态分析
- 在编译阶段分析代码的
import/export
依赖关系。 - 识别未被引用的模块或函数。
- 在编译阶段分析代码的
-
标记未使用代码
- 通过
/*#__PURE__*/
标记无副作用的函数调用。 - 结合
package.json
的"sideEffects"
字段排除副作用模块。
- 通过
-
移除死代码
- 在压缩阶段(如 Terser)移除标记为未使用的代码。
Tree shaking的工作流程可以分为
1.标记哪些导出值没有被使用; 2. 使用Terser将这些没用到的导出语句删除
标记的流程如下:
-
make阶段:收集模块导出变量并记录到模块依赖关系图中
-
seal阶段:遍历模块依赖关系图并标记那些导出变量有没有被使用
-
构建阶段:利用Terser将没有被用到的导出语句删除
三、实现条件
-
使用 ES 模块
- 代码必须使用
import/export
语法(CommonJS 不支持 Tree Shaking)。
- 代码必须使用
-
配置生产模式
- Webpack 的
mode
设置为production
,默认启用 Tree Shaking。
- Webpack 的
-
标记副作用
- 在
package.json
中声明无副作用的模块:{ "sideEffects": false // 或指定有副作用的文件 ["*.css", "*.scss"] }
- 在
四、Webpack 配置
-
启用 Tree Shaking
module.exports = { mode: 'production', // 生产模式默认启用 optimization: { usedExports: true, // 标记未使用的导出 启动 minimize: true // 压缩时移除未使用代码 } };
-
标记无副作用函数
/*#__PURE__*/ someFunction(); // 标记为无副作用
五、验证 Tree Shaking 效果
-
检查打包结果
- 使用
webpack-bundle-analyzer
分析打包文件,确认未使用代码被移除。
- 使用
-
查看 Terser 日志
- 启用
TerserPlugin
的extractComments
选项,查看移除的代码。
- 启用
六、注意事项
-
避免副作用
- 确保模块的导入不会触发副作用(如全局变量修改)。
-
第三方库支持
- 确保第三方库提供 ES 模块版本(如
lodash-es
)。
- 确保第三方库提供 ES 模块版本(如
-
Babel 配置
- 避免 Babel 将 ES 模块转换为 CommonJS(设置
modules: false
):{ "presets": [["@babel/preset-env", { "modules": false }]] }
- 避免 Babel 将 ES 模块转换为 CommonJS(设置
通过以上配置和优化,可有效实现 Tree Shaking,减少打包体积,提升应用性能。
dev-server是怎么跑起来?
-
初始化配置
- 读取
webpack.config.js
中的devServer
配置(如端口、代理、静态目录等)。 - 合并 Webpack 的默认配置和用户自定义配置。
- 读取
-
创建本地服务器
- 基于 Express 框架启动 HTTP 服务,托管静态资源。
- 使用 webpack-dev-middleware 中间件将 Webpack 的编译结果写入内存(而非磁盘),提升性能。
-
绑定 WebSocket 通信
- 通过 sockjs 或原生 WebSocket 建立浏览器与服务器的长连接,用于推送热更新(HMR)消息。
-
启动 Webpack 编译
- 调用 Webpack 的 API 触发初次编译,生成内存中的打包文件。
- 监听文件变化,触发增量编译。
基础配置
// webpack.config.js
module.exports = {
devServer: {
port: 8080, // 端口
hot: true, // 启用热更新
static: './dist', // 托管静态目录(优先使用内存文件)
open: true, // 自动打开浏览器
historyApiFallback: true // 支持前端路由(如 React Router)
}
};
核心运行原理
1. 内存资源托管
2. 热更新(HMR)机制
3.解决跨域:将特定 API 请求转发到后端服务器:
总结
Webpack Dev Server 通过内存编译 + WebSocket 通信 + HMR 运行时的组合,实现了高效的本地开发体验。理解其原理有助于优化配置(如调整 watchOptions
)和解决热更新失效等疑难问题。
babel概念及原理
babel 可以将代码转译为想要的目标代码,并且对目标环境不支持的api 自动 polyfill
。而babel实现这些功能的流程是 解析(parse)-转换(transfrom)-生产(generator)
,接下来我们就看看每个流程都做了啥工作
-
解析
:根据代码生成对应的AST
结构- 进行代码分析,将代码分割成token流(语法单元数组),再根据token流生成对应的
AST
- 进行代码分析,将代码分割成token流(语法单元数组),再根据token流生成对应的
-
转换
:遍历AST
节点并生成新的AST
节点 -
生成
:根据新的AST
生成目标代码
SourceMap 原理(高频)
source map是将编译打包后的代码映射回源码 可以通过devtool配置项来设置,还可以通过SourceMapDevToolPlugin
实现更加精细粒度的控制
devtool配置项和
SourceMapDevToolPlugin
不能同时使用,因为devtool选项已经内置了这些插件,如果同时使用相当于应用了两次插件
配置 devtool: 'source-map'
后,在编译过程中,会生成一个 .map
文件,一般用于代码调试和错误跟踪。
-
包含了源代码、编译后的代码、以及它们之间的映射关系。
-
编译后的文件通常会在文件末尾添加一个注释,指向 SourceMap文件的位置。
// # sourceMappingURL=example.js.map
-
当在浏览器开发者工具调试时,浏览器会读取这行注释并加载对应的 SourceMap 文件
报错时,点击跳转。即使运行的是编译后的代码,也能够追溯到原始源代码的具体位置,而不是处理经过转换或压缩后的代码,从而提高了调试效率。
模式 | 特点 | 适用场景 |
---|---|---|
eval | 最快,但映射信息内联在 eval 中,只能映射到转换后的代码(非原始代码)。 | 开发环境(快速构建) |
cheap-source-map | 生成单独的 .map 文件,仅映射行号(不映射列号),速度较快。 | 开发环境(平衡速度与精度) |
source-map | 完整独立的 .map 文件,包含行列精确映射,但构建速度较慢。 | 生产环境(需精确调试) |
hidden-source-map | 生成 .map 文件,但 Bundle 中不包含引用注释,需手动关联。 | 生产环境(保护源码隐私) |
cheap-module-source-map | 类似 cheap-source-map ,但映射到 Loader 转换前的源码(如 Babel 前的代码)。 | 开发环境(需调试原始代码) |
这么多的选择,那么我们应该如何使用呢,根据我的实践,我觉得比较好的设置应该是下面这样
- 开发环境:
cheap-module-eval-source-map
,生产这种source map
速度最快,并且由于开发环境下没有代码压缩,所以不会影响断点调试 - 生产环境:
hidden-source-map
,由于进行了代码压缩,所以并不会占用多大的体积
避免在生产中使用
inline-
和eval-
因为它们会增加 bundle 体积大小 并且降低整体性能
选择原则
- 开发环境:优先速度,选
eval
、cheap-
系列。 - 生产环境:需调试则用
source-map
,否则禁用或选hidden-source-map
。 - 精度要求:精确调试选含
source-map
的模式,快速构建选eval
或cheap-
。
跟 Mainfest 的区别
-
SourceMap 主要用于调试目的,让开发者能够在压缩或转译后的代码中追踪到原始代码。
-
Manifest 文件用于资源管理,用于优化资源的加载和缓存。
如何对bundle体积进行监控和分析
VSCode
中有一个插件 Import Cost
可以帮助我们对引入模块的大小进行实时监测,还可以使用 webpack-bundle-analyzer
生成 bundle
的模块组成图,显示所占体积。
bundlesize
工具包可以进行自动化资源体积监控。
文件指纹是什么?怎么用?
概念
文件指纹是指文件打包后的一连串后缀,如哈希值。
作用
- 版本管理: 在发布版本时,通过文件指纹来区分 修改的文件 和 未修改的文件。
- 使用缓存: 浏览器通过文件指纹是否改变来决定使用缓存文件还是请求新文件(浏览器可复用本地缓存,提升加载速度)。
种类
Hash
:和整个项目的构建相关,只要项目有修改(compilation
实例改变),Hash
就会更新粒度最粗)Contenthash
:和文件的内容有关,只有内容发生改变时才会修改Chunkhash
:和webpack构架的chunk有关 不同的entry会构建出不同的chunk (不同ChunkHash
之间的变化互不影响) javascript
复制
// webpack.config.js
module.exports = {
output: {
filename: 'js/[name].[chunkhash:8].js', // JS 使用 chunkhash
chunkFilename: 'js/[name].[chunkhash:8].js',
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css', // CSS 使用 contenthash
}),
],
module: {
rules: [
{
test: /.(png|jpe?g|gif)$/,
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]', // 图片使用 hash
},
},
],
},
};
如何使用
- JS文件:使用
Chunkhash
- CSS文件:使用
Contenthash
- 图片等静态资源: 使用
hash
生产环境的output为了区分版本变动,通过
Contenthash
来达到清理缓存及时更新的效果,而开发环境中为了加快构建效率,一般不引入Contenthash
为什么pnpm比npm快
Pnpm 比 npm 快的原因在于其优化的文件存储方式、依赖管理方式以及并行下载能力。 以下是详细介绍:
-
Pnpm 使用基于内容寻址的文件系统来存储磁盘上的所有文件,这意味着它不会在磁盘中重复存储相同的依赖包,即使这些依赖包被不同的项目所依赖。这种存储方式使得Pnpm在安装依赖时能够更高效地利用磁盘空间,同时也减少了下载和安装的时间。
-
Pnpm 在下载和安装依赖时采用了并行下载的能力,这进一步提高了安装速度。
-
Pnpm 还具有一些其他特性,例如节省空间的硬链接和符号链接的使用,这些都有助于提高其性能。
npm run start 的整个过程
以下是 npm run start
的简明执行过程:
1. 解析命令
- 查找脚本:在
package.json
的scripts
字段中查找start
命令。{ "scripts": { "start": "react-scripts start" } }
2. 执行脚本
- 调用命令:运行
start
对应的命令(如react-scripts start
)。 - 环境变量注入:自动注入
NODE_ENV=development
(开发环境)。
3. 启动开发服务器
- 初始化构建工具:调用 Webpack/Vite 等工具,加载配置文件。
- 编译代码:将源代码打包为浏览器可执行的文件(如 JS、CSS)。
- 启动本地服务器:通过 Express 或 Webpack Dev Server 托管静态资源。
4. 热更新(HMR)
- 监听文件变化:实时监控文件修改。
- 局部更新:通过 WebSocket 推送更新内容,浏览器局部刷新。
5. 打开浏览器
- 自动访问:默认打开
http://localhost:3000
(端口可配置)。
npm install的执行过程
详细细节copy title dp
执行过程大致如下:
- 读取
package.json
文件,该文件列出了项目所需要的依赖。 - 根据
package.json
中的依赖信息以及node_modules
目录状态,npm 会决定哪些模块需要下载和安装。 - npm 会查看每个模块的可用版本,并选择符合
package.json
中指定版本范围的最新版本进行安装。 - 下载所需模块到本地的
node_modules
目录。 - 如果模块包含子模块(
package.json
中dependencies
或devDependencies
中的模块),则递归执行上述步骤安装这些子模块
常见问题与优化
问题 | 原因与解决 |
---|---|
安装速度慢 | 使用国内镜像(npm config set registry https://registry.npmmirror.com )或 pnpm |
node_modules 体积过大 | 使用 npm dedupe 减少冗余,或切换到 pnpm (硬链接节省空间) |
版本冲突(Peer Deps) | 根据警告手动调整版本,或使用 npm install --force 强制覆盖 |
锁文件冲突 | 禁止手动修改锁文件,始终通过 npm install 自动更新 |
总结
npm install
的核心流程是:解析依赖 → 下载包 → 扁平化安装 → 执行脚本 → 生成锁文件。
理解这一过程有助于:
- 解决依赖冲突问题。
- 优化安装速度和体积。
- 保证多环境的一致性(通过锁文件)。
eslint概念以及原理
ESLint 概念:
ESLint 是一个用于 JavaScript/TypeScript 的静态代码分析工具,用于检测代码中代码质量检查(如未定义变量) 和风格统一。
核心原理:
-
解析代码为 AST
将源代码解析为抽象语法树(AST),结构化表示代码逻辑,便于分析。 -
遍历 AST 应用规则
通过预定义的规则(如变量未使用、缩进错误等)检查 AST 节点,识别问题。规则可自定义或通过插件扩展(如 Vue、React 专用规则)。 -
报告与修复
标记问题位置并输出警告/错误,部分问题可通过--fix
自动修复(如自动校正缩进)。 -
配置灵活
支持全局/项目级配置(如.eslintrc
),可继承共享配置(如eslint-config-airbnb
),集成到编辑器、构建流程中实时反馈。
作用场景:
- 代码质量检查(如未定义变量)
- 风格统一(如引号、缩进)
- 最佳实践约束(如避免
eval()
) - 团队协作规范强制执行。
package.json文件中的devDependdencies和dependdencies对象有什么区别
总结
dependencies
:生产环境依赖,打包时会包含。devDependencies
:开发环境依赖,不会打包到最终产物。
正确区分两者可以优化项目体积和构建速度。
什么是CI/CD dp
- CI(持续集成,Continuous Integration)
开发者频繁将代码合并到主分支,自动触发构建和测试
,确保代码质量。 - CD(持续交付/部署,Continuous Delivery/Deployent)
将通过测试的代码自动部署
到测试或生产环境
,快速交付新功能。
Mainifest 文件是什么,有什么用
Mainfest(更新清单),通常是一个 JSON 文件。需要配置 WebpackManifestPlugin
插件
在 Webpack 输出阶段生成,用于记录所有模块及其依赖关系的映射用来管理模块加载、优化浏览器缓存。 包含:
- 模块标识符: 每个模块都有一个唯一标识符,这些标识符用于在运行时查找和加载模块。
- Chunk 映射关系:包含 chunk 与包含的模块之间的映射关系,以及 chunk 之间的依赖关系。这有助于运行时确定哪些 chunk 需要被加载。
- Hash 值: 每个输出文件的 hash 值。有助于浏览器判断文件是否有更新,从而决定是加载缓存中的资源还是重新请求新的资源。
{
"main.js": "main.1a2b3c4d5e6f7g8h9i0j.js",
"vendor.js": "vendor.1a2b3c4d5e6f7g8h9i0j.js"
}
生成的 Manifest 文件可以用于以下场景:
- 服务端渲染: 在服务端渲染时,可以使用 Manifest 文件来生成正确的脚本标签,确保引用最新的资源。
- 缓存管理: 通过记录文件的哈希值,确保在文件内容变化时,客户端能够获取到最新的文件,而不是使用缓存的旧文件。
- 动态加载: 在需要按需加载模块时,可以使用 Manifest 文件来查找模块的路径。
如何在 WebPack 中代码分割/提取一个公共模块
这里方案我们使用 SplitChunksPlugin,这是 Webpack 的内置插件,用于将公共的依赖模块提取到单独的 chunk 中,减少代码重复、提高加载速度。
在 webpack.config.js 文件中,你可以在配置 optimization.splitChunks
选项来指定如何提取公共模块
基本配置 module.exports = { // 其他配置... optimization: { splitChunks: { chunks: 'all', // 对所有模块进行优化 } } };
高级配置: 通过cacheGroups
自定义分割策略
js
代码解读
复制代码
module.exports = {
// 其他配置...
optimization: {
splitChunks: {
chunks: 'all', // 对所有模块进行优化
minSize: 20000, // 生成chunk的最小大小(以字节为单位)
minChunks: 1, // 分割前必须共享模块的最小块数
maxAsyncRequests: 30, // 按需加载时的最大并行请求数
maxInitialRequests: 30, // 入口点的最大并行请求数
automaticNameDelimiter: '~', // 默认情况下,webpack将使用块的来源和名称生成名称(例如vendors~main.js)
cacheGroups: { // 缓存组可以继承或覆盖splitChunks.*的任何选项
vendors: {
test: /[/]node_modules[/]/, // 控制哪些模块被这个缓存组选中
priority: -10 // 一个模块可以属于多个缓存组。优化将优先考虑具有更高优先级的缓存组
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true // 如果当前块包含已经从主束分离出的模块,则将重用它而不是生成新的块
}
}
}
}
};
-
提取第三方库为chunk:通过
vendors
缓存组。- 可以将
node_modules
的模块提取到单独的文件中,这对于提取大型的第三方库(如React, Vue等)特别有用。
- 可以将
-
提取公共模块为chunk:通过
default
缓存组。- Webpack 会自动提取,被多个入口共享的模块到一个或多个公共块中。
Webpack的持久化缓存(Persistent Caching)和模块联邦(Module Federation)如何提升构建速度?
如何通过代码分割(Dynamic Imports)实现按需加载?列举magic comments
的高级用法。
对比Rollup、Vite和Webpack在性能优化上的差异。
如何通过Webpack实现代码分割(Code Splitting)和Tree Shaking?异步加载模块时需要注意哪些问题?
通过 Webpack 实现代码分割(Code Splitting)和 Tree Shaking 是优化前端应用性能的核心技术。以下是具体实现方法和注意事项:
一、代码分割(Code Splitting)
1. 入口起点分割
// webpack.config.js
module.exports = {
entry: {
main: './src/main.js',
vendor: './src/vendor.js'
},
// ...
};
缺点:需手动管理重复依赖。
2. 动态导入(Dynamic Imports)
使用 import()
语法实现按需加载:
// 触发代码分割
button.addEventListener('click', async () => {
const module = await import('./module.js');
module.doSomething();
});
Webpack 会自动生成一个独立的 chunk(如 1.bundle.js
)。
3. 使用 SplitChunksPlugin
优化
在 webpack.config.js
中配置:
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10
},
common: {
minChunks: 2, // 至少被两个入口引用
name: 'common',
priority: -20
}
}
}
}
二、Tree Shaking
1. 基本配置
- 使用 ES Modules(
import/export
)。 - 生产模式自动启用:
mode: 'production', // 自动启用 Tree Shaking
- 开发模式手动启用:
optimization: { usedExports: true, // 标记未使用代码 minimize: true, // 删除未使用的导出 }
2. 配置 package.json
标记模块无副作用:
{
"sideEffects": false // 或指定有副作用的文件,如 ["*.css"]
}
3. 避免破坏 Tree Shaking
- 避免使用 CommonJS(
require
)。 - 确保第三方库支持 Tree Shaking(如 Lodash ESM 版本:
lodash-es
)。
三、异步加载模块的注意事项
1. 加载顺序与依赖
- 如果异步模块依赖其他模块,需确保依赖已加载:
import('./dep.js').then(() => import('./module.js'));
2. 错误处理
捕获加载失败:
import('./module.js')
.then(module => { /* ... */ })
.catch(error => { /* 处理错误 */ });
3. 预加载与预获取
使用 Webpack 魔法注释优化加载策略:
import(/* webpackPrefetch: true */ './module.js'); // 空闲时预加载
import(/* webpackPreload: true */ './module.js'); // 高优先级预加载
4. 公共模块提取
避免重复打包公共代码,通过 SplitChunksPlugin
配置 minChunks
或 maxSize
。
5. 性能监控
使用 webpack-bundle-analyzer
分析 chunk 大小和依赖关系:
npm install --save-dev webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [new BundleAnalyzerPlugin()]
};
6. 用户体验优化
- 添加加载状态提示(如 Loading 动画)。
- 使用 React/Vue 的异步组件(如
React.lazy
+Suspense
)。
总结
技术 | 实现方式 | 关键点 |
---|---|---|
代码分割 | 动态 import() + SplitChunksPlugin | 按需加载、提取公共代码 |
Tree Shaking | ES Modules + sideEffects 配置 + 生产模式 | 避免副作用、检查第三方库兼容性 |
异步加载 | 错误处理、预加载、分析工具 | 用户体验、依赖管理、性能监控 |
通过合理配置 Webpack,可显著减少初始加载时间并提升应用性能。
Webpack 高频面试题的总结
以下是 Webpack 高频面试题的总结,结合了多个来源的常见问题及核心知识点,并附上对应的引用来源:
1. Webpack 的核心概念与作用
- 核心概念:
- Entry:入口文件,Webpack 构建的起点(默认
./src/index.js
)。 - Output:输出文件的路径和名称(默认
./dist
)。 - Loader:处理非 JavaScript 文件(如 CSS、图片),转换为 Webpack 可处理的模块。
- Plugin:扩展 Webpack 功能,在构建生命周期中执行自定义逻辑。
- Module:一切文件皆模块,支持递归依赖分析。
- Chunk:代码块,由多个模块组成,用于代码分割。
- Entry:入口文件,Webpack 构建的起点(默认
- 作用:模块化打包、代码转换(ES6/TypeScript 等)、资源优化(压缩、合并)、开发环境支持(热更新)等。
2. Loader 与 Plugin 的区别
- Loader:
- 功能:处理单个文件,转换为 Webpack 可识别的模块(如
babel-loader
转译 ES6)。 - 配置:在
module.rules
中定义,执行顺序从后到前。 - 示例:
css-loader
解析 CSS 文件,style-loader
将 CSS 注入 DOM。
- 功能:处理单个文件,转换为 Webpack 可识别的模块(如
- Plugin:
- 功能:扩展 Webpack 能力,介入整个构建流程(如
HtmlWebpackPlugin
生成 HTML 文件)。 - 配置:在
plugins
数组中实例化,监听 Webpack 生命周期事件。 - 示例:
CleanWebpackPlugin
清理构建目录,MiniCssExtractPlugin
提取 CSS 为独立文件。
- 功能:扩展 Webpack 能力,介入整个构建流程(如
3. Webpack 构建流程
- 初始化参数:合并配置文件和命令行参数。
- 开始编译:创建
Compiler
对象,加载插件。 - 确定入口:根据
entry
配置找到所有入口文件。 - 编译模块:递归解析入口文件的依赖,通过 Loader 转换代码。
- 完成编译:得到模块的最终内容和依赖图。
- 输出资源:将模块组装成 Chunk,生成文件列表。
- 写入文件系统:根据
output
配置输出到指定目录。
4. 热更新(HMR)原理
- 文件监听:Webpack 的
watch
模式检测文件变化,重新编译。 - 内存编译:新生成的代码保存在内存中(通过
webpack-dev-middleware
)。 - 客户端通信:
webpack-dev-server
通过 WebSocket 通知浏览器更新。 - 模块替换:HMR Runtime 替换更新的模块,无需刷新页面。
5. 优化 Webpack 构建速度
- 减少处理文件:通过
exclude
排除node_modules
。 - 缓存:使用
cache-loader
或 Webpack 5 内置缓存。 - 多进程处理:
Happypack
或thread-loader
加速 Loader 执行。 - 代码分割:
SplitChunksPlugin
提取公共代码。 - 预编译:
DllPlugin
预编译不常变动的库(如 React)。
6. Tree Shaking 与代码压缩
- Tree Shaking:
- 原理:静态分析代码,移除未使用的导出。
- 条件:使用 ES6 模块语法(
import/export
),配置optimization.usedExports: true
。
- 压缩:
- JS:
TerserWebpackPlugin
压缩并混淆代码。 - CSS:
CssMinimizerWebpackPlugin
压缩 CSS。
- JS:
7. Source Map 配置
- 作用:映射编译后代码到源码,便于调试。
- 开发环境:推荐
cheap-module-eval-source-map
(快速,包含模块信息)。 - 生产环境:推荐
cheap-module-source-map
(不暴露源码)。
8. 代码分割与按需加载
- 动态导入:使用
import()
语法实现懒加载,返回 Promise。 - 配置示例:
// 路由懒加载 const Home = () => import('./Home.vue');
- 输出文件名:通过
[chunkhash]
或[contenthash]
实现缓存优化。
9. Webpack 与竞品对比(Gulp/Rollup/Vite)
- Gulp/Grunt:基于任务流,适合简单任务(如文件移动);Webpack 基于模块化,适合复杂应用。
- Rollup:适合库打包(Tree Shaking 更高效),但生态不如 Webpack。
- Vite:基于 ES Module 的按需编译,启动更快,适合现代浏览器项目。
10. 常见插件(Plugin)与 Loader
- Loader 示例:
babel-loader
:转译 ES6+。file-loader
/url-loader
:处理图片/字体。sass-loader
:编译 SCSS。
- Plugin 示例:
HtmlWebpackPlugin
:生成 HTML 并注入资源。DefinePlugin
:定义全局常量(如环境变量)。BundleAnalyzerPlugin
:分析打包体积。
以上内容综合了多个来源的高频问题,涵盖核心概念、原理、优化策略等。如需进一步了解某一知识点,可参考对应引用来源的详细解析。