2025.01 zmi创建服务流程

181 阅读4分钟

最近公司新出了个zmi框架,是仿的umi框架,umi是react应用框架,我们主要是以vue应用为主。

在此记录主要以学习思想为主

封装zmi框架的主要目的:

  • 技术收敛(例如:依赖的组件库、构建工具(pc webpack+vue2)、(h5 vite+vue3))
  • 通用业务逻辑收敛(例如:soo登录拦截、通用水印、zcat错误上报、统一请求等)

我的理解:

zmi相当于是一个脚手架工具能够快速帮我们按照公司规范搭建项目,防止我们重复写代码,例如,我们拿到一个项目之后虽然有脚手架工具的加持,但是还是需要先封装统一的路由、菜单、缓存、登录拦截等等这一系列操作后才能进入业务开发。

zmi就是能快速帮我们搞定以上内容,让我们直接进入业务开发。

技术栈

lerna rollup webpack typescript

整体目录结构

image-20250119173405311.png

从上面流程图可以看出来,主要模块为zmicore模块,其他都是一些插件相关的还有一些预置的内容

运行流程

使用zmi后的项目,通过npm run dev启动,主要运行的是zmi serve命令,说明这个是一个全局可执行的命令。zmi serve做了什么可以参考上面的bin主要就是在node_modules中查找到.bin文件夹下的zmi文件进行执行,可以看到最终执行的文件是zmi\bin\zmi.js

image-20250119190227326.png

我们执行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,
})

预设信息内容如下:

image-20250119190227326.png

以注册serve为例:zmi.config.js配置了eMicrotrue,说明开启了门户微应用,走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时后面的命令到底做了什么

大项目时,项目结构怎么划分,需要考虑哪些东西?我只是粗略看了下整体的逻辑,后面还需要慢慢深入了解的