@lark-apaas/coding-template-vite-react 0.1.0-alpha.1 → 0.1.0
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.
- package/package.json +1 -1
- package/template/.spark_project +12 -0
- package/template/README.md +10 -10
- package/template/components.json +1 -1
- package/template/package.json +5 -3
- package/template/scripts/dev.mjs +125 -0
- package/template/shared/capabilities/README.md +5 -0
- package/template/shared/static/README.md +16 -0
- package/template/{client/src → src}/hooks/use-mobile.ts +4 -3
- package/template/{client/src → src}/index.tsx +9 -2
- package/template/tsconfig.app.json +3 -3
- package/template/tsconfig.json +1 -1
- package/template/vite.config.ts +1 -2
- package/template/package-lock.json +0 -10533
- package/template/shared/types.ts +0 -1
- /package/template/{client/index.html → index.html} +0 -0
- /package/template/{client/public → public}/favicon.svg +0 -0
- /package/template/{client/public → public}/icons.svg +0 -0
- /package/template/{client/src → src}/app.tsx +0 -0
- /package/template/{client/src → src}/components/layout.tsx +0 -0
- /package/template/{client/src → src}/components/ui/accordion.tsx +0 -0
- /package/template/{client/src → src}/components/ui/alert-dialog.tsx +0 -0
- /package/template/{client/src → src}/components/ui/alert.tsx +0 -0
- /package/template/{client/src → src}/components/ui/aspect-ratio.tsx +0 -0
- /package/template/{client/src → src}/components/ui/avatar.tsx +0 -0
- /package/template/{client/src → src}/components/ui/badge.tsx +0 -0
- /package/template/{client/src → src}/components/ui/breadcrumb.tsx +0 -0
- /package/template/{client/src → src}/components/ui/button-group.tsx +0 -0
- /package/template/{client/src → src}/components/ui/button.tsx +0 -0
- /package/template/{client/src → src}/components/ui/calendar.tsx +0 -0
- /package/template/{client/src → src}/components/ui/card.tsx +0 -0
- /package/template/{client/src → src}/components/ui/carousel.tsx +0 -0
- /package/template/{client/src → src}/components/ui/chart.tsx +0 -0
- /package/template/{client/src → src}/components/ui/checkbox.tsx +0 -0
- /package/template/{client/src → src}/components/ui/collapsible.tsx +0 -0
- /package/template/{client/src → src}/components/ui/command.tsx +0 -0
- /package/template/{client/src → src}/components/ui/context-menu.tsx +0 -0
- /package/template/{client/src → src}/components/ui/dialog.tsx +0 -0
- /package/template/{client/src → src}/components/ui/drawer.tsx +0 -0
- /package/template/{client/src → src}/components/ui/dropdown-menu.tsx +0 -0
- /package/template/{client/src → src}/components/ui/empty.tsx +0 -0
- /package/template/{client/src → src}/components/ui/field.tsx +0 -0
- /package/template/{client/src → src}/components/ui/form.tsx +0 -0
- /package/template/{client/src → src}/components/ui/hover-card.tsx +0 -0
- /package/template/{client/src → src}/components/ui/image.tsx +0 -0
- /package/template/{client/src → src}/components/ui/input-group.tsx +0 -0
- /package/template/{client/src → src}/components/ui/input-otp.tsx +0 -0
- /package/template/{client/src → src}/components/ui/input.tsx +0 -0
- /package/template/{client/src → src}/components/ui/item.tsx +0 -0
- /package/template/{client/src → src}/components/ui/kbd.tsx +0 -0
- /package/template/{client/src → src}/components/ui/label.tsx +0 -0
- /package/template/{client/src → src}/components/ui/menubar.tsx +0 -0
- /package/template/{client/src → src}/components/ui/native-select.tsx +0 -0
- /package/template/{client/src → src}/components/ui/navigation-menu.tsx +0 -0
- /package/template/{client/src → src}/components/ui/pagination.tsx +0 -0
- /package/template/{client/src → src}/components/ui/popover.tsx +0 -0
- /package/template/{client/src → src}/components/ui/progress.tsx +0 -0
- /package/template/{client/src → src}/components/ui/radio-group.tsx +0 -0
- /package/template/{client/src → src}/components/ui/resizable.tsx +0 -0
- /package/template/{client/src → src}/components/ui/scroll-area.tsx +0 -0
- /package/template/{client/src → src}/components/ui/select.tsx +0 -0
- /package/template/{client/src → src}/components/ui/separator.tsx +0 -0
- /package/template/{client/src → src}/components/ui/sheet.tsx +0 -0
- /package/template/{client/src → src}/components/ui/sidebar.tsx +0 -0
- /package/template/{client/src → src}/components/ui/skeleton.tsx +0 -0
- /package/template/{client/src → src}/components/ui/slider.tsx +0 -0
- /package/template/{client/src → src}/components/ui/sonner.tsx +0 -0
- /package/template/{client/src → src}/components/ui/spinner.tsx +0 -0
- /package/template/{client/src → src}/components/ui/switch.tsx +0 -0
- /package/template/{client/src → src}/components/ui/table.tsx +0 -0
- /package/template/{client/src → src}/components/ui/tabs.tsx +0 -0
- /package/template/{client/src → src}/components/ui/textarea.tsx +0 -0
- /package/template/{client/src → src}/components/ui/toggle-group.tsx +0 -0
- /package/template/{client/src → src}/components/ui/toggle.tsx +0 -0
- /package/template/{client/src → src}/components/ui/tooltip.tsx +0 -0
- /package/template/{client/src → src}/index.css +0 -0
- /package/template/{client/src → src}/pages/NotFoundPage/NotFoundPage.tsx +0 -0
package/package.json
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
run = ["npm", "run", "dev"]
|
|
2
|
+
lint = ["npm", "run", "lint"]
|
|
3
|
+
hidden = [".config", ".git", "scripts", "node_modules", "dist", ".spark", ".agent", ".agents", "tmp", ".spark_project", ".playwright-cli"]
|
|
4
|
+
|
|
5
|
+
[deployment]
|
|
6
|
+
build = ["npm", "run", "build"]
|
|
7
|
+
|
|
8
|
+
[files]
|
|
9
|
+
[files.restrict]
|
|
10
|
+
pathPatterns = ["package.json", ".spark_project", ".spark/meta.json", ".gitignore"]
|
|
11
|
+
[files.hidden]
|
|
12
|
+
pathPatterns = [".config", ".git", "scripts", "node_modules", "dist", ".spark", ".agent", ".agents", "tmp", ".spark_project", ".playwright-cli"]
|
package/template/README.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
## 目录结构
|
|
16
16
|
|
|
17
17
|
```
|
|
18
|
-
|
|
18
|
+
src/
|
|
19
19
|
├── index.tsx # 入口(勿修改)
|
|
20
20
|
├── app.tsx # 路由配置(仅在 <Routes> 内增删 <Route>)
|
|
21
21
|
├── index.css # 全局样式 + 主题变量
|
|
@@ -51,8 +51,8 @@ shared/
|
|
|
51
51
|
|
|
52
52
|
| 文件 | 原因 |
|
|
53
53
|
|------|------|
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
54
|
+
| `src/index.tsx` | Provider 层级 + 样式引入,由模板管理 |
|
|
55
|
+
| `src/components/ui/*` | shadcn/ui 内置组件,版本锁定 |
|
|
56
56
|
|
|
57
57
|
---
|
|
58
58
|
|
|
@@ -60,10 +60,10 @@ shared/
|
|
|
60
60
|
|
|
61
61
|
| 内容类型 | 放置位置 |
|
|
62
62
|
|---------|---------|
|
|
63
|
-
| 新页面 | `
|
|
64
|
-
| 页面专属组件 | `
|
|
65
|
-
| 自定义 Hooks | `
|
|
66
|
-
| 工具函数 | `
|
|
63
|
+
| 新页面 | `src/pages/<PageName>/PageName.tsx` |
|
|
64
|
+
| 页面专属组件 | `src/pages/<PageName>/components/` |
|
|
65
|
+
| 自定义 Hooks | `src/hooks/` |
|
|
66
|
+
| 工具函数 | `src/lib/` |
|
|
67
67
|
| 静态数据文件 | `shared/static/data/` |
|
|
68
68
|
| 静态图片 | `shared/static/images/` |
|
|
69
69
|
|
|
@@ -72,7 +72,7 @@ shared/
|
|
|
72
72
|
## 导入路径
|
|
73
73
|
|
|
74
74
|
```typescript
|
|
75
|
-
// @/ 别名 →
|
|
75
|
+
// @/ 别名 → src/
|
|
76
76
|
import { cn } from "@/lib/utils";
|
|
77
77
|
import { useIsMobile } from "@/hooks/use-mobile";
|
|
78
78
|
|
|
@@ -85,14 +85,14 @@ import configData from "@shared/static/config.json";
|
|
|
85
85
|
|
|
86
86
|
## 路由配置
|
|
87
87
|
|
|
88
|
-
- 新增页面需在 `
|
|
88
|
+
- 新增页面需在 `src/app.tsx` 的 `<Routes>` 内注册 `<Route>`
|
|
89
89
|
- `BrowserRouter` 已在 `index.tsx` 中配置,`app.tsx` 中**禁止**再包裹 Router
|
|
90
90
|
|
|
91
91
|
---
|
|
92
92
|
|
|
93
93
|
## 主题变量
|
|
94
94
|
|
|
95
|
-
主题色定义在 `
|
|
95
|
+
主题色定义在 `src/index.css`,通过 `:root` CSS 变量 + `@theme inline` 注册到 Tailwind。
|
|
96
96
|
|
|
97
97
|
| 用途 | Tailwind 类 | CSS 变量 |
|
|
98
98
|
|------|------------|----------|
|
package/template/components.json
CHANGED
package/template/package.json
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
|
+
"dev": "node scripts/dev.mjs",
|
|
6
7
|
"build": "bash scripts/build.sh",
|
|
7
8
|
"typecheck": "tsc -p tsconfig.app.json",
|
|
8
9
|
"lint": "concurrently npm:typecheck npm:lint:eslint",
|
|
9
|
-
"lint:eslint": "eslint
|
|
10
|
+
"lint:eslint": "eslint src"
|
|
10
11
|
},
|
|
11
12
|
"dependencies": {
|
|
12
13
|
"@hookform/resolvers": "^5.2.2",
|
|
@@ -36,7 +37,7 @@
|
|
|
36
37
|
"@radix-ui/react-toggle": "^1.1.9",
|
|
37
38
|
"@radix-ui/react-toggle-group": "^1.1.10",
|
|
38
39
|
"@radix-ui/react-tooltip": "^1.1.18",
|
|
39
|
-
"@lark-apaas/client-toolkit-lite": "
|
|
40
|
+
"@lark-apaas/client-toolkit-lite": "1.1.7-alpha.17",
|
|
40
41
|
"@formkit/auto-animate": "^0.9.0",
|
|
41
42
|
"@gsap/react": "^2.1.2",
|
|
42
43
|
"framer-motion": "^12.38.0",
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"react": "^19.2.4",
|
|
55
56
|
"react-day-picker": "^9.14.0",
|
|
56
57
|
"react-dom": "^19.2.4",
|
|
58
|
+
"react-error-boundary": "^6.0.0",
|
|
57
59
|
"react-hook-form": "^7.72.0",
|
|
58
60
|
"react-resizable-panels": "^4.7.6",
|
|
59
61
|
"react-router-dom": "^7.13.2",
|
|
@@ -71,7 +73,7 @@
|
|
|
71
73
|
"@types/react": "^19",
|
|
72
74
|
"@types/react-dom": "^19",
|
|
73
75
|
"@lark-apaas/coding-presets": "^1.0.0",
|
|
74
|
-
"@lark-apaas/coding-vite-preset": "
|
|
76
|
+
"@lark-apaas/coding-vite-preset": "1.0.7-alpha.13",
|
|
75
77
|
"concurrently": "^9",
|
|
76
78
|
"eslint": "^9",
|
|
77
79
|
"eslint-plugin-react-hooks": "^7",
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Dev launcher: 启动 vite dev server,把 stdout/stderr 加时间戳前缀
|
|
4
|
+
* 写到 logs/<name>.std.log + 当前 stdout;负责子进程组管理与端口孤儿清理。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import readline from 'node:readline';
|
|
10
|
+
import { spawn, execSync } from 'node:child_process';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
|
|
13
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
15
|
+
process.chdir(ROOT);
|
|
16
|
+
|
|
17
|
+
const LOG_DIR = process.env.LOG_DIR || 'logs';
|
|
18
|
+
const CLIENT_DEV_PORT = process.env.CLIENT_DEV_PORT || '8001';
|
|
19
|
+
|
|
20
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
21
|
+
|
|
22
|
+
function timestamp() {
|
|
23
|
+
const now = new Date();
|
|
24
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
25
|
+
return (
|
|
26
|
+
`${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ` +
|
|
27
|
+
`${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function log(msg) {
|
|
32
|
+
const line = `[${timestamp()}] [dev] ${msg}\n`;
|
|
33
|
+
try { process.stdout.write(line); } catch {}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 清理端口占用:lsof -ti:<port> | kill -9 */
|
|
37
|
+
function killOrphansByPort(port) {
|
|
38
|
+
try {
|
|
39
|
+
const out = execSync(`lsof -ti:${port}`, { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
40
|
+
.toString()
|
|
41
|
+
.trim();
|
|
42
|
+
if (!out) return [];
|
|
43
|
+
const pids = out.split('\n').filter(Boolean);
|
|
44
|
+
for (const pid of pids) {
|
|
45
|
+
try {
|
|
46
|
+
process.kill(Number(pid), 'SIGKILL');
|
|
47
|
+
log(`killed orphan pid=${pid} on :${port}`);
|
|
48
|
+
} catch {}
|
|
49
|
+
}
|
|
50
|
+
return pids;
|
|
51
|
+
} catch {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const managed = [];
|
|
57
|
+
|
|
58
|
+
function startProcess({ name, command, args, logFileName }) {
|
|
59
|
+
const logFd = logFileName
|
|
60
|
+
? fs.openSync(path.join(LOG_DIR, logFileName), 'a')
|
|
61
|
+
: null;
|
|
62
|
+
|
|
63
|
+
const child = spawn(command, args, {
|
|
64
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
65
|
+
shell: false,
|
|
66
|
+
cwd: ROOT,
|
|
67
|
+
env: process.env,
|
|
68
|
+
detached: true,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const pipeLines = (stream) => {
|
|
72
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
73
|
+
rl.on('line', (line) => {
|
|
74
|
+
const msg = `[${timestamp()}] [${name}] ${line}\n`;
|
|
75
|
+
try { process.stdout.write(msg); } catch {}
|
|
76
|
+
if (logFd != null) {
|
|
77
|
+
try { fs.writeSync(logFd, msg); } catch {}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
pipeLines(child.stdout);
|
|
82
|
+
pipeLines(child.stderr);
|
|
83
|
+
|
|
84
|
+
managed.push({ name, child });
|
|
85
|
+
return child;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
killOrphansByPort(CLIENT_DEV_PORT);
|
|
89
|
+
|
|
90
|
+
startProcess({
|
|
91
|
+
name: 'client',
|
|
92
|
+
command: 'npx',
|
|
93
|
+
args: ['vite', '--port', CLIENT_DEV_PORT, '--host', '0.0.0.0'],
|
|
94
|
+
logFileName: 'client.std.log',
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
let stopping = false;
|
|
98
|
+
function cleanup(signal) {
|
|
99
|
+
if (stopping) return;
|
|
100
|
+
stopping = true;
|
|
101
|
+
log(`cleanup triggered by ${signal}`);
|
|
102
|
+
|
|
103
|
+
for (const { child } of managed) {
|
|
104
|
+
if (!child.pid) continue;
|
|
105
|
+
try { process.kill(-child.pid, signal || 'SIGTERM'); } catch {}
|
|
106
|
+
}
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
for (const { child } of managed) {
|
|
109
|
+
if (!child.pid) continue;
|
|
110
|
+
try { process.kill(-child.pid, 'SIGKILL'); } catch {}
|
|
111
|
+
}
|
|
112
|
+
killOrphansByPort(CLIENT_DEV_PORT);
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}, 2000);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
process.on('SIGINT', () => cleanup('SIGTERM'));
|
|
118
|
+
process.on('SIGTERM', () => cleanup('SIGTERM'));
|
|
119
|
+
// pkill 杀父 npm 后会收 SIGHUP(controlling tty 关闭);
|
|
120
|
+
// Node 默认直接退出不跑 handler,注册 handler 触发 cleanup
|
|
121
|
+
process.on('SIGHUP', () => cleanup('SIGTERM'));
|
|
122
|
+
|
|
123
|
+
Promise.race(
|
|
124
|
+
managed.map(({ child }) => new Promise((r) => child.on('exit', r))),
|
|
125
|
+
).then(() => cleanup('SIGTERM'));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# shared/static/
|
|
2
|
+
|
|
3
|
+
⚠️ **此目录资源经鉴权访问**——由平台托管在私有源,仅登录用户可拉取。请勿放公开资源。
|
|
4
|
+
|
|
5
|
+
## 与其他资源目录的区分
|
|
6
|
+
|
|
7
|
+
| 目录 | 访问 | 部署目标 | 适用场景 |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| `src/assets/` | 代码 `import` | 随 JS 一起进 CDN | 图标、被组件 import 的小图(多数情况选这里) |
|
|
10
|
+
| `public/` | 公网匿名可访问 | 公网 CDN | favicon、被外部按固定 URL 引用的资源 |
|
|
11
|
+
| `shared/static/` | 需应用鉴权 | 私有源 | 仅登录用户可见的图片、PDF、配置文件等 |
|
|
12
|
+
|
|
13
|
+
## 引用方式
|
|
14
|
+
|
|
15
|
+
`shared/static/` 下文件**不要**用 `/shared/static/foo.png` 这种相对路径直接引用——浏览器无法匿名访问。
|
|
16
|
+
应通过平台运行时 SDK 拿到带 token 的 URL 后再使用。
|
|
@@ -3,7 +3,9 @@ import * as React from "react"
|
|
|
3
3
|
const MOBILE_BREAKPOINT = 768
|
|
4
4
|
|
|
5
5
|
export function useIsMobile() {
|
|
6
|
-
const [isMobile, setIsMobile] = React.useState<boolean
|
|
6
|
+
const [isMobile, setIsMobile] = React.useState<boolean>(() =>
|
|
7
|
+
typeof window !== "undefined" && window.innerWidth < MOBILE_BREAKPOINT,
|
|
8
|
+
)
|
|
7
9
|
|
|
8
10
|
React.useEffect(() => {
|
|
9
11
|
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
|
@@ -11,9 +13,8 @@ export function useIsMobile() {
|
|
|
11
13
|
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
12
14
|
}
|
|
13
15
|
mql.addEventListener("change", onChange)
|
|
14
|
-
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
15
16
|
return () => mql.removeEventListener("change", onChange)
|
|
16
17
|
}, [])
|
|
17
18
|
|
|
18
|
-
return
|
|
19
|
+
return isMobile
|
|
19
20
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { StrictMode } from "react";
|
|
2
2
|
import { createRoot } from "react-dom/client";
|
|
3
3
|
import { BrowserRouter } from "react-router-dom";
|
|
4
|
-
import {
|
|
4
|
+
import { ErrorBoundary } from "react-error-boundary";
|
|
5
|
+
import { AppContainer, ErrorRender } from "@lark-apaas/client-toolkit-lite";
|
|
5
6
|
import App from "./app";
|
|
6
7
|
import "./index.css";
|
|
7
8
|
|
|
@@ -9,7 +10,13 @@ createRoot(document.getElementById("root")!).render(
|
|
|
9
10
|
<StrictMode>
|
|
10
11
|
<BrowserRouter basename={process.env.CLIENT_BASE_PATH || "/"}>
|
|
11
12
|
<AppContainer>
|
|
12
|
-
<
|
|
13
|
+
<ErrorBoundary
|
|
14
|
+
fallbackRender={({ error, resetErrorBoundary }) => (
|
|
15
|
+
<ErrorRender error={error} resetErrorBoundary={resetErrorBoundary} />
|
|
16
|
+
)}
|
|
17
|
+
>
|
|
18
|
+
<App />
|
|
19
|
+
</ErrorBoundary>
|
|
13
20
|
</AppContainer>
|
|
14
21
|
</BrowserRouter>
|
|
15
22
|
</StrictMode>,
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
5
5
|
"baseUrl": ".",
|
|
6
6
|
"paths": {
|
|
7
|
-
"@/*": ["./
|
|
7
|
+
"@/*": ["./src/*"],
|
|
8
8
|
"@shared/*": ["./shared/*"]
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
|
-
"include": ["
|
|
12
|
-
"exclude": ["
|
|
11
|
+
"include": ["src"],
|
|
12
|
+
"exclude": ["src/components/ui"]
|
|
13
13
|
}
|
package/template/tsconfig.json
CHANGED
package/template/vite.config.ts
CHANGED
|
@@ -2,10 +2,9 @@ import path from 'path'
|
|
|
2
2
|
import { defineConfig } from '@lark-apaas/coding-vite-preset'
|
|
3
3
|
|
|
4
4
|
export default defineConfig({
|
|
5
|
-
root: path.resolve(__dirname, 'client'),
|
|
6
5
|
resolve: {
|
|
7
6
|
alias: {
|
|
8
|
-
'@': path.resolve(__dirname, '
|
|
7
|
+
'@': path.resolve(__dirname, 'src'),
|
|
9
8
|
'@shared': path.resolve(__dirname, 'shared'),
|
|
10
9
|
},
|
|
11
10
|
},
|