create-yoya-ui 0.1.0 → 0.1.1
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/README.md +20 -0
- package/bin/create-yoya-ui.js +18 -4
- package/package.json +2 -2
- package/templates/basic/package.json +1 -1
- package/templates/ssr/README.md +32 -0
- package/templates/ssr/package.json +17 -0
- package/templates/ssr/src/client.js +6 -0
- package/templates/ssr/src/home-page.js +21 -0
- package/templates/ssr/src/server.mjs +49 -0
- package/templates/ssr/vite.config.js +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# create-yoya-ui
|
|
2
|
+
|
|
3
|
+
快速搭建 [yoya-ui](https://github.com/yoyaflow/yoya-ui) 项目的脚手架。
|
|
4
|
+
|
|
5
|
+
## 用法
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx create-yoya-ui@latest my-app # basic 模板(SPA)
|
|
9
|
+
npx create-yoya-ui@latest my-app --template ssr # SSR 模板
|
|
10
|
+
cd my-app
|
|
11
|
+
npm install
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
- **basic**:默认模板,`npm run dev` 启动 Vite 开发服务器
|
|
15
|
+
- **ssr**:服务端渲染模板,`npm run build && npm start`(renderPage 整页渲染 + hydrateOrMount 客户端接入)
|
|
16
|
+
|
|
17
|
+
## 模板内容
|
|
18
|
+
|
|
19
|
+
- `templates/basic`:页面壳、按钮事件、表单收集校验、主题切换
|
|
20
|
+
- `templates/ssr`:home-page.js(页面组件 + 词典)、server.mjs(node:http + renderPage)、client.js(hydrateOrMount)
|
package/bin/create-yoya-ui.js
CHANGED
|
@@ -4,10 +4,18 @@ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync
|
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
|
|
7
|
-
const templateDir = resolve(fileURLToPath(new URL('../templates/basic', import.meta.url)));
|
|
8
7
|
const args = process.argv.slice(2);
|
|
9
8
|
const targetName = args[0] || 'yoya-ui-app';
|
|
10
9
|
const targetDir = resolve(process.cwd(), targetName);
|
|
10
|
+
const templateArgIndex = args.indexOf('--template');
|
|
11
|
+
const templateName =
|
|
12
|
+
templateArgIndex !== -1 && args[templateArgIndex + 1] ? args[templateArgIndex + 1] : 'basic';
|
|
13
|
+
const templateRoot = resolve(fileURLToPath(new URL(`../templates/${templateName}`, import.meta.url)));
|
|
14
|
+
|
|
15
|
+
if (!existsSync(templateRoot)) {
|
|
16
|
+
console.error(`未知模板:${templateName}(可用:basic、ssr)`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
11
19
|
|
|
12
20
|
if (existsSync(targetDir) && readdirSafe(targetDir).length > 0) {
|
|
13
21
|
console.error(`目标目录已存在且非空:${targetDir}`);
|
|
@@ -15,7 +23,7 @@ if (existsSync(targetDir) && readdirSafe(targetDir).length > 0) {
|
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
mkdirSync(targetDir, { recursive: true });
|
|
18
|
-
cpSync(
|
|
26
|
+
cpSync(templateRoot, targetDir, { recursive: true });
|
|
19
27
|
|
|
20
28
|
// 把模板 package.json 的 name 改为目标目录名
|
|
21
29
|
const packagePath = join(targetDir, 'package.json');
|
|
@@ -26,8 +34,14 @@ writeFileSync(packagePath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
|
26
34
|
console.log(`\n已创建 yoya-ui 项目:${targetName}\n`);
|
|
27
35
|
console.log(` cd ${targetName}`);
|
|
28
36
|
console.log(' npm install');
|
|
29
|
-
|
|
30
|
-
console.log('
|
|
37
|
+
if (templateName === 'ssr') {
|
|
38
|
+
console.log(' npm run build');
|
|
39
|
+
console.log(' npm start');
|
|
40
|
+
console.log('\nSSR 模板:renderPage 整页渲染 + hydrateOrMount 客户端接入。\n');
|
|
41
|
+
} else {
|
|
42
|
+
console.log(' npm run dev');
|
|
43
|
+
console.log('\n更多:npm run build 构建,npm run preview 预览产物。\n');
|
|
44
|
+
}
|
|
31
45
|
|
|
32
46
|
function sanitizeName(name) {
|
|
33
47
|
return name
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-yoya-ui",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Scaffold a new yoya-ui project with Vite:
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Scaffold a new yoya-ui project with Vite: basic template (SPA) or SSR template (renderPage + hydrateOrMount).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-yoya-ui": "bin/create-yoya-ui.js"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# yoya-ui SSR 示例项目
|
|
2
|
+
|
|
3
|
+
基于 [yoya-ui](https://github.com/yoyaflow/yoya-ui) 的服务端渲染模板:
|
|
4
|
+
|
|
5
|
+
- 页面即组件:`HomePage(state)` 服务端与客户端共用
|
|
6
|
+
- 整页渲染:`renderPage({ page }, state, { messages })` 一行输出完整 HTML
|
|
7
|
+
- 客户端一行接入:`hydrateOrMount(HomePage, { messages })`
|
|
8
|
+
- 每请求 i18n:`messages` 按 `state.lang` 建实例,`.s()` 自动作用域
|
|
9
|
+
|
|
10
|
+
## 启动
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install
|
|
14
|
+
npm run build # 生成 dist/client.js 与 dist/assets/yoya.ui.css
|
|
15
|
+
npm start # http://localhost:3000
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 结构
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
src/
|
|
22
|
+
home-page.js 页面组件 + 词典(两端共用)
|
|
23
|
+
server.mjs node:http 服务端(renderPage 整页渲染 + 静态资源)
|
|
24
|
+
client.js 浏览器入口(hydrateOrMount)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
语言标识:切换语言时写 cookie(如 `document.cookie = 'yoya-lang=en; path=/'`),服务端从 cookie 解析 `lang` 传入 `renderPage`。
|
|
28
|
+
|
|
29
|
+
## 学习资源
|
|
30
|
+
|
|
31
|
+
- SSR 集成与底层原语(renderToString / hydrate / mount)见 [docs/ssr.md](https://github.com/yoyaflow/yoya-ui/blob/main/docs/ssr.md)
|
|
32
|
+
- 完整 API 见包内类型声明(`types/*.d.ts`)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yoya-ui-ssr-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"start": "node src/server.mjs",
|
|
9
|
+
"preview": "npm run build && npm start"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@yoyaflow/yoya-ui": "^0.2.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"vite": "^8.2.0"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createRouter, div } from '@yoyaflow/yoya-ui/ssr';
|
|
2
|
+
|
|
3
|
+
export const messages = {
|
|
4
|
+
'zh-CN': { title: 'SSR 示例', home: '首页' },
|
|
5
|
+
'en-US': { title: 'SSR Demo', home: 'Home' }
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// 页面即形态 A 组件:服务端与客户端共用同一份定义
|
|
9
|
+
export function HomePage(state) {
|
|
10
|
+
const router = createRouter();
|
|
11
|
+
router.mode(state.mode || 'history');
|
|
12
|
+
router.route('/home', '首页'.s('home'));
|
|
13
|
+
router.notFound('未找到');
|
|
14
|
+
router.renderPath(state.path || '/home');
|
|
15
|
+
|
|
16
|
+
return div((root) => {
|
|
17
|
+
root.h1('SSR 示例'.s('title'));
|
|
18
|
+
root.child(router);
|
|
19
|
+
root.p('服务端渲染完成,事件由 hydrate 在浏览器端绑定。');
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { extname, join } from 'node:path';
|
|
4
|
+
import { renderPage } from '@yoyaflow/yoya-ui/ssr';
|
|
5
|
+
import { HomePage, messages } from './home-page.js';
|
|
6
|
+
|
|
7
|
+
const DIST = join(import.meta.dirname, '..', 'dist');
|
|
8
|
+
const MIME = { '.css': 'text/css', '.js': 'text/javascript' };
|
|
9
|
+
const PORT = Number(process.env.PORT || 3000);
|
|
10
|
+
|
|
11
|
+
createServer((req, res) => {
|
|
12
|
+
const path = new URL(req.url, 'http://localhost').pathname;
|
|
13
|
+
|
|
14
|
+
// 静态资源:client.js 与 assets/yoya.ui.css 由 npm run build 产出
|
|
15
|
+
if (path !== '/') {
|
|
16
|
+
const file = join(DIST, path.slice(1));
|
|
17
|
+
if (existsSync(file)) {
|
|
18
|
+
res.writeHead(200, { 'Content-Type': MIME[extname(file)] || 'application/octet-stream' });
|
|
19
|
+
res.end(readFileSync(file));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// lang 由你的服务端解析(cookie / query / 登录态都行)
|
|
25
|
+
const lang = req.headers.cookie?.includes('yoya-lang=en') ? 'en' : 'zh-CN';
|
|
26
|
+
|
|
27
|
+
const html = renderPage(
|
|
28
|
+
{
|
|
29
|
+
page: (page, state) => {
|
|
30
|
+
page.head((head) => {
|
|
31
|
+
head.title('SSR 示例'.s('title'));
|
|
32
|
+
head.meta({ charset: 'utf-8' });
|
|
33
|
+
head.link({ rel: 'stylesheet', href: '/assets/yoya.ui.css' });
|
|
34
|
+
});
|
|
35
|
+
page.body((body) => {
|
|
36
|
+
body.vBody((shell) => {
|
|
37
|
+
shell.h1('SSR 示例'.s('title'));
|
|
38
|
+
shell.child(HomePage(state)); // state = { lang, path, mode }
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{ lang, mode: 'history', path },
|
|
44
|
+
{ messages } // 按 state.lang 建每请求 i18n,.s() 自动作用域
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
48
|
+
res.end(html);
|
|
49
|
+
}).listen(PORT, () => console.log(`SSR server: http://localhost:${PORT}`));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
build: {
|
|
5
|
+
emptyOutDir: true,
|
|
6
|
+
outDir: 'dist',
|
|
7
|
+
rollupOptions: {
|
|
8
|
+
input: 'src/client.js',
|
|
9
|
+
output: {
|
|
10
|
+
assetFileNames: (assetInfo) =>
|
|
11
|
+
assetInfo.name?.endsWith('.css') ? 'assets/yoya.ui.css' : 'assets/[name][extname]',
|
|
12
|
+
entryFileNames: 'client.js'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
});
|