cdui-js 1.0.2 → 1.0.4

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 CHANGED
@@ -4,8 +4,6 @@
4
4
 
5
5
  # MVVM响应式编程
6
6
 
7
- 为便于团队合作,建议项目中的所有代码都使用`typescrpt`,本项目使用与`React`类似的响应式`MVVM`框架作为主要开发方式。
8
-
9
7
  响应式`(Reactivity)`增强了应用程序的交互性。这种编程范式是指系统自动响应数据或状态变化的能力,确保用户界面 (UI) 和状态保持同步,从而减少手动更新的需要。
10
8
 
11
9
 
package/build/icons.ts CHANGED
@@ -2,12 +2,12 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
 
4
4
  /**
5
- * 从指定 svg 文件读取 symbol 内容
5
+ * 从指定 svg 文件加载 symbol 内容
6
6
  *
7
7
  * @param svgFile 指定 svg 文件
8
8
  * @param removeColor 去色标记 fill: 去填充色 stroke:去描边色 both:都去
9
9
  */
10
- export const readIconFile = (svgFile: string, removeColor?: 'fill' | 'stroke' | 'both'): string => {
10
+ export const loadIconFile = (svgFile: string, removeColor?: 'fill' | 'stroke' | 'both'): string => {
11
11
  let name = path.basename(svgFile).replace('.svg', '');
12
12
  let svg = fs.readFileSync(svgFile, 'utf8');
13
13
  let index = svg.indexOf(' viewBox="');
@@ -46,7 +46,7 @@ export const readIconFile = (svgFile: string, removeColor?: 'fill' | 'stroke' |
46
46
  * @param svgDirectory 指定 svg 目录
47
47
  * @param removeColor 去色标记 fill: 去填充色 stroke:去描边色 both:都去
48
48
  */
49
- export const readIconsDirectory = (svgDirectory: string, removeColor?: 'fill' | 'stroke' | 'both'): string => {
49
+ export const loadIconsDirectory = (svgDirectory: string, removeColor?: 'fill' | 'stroke' | 'both'): string => {
50
50
  const outputs = [];
51
51
  const files = fs.readdirSync(svgDirectory);
52
52
 
@@ -54,7 +54,7 @@ export const readIconsDirectory = (svgDirectory: string, removeColor?: 'fill' |
54
54
  let file = path.join(svgDirectory, files[i]);
55
55
 
56
56
  if (path.extname(file) === '.svg') {
57
- outputs.push(readIconFile(file, removeColor));
57
+ outputs.push(loadIconFile(file, removeColor));
58
58
  }
59
59
  }
60
60
 
@@ -62,12 +62,12 @@ export const readIconsDirectory = (svgDirectory: string, removeColor?: 'fill' |
62
62
  };
63
63
 
64
64
  /**
65
- * 把图标 symbol 内容写入 html 文件
65
+ * 把图标 symbol 内容保存到 html 文件中
66
66
  *
67
67
  * @param htmlFile html 文件路径
68
68
  * @param symbols 图标 symbol 集合
69
69
  */
70
- export const writeIconsToHtml = (htmlFile: string, symbols: string) => {
70
+ export const saveIconsToHtml = (htmlFile: string, symbols: string) => {
71
71
  let html = fs.readFileSync(htmlFile, 'utf8');
72
72
  let index = html.indexOf('<svg id="ICONS" ');
73
73
 
@@ -91,12 +91,12 @@ export const writeIconsToHtml = (htmlFile: string, symbols: string) => {
91
91
  };
92
92
 
93
93
  /**
94
- * svg 图标到模块文件
94
+ * svg 图标保存到模块文件中
95
95
  *
96
96
  * @param moduleFile 模块文件名
97
97
  * @param symbols svg 图标内容
98
98
  */
99
- export const writeIconsModule = (moduleFile: string, symbols: string) => {
99
+ export const saveIconsModule = (moduleFile: string, symbols: string) => {
100
100
  fs.writeFileSync(
101
101
  moduleFile,
102
102
  `import { loadSvgIcons } from 'cdui-js';
package/package.json CHANGED
@@ -1,25 +1,24 @@
1
1
  {
2
2
  "name": "cdui-js",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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
+ }
@@ -10,7 +10,7 @@ export interface SwitchProps {
10
10
  */
11
11
  component: () => JSX.Element;
12
12
  /**
13
- * 捉拿
13
+ * 缓存 key
14
14
  */
15
15
  keepalive?: string;
16
16
  };
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
- declare global {
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: Promise<any>[];
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,178 @@
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
+ // @ts-ignore
148
+ if (import.meta.env.SSR) {
149
+ for (let i = 0, l = pages.length; i < l; i++) {
150
+ let page = pages[i];
151
+
152
+ try {
153
+ let now = Date.now();
154
+
155
+ console.log(`rendering:${language} ${page.path}`);
156
+
157
+ await renderPage(App, languages, language, root, template, page);
158
+
159
+ console.log(`rendered: ${language} ${page.path} time: ${Date.now() - now}`);
160
+ } catch (err) {
161
+ console.error(err);
162
+ SSR_ERRORS.push(err.message + ': ' + (err.cause || { message: '...' }).message);
163
+
164
+ // 页面渲染失败终止渲染
165
+ if (page.abort) {
166
+ break;
167
+ }
168
+ }
169
+ }
170
+
171
+ if (SSR_ERRORS[0]) {
172
+ return Promise.reject({
173
+ language,
174
+ errors: SSR_ERRORS,
175
+ });
176
+ }
177
+ }
178
+ };
@@ -0,0 +1 @@
1
+ import './storage';
@@ -0,0 +1,27 @@
1
+ // @ts-ignore
2
+ if (import.meta.env.SSR) {
3
+ // @ts-ignore
4
+ const localStorage = new Map();
5
+
6
+ // @ts-ignore
7
+ global.localStorage = global.sessionStorage = {
8
+ get length() {
9
+ return localStorage.size;
10
+ },
11
+ key(index) {
12
+ return localStorage.keys()[index];
13
+ },
14
+ getItem(key) {
15
+ return localStorage.get(key) || '';
16
+ },
17
+ setItem(key, value) {
18
+ localStorage.set('' + key, '' + value);
19
+ },
20
+ removeItem(key) {
21
+ localStorage.delete(key);
22
+ },
23
+ clear() {
24
+ localStorage.clear();
25
+ },
26
+ };
27
+ }
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
- });