cdui-js 1.0.2 → 1.0.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.
- package/README.md +0 -2
- package/package.json +5 -6
- package/src/index.ts +1 -31
- package/src/location.ts +3 -3
- package/src/reactive.ts +31 -1
- package/src/ssr/render.ts +175 -0
- package/src/ssr/window/index.ts +1 -0
- package/src/ssr/window/storage.ts +23 -0
- package/ssr-package.js +14 -0
- package/tsconfig.json +42 -0
- package/vite.config.ts +0 -35
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdui-js",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cdui-js": "./cli/bin.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"dev": "vite --config vite.config.ts",
|
|
11
|
-
"build": "vite build --config vite.config.ts"
|
|
12
10
|
},
|
|
13
11
|
"dependencies": {
|
|
12
|
+
"chalk": "^5.6.2",
|
|
14
13
|
"commander": "^14.0.3",
|
|
15
|
-
"inquirer": "^13.3.0",
|
|
16
14
|
"download-git-repo": "^3.0.2",
|
|
17
|
-
"chalk": "^5.6.2",
|
|
18
15
|
"fs-extra": "^11.3.3",
|
|
16
|
+
"inquirer": "^13.3.0",
|
|
19
17
|
"solid-js": "^1.9.10",
|
|
20
18
|
"vite-plugin-solid": "^2.11.10"
|
|
21
19
|
},
|
|
22
20
|
"devDependencies": {
|
|
21
|
+
"@types/node": "^25.3.3",
|
|
23
22
|
"typescript": "~5.6.2"
|
|
24
23
|
}
|
|
25
|
-
}
|
|
24
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -35,34 +35,4 @@ export * from './components/Carousel';
|
|
|
35
35
|
export * from './components/KeepAlive';
|
|
36
36
|
export * from './components/Dialog';
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
export interface ImportMeta {
|
|
40
|
-
// 配置环境变量类型
|
|
41
|
-
env: {
|
|
42
|
-
/**
|
|
43
|
-
* 应用运行模式
|
|
44
|
-
*/
|
|
45
|
-
MODE: string;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* 是否服务端渲染
|
|
49
|
-
*/
|
|
50
|
-
SSR: boolean;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* 是否浏览器模式
|
|
54
|
-
*/
|
|
55
|
-
VITE_BROWSER: boolean;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* API基础路径
|
|
59
|
-
*/
|
|
60
|
-
VITE_API_BASE_URL: string;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 是否开启 API Mock
|
|
64
|
-
*/
|
|
65
|
-
VITE_API_MOCK: boolean;
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
}
|
|
38
|
+
export * from './ssr/render';
|
package/src/location.ts
CHANGED
|
@@ -62,7 +62,7 @@ export const location: Location = reactive({
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
export const parseQuery = (search: string) => {
|
|
65
|
-
let query = {};
|
|
65
|
+
let query: any = {};
|
|
66
66
|
let items = search.slice(1).split('&');
|
|
67
67
|
let item;
|
|
68
68
|
|
|
@@ -95,12 +95,12 @@ export const parseQuery = (search: string) => {
|
|
|
95
95
|
*/
|
|
96
96
|
export const updateURL = (path: string, search?: string, hash?: string) => {
|
|
97
97
|
location.url = path + (search || '') + (hash || '');
|
|
98
|
-
location.hash = hash;
|
|
98
|
+
location.hash = hash || '';
|
|
99
99
|
|
|
100
100
|
if (location.path !== path || location.search !== search) {
|
|
101
101
|
location.path = path;
|
|
102
102
|
location.paths = path.match(/\/[^/]*/g) || [];
|
|
103
|
-
location.search = search;
|
|
103
|
+
location.search = search || '';
|
|
104
104
|
location.query = search ? parseQuery(search) : {};
|
|
105
105
|
}
|
|
106
106
|
};
|
package/src/reactive.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface ServerContext {
|
|
|
8
8
|
/**
|
|
9
9
|
* 异步等待集合
|
|
10
10
|
*/
|
|
11
|
-
promises:
|
|
11
|
+
promises: any[];
|
|
12
12
|
/**
|
|
13
13
|
* 已经获取的异步缓存
|
|
14
14
|
*/
|
|
@@ -33,6 +33,36 @@ export const setServerContext = (context: ServerContext) => {
|
|
|
33
33
|
serverContext = context;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* 服务端渲染页面配置
|
|
38
|
+
*/
|
|
39
|
+
export interface SSRRenderPage {
|
|
40
|
+
/**
|
|
41
|
+
* 页面路径
|
|
42
|
+
*/
|
|
43
|
+
path: string;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 搜索
|
|
47
|
+
*/
|
|
48
|
+
search?: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 页面 title 集合,(key 为语言代码)
|
|
52
|
+
*/
|
|
53
|
+
title?: string;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 页面描述集合,(key 为语言代码)
|
|
57
|
+
*/
|
|
58
|
+
description?: string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 出现异常时是否终止渲染(渲染终止)
|
|
62
|
+
*/
|
|
63
|
+
abort?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
36
66
|
class ReactiveArray {}
|
|
37
67
|
|
|
38
68
|
/**
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
export type { SSRRenderPage } from '../reactive';
|
|
2
|
+
|
|
3
|
+
// 预先加载 window 补丁
|
|
4
|
+
import './window';
|
|
5
|
+
|
|
6
|
+
import path from 'path';
|
|
7
|
+
// fs/promises 不稳定,有时会出现异步错误无法捕获的异步
|
|
8
|
+
import fs, { PathLike, PathOrFileDescriptor, WriteFileOptions } from 'fs';
|
|
9
|
+
|
|
10
|
+
import { Component, createComponent } from 'solid-js';
|
|
11
|
+
import { generateHydrationScript, renderToString } from 'solid-js/web';
|
|
12
|
+
|
|
13
|
+
import { updateURL } from '../location';
|
|
14
|
+
import { SSRRenderPage, ServerContext, setServerContext } from '../reactive';
|
|
15
|
+
|
|
16
|
+
const exists = (path: PathLike): Promise<boolean> => {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
fs.stat(path, (err, stats) => {
|
|
19
|
+
if (err) {
|
|
20
|
+
resolve(false);
|
|
21
|
+
} else {
|
|
22
|
+
resolve(true);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const mkdir = (path: PathLike) => {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
fs.mkdir(path, { recursive: true }, (err) => {
|
|
31
|
+
if (err) {
|
|
32
|
+
reject(err);
|
|
33
|
+
} else {
|
|
34
|
+
resolve(void 0);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const writeFile = (path: PathOrFileDescriptor, data: string | DataView, options: WriteFileOptions): Promise<void> => {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
fs.writeFile(path, data, options, (err) => {
|
|
43
|
+
if (err) {
|
|
44
|
+
reject(err);
|
|
45
|
+
} else {
|
|
46
|
+
resolve(void 0);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const renderToStringAsync = async <T>(context: ServerContext, fn: () => T) => {
|
|
53
|
+
while (true) {
|
|
54
|
+
setServerContext(context);
|
|
55
|
+
|
|
56
|
+
let html = renderToString(fn);
|
|
57
|
+
|
|
58
|
+
if (context.promises[0]) {
|
|
59
|
+
await Promise.all(context.promises);
|
|
60
|
+
context.promises.length = 0;
|
|
61
|
+
} else {
|
|
62
|
+
return html;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 渲染单个页面
|
|
69
|
+
*/
|
|
70
|
+
const renderPage = async (
|
|
71
|
+
App: Component<any>,
|
|
72
|
+
languages: { [key: string]: any },
|
|
73
|
+
language: string,
|
|
74
|
+
root: string,
|
|
75
|
+
template: string,
|
|
76
|
+
page: SSRRenderPage,
|
|
77
|
+
) => {
|
|
78
|
+
let context = { promises: [], cache: new Map(), ssr: {} };
|
|
79
|
+
let title = page.title;
|
|
80
|
+
let description = page.description;
|
|
81
|
+
let scripts = [];
|
|
82
|
+
|
|
83
|
+
// 设置当前路由路径
|
|
84
|
+
updateURL(page.path, page.search);
|
|
85
|
+
|
|
86
|
+
// 渲染html
|
|
87
|
+
let html = await renderToStringAsync(context, () => createComponent(App, null));
|
|
88
|
+
|
|
89
|
+
// 多语言
|
|
90
|
+
if (language !== 'en') {
|
|
91
|
+
template = template.replace('lang="en"', 'lang="' + language + '"');
|
|
92
|
+
|
|
93
|
+
if (languages[language]) {
|
|
94
|
+
scripts.push('<script type="text/javascript">window.I18N=' + JSON.stringify(languages[language]) + '</script>');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (Object.getOwnPropertyNames(context.ssr).length > 0) {
|
|
99
|
+
scripts.push('<script type="text/javascript">window.SSR=' + JSON.stringify(context.ssr) + '</script>');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
scripts.push(generateHydrationScript());
|
|
103
|
+
|
|
104
|
+
// 插入内容
|
|
105
|
+
html = template.replace('<!-- ssr-body -->', html);
|
|
106
|
+
|
|
107
|
+
// 插入头部脚本
|
|
108
|
+
html = html.replace('<!-- ssr-head -->', scripts.join(''));
|
|
109
|
+
|
|
110
|
+
if (title) {
|
|
111
|
+
html = html.replace(/\<title\>[^<]+/, '<title>' + title);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (description) {
|
|
115
|
+
html = html.replace(
|
|
116
|
+
/\<meta\s+name\=\"description\"\s+content\=\"[^"]+/,
|
|
117
|
+
'<meta name="description" content="' + description,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let file = path.join(root, 'ssr', language, page.path);
|
|
122
|
+
let dir = path.dirname(file);
|
|
123
|
+
|
|
124
|
+
if (!(await exists(dir))) {
|
|
125
|
+
await mkdir(dir);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await writeFile(file, html, 'utf8');
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 服务端渲染错误集合
|
|
133
|
+
*/
|
|
134
|
+
export const SSR_ERRORS = [];
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 渲染服务端页面集合
|
|
138
|
+
*/
|
|
139
|
+
export const renderSSRPages = async (
|
|
140
|
+
App: Component<any>,
|
|
141
|
+
languages: { [key: string]: any },
|
|
142
|
+
language: string,
|
|
143
|
+
root: string,
|
|
144
|
+
template: string,
|
|
145
|
+
pages: SSRRenderPage[],
|
|
146
|
+
) => {
|
|
147
|
+
for (let i = 0, l = pages.length; i < l; i++) {
|
|
148
|
+
let page = pages[i];
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
let now = Date.now();
|
|
152
|
+
|
|
153
|
+
console.log(`rendering:${language} ${page.path}`);
|
|
154
|
+
|
|
155
|
+
await renderPage(App, languages, language, root, template, page);
|
|
156
|
+
|
|
157
|
+
console.log(`rendered: ${language} ${page.path} time: ${Date.now() - now}`);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.error(err);
|
|
160
|
+
SSR_ERRORS.push(err.message + ': ' + (err.cause || { message: '...' }).message);
|
|
161
|
+
|
|
162
|
+
// 页面渲染失败终止渲染
|
|
163
|
+
if (page.abort) {
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (SSR_ERRORS[0]) {
|
|
170
|
+
return Promise.reject({
|
|
171
|
+
language,
|
|
172
|
+
errors: SSR_ERRORS,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './storage';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
const localStorage = new Map();
|
|
3
|
+
|
|
4
|
+
global.localStorage = global.sessionStorage = {
|
|
5
|
+
get length() {
|
|
6
|
+
return localStorage.size;
|
|
7
|
+
},
|
|
8
|
+
key(index) {
|
|
9
|
+
return localStorage.keys()[index];
|
|
10
|
+
},
|
|
11
|
+
getItem(key) {
|
|
12
|
+
return localStorage.get(key) || '';
|
|
13
|
+
},
|
|
14
|
+
setItem(key, value) {
|
|
15
|
+
localStorage.set('' + key, '' + value);
|
|
16
|
+
},
|
|
17
|
+
removeItem(key) {
|
|
18
|
+
localStorage.delete(key);
|
|
19
|
+
},
|
|
20
|
+
clear() {
|
|
21
|
+
localStorage.clear();
|
|
22
|
+
}
|
|
23
|
+
};
|
package/ssr-package.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const SSRPackageJSON = JSON.stringify({
|
|
2
|
+
name: '@cdui-js/ssr',
|
|
3
|
+
private: true,
|
|
4
|
+
version: '0.0.0',
|
|
5
|
+
type: 'module',
|
|
6
|
+
main: 'server.js',
|
|
7
|
+
devDependencies: {
|
|
8
|
+
express: '^4.21.2',
|
|
9
|
+
commander: '^12.1.0',
|
|
10
|
+
'html-minifier': '^4.0.0',
|
|
11
|
+
'solid-js': '^1.9.10',
|
|
12
|
+
},
|
|
13
|
+
dependencies: {},
|
|
14
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @link https://www.typescriptlang.org/tsconfig
|
|
3
|
+
* @link https://cn.vuejs.org/guide/typescript/overview#configuring-tsconfig-json
|
|
4
|
+
* @link https://cn.vite.dev/guide/features#typescript-compiler-options
|
|
5
|
+
*/
|
|
6
|
+
{
|
|
7
|
+
"compilerOptions": {
|
|
8
|
+
"target": "esnext",
|
|
9
|
+
"jsx": "preserve",
|
|
10
|
+
"jsxImportSource": "solid-js",
|
|
11
|
+
// "jsxFactory": "h",
|
|
12
|
+
// "jsxFragmentFactory": "Fragment",
|
|
13
|
+
"lib": [
|
|
14
|
+
"esnext",
|
|
15
|
+
"dom"
|
|
16
|
+
],
|
|
17
|
+
"useDefineForClassFields": true,
|
|
18
|
+
"experimentalDecorators": true,
|
|
19
|
+
// baseUrl 用来告诉编译器到哪里去查找模块,使用非相对模块时必须配置此项
|
|
20
|
+
"baseUrl": ".",
|
|
21
|
+
"module": "esnext",
|
|
22
|
+
"moduleResolution": "bundler",
|
|
23
|
+
// 非相对模块导入的路径映射配置,根据 baseUrl 配置进行路径计算,与 vite.config 中 alias 配置同步
|
|
24
|
+
"paths": {},
|
|
25
|
+
"resolveJsonModule": true,
|
|
26
|
+
"types": [],
|
|
27
|
+
// 允许导入 .ts .mts .tsx 拓展名的文件
|
|
28
|
+
"allowImportingTsExtensions": true,
|
|
29
|
+
// 允许 JS
|
|
30
|
+
// "allowJs": true,
|
|
31
|
+
// TS 严格模式
|
|
32
|
+
// "strict": true,
|
|
33
|
+
"importHelpers": true,
|
|
34
|
+
// 不输出任何编译后的文件,只进行类型检查
|
|
35
|
+
"noEmit": true,
|
|
36
|
+
"sourceMap": true,
|
|
37
|
+
"allowSyntheticDefaultImports": true,
|
|
38
|
+
"esModuleInterop": true,
|
|
39
|
+
"isolatedModules": true,
|
|
40
|
+
"skipLibCheck": true
|
|
41
|
+
}
|
|
42
|
+
}
|
package/vite.config.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { fileURLToPath } from 'url';
|
|
3
|
-
import { defineConfig } from 'vite';
|
|
4
|
-
|
|
5
|
-
import VitePlugin from 'vite-plugin-solid';
|
|
6
|
-
|
|
7
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
-
const __dirname = path.dirname(__filename);
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* h5构建方法
|
|
12
|
-
*/
|
|
13
|
-
export default defineConfig({
|
|
14
|
-
root: __dirname,
|
|
15
|
-
base: '/',
|
|
16
|
-
plugins: [
|
|
17
|
-
VitePlugin({})
|
|
18
|
-
],
|
|
19
|
-
build: {
|
|
20
|
-
// target: 'es2015',
|
|
21
|
-
modulePreload: false,
|
|
22
|
-
outDir: path.join(__dirname, 'dist'),
|
|
23
|
-
emptyOutDir: true,
|
|
24
|
-
rollupOptions: {
|
|
25
|
-
input: path.join(__dirname, 'index.html'),
|
|
26
|
-
output: {
|
|
27
|
-
entryFileNames: 'js/[name]-[hash].js',
|
|
28
|
-
chunkFileNames: 'js/[name]-[hash].js',
|
|
29
|
-
assetFileNames: '[ext]/[name]-[hash].[ext]',
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
minify: false,
|
|
33
|
-
sourcemap: true,
|
|
34
|
-
}
|
|
35
|
-
});
|