最近公司新出了个zmi框架,是仿的umi框架,umi是react应用框架,我们主要是以vue应用为主。
在此记录主要以学习思想为主
封装zmi框架的主要目的:
- 技术收敛(例如:依赖的组件库、构建工具(pc webpack+vue2)、(h5 vite+vue3))
- 通用业务逻辑收敛(例如:soo登录拦截、通用水印、zcat错误上报、统一请求等)
我的理解:
zmi相当于是一个脚手架工具能够快速帮我们按照公司规范搭建项目,防止我们重复写代码,例如,我们拿到一个项目之后虽然有脚手架工具的加持,但是还是需要先封装统一的路由、菜单、缓存、登录拦截等等这一系列操作后才能进入业务开发。
zmi就是能快速帮我们搞定以上内容,让我们直接进入业务开发。
技术栈
lerna rollup webpack typescript
整体目录结构
从上面流程图可以看出来,主要模块为zmi和core模块,其他都是一些插件相关的还有一些预置的内容
运行流程
使用zmi后的项目,通过npm run dev启动,主要运行的是zmi serve命令,说明这个是一个全局可执行的命令。zmi serve做了什么可以参考上面的bin主要就是在node_modules中查找到.bin文件夹下的zmi文件进行执行,可以看到最终执行的文件是zmi\bin\zmi.js
我们执行zmi serve就是启动一个服务,和日常的webapck vite是一样的
"scripts": {
"dev": "zmi serve",
"serve": "zmi serve",
"build": "zmi build",
"analyse": "zmi build --report"
}
入口文件zmi.js,首先校验了node版本,要求必须版本>16.17.1,接着执行dist/cli.js文件
#!/usr/bin/env node
const semver = require('semver')
if (semver.lt(process.version, '16.17.1')) {
console.error('Node.js版本必须至少为v16.17.1,但当前版本为' + process.version)
process.exit(1)
}
require('../dist/cli')
dist/cli.js文件,在这个文件中看到了关键代码,首先是引入zmi-core,再是创建一个服务,再是启动服务,接下来看下zmi-core暴露出来是个啥
'use strict';
var zmiCore = require('@zto/zmi-core');
const service = new zmiCore.Service();
// 若执行命令为zmi serve,则command为serve,
const command = args._[0];
// ... 一些参数处理逻辑
service.run({ name: command, args });
packages\core\main.ts
import Service from './lib/service'
// ... 一些其他的引入
export { Service }
packages\core\lib\service.ts 上面代码创建了一个service对象,并执行其run 方法,因此service.ts确定是用来创建服务的。
run方法,先进行应用初始化,再执行
class Service {
logger!: Logger
commands: Record<string, Command> = {}
hooks: Record<string, Hook[]> = {}
plugins: Plugin[] = []
presets: NonNullable<IOpts['presets']> = []
webpackChainFns: { (config: Config): void }[] = []
appData = {
paths,
env: 'development',
userConfig: {} as UserConfig,
pkg: {} as Record<string, any>,
presetsName: 'micro' as 'pc' | 'h5' | 'micro' | 'docs',
debug: false,
args: {} as Record<string, any>,
commandName: '' as string,
}
constructor(opts?: IOpts) {
this.plugins = opts?.plugins || [] // 所有的插件
}
async run(args: Record<string, any>) {
await this.init(args)
return this.runCommand(args)
}
async init({ name, args }: Record<string, any>) {
// 应用初始化&插件注册等。。。
// 加载一些预设的东西
this.setPresets();
}
// 入参为执行service.run方法时传入的参数 例如:执行的命令是npm run dev,真正执行的命令是zmi serve,
runCommand({ name, args }: Record<string, any>) {
// 这些命令是何时注入的?
const command = this.commands[name]
command.fn(args)
}
// 加载一些预设, appData.userConfig的为项目根目录下的配置文件zmi.config.js,读取presets配置信息
setPresets() {
try {
// "D:\workspace\huiyan\huiyan-policyonline-static\node_modules\@zto\zmi-presets-pc\dist\index.js" path的值为
const path = this.appData.userConfig.presets;
// eslint-disable-next-line @typescript-eslint/no-var-requires
const presetsExport = require(path);
// 注册预设信息
const presetsPkg = typeof presetsExport === 'function' ? presetsExport(this.appData) : presetsExport;
this.logger.info(`正在加载presets: ${presetsPkg.name}`);
this.presets.push(presetsPkg);
this.appData.presetsName = presetsPkg.name;
}
catch (e) {
const error = e;
throw new Error(`load preset失败: ${error.message}`);
}
}
}
export default Service
zmi.config.js
const { defineConfig } = require('@xxx/zmi')
module.exports = defineConfig({
appId:'my-appid',
presets: require.resolve('zmi-presets-pc'),
// 开启门户微应用
eMicro: true,
})
预设信息内容如下:
以注册serve为例:zmi.config.js配置了eMicro为true,说明开启了门户微应用,走microPresets逻辑
var serve$1 = require('./eMicro/commands/serve.js');
const microPresets = {
name: 'micro',
plugins: [{key: 'serveCommand', apply: serve$1}]
}
// 根据应用类型判断取microPresets
export default (appData: Service['appData']) => {
return appData.userConfig.eMicro ? microPresets : pcPresets
}
commands的结果就为microPresets的映射内容
{
name: 'serve',
description: '启动服务命令',
fn: async function(args){}
}
以上就是执行zmi serve发生了什么的整体流程。
对于怎么注册预设信息的以及注册预设信息时做了什么后面继续...
学到了什么?
当执行npm run dev时后面的命令到底做了什么
大项目时,项目结构怎么划分,需要考虑哪些东西?我只是粗略看了下整体的逻辑,后面还需要慢慢深入了解的