以 Cordis 为插件宿主的前端全栈框架
路由、HTML、打包配置、服务端和浏览器运行时,都由插件在 ctx 上注册。修改一个插件的配置,只有它会重载;卸载一个插件,它注册过的一切随之撤回。
.extension('.tsx', '.jsx')react.layout('plugin-layout/Layout.tsx')plugin-layout.head(<meta name="generator">)./plugin.ts.plugin(react())react.plugin(tailwindcss())plugin-tailwindcss.entry('src/app.tsx')preset.file('entry.tsx')react.entry('@cormijs/plugin-layout/client')plugin-layout.import('tailwindcss')plugin-tailwindcss› 点击 cormi.yml 中的条目,停用或启用一个插件。
这是 dev 模式下修改 cormi.yml 时发生的事:停用一个条目,loader 写入 disabled: true 并卸载该插件。停用 @cormijs/vite 试试:依赖 ctx.vite 的 React 渲染器与 Tailwind 转入 pending,直到打包器回来——换一个打包器,也只是换掉这一行。
声明依赖,在 ctx 上注册,清理交给 cordis。应用自己的代码和官方插件用的是同一套写法。
plugin.tsNode 端,写进 cormi.yml:- name: ./plugin.tsimport { type Context, Schema } from '@cormijs/cormi'
export const name = 'generator-meta'
export const inject = ['html']
export const Config = Schema.object({
generator: Schema.string().default('cormi'),
})
export function apply(ctx: Context, config: { generator: string }) {
ctx.html.head({
tag: 'meta',
attrs: { name: 'generator', content: config.generator },
})
}src/app.tsx浏览器端,按约定自动挂载import type { Context } from '@cormijs/react/client'
import { StrictMode } from 'react'
export default (ctx: Context) => {
ctx.on('router/change', (location) => {
document.title = `Cormi · ${location.pathname}`
})
ctx.wrap(({ children }) => <StrictMode>{children}</StrictMode>)
}injectctx.html 就绪之前插件不会加载,加载顺序由依赖决定,不分阶段。Configcormi.yml 中的配置先经 Schema 校验、填充默认值。改动配置只会重载这一个插件。ctx.html.head()ctx.on() · ctx.wrap()Node 端的插件构建应用,浏览器端的插件运行应用。两边是同一种写法,中间只有一座桥:Node 插件按包名注册浏览器插件,配置序列化后传过去。
cormictx.app.entry(
'@scope/plugin-foo/client',
config)cormi/client · @cormijs/react/client 核心服务在根上提供,名字带 $,与 Node 端的 ctx.app、ctx.router 互不冲突——koishi 的客户端也是这样做的。
每个都是普通的 cordis 插件:写进 cormi.yml 就启用,删掉或标记 disabled 就卸载。
plugin-modelsrc/models 中的 hook 即全局模型,useModel()plugin-initial-state运行时插件提供初始状态,useInitialState()plugin-request基于 fetch 的 request() 与 useRequest()plugin-react-query每个应用一个 QueryClientplugin-dvadva-core 与 dva-loading,可选 immerplugin-valtiodefineStore(),dev 下接入 DevToolsplugin-antd主题、语言,服务端渲染时抽取样式plugin-layout由路由生成菜单和面包屑的布局plugin-tailwindcssTailwind CSS v4plugin-unocssUnoCSS,没有配置文件时用预设plugin-styled-componentsThemeProvider 与 SSR 样式plugin-mockmock/** 中的接口,优先于真实 APIplugin-export-static构建后把路由预渲染为 HTMLplugin-legacy为不支持模块脚本的浏览器构建plugin-mpa多页应用,每页一个 HTMLplugin-analyticsGA4 与百度统计,导航自动上报plugin-lintcormi lint,自带默认规则plugin-federation子包单独部署,底座按 URL 汇集成一个应用plugin-mf模块联邦,基于 @module-federation/viteplugin-qiankunqiankun 主应用与 Vite 构建的子应用能力基本对得上,形态不同:umi 的插件 API 在 Cormi 里是服务上的注册方法和类型化事件。
| umi | Cormi |
|---|---|
api.addHTMLMetas / addHTMLScripts | ctx.html.head(tag) / ctx.html.body(tag) |
api.addRuntimePlugin | ctx.app.entry(module, config),浏览器端用 ctx.wrap() 与事件 |
api.modifyRoutes / addLayouts | ctx.router.patch(fn) / ctx.router.layout(file) |
api.modifyViteConfig | ctx.vite.configure(fn) / ctx.vite.plugin(plugin) |
api.onBuildComplete | ctx.on('build/after', fn) |
api.restartServer() | 不需要:注册一变化,产物就重新计算 |
Cormi 还在 0.x,尚未发布到 npm。现在体验它,最快的方式是运行仓库里的示例。
git clone https://github.com/cormijs/cormi.git
cd cormi && pnpm install && pnpm buildcd examples/basic
pnpm devdev server 运行时,把 cormi.yml 里 ./plugin.ts 的 generator 改成别的值。日志里只有这个插件重载,页面的 meta 标签随之更新。