工程化是什么
工程化的目的是用于将我们书写的前端文件转换成html,css,js文件以及其他的一些资源文件。
所以这些构建工具,它们主要做的事情就是从入口开始 对文件的js,css,字体,图片等各种资源进行编译和压缩转换成浏览器能懂的格式(这一系列可以称为资源处理),在注入html文件中。当文件过多的时候构建工具做这一系列的转换耗时较久,所以又诞生出构建加速和优化来加快时间处理。
所以最后工程化核心就可以理解为两件事情
-
资源文件处理
-
加快处理速度和优化
因为常用webpack,所以就以webpack举例一下,先介绍一下资源处理和构建加速,优化有哪些。最后在介绍一下如何配置一个自己的本地化方案。
webpack
资源处理
在webpack中处理资源首先我们需要一个配置来告诉我们资源在哪里。在webpack配置文件中,起这个作用的就是说entry
-
entry 作用是配置入口文件的数量,可以是单文件也可以是多文件
三种配置方式 string,[ string ],{ key: value }
既然有输入那就有输出output
- output
具体的 配置需要参考 webpack.docschina.org/configurati…
path 打包生成的目录位置
filename 入口 打包生成的名称 可以是具体的名称 也可以是文件指纹
[name] chunk的名称 就是entry里面的key
[chunkhash] 一般js文件使用 表示js的内容的hash
[contenthash] 一般css和图片使用
clean 清除path目录里面的文件
chunkFilename 非入口打包生成的名称 例如 css js
publicPath 用于指定打包后资源(JS、CSS、图片等)的发布路径
crossOriginLoading
library
类型:string | string[] | object
输入的文件 需要经过一些列的处理,最后才能到输出。这里的处理 就是module
和 plugins
。
-
module 处理不同类型的文件(如 JS、CSS、图片),通过loader转换文件内容。
常见的loader 知道就行,需要用的时候再去才文档 babel-loader 转换ES6,7新特性 css-loader 解析css文件 style-loader 将css文件插入到head标签中 file-loader 解析图片 url-loader 解析图片 html-loader 解析html文件 thread-loader 多进程打包 less-loader 解析less文件 postcss-loader 解析postcss文件 px2rem-loader 将px转换为rem
-
plugins 在 Webpack 构建流程的特定阶段执行自定义任务,如打包优化、资源管理、环境变量注入等。
commonsChunkPlugin 将chuck相同的模块代码提前公共js cleanWebpackPlugin 清理构建目录 MiniCssExtractPlugin 将css从bunlde文件提取成独立的css copyWebpackPlugin 将文件copy到构建输出目录 HtmlWebpackPlugin 创建html文件 terser-webpack-plugin 压缩js zipWebpackPlugin 将打包出的资源压缩 HardSourceWebpackPlugin 进行缓存
加快处理速度和优化
想要加快速度那首先文件需要快速查找
-
resolve 缩小文件搜索范围
extensions 自动解析确定的扩展 alias 配置别名 modules 告诉 webpack 解析模块时应该搜索的目录 mainFields 配置入口导入 mainFiles 配置入口文件 include 缩小范围 exclude 排除范围
并且可以把文件进行压缩
-
压缩
html的压缩 html-webpack-plugin 设置压缩参数 css的压缩 optimize-css-assets-webpack-plugin postcss-loader cssnao css 压缩 js的压缩 terser-webpack-plugin 图片的压缩 image-webpack-loader compression-webpack-plugin gizp压缩和br压缩 optimization minimizer 压缩
有了快速查找和压缩 现在需要提速和缓存
-
提速
thread-loader 多进程打包 使用HardSourceWebpackPlugin 进行缓存 terser-webpack-plugin praplle:true 启用多线程 cache:true 启用缓存
-
缓存
babel-loader 缓存 webpack.DllPlugin 和 webpack.DllReferencePlugin terser-webpack-plugin 缓存 hard-source-webpack-plugin 缓存 二次构建
并且在开发的过程中有热更新
-
dev server 开发环境热更新 开发环境提速
开发服务 hot:true open:true port:8080 contentBase:path.resolve(__dirname,'dist') webpack-dev-server 是一个服务器 内部是使用 webpack-dev-middleware 和 webpack-hot-middlewar 来完成 整个re更新流程 webpack-dev-middleware 监听文件改动 并将改动的文件编译到内存 webpack-hot-middlewar 将改动的文件从内容和页面通讯完成热更新流程 watch webpack的文件监听 全量编译 watchOptions poll 轮询间隔 aggregateTimeout 防抖时间 ignored 忽略文件
最后还有分包策略
-
分包策略
MiniCssExtractPlugin 将css从bunlde文件提取成独立的css optimization splitChunks 压缩 speed-measure-webpack-plugin 测量构建速度 webpack-bundle-analyzer 分析包的大小
由于不同环境在运行时的目的不一样,所以在最后配置的时候有需要分为三个部分
- 基础配置
- 生成环境配置
- 开发环境配置
配置讲解
基础配置
基础配置的目的主要有以下几个内容
入口配置
入口的配置 可以分为单入口和多入口,这里就可以对应前端的单页和多页两种不同的应用。因为多入口配置可以包含单入口配置。所以我这里就直接以多入口构建。
因为入口文件都是放到统一目录下面的并且有统一的格式,所以可以通过glob来获取到所有的文件,动态的创建入口好html的文件注入 这种方式可以有效的解决多页面的快速构建。
const glob = require('glob');
const entry = {};
const htmlWebpackPlugins = [];
const entrys = glob.sync(path.join(__dirname, './src/test/*/index.js'));
entrys.forEach((item)=>{
const match = item.match(/src\/test\/(.*)\/index\.js/);
const pageName = match && match[1];
entry[pageName] = item;
// html可以进行压缩 和注入配置 不了解的可以参考官方文档
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
template: path.resolve(__dirname, `./public/${pageName}.html`),
filename: `${pageName}.html`,
chunks: [pageName],
inject: true,
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
)
})
module.exports = {
entry: entry,
plugins:[
...,
...htmlWebpackPlugins
]
...
}
资源的解析和加速
在解析的时候可以对js,css,文字,图片等解析和补全,并多进程解析和缓存 让第二次速度更快
/**
*
* 资源解析 可以分为以下几个部分
* 解析js
* 解析ES6
* 解析Vue
* 解析CSS
* 解析less
* 解析图片
* 解析字体
*
*
* 样式增强
* css自动补全
* px自动转rem
*
*
* 多进程打包
* 缓存
*/
module.exports = {
...
module:{
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'thread-loader',
options: {
workers: 3,
},
},
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
},
}
]
},
{
test: /\.vue$/,
loader: 'vue-loader',
},
{
test: /\.css$/,
use: ['css-loader',{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require('autoprefixer')({
overrideBrowserslist: ['> 1%', 'last 2 versions']
}),
require('postcss-pxtorem')({
rootValue: 15, // 换算的基数
unitPrecision: 5, // 允许REM单位增长到的十进制数字
propList: ['*'],
selectorBlackList: [], // 要忽略并保留为px的选择器
ignoreIdentifier: false, // 忽略单个属性的方法
replace: true, // 替换包含REM的规则,而不是添加回退。
mediaQuery: false, // 允许在媒体查询中转换px。
minPixelValue: 0 // 设置要替换的最小像素值(3px会被转rem)。默认 0
}),
]
}
}
}]
},
{
test: /\.less$/,
use: ['css-loader','less-loader',{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require('autoprefixer')({
overrideBrowserslist: ['> 1%', 'last 2 versions']
}),
require('postcss-pxtorem')({
rootValue: 15, // 换算的基数
unitPrecision: 5, // 允许REM单位增长到的十进制数字
propList: ['*'],
selectorBlackList: [], // 要忽略并保留为px的选择器
ignoreIdentifier: false, // 忽略单个属性的方法
replace: true, // 替换包含REM的规则,而不是添加回退。
mediaQuery: false, // 允许在媒体查询中转换px。
minPixelValue: 0 // 设置要替换的最小像素值(3px会被转rem)。默认 0
}),
]
}
}
}]
},
{
test: /\.(png|jpg|gif|svg)$/,
use:[
{
loader:'file-loader',
options: {
name: '[name]_[hash:8].[ext]'
}
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
gifsicle: {
interlaced: false,
},
// the webp option will enable WEBP
webp: {
quality: 75
}
}
}
],
},{
test: /\.(woff|woff2|eot|ttf|otf)$/,
loader: 'file-loader',
options: {
name: '[name]_[hash:8].[ext]'
}
}
]
},
...
常用插件
module.exports = {
...
plugins:[
new CleanWebpackPlugin(),
new VueLoaderPlugin(),
// 把第三方库暴露到window context下
new webpack.ProvidePlugin({
Vue: 'vue',
axios:'axios',
_: 'lodash'
}),
// 定义全局常量
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: 'true', // 支持vue解析options api
__VUE_PROD_DEVTOOLS__: 'false', // 禁用Vue的调试工具
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false' //禁用生产环境显示”水合“信息
}),
],
...
}
快速查找
module.exports = {
...
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
},
extensions: ['.js', '.vue', '.json']
},
...
}
生产环境
首先我们会引入基础配置,并在基础的配置上新增和开发换不一样的配置
* 输出
* 代码压缩
* 文件指纹
* tree shaking
* scope hosting
* 速度优化
* 体积优化
首先是输出output 用于指定
- output用于指定输出文件的存放地址和存放格式
module.export = {
...
output: {
filename: 'js/[name]_[chunkhash:8].bundle.js',
path: path.join(process.cwd(),'./app/public/dist/prod'),
publicPath: '/dist/prod',
crossOriginLoading: 'anonymous'
},
...
}
在指定 打包的模式为 production 这样 webpack会自动tree shaKing 将js没有使用的删除掉。
- mode
module.exports = {
...
mode: "production",
...
}
- devtool 大概设置项是下面几个组合 prod 大多直接使用 sourcemap
参数 | 参数解释 |
---|---|
eval | 打包后的模块都使用 eval() 执行,行映射可能不准;不产生独立的 map 文件 |
cheap | map 映射只显示行不显示列,忽略源自 loader 的 source map |
inline | 映射文件以 base64 格式编码,加在 bundle 文件最后,不产生独立的 map 文件 |
module | 增加对 loader source map 和第三方模块的映射 |
source-map | 生成独立map文件 |
module.exports = {
...
mode: "source-map",
...
}
- module
module.exports = {
module: {
rules:[
{
test: /\.css$/,
use: [ miniCssExtractPlugin.loader ]
}
]
},
...
}
- plugins 生产环境需要把css从文件中提取出来并进行压缩和资源文件的压缩
module.exports = {
...
plugins:[
new miniCssExtractPlugin({
filename: '[name]_[contenthash:8].css'
}),
new CompressionWebpackPlugin({
test: /\.(js|css|html|svg)$/,
threshold: 10240, // 只有大小大于该值的资源会被处理
deleteOriginalAssets: false, // 是否删除原文件
algorithm: 'gzip', // 压缩算法
minRatio: 0.7 // 只有压缩率小于这个值的资源才会被处理
}),
// 提取css的公共部分,有效利用缓存。非公共部分使用inline
new MiniCssExtractPlugin({
chunkFilename: 'css/[name]_[chunkhash:8].bundle.css'
}),
// 优化压缩css 资源
new CssMinimizerPlugin(),
// 浏览器请求资源时,不发送用户的身份凭证
new HtmlWebpackInjectAttributesPlugin({
corssorigin: 'anonymous'
})
],
...
}
- cache 将文件缓存到本地 让后续构建加快速度
module.exports = {
...
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
},
...
}
这里主要就要将 各种优化 说的最多的就是分包的策略。我这里主要把js文件打包成三种类型
- vender 第三方lib 基本不会改动,除非依赖版本升级
- common 业务组件代码的公共部分抽取出来,改动较少
- entry 不同页面entry 里的业务组件代码差异部分,会经常改动
目的,把改动和引用频率不一样的js区分出来,已达到更好利用浏览器缓存的效果
- optimization:
module.exports = {
...
optimization: {
minimizer: [
new CssMinimizerPlugin(),
new terserWebpackPlugin({
cache: true, //启用缓存加速构建
parallel: true, //利用多喝cpu的优势加快压缩速度
terserOptions: {
compress: {
pure_funcs: ['console.log']
}
}
})
],
splitChunks: {
chunks: 'all',// 对同步和异步模块都会分隔
maxAsyncRequests: 10, //每次异步加载的最大并行请求数
maxInitialRequests: 10,// 入口点的最大并行请求数
cacheGroups: {
vendor: {// 第三方依赖库
test: /[\\/]node_modules[\\/]/, //打包 node_modules中的文件
name: 'vendor', //模块名称
priority: 20, // 优先级,数字越大,优先级高
enforce: true, //强制执行
reuseExistingChunk: true, // 复用已有的公共chunk
},
common: { // 公共模块
name: 'common', //模块名称
minChunks: 2, //被两处引用即被归为公共模块
minSize : 1,// 最小分隔文件大小 1 byte
priority: 10,// 优先级
reuseExistingChunk: true, // 复用已有的公共chunk
}
}
}
},
...
}
最后就到了开发环境
开发环境
- mode 指定development 让webpack 减少开销
module.exports = {
mode: "development",
...
}
- devtool 同理参照 prod
module.exports = {
devtool: "eval-source-map",
...
}
这个是开发环境的重点,用于在开发的时候将目录的内容存放缓存,然后在请求地址的时候直接从缓存获取内容。并且可以热更新文件。
devServer 其实是起了一个服务器来监控目录文件是否有更新。如果有更新使用webpack-dev-plugin 来将文件缓存到内存里面,并且在浏览器获取某个文件的时候从内存中,将文件取出返回给浏览器。不使用硬盘文件存储。加快构建速度。并且当某个文件更新之后,使用webpack-hot-plugin插件来实现热更新。热更新其实就是网页和devServer启动的服务器之间构建一个WebSocket或者SSE,当webpack-dev-plugin将更新的文件缓存到内存之后,webpack-hot-plugin就回合页面通讯将更新的内容发送到网页 实现热更新功能
- devServer
module.exports = {
...
devServer: {
static: './dist',
port: 8080,
open: true,
},
...
}
- plugins 实现热更新需要的插件
module.exports = {
...
plugins:[
new webpack.HotModuleReplacementPlugin(),
]
...
}
到此,webpack 工程化打包的就已经结束了。其实,对于工程化,我们不需要记住module用什么,plugin用什么,而是要记住 我们用工程化做什么事需要做成的目的。然后根据目的选取使用的工具。这样我们后面不管使用webpack也好,vite和roullup,roulldown等构建工具都能举一反三。
备注引用: 抖音“哲玄前端”《大前端全栈实践》