工程化思想
业务代码 -> 解析引擎 -> 产物文件
解析引擎:将业务文件进行处理生成产物文件
- 解析编译:编译文件,如.vue => vue-loader 以及其他loader 输出xx.js xx.css, xx.tpl
- 压缩优化:生产环境中压缩混合js,压缩css,输出文件产物;开发环境中,资源注入模版中,热更新
- 模块分包:所有的文件在编辑后打包到一个bundle文件中太大,不利于复用,所以需要将打包内容按照内容或者其他规则进行分别打包
webpack配置
在webpack配置文件中,按照生产和开发环境进行分别配置,先提取出公共配置,再分别配置。 配置项:
- entry:由于有多个入口,所以需要动态构造入口路径
- module:各种lodaer,模块解析配置,决定了要加载哪些模块,以及用什么方式去加载;loader的运行顺序是从后向前,如[style-loader, css-loader]先运行css-loader再运行style-loader
- output:打包后的产物要放在那个路径下,生产和开发环境配置不同
- resolve:配置模块解析的具体行为,可以定义扩展名列表,别名配置,给某个相对路径起个别名,方便后续引入时使用
- plugins:插件,在打包编译的过程中可以执行任务优化等
- optimization:配置打包输出优化的配置,代码分割,缓存,treeshaking,压缩等优化
wepack公共配置文件 webpack.base.js
const path = require('path');
const webpack = require('webpack');
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 动态构造 pageEntries htmlWebpackPluginList
const pageEntries = {};
const htmlWebpackPluginList = [];
// 获取app/pages目录下所有入口文件(entry.xx.js)
const entryList = path.resolve(process.cwd(), './app/pages/**/entry.*.js');
glob.sync(entryList).forEach((file) => {
const entryName = path.basename(file, '.js');
// 构造entry
pageEntries[entryName] = file;
// 构造最终渲染的页面文件
htmlWebpackPluginList.push(
// htmlWebpackPlugin 辅助注入打包后的 bundle 文件到tpl文件中
new HtmlWebpackPlugin({
// 产物(最终模版)输出路径
filename: path.resolve(
process.cwd(),
'./app/public/dist/',
`${entryName}.tpl`
),
// 指定要使用的模版文件
template: path.resolve(process.cwd(), './app/view/entry.tpl'),
// 要注入的代码块
chunks: [entryName],
})
);
});
/**
* webpack基础配置
*/
module.exports = {
// 入口配置
entry: pageEntries,
// 模块解析配置(决定了要加载解析哪些模块,以及用什么方式去解析)
// loader
module: {
rules: [
{
test: /\.vue$/,
use: { loader: 'vue-loader' },
},
{
test: /\.js$/,
include: [
// 只对业务代码进行babel,加快webpack打包速度
path.resolve(process.cwd(), './app/pages'),
],
use: { loader: 'babel-loader' },
},
{
test: /\.(png|jpe?g]|gif)(\?.+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 300,
esModule: false,
},
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader'],
},
{
test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/,
use: 'file-loader',
},
],
},
// 产物输出路径,因为开发和生产环境输出不一致,应在各自环境配置中配置
output: {},
// 配置模块解析的具体行为(定义 webpack 在打包时,如何找到并解析具体模块的路径)
resolve: {
// 如在import时,不写后缀 import page from '/page' 按照配置的后缀顺序去找文件
extensions: ['.js', '.vue', ' .less', '.css'],
alias: {
$pages: path.resolve(process.cwd(), './app/pages'),
$common: path.resolve(process.cwd(), './app/pages/common'),
$widgets: path.resolve(process.cwd(), './app/pages/widgets'),
$store: path.resolve(process.cwd(), './app/pages/store'),
},
},
// 配置webpack插件
plugins: [
// 处理.vue文件,这个插件是必须的
// 它的功能是将你定义过的其他规则复制并应用到.vue文件里
// 例如,如果有一条规则 /.\js$/的规则,那么他会应用到.vue文件中的<script>板块中
new VueLoaderPlugin(),
// 把第三方库暴露到window context下,如window.vue
new webpack.ProvidePlugin({
Vue: 'vue',
axios: 'axios',
lodash: 'lodash',
}),
// 定义全局常量 允许创建一个在编译时可配置的全局常量
// 例如vue有几个全局变量,打包时将常量设置一下
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: 'true', // 支持vue 解析 option API
__VUE_PROD_DEVTOOLS__: 'false', // 禁用 Vue 调试工具
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false', // 禁用生产环境显示‘水合’信息
}),
// 构造最终渲染的页面模版
...htmlWebpackPluginList,
],
// 配置打包输出优化(配置代码分割,模块合并,缓存,TreeShaking,压缩等优化策略}
optimization: {
/**
* 把 js 文件打包成三种类型
* 1、vendor:第三方lib库,基本不会改动,除非依赖升级
* 2、common:业务组件代码的公共部分抽取出来,改动较少
* 3、entry.{page};不同页面 entry 里的业务组件代码的差异部分,会经常改动
* 目的:把改动和引用频率不一样的js区分出来,以达到更好利用浏览器缓存的效果
*/
splitChunks: {
chunks: 'all', // 对同步和异步代码都进行分割
maxAsyncRequests: 10, // 每次异步架子啊的最大并行请求数
maxInitialRequests: 10, // 入口点的最大并行请求数
//具体打包的规则
cacheGroups: {
vendor: {
//第三方库
test: /[\\/]node_modules[\\/]/,
name: 'vendor', // 模块名称
priority: 20, // 优先级,数字越大,优先级越高,默认为0(一个有满足这个有满足下边的规则,按照设定的优先级来处理)
enforce: true, // 强制执行
reuseExistingChunk: true, // 复用已有的公共chunk
},
common: {
// 公共模块
name: 'common', // 模块名称
minChunks: 2, // 被两处引用即被归为公共模块
minSize: 1, // 最小分割文件大小(1byte)文件超过这个值就被分割
priority: 10, // 优先级
reuseExistingChunk: true, // 复用已有的公共chunk
},
},
},
// 将webpack运行时生成的代码打包到runtime.js
runtimeChunk: true,
},
};
Happypack 可开启多线程打包,目前可以使用thread-loader替代happypack进行多线程打包,要注意使用方法。
常用插件:
- CleanWebpackPlugin:每次build前清空指定目录
- MiniCssExtractPlugin:提取css的公共部分,有效利用缓存(打包到一个css单独文件,在tpl中以link形式引入)
- CssMinizerPlugin:优化并压缩css资源
- TerserWebpackPlugin:使用该插件的并发和缓存,提升压缩阶段的性能
- VueLoaderPlugin:处理.vue文件,这个插件式vue开发必须的,功能是将定义过的功能复制并应用到.vue文件里,如js相关规则会应用到vue文件的script模块中
- HtmlWebpackPlugin:将打包后的buldle模块注入到相应模版中
- Webpack.HotModuleReplacementPlugin:开发模式下使用,HMR用于实现热模块替换,热模块允许在应用程序运行时替换模块,极大提高开发效率,因为能让应用程序一直保持运行状态
开发环境实现热更新
开发环境打包:为了方便开发,生产环境需要配置热更新模块。开发环境除了最终产物(即koa服务要渲染的tpl文件,其他的bundle包都会以代码片段的形式存储在express服务的内存中)
- 使用express服务和webpack-dev-middleware 以及webpack-hot-middleware两个模块来实现模块热更新。
- devMiddleware用来监控业务文件的改动,用户改动保存后,有改动后就重新编译构建相应的代码到这个express服务的内存中。
- hotMiddleware用eventsource的方式来通知客户端代码有改动,客户端即浏览器就会再次从内存中请求资源,进而刷新页面。 开发阶段的ertry配置需要加入hmr更新入口
Object.keys(baseConfig.entry).forEach((v) => {
// 第三方包不作为 hmr 入口
if (v !== 'vendor') {
baseConfig.entry[v] = [
// 主入口文件
baseConfig.entry[v],
// hmr 更新入口,官方指定的hmr路径
`webpack-hot-middleware/client?path=http://${DEV_SERVER_CONFIG.HOST}:${DEV_SERVER_CONFIG.PORT}/${DEV_SERVER_CONFIG.HMR_PATH}&timeout=${DEV_SERVER_CONFIG.TIMEOUT}`,
];
}
});
提取boot.js文件,将vue应用注册挂载动作提到文件中:
因为会有多个页面入口,入口文件挂载vue操作可提出到一个文件中,公共的引入可以放入里边,引入需要的ElementUI、pinia、路由相关注册等,注意,需要将这些都引入后,再挂载到root根元素下,否则这些应用在页面刷新后,不能正常应用。
封装curl方法,使用axios进行请求:
构造请求时需要用到的参数; 请求后得到请求结果的处理,catch错误处理,业务错误处理,成功处理,都是返回一个Promise。