@farris/cli 2.3.1 → 2.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/lib/commands/add-app.js +1 -1
  2. package/lib/commands/add-app.js.map +1 -1
  3. package/lib/commands/add-lib.js +1 -1
  4. package/lib/commands/add-lib.js.map +1 -1
  5. package/lib/commands/add-project.js +4 -3
  6. package/lib/commands/add-project.js.map +1 -1
  7. package/lib/commands/build.js +2 -2
  8. package/lib/commands/build.js.map +1 -1
  9. package/lib/commands/create-app-options.js +5 -3
  10. package/lib/commands/create-app-options.js.map +1 -1
  11. package/lib/commands/create-app.js +4 -4
  12. package/lib/commands/create-app.js.map +1 -1
  13. package/lib/commands/create.js +221 -160
  14. package/lib/commands/create.js.map +1 -1
  15. package/lib/commands/dev-serve.js +1 -1
  16. package/lib/commands/dev-serve.js.map +1 -1
  17. package/lib/common/constant.js +2 -0
  18. package/lib/common/constant.js.map +1 -1
  19. package/lib/common/get-vite-config.js +91 -5
  20. package/lib/common/get-vite-config.js.map +1 -1
  21. package/lib/common/project-layout.js +53 -12
  22. package/lib/common/project-layout.js.map +1 -1
  23. package/lib/common/safety.js +4 -4
  24. package/lib/common/safety.js.map +1 -1
  25. package/lib/common/verifier.js +14 -10
  26. package/lib/common/verifier.js.map +1 -1
  27. package/lib/common/workspace-contract.js +2 -0
  28. package/lib/common/workspace-contract.js.map +1 -1
  29. package/lib/index.js +10 -6
  30. package/lib/index.js.map +1 -1
  31. package/lib/plugins/mock.js +293 -0
  32. package/lib/plugins/mock.js.map +1 -0
  33. package/package.json +87 -87
  34. package/templates/create/.gitignore.tpl +1 -0
  35. package/templates/create/AGENTS.md +5 -0
  36. package/templates/create/package.json +1 -0
  37. package/templates/mobile/.gitignore.tpl +1 -0
  38. package/templates/mobile/farris.config.mjs +10 -0
  39. package/templates/mobile/mock/README.md +34 -0
  40. package/templates/mobile/package.json +1 -0
  41. package/templates/mobile/src/main.ts +39 -5
  42. package/templates/mobile/src/micro-app.d.ts +20 -0
  43. package/templates/mobile/src/router/index.ts +3 -2
  44. package/templates/web/.gitignore.tpl +1 -0
  45. package/templates/web/farris.config.mjs +10 -0
  46. package/templates/web/mock/README.md +34 -0
  47. package/templates/web/package.json +1 -0
  48. package/templates/web/src/App.vue +4 -0
  49. package/templates/web/src/main.ts +39 -5
  50. package/templates/web/src/micro/context.ts +26 -0
  51. package/templates/web/src/micro-app.d.ts +20 -0
  52. package/templates/web/src/router/index.ts +3 -2
@@ -1,6 +1,10 @@
1
1
  import { fileURLToPath, URL } from 'node:url';
2
2
 
3
3
  export default {
4
+ // 相对路径 base:微前端(IBP)与 iframe 嵌入场景都适用
5
+ base: './',
6
+ routerMode: 'hash',
7
+ // iife: IBP 微前端单文件产物(默认);如需普通 ESM 站点产物,执行 pnpm build:es
4
8
  format: "<%= format %>",
5
9
  // 输出目录 App模式默认值 './dist' Lib模式 './lib'
6
10
  // outDir: fileURLToPath(new URL('./dist', import.meta.url)),
@@ -19,6 +23,12 @@ export default {
19
23
  ],
20
24
  // 插件 默认值 [vue(), vueJsx()] 不要重复添加
21
25
  // plugins: [],
26
+ // 开发期 mock 默认开启:接口数据来自工程内 mock/ 目录,未命中的请求按下方 proxy 转发
27
+ // mock: true,
28
+ // 开发代理:未命中 mock 的接口转发到真实后端;联调时执行 pnpm dev --no-mock 可整体关闭 mock
29
+ // proxy: {
30
+ // '/api': { target: 'http://localhost:8080', changeOrigin: true }
31
+ // },
22
32
  // viteConfig 配置项
23
33
  viteConfig: {}
24
34
  };
@@ -0,0 +1,34 @@
1
+ # mock 目录
2
+
3
+ 开发期接口数据。一个领域一个文件,命名 `*.mock.ts`,默认导出路由数组:
4
+
5
+ ```ts
6
+ export default [
7
+ {
8
+ url: '/api/v1/users', // 必须与代码中的请求路径逐字一致
9
+ method: 'GET', // 默认 GET
10
+ timeout: 300, // 可选 模拟延迟毫秒
11
+ status: 200, // 可选 响应状态码
12
+ response: ({ query, params, body }) => ({
13
+ data: [],
14
+ total: 0
15
+ })
16
+ }
17
+ ];
18
+ ```
19
+
20
+ 规则:
21
+
22
+ - url 必须与代码中的请求路径逐字一致,见 `src/api/` 下的接口模块。
23
+ - 路径参数在 url 里用 `:参数名` 表示,例如 `/api/v1/users/:id`;查询参数不写进 url,在 response 的 `query` 里读取。
24
+ - mock 响应体必须能被工程的 HTTP 客户端正确消费,形状跟着客户端出口走。
25
+ - 同一 url 不允许在多个 mock 文件中重复定义,重复时开发服务启动即报错。
26
+ - mock 只用于开发期:不进构建产物,也不要在组件里散落假数据。
27
+
28
+ 行为:
29
+
30
+ - 命中 mock → 返回 mock 数据。
31
+ - 未命中 → 按 `farris.config.mjs` 的 `proxy` 配置转发真实后端。
32
+ - 未命中且未配代理的 `/api` 路径 → 返回 404 并提示,便于排查漏配。
33
+
34
+ 开关:在工程目录执行 `pnpm dev` 默认生效;`pnpm dev --no-mock` 或 `MOCK=none pnpm dev` 进入真实后端联调。
@@ -6,6 +6,7 @@
6
6
  "scripts": {
7
7
  "dev": "farris-cli dev",
8
8
  "build": "farris-cli build -ep",
9
+ "build:es": "farris-cli build --format es --outDir dist-es",
9
10
  "preview": "farris-cli preview",
10
11
  "test": "vitest run",
11
12
  "test:watch": "vitest",
@@ -1,13 +1,47 @@
1
- import { createApp } from 'vue';
1
+ import { createApp, type App as VueApp } from 'vue';
2
2
  import FarrisVue from '@farris/mobile-ui-vue';
3
3
  import '@farris/mobile-ui-vue/style.css';
4
4
  import App from './App.vue';
5
5
  import router from './router';<%= styleImport %>
6
6
  import { localResources } from './locales';
7
7
 
8
- const app = createApp(App);
8
+ let app: VueApp | null = null;
9
9
 
10
- app.use(FarrisVue, { basePath: '', locale: 'zh-CN', localResources });
11
- app.use(router);
10
+ /** 挂载应用(微前端宿主会调用 window.mount) */
11
+ function mount(): void {
12
+ if (app) {
13
+ return;
14
+ }
15
+ app = createApp(App);
16
+ app.use(FarrisVue, { basePath: '', locale: 'zh-CN', localResources });
17
+ app.use(router);
18
+ app.mount('#app');
12
19
 
13
- app.mount('#app');
20
+ // 微前端环境:按平台下发的 initialPath 定位菜单配置的页面(fullPath)
21
+ // hash 路由必须保留这段兜底;history 路由下平台 defaultPage 已能直接命中,这里是幂等兜底
22
+ if (window.__MICRO_APP_ENVIRONMENT__) {
23
+ const rawPath = window.microApp?.getData?.()?.initialPath;
24
+ if (typeof rawPath === 'string' && rawPath !== '') {
25
+ // 平台下发的 initialPath 通常不带前导斜杠(如 'form'),这里统一归一化
26
+ const initialPath = rawPath.startsWith('/') ? rawPath : '/' + rawPath;
27
+ if (initialPath !== '/') {
28
+ router.isReady().then(() => router.replace(initialPath)).catch(() => undefined);
29
+ }
30
+ }
31
+ }
32
+ }
33
+
34
+ /** 卸载应用(微前端宿主会调用 window.unmount) */
35
+ function unmount(): void {
36
+ app?.unmount();
37
+ app = null;
38
+ }
39
+
40
+ // 微前端(micro-app)生命周期契约:必须暴露到 window
41
+ window.mount = mount;
42
+ window.unmount = unmount;
43
+
44
+ // 独立运行(浏览器直接访问 / iframe 嵌入)时自动挂载
45
+ if (!window.__MICRO_APP_ENVIRONMENT__) {
46
+ mount();
47
+ }
@@ -0,0 +1,20 @@
1
+ export {};
2
+
3
+ declare global {
4
+ interface Window {
5
+ /** micro-app 子应用环境标记(由宿主注入) */
6
+ __MICRO_APP_ENVIRONMENT__?: boolean;
7
+ /** 宿主注入的 micro-app API */
8
+ microApp?: {
9
+ addGlobalDataListener?: (callback: (data: any) => void, immediately?: boolean) => void;
10
+ setGlobalData?: (data: Record<string, any>) => void;
11
+ getData?: () => Record<string, any> | null;
12
+ router?: {
13
+ getBaseAppRouter?: () => any;
14
+ };
15
+ };
16
+ /** 微前端生命周期:宿主会调用这两个方法 */
17
+ mount?: () => void;
18
+ unmount?: () => void;
19
+ }
20
+ }
@@ -1,4 +1,4 @@
1
- import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';
1
+ import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router';
2
2
 
3
3
  // 生成器(farris-cli generate view --with-route)依赖下面的 routes 数组赋值形式;
4
4
  // 改动结构会导致路由无法自动注册。
@@ -16,7 +16,8 @@ export const routes: RouteRecordRaw[] = [
16
16
  ];
17
17
 
18
18
  const router = createRouter({
19
- history: createWebHistory(import.meta.env.BASE_URL),
19
+ // hash 路由:微前端(IBP)下子应用 location 由宿主接管,hash 模式最稳妥
20
+ history: createWebHashHistory(),
20
21
  routes
21
22
  });
22
23
 
@@ -11,6 +11,7 @@ node_modules
11
11
  package-lock.json
12
12
  .DS_Store
13
13
  dist
14
+ dist-es
14
15
  dist-ssr
15
16
  coverage
16
17
  *.local
@@ -1,6 +1,10 @@
1
1
  import { fileURLToPath, URL } from 'node:url';
2
2
 
3
3
  export default {
4
+ // 相对路径 base:微前端(IBP)与 iframe 嵌入场景都适用
5
+ base: './',
6
+ routerMode: 'hash',
7
+ // iife: IBP 微前端单文件产物(默认);如需普通 ESM 站点产物,执行 pnpm build:es
4
8
  format: "<%= format %>",
5
9
  // 输出目录 App模式默认值 './dist' Lib模式 './lib'
6
10
  // outDir: fileURLToPath(new URL('./dist', import.meta.url)),
@@ -19,6 +23,12 @@ export default {
19
23
  ],
20
24
  // 插件 默认值 [vue(), vueJsx()] 不要重复添加
21
25
  // plugins: [],
26
+ // 开发期 mock 默认开启:接口数据来自工程内 mock/ 目录,未命中的请求按下方 proxy 转发
27
+ // mock: true,
28
+ // 开发代理:未命中 mock 的接口转发到真实后端;联调时执行 pnpm dev --no-mock 可整体关闭 mock
29
+ // proxy: {
30
+ // '/api': { target: 'http://localhost:8080', changeOrigin: true }
31
+ // },
22
32
  // viteConfig 配置项
23
33
  viteConfig: {}
24
34
  };
@@ -0,0 +1,34 @@
1
+ # mock 目录
2
+
3
+ 开发期接口数据。一个领域一个文件,命名 `*.mock.ts`,默认导出路由数组:
4
+
5
+ ```ts
6
+ export default [
7
+ {
8
+ url: '/api/v1/users', // 必须与代码中的请求路径逐字一致
9
+ method: 'GET', // 默认 GET
10
+ timeout: 300, // 可选 模拟延迟毫秒
11
+ status: 200, // 可选 响应状态码
12
+ response: ({ query, params, body }) => ({
13
+ data: [],
14
+ total: 0
15
+ })
16
+ }
17
+ ];
18
+ ```
19
+
20
+ 规则:
21
+
22
+ - url 必须与代码中的请求路径逐字一致,见 `src/api/` 下的接口模块。
23
+ - 路径参数在 url 里用 `:参数名` 表示,例如 `/api/v1/users/:id`;查询参数不写进 url,在 response 的 `query` 里读取。
24
+ - mock 响应体必须能被工程的 HTTP 客户端正确消费,形状跟着客户端出口走。
25
+ - 同一 url 不允许在多个 mock 文件中重复定义,重复时开发服务启动即报错。
26
+ - mock 只用于开发期:不进构建产物,也不要在组件里散落假数据。
27
+
28
+ 行为:
29
+
30
+ - 命中 mock → 返回 mock 数据。
31
+ - 未命中 → 按 `farris.config.mjs` 的 `proxy` 配置转发真实后端。
32
+ - 未命中且未配代理的 `/api` 路径 → 返回 404 并提示,便于排查漏配。
33
+
34
+ 开关:在工程目录执行 `pnpm dev` 默认生效;`pnpm dev --no-mock` 或 `MOCK=none pnpm dev` 进入真实后端联调。
@@ -6,6 +6,7 @@
6
6
  "scripts": {
7
7
  "dev": "farris-cli dev",
8
8
  "build": "farris-cli build -ep",
9
+ "build:es": "farris-cli build --format es --outDir dist-es",
9
10
  "preview": "farris-cli preview",
10
11
  "test": "vitest run",
11
12
  "test:watch": "vitest",
@@ -1,5 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { RouterLink, RouterView } from 'vue-router';
3
+ import { setupMicroAppContext } from './micro/context';
4
+
5
+ // 微前端环境:订阅主应用下发的上下文(语言 / 主题)
6
+ setupMicroAppContext();
3
7
  </script>
4
8
 
5
9
  <template>
@@ -1,13 +1,47 @@
1
- import { createApp } from 'vue';
1
+ import { createApp, type App as VueApp } from 'vue';
2
2
  import FarrisVue from '@farris/ui-vue';
3
3
  import '@farris/ui-vue/index.css';
4
4
  import App from './App.vue';
5
5
  import router from './router';<%= styleImport %>
6
6
  import { localResources } from './locales';
7
7
 
8
- const app = createApp(App);
8
+ let app: VueApp | null = null;
9
9
 
10
- app.use(FarrisVue, { basePath: '', locale: 'zh-CN', localResources });
11
- app.use(router);
10
+ /** 挂载应用(微前端宿主会调用 window.mount) */
11
+ function mount(): void {
12
+ if (app) {
13
+ return;
14
+ }
15
+ app = createApp(App);
16
+ app.use(FarrisVue, { basePath: '', locale: 'zh-CN', localResources });
17
+ app.use(router);
18
+ app.mount('#app');
12
19
 
13
- app.mount('#app');
20
+ // 微前端环境:按平台下发的 initialPath 定位菜单配置的页面(fullPath)
21
+ // hash 路由必须保留这段兜底;history 路由下平台 defaultPage 已能直接命中,这里是幂等兜底
22
+ if (window.__MICRO_APP_ENVIRONMENT__) {
23
+ const rawPath = window.microApp?.getData?.()?.initialPath;
24
+ if (typeof rawPath === 'string' && rawPath !== '') {
25
+ // 平台下发的 initialPath 通常不带前导斜杠(如 'form'),这里统一归一化
26
+ const initialPath = rawPath.startsWith('/') ? rawPath : '/' + rawPath;
27
+ if (initialPath !== '/') {
28
+ router.isReady().then(() => router.replace(initialPath)).catch(() => undefined);
29
+ }
30
+ }
31
+ }
32
+ }
33
+
34
+ /** 卸载应用(微前端宿主会调用 window.unmount) */
35
+ function unmount(): void {
36
+ app?.unmount();
37
+ app = null;
38
+ }
39
+
40
+ // 微前端(micro-app)生命周期契约:必须暴露到 window
41
+ window.mount = mount;
42
+ window.unmount = unmount;
43
+
44
+ // 独立运行(浏览器直接访问 / iframe 嵌入)时自动挂载
45
+ if (!window.__MICRO_APP_ENVIRONMENT__) {
46
+ mount();
47
+ }
@@ -0,0 +1,26 @@
1
+ import { LocaleService } from '@farris/ui-vue';
2
+
3
+ /**
4
+ * 订阅主应用通过 microApp.setGlobalData 下发的运行上下文(语言 / 主题)。
5
+ * 仅在微前端(micro-app)环境中生效,独立运行时不做任何处理。
6
+ */
7
+ export function setupMicroAppContext(): void {
8
+ if (!window.__MICRO_APP_ENVIRONMENT__) {
9
+ return;
10
+ }
11
+
12
+ window.microApp?.addGlobalDataListener?.((data) => {
13
+ if (!data) {
14
+ return;
15
+ }
16
+ // 语言:主应用下发 language,同步 Farris 组件库语言
17
+ const i18n = (LocaleService as any)?.i18n;
18
+ if (typeof data.language === 'string' && i18n?.global?.locale) {
19
+ i18n.global.locale.value = data.language;
20
+ }
21
+ // 主题:主应用下发 theme,写入根元素供主题样式使用
22
+ if (typeof data.theme === 'string') {
23
+ document.documentElement.setAttribute('data-theme', data.theme);
24
+ }
25
+ }, true);
26
+ }
@@ -0,0 +1,20 @@
1
+ export {};
2
+
3
+ declare global {
4
+ interface Window {
5
+ /** micro-app 子应用环境标记(由宿主注入) */
6
+ __MICRO_APP_ENVIRONMENT__?: boolean;
7
+ /** 宿主注入的 micro-app API */
8
+ microApp?: {
9
+ addGlobalDataListener?: (callback: (data: any) => void, immediately?: boolean) => void;
10
+ setGlobalData?: (data: Record<string, any>) => void;
11
+ getData?: () => Record<string, any> | null;
12
+ router?: {
13
+ getBaseAppRouter?: () => any;
14
+ };
15
+ };
16
+ /** 微前端生命周期:宿主会调用这两个方法 */
17
+ mount?: () => void;
18
+ unmount?: () => void;
19
+ }
20
+ }
@@ -1,4 +1,4 @@
1
- import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';
1
+ import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router';
2
2
 
3
3
  // 生成器(farris-cli generate view --with-route)依赖下面的 routes 数组赋值形式;
4
4
  // 改动结构会导致路由无法自动注册。
@@ -16,7 +16,8 @@ export const routes: RouteRecordRaw[] = [
16
16
  ];
17
17
 
18
18
  const router = createRouter({
19
- history: createWebHistory(import.meta.env.BASE_URL),
19
+ // hash 路由:微前端(IBP)下子应用 location 由宿主接管,hash 模式最稳妥
20
+ history: createWebHashHistory(),
20
21
  routes
21
22
  });
22
23