前端代码为什么要构建和打包
代码方面
- 体积更小(Tree-Shaking,压缩,合并)
- 编译高级语言或语法(TS,ES6+,模块化,scss)
- 兼容性和错误检查(PolyFill,postcss,eslint)
研发流程方面
- 统一,高效的开发环境
- 统一的构建流程和产出标准
- 集成公司构建规范(提测,上线等)
- 代码压缩,减小文件体积,
- 代码分割,按需加载提高性能
- 资源优化:压缩图片,移除未使用的字体,压缩字体。移除未使用的代码
-
解决兼容性
- 使用 Babel 将 ES6+转化为 ES5
- 确保 css 在不同浏览器的兼容性
-
优化部署
- 文件哈希命名,避免浏览器缓存导致的更新问题
- 区分环境:加载不同配置
-
提高开发效率
- 减少手动操作
- 统一规范
- 错误检测(Eslint)
-
解决复杂依赖关系
- 自动解析和安装依赖
- 将所有依赖打包到一个文件,避免重复加载
module chunk bundle 什么意思,有什么区别?
-
module:模块,一个模块就是一个文件,一个文件就是一个模块
-
chunk:一个 chunk 就是一个模块集合,是打包的中间产物
- Chunk 的生成方式取决于 Webpack 的配置:
入口文件:每个入口文件会生成一个初始 chunk。
动态导入:动态 import() 会生成一个新的 chunk。
代码分割:通过 splitChunks 配置可以进一步优化 chunk 的生成。
-
bundle:最终的输出文件,每个 bundle 对应一个 chunk,通常是经过优化和压缩后的代码文件。output 输出的文件
loader plugin 区别
- loader
- webpack 默认只能处理 js 文件,使用 loader 后可以处理不同类型文件
- loader 是链式调用按照从后往前的顺序执行
- plugin
- 通过监听 webpack 构建生命周期中的事件,在构建过程中执行自定义逻辑。
- 可以修改输出文件,优化资源,生成额外文件等
- 典型:清理构建目录,压缩 js,css 文件,自动生成 html 等
webpack 如何实现懒加载
webpack 支持使用 import()动态导入,打包时会将动态导入的包打包成独立文件,会返回一个 Promise,模块加载完后会返回 resolve。只有在 import()调用才会加载
常见性能优化
优化打包构建速度
- 优化 babel-loader
use:['babel-loader?cacheDirectory'] 开启缓存
include 和 exclude 可以选取明确需要缓存处理的文件范围
只要代码没有改就不会重新编译,使用缓存
- ingorePlugin
- noParse
module: {
noParse: /jquery|lodash/
}
- happyPack
module: {
rules: [
{
test: /\.js$/,
use: happypack/loader?id=js,
},
]
},
plugins: [
new HappyPack({
id: 'js', // 与上面的 loader 中的 id 对应
loaders: ['babel-loader?cacheDirectory=true'], // 实际使用的 loader
threadPool: happyThreadPool, // 使用共享的线程池
verbose: true, // 显示详细日志
}),
]
- paralleIUglifyPlugin
- 多进程压缩 js,项目比较小,开启多线程会增大开销
plugins: [
new ParallelUglifyPlugin({
// 设置使用的 uglifyjs 版本,默认是自带版本
uglifyJS: {
output: {
beautify: false, // 是否美化输出
comments: false, // 是否保留注释
},
compress: {
warnings: false, // 如果为 true,则显示压缩警告
drop_console: true, // 删除所有的 `console` 语句
collapse_vars: true, // 内嵌已定义但只用到一次的变量
reduce_vars: true, // 提取出出现多次但是没有定义成变量去引用的静态值
}
},
// 指定要并行处理的文件数量,默认为当前系统的核心数减去1
workerCount: os.cpus().length - 1,
// 可选参数:指定缓存目录,默认不缓存
cacheDir: '.cache/',
}),
],
- terser-webpack-plugin
webpack4 的产物,它被引入作为 UglifyJSPlugin 的替代品,因为 UglifyJS 对 ES6+ 代码的支持有限,而 Terser 提供了更好的兼容性。
optimization: {
minimize: true, // 启用代码压缩功能(默认在生产模式下为 true)
minimizer: [
new TerserPlugin({
parallel: true, // 启用多线程压缩,利用多核 CPU 提高构建速度
terserOptions: {
output: {
comments: false, // 移除所有注释,包括版权信息等,减少文件体积
},
compress: {
drop_console: true, // 删除所有的 `console` 语句(如 console.log、console.warn 等),避免在生产环境中暴露调试信息
},
},
}),
],
},
- 自动刷新(非生产环境)
- 判断文件是否变化是通过不断的去询问系统指定文件是否变化
module.exports = {
watch: true,// 开启监听模式,默认false
//注意,使用了webpack-dev-server会开启自动刷新
watchOptions: {
ignored: /node_modules/, //忽略node_modules下的文件
poll: 1000, // 轮询间隔,默认为 3000ms
aggregateTimeout: 500,//监听到会等待500ms,防止编译的频繁
}
}
- 热更新(非生产环境)
- 正常刷新
整个网页全部刷新,加载慢,状态丢失
- 热更新
新代码生效,网页不刷新,状态不会丢失
entry:[
index: ['webpack-dev-server/client?http://localhost:5500',
'webpack/hot/dev-server',
path.join(__dirname, '../src', 'index.js')
],
]
devServer: {
hot: true,
},
plugins: [
new HotModuleReplacementPlugin()
]
<!-- 在程序的入口文件里引入以下代码 -->
if (module.hot) {
//需要监听的文件,数组,目录
//module.hot.accept(); // 监听所有模块的变化
module.hot.accept('./App', () => {
//监听到后的回调函数
const NextApp = require('./App').default;
render(NextApp);
});
}
- Dllplugin(非生产环境)
将不常更新的第三方库打包成动态链接库 Dll
//在启动配置下
module:{
rules: [
exclude: /node_modules/,可以忽略,已经把一些模块(react)打包了
]
}
plugins: [
new webpack.DllReferencePlugin({
manifest: require('./dist/vendor-manifest.json'), // 引用之前生成的 manifest的json 文件
}),
],
优化产出代码
-
小图片 base64 编码
-
文件名使用 bundle 加哈希
-
懒加载
-
提取公共代码
-
IngorePlugin
-
使用 cdn,http://cdn...,配置 publicPath 为 cdn
-
使用 production
- 自动压缩代码
- vue React 会自动删掉调试代码(如开发时的警告)
- 启用 Tree-Shaking
-
使用 Scope Hosting
- 将多个模块合并到同一个函数作用域内,从而避免了每个模块被包裹在单独的立即执行函数表达式(IIFE)中,减少了函数调用的开销,并提高了代码压缩的效果
HtmlWebpackPlugin
- template 自定义 html 的模板路径
- chunks: 当存在多个路口文件时,指定需要在 html 中引入的 chunks(包)
entry: {
index: './src/index.js',
print: './src/print.js',
},
plugins: [
new HtmlWebpackPlugin({
title: '管理输出',
chunks: ['index', 'print']
})
],
- inject:控制 chunks 注入到 html 的哪个标签中,'head' or ‘body’ or false
- minify:在生产环境中,你可能希望最小化生成的 HTML 文件。可以通过设置这个选项为 true 或者传递一个对象来自定义最小化选项。
minify: {
collapseWhitespace: true, // 折叠空白区域
removeComments: true, // 移除注释
removeRedundantAttributes: true, // 删除多余的属性
}
source map
更容易地追踪错误与警告在源代码中的原始位置
devtool: 'inline-source-map'
自动编译工具
-
观察模式:script 添加 "watch": "webpack --watch",npm run watch 启动。保存文件后会自动重新编译修改后的文件。
缺点:需要手动刷新浏览器才能看到修改后效果
-
webpack-dev-server:
-
优点:自动刷新浏览器,不需要手动刷新浏览器
-
webpack 配置添加
devServer: {
static: './dist' //多个可以是数组
open: true, //自动打开浏览器,boolean or 浏览器名称
port: 8080, //监听的端口号,默认8080
hot: true, //启用 Hot Module Replacement (HMR),提高开发效率。
proxy: {
'/api': {}配置代理
}//将我们本地前端 http://localhost:5137/api 代理到服务器地址 http://localhost:3000
},
-
script 添加 "start": "webpack serve --open",
-
它会将在 output.path 中定义的目录中的 bundle 文件作为可访问资源部署在 server 中,简而言之就是可以直接浏览器访问该路径。
-
如果页面希望在不同路径中找到 bundle 文件,可以修改 dev server 配置中的 devMiddleware.publicPath 选项。
-
webpack-dev-middleware
- 包装器:它可以把 webpack 处理过的文件发送到 server,这是 webpack-dev-server 内部的原理,但是它也可以作为一个单独的包使用
optimization
多个 chunks 只需要实例化一次
多个 chunks 所依赖的模块可以提取到多个页面使用的公共包中,但是有些模块在很少的页面使用,打包器可能会将他们内联到每个 chunks 中而不需要提取到共享包。
无论提取还是内联,esmodule 和 commonJS 都规定模块只能在每个 js 上下文实例化一次,保证模块的顶级范围是全局并在该模块的所有用法之间共享
实例化多次会为正确的代码引入错误或者效率低下
基本配置
loader 的执行顺序是从后往前的
-
拆分、merge公共的配置
使用 const {smart} = require('webpack-merge');
module.exports = smart(baseConfig, {})// baseConfig 为公共配置
-
处理 ES6
-
处理样式
loader:css-loader,style-loader...
使用 postcss-loader 需要配置 postcss-config.js
-
处理图片
-
使用 loader:url-loader,图片小可以直接使用 base64,减少请求
-
output
output: {
filename: '[name].[contenthash:8].js',//动态名称,为入口名称
//使用 8 位的哈希字符串设置文件,文件内容没有更改哈希就不变,请求会命中缓存,加载更快
},
高级配置
- 生成多入口 html 并引入指定包:
entry:{
index: './src/index.js',
print: './src/print.js',
}
output:{
filename: '[name].[contentHash:8].js',//动态名称,为入口名称
}
//多个 html 需要 new 多个 HtmlWebpackPlugin
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',//自定义引用的 html 的模板路径
filename: 'index.html',//自定义 html 的文件名
chunks: ['index', 'print']//指定需要在 html 中引入的 chunks(包),不指定会把入口的 js 文件全部引入
})
]
- 抽离压缩 css 文件
- 使用 loader:mini-css-extract-plugin
在 module 的 rule 里面使用,在 plugins 里面使用。
压缩需要配置
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
}]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].bundle.css',
})
]
optimization: {
runtimeChunk: 'single',//生成共享文件
minimizer: [
// 默认情况下会包含 TerserPlugin 来压缩 JS 文件
`...`,
new CssMinimizerPlugin(), // 添加 CSS 压缩插件
],
},
- css 文件并没有在入口文件中引入,而是直接在 html 引入,所以需要手动引入,使用'copy-webpack-plugin'
new CopyWebpackPlugin({
patterns: [
{ from: 'src/index.css', to: 'assets' },//to 默认指向打包路径这里会在 dist 下创建 assets 文件夹存储
],
})
- 抽离公共代码
抽离公共代码,使得文件修改后不会重新请求,而是使用缓存
optimization: {
//分割代码
splitChunks: {
chunks: 'all',
/\*\*
_ all: 所有的 chunks
_ initial: 直接引入了的入口 chunks,不处理异步文件
_ async: 只针对所有的异步 chunks
_ function: 自定义函数,返回 true 或 false。
\*/
cacheGroups: {
//第三方模块
vendor: {
name: 'vendor',//chunk 名称
priority: 1,//优先级,越大优先级越高
test: /node_modules/,
minSize: 0,//最小尺寸,默认 0
minChunks: 1,//最少被几个 chunk 引用
},
// 公共模块
common: {
name: 'common',//chunk 名称
priority: 0,//优先级
minSize: 0,//最小尺寸,默认 0
minChunks: 2,//最少被几个 chunk 引用
}
}
}
-
懒加载
使用 import('文件路径').then(res => {}),webpack 会打包出一个单独的 js 文件,然后异步加载,不会阻塞页面的渲染
-
处理 jsx
在.babelrc 里面使用
使用'@babel/preset-react'
在 module 的 rules 会使用 loader 处理 jsx,匹配 jsx 文件,他会自动处理 jsx
-
vue
vue loader
在 module 的 rules 会使用 loader 处理 vue 的规则,匹配 vue 文件,使用 vue loader
ESModule 和 CommonJS
ESModule 静态引入,编译时引入
CommonJS 动态引入,执行时引入
静态分析才能实现 Tree-Shaking,执行时引入无法分析
babel
- 环境搭建
- .babelrc 配置
- presets 和 plugins
babel-runtime 和 babel-polyfill 的区别
babel-polyfill
旨在填补不同环境之间的兼容性差距
- core-js 和 regenerator-runtime 的集合
说明:从 7.4 开始被弃用。
- 它通过修改原生对象(Object.prototype)实现向后兼容,可能与其他库产生冲突。
Array.prototype.includes = function () {}//重新定义方法
- 会引入整个 core-js 的 polyfills,无法按需引入,增大打包体积
如何按需引入
"presets": [
[
"@babel/preset-env",
//添加如下代码
{
"useBuiltIns": "usage",
"corejs": 3 //版本
}
]
],
babel-runtime
用于支持模块化和非全局污染的 polyfill 和 helper 函数。它与 @babel/polyfill 不同,后者会全局修改原生对象(如 Array.prototype),而 babel-runtime 通过模块化的方式引入 polyfills 和 helpers,避免了全局污染。
核心概念
-
核心概念
(1) Helper Functions
Babel 在编译代码时会生成一些辅助函数(helper functions),例如 _classCallCheck、_defineProperty 等。这些函数通常用于实现类、继承等特性。
默认情况下,这些辅助函数会被重复嵌入到每个文件中。这会导致打包体积增大,尤其是在大型项目中。
(2) Polyfills
为了支持新的 JavaScript API(如 Promise、Map 等),需要引入 polyfills。babel-runtime 提供了一种方式,通过模块化的方式引入这些 polyfills,而不是全局污染。
如何产出一个 lib
配置 webpack 文件,output 的 library 可以定义库的全局名称。
使用 npm publish 发布到 npm。更新需要更新版本号 npm version patch # 更新补丁版本
-
支持 ts
需要 webpack 去处理 ts 文件
-
支持多平台
为了让库能够同时支持浏览器和 Node.js 环境,建议:
设置 libraryTarget: 'umd'。
使用 globalObject: 'this' 来兼容不同环境的全局对象。