nativescript-web-adapter 0.1.5 → 0.1.6

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 (70) hide show
  1. package/README.md +32 -14
  2. package/angular/components/absolute-layout.component.ts +18 -0
  3. package/angular/components/action-bar.component.ts +16 -0
  4. package/angular/components/action-item.component.ts +22 -0
  5. package/angular/components/activity-indicator.component.ts +32 -0
  6. package/angular/components/button.component.ts +45 -0
  7. package/angular/components/date-picker.component.ts +35 -0
  8. package/angular/components/directives/absolute-position.directive.ts +31 -0
  9. package/angular/components/directives/grid-position.directive.ts +43 -0
  10. package/angular/components/dock-layout.component.ts +46 -0
  11. package/angular/components/flexbox-layout.component.ts +16 -0
  12. package/angular/components/frame.component.ts +9 -0
  13. package/angular/components/grid-layout.component.ts +48 -0
  14. package/angular/components/html-view.component.ts +23 -0
  15. package/angular/components/image-cache-it.component.ts +21 -0
  16. package/angular/components/image.component.ts +22 -0
  17. package/angular/components/index.ts +156 -0
  18. package/angular/components/label.component.ts +27 -0
  19. package/angular/components/list-picker.component.ts +33 -0
  20. package/angular/components/list-view.component.ts +22 -0
  21. package/angular/components/navigation-button.component.ts +36 -0
  22. package/angular/components/page.component.ts +21 -0
  23. package/angular/components/placeholder.component.ts +16 -0
  24. package/angular/components/progress.component.ts +20 -0
  25. package/angular/components/root-layout.component.ts +19 -0
  26. package/angular/components/scroll-view.component.ts +17 -0
  27. package/angular/components/search-bar.component.ts +34 -0
  28. package/angular/components/segmented-bar-item.component.ts +27 -0
  29. package/angular/components/segmented-bar.component.ts +101 -0
  30. package/angular/components/slider.component.ts +41 -0
  31. package/angular/components/stack-layout.component.ts +17 -0
  32. package/angular/components/switch.component.ts +62 -0
  33. package/angular/components/tab-view-item.component.ts +27 -0
  34. package/angular/components/tab-view.component.ts +89 -0
  35. package/angular/components/text-field.component.ts +38 -0
  36. package/angular/components/text-view.component.ts +29 -0
  37. package/angular/components/time-picker.component.ts +27 -0
  38. package/angular/components/web-view.component.ts +25 -0
  39. package/angular/components/wrap-layout.component.ts +17 -0
  40. package/angular/composables/dialogs.ts +54 -0
  41. package/angular/composables/index.ts +6 -0
  42. package/angular/composables/ref.ts +8 -0
  43. package/angular/composables/useActionBar.ts +20 -0
  44. package/angular/composables/useFrame.ts +26 -0
  45. package/angular/composables/usePage.ts +26 -0
  46. package/angular/index.ts +8 -0
  47. package/core/components/Button.vue +2 -2
  48. package/core/components/GridLayout.vue +2 -2
  49. package/core/components/HtmlView.vue +2 -2
  50. package/core/components/Image.vue +2 -2
  51. package/core/components/ImageCacheIt.vue +2 -2
  52. package/core/components/Label.vue +2 -2
  53. package/core/components/ListPicker.vue +2 -2
  54. package/core/components/NavigationButton.vue +2 -2
  55. package/core/components/Progress.vue +2 -2
  56. package/core/components/SearchBar.vue +2 -2
  57. package/core/components/Slider.vue +2 -2
  58. package/core/components/Switch.vue +2 -2
  59. package/core/components/TextField.vue +2 -2
  60. package/core/components/TextView.vue +2 -2
  61. package/core/components/WebView.vue +2 -2
  62. package/core/composables/dialogs.ts +5 -5
  63. package/dist/nativescript-web-adapter.es.js +22375 -45
  64. package/dist/nativescript-web-adapter.umd.js +534 -1
  65. package/package.json +37 -7
  66. package/tools/cli.cjs +17 -4
  67. package/tools/create-web-platform.cjs +40 -18
  68. package/tools/modules/appPatch.cjs +125 -1
  69. package/tools/modules/templates.cjs +161 -84
  70. package/tools/modules/transform.cjs +69 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nativescript-web-adapter",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Web adapter for NativeScript applications",
5
5
  "type": "module",
6
6
  "main": "./dist/nativescript-web-adapter.umd.js",
@@ -19,6 +19,7 @@
19
19
  "build": "vite build",
20
20
  "preview": "vite preview",
21
21
  "type-check": "vue-tsc --noEmit",
22
+ "test": "vitest run",
22
23
  "prepublishOnly": "npm run build"
23
24
  },
24
25
  "bin": {
@@ -28,20 +29,49 @@
28
29
  "dist",
29
30
  "tools",
30
31
  "templates",
31
- "core"
32
+ "core",
33
+ "angular"
32
34
  ],
33
35
  "dependencies": {
34
- "vue": "^3.4.0",
35
- "commander": "^11.0.0"
36
+ "commander": "^11.0.0",
37
+ "vue": "^3.0.0"
36
38
  },
37
39
  "devDependencies": {
40
+ "@angular/common": "^18.2.0",
41
+ "@angular/core": "^18.2.0",
42
+ "@angular/platform-browser": "^18.2.0",
43
+ "@angular/router": "^18.2.0",
38
44
  "@types/node": "^20.0.0",
39
- "typescript": "^5.0.0",
40
45
  "@vitejs/plugin-vue": "^5.0.0",
46
+ "rxjs": "^7.8.1",
47
+ "typescript": "^5.0.0",
41
48
  "vite": "^5.0.0",
42
- "vue-tsc": "^1.8.0"
49
+ "vitest": "^2.1.8",
50
+ "vue-tsc": "^2.2.12"
43
51
  },
44
52
  "peerDependencies": {
45
- "vue": "^3.0.0"
53
+ "vue": "^3.0.0",
54
+ "@angular/core": "^18.0.0",
55
+ "@angular/common": "^18.0.0",
56
+ "@angular/platform-browser": "^18.0.0",
57
+ "@angular/router": "^18.0.0",
58
+ "rxjs": "^7.0.0"
59
+ },
60
+ "peerDependenciesMeta": {
61
+ "@angular/core": {
62
+ "optional": true
63
+ },
64
+ "@angular/common": {
65
+ "optional": true
66
+ },
67
+ "@angular/platform-browser": {
68
+ "optional": true
69
+ },
70
+ "@angular/router": {
71
+ "optional": true
72
+ },
73
+ "rxjs": {
74
+ "optional": true
75
+ }
46
76
  }
47
77
  }
package/tools/cli.cjs CHANGED
@@ -18,11 +18,11 @@ program
18
18
 
19
19
  program
20
20
  .command('build')
21
- .description('生成 Vue 平台并在 platforms/vue 内构建')
21
+ .description('根据项目配置自动选择框架并在对应 platforms/<framework> 内构建')
22
22
  .option('--skip-install', '跳过依赖安装')
23
23
  .action(async (opts) => {
24
- await createWebPlatform();
25
- const webDir = path.join(process.cwd(), 'platforms', 'vue');
24
+ const res = await createWebPlatform();
25
+ const webDir = res?.webDir || path.join(process.cwd(), 'platforms', 'vue');
26
26
  if (!opts.skipInstall) {
27
27
  execSync('npm install', { stdio: 'inherit', cwd: webDir });
28
28
  }
@@ -34,7 +34,7 @@ program
34
34
  .description('生成 Vue 平台并在 platforms/vue 内启动开发服务器')
35
35
  .option('--skip-install', '跳过依赖安装')
36
36
  .action(async (opts) => {
37
- await createWebPlatform();
37
+ await createWebPlatform({ framework: 'vue' });
38
38
  const webDir = path.join(process.cwd(), 'platforms', 'vue');
39
39
  if (!opts.skipInstall) {
40
40
  execSync('npm install', { stdio: 'inherit', cwd: webDir });
@@ -42,6 +42,19 @@ program
42
42
  execSync('npm run dev', { stdio: 'inherit', cwd: webDir });
43
43
  });
44
44
 
45
+ program
46
+ .command('angular')
47
+ .description('生成 Angular 平台并在 platforms/angular 内执行构建')
48
+ .option('--skip-install', '跳过依赖安装')
49
+ .action(async (opts) => {
50
+ const res = await createWebPlatform({ framework: 'angular' });
51
+ const webDir = res?.webDir || path.join(process.cwd(), 'platforms', 'angular');
52
+ if (!opts.skipInstall) {
53
+ execSync('npm install', { stdio: 'inherit', cwd: webDir });
54
+ }
55
+ execSync('npm run build', { stdio: 'inherit', cwd: webDir });
56
+ });
57
+
45
58
  program
46
59
  .command('nuxt')
47
60
  .description('生成 Nuxt 平台并在 platforms/nuxt 内执行构建')
@@ -14,52 +14,57 @@ function createWebPlatform(options = {}) {
14
14
  // 当作为 node_modules 使用时,项目根应为当前工作目录
15
15
  const projectRoot = options.projectRoot ? path.resolve(options.projectRoot) : process.cwd();
16
16
  const platformsDir = path.join(projectRoot, 'platforms');
17
- const webDir = path.join(platformsDir, 'vue');
17
+ const framework = options.framework || detectFramework(projectRoot);
18
+ const webDir = path.join(platformsDir, framework);
18
19
  const srcDir = path.join(projectRoot, 'src');
19
20
 
20
21
  console.log('[web-adapter] 项目根目录:', projectRoot);
21
22
  console.log('[web-adapter] platforms 目录:', platformsDir);
22
- console.log('[web-adapter] vue 目录:', webDir);
23
+ console.log('[web-adapter] framework:', framework);
24
+ console.log('[web-adapter] web 目录:', webDir);
23
25
  console.log('[web-adapter] src 目录:', srcDir);
24
26
 
25
27
  try {
26
28
  if (!fs.existsSync(webDir)) {
27
29
  fs.mkdirSync(webDir, { recursive: true });
28
- console.log('[web-adapter] 已创建 vue 目录');
30
+ console.log('[web-adapter] 已创建 web 目录');
29
31
  } else {
30
- console.log('[web-adapter] vue 目录已存在');
32
+ console.log('[web-adapter] web 目录已存在');
31
33
  }
32
34
 
33
35
  // 1) 复制源码(仅脚本层转换,保留模板标签)
34
- copy.copySourceFiles(srcDir, path.join(webDir, 'src'), transform.transformContent);
36
+ copy.copySourceFiles(srcDir, path.join(webDir, 'src'), (c, p) => transform.transformContent(c, p, framework));
35
37
  console.log('[web-adapter] 已复制 src 源代码');
36
38
 
37
39
  // 2) 复制适配器 web 组件与 composables
38
- const adapterComponentsDir = path.join(adapterDir, 'core', 'components');
40
+ const adapterComponentsDir =
41
+ framework === 'angular' ? path.join(adapterDir, 'angular', 'components') : path.join(adapterDir, 'core', 'components');
39
42
  const targetComponentsDir = path.join(webDir, 'src', 'components', 'websfc');
40
43
  copy.copyAdapterComponents(adapterComponentsDir, targetComponentsDir);
41
44
  console.log('[web-adapter] 已复制适配器组件到 websfc');
42
45
 
43
- const adapterComposablesDir = path.join(adapterDir, 'core', 'composables');
46
+ const adapterComposablesDir =
47
+ framework === 'angular' ? path.join(adapterDir, 'angular', 'composables') : path.join(adapterDir, 'core', 'composables');
44
48
  const targetComposablesDir = path.join(webDir, 'src', 'composables', 'websfc');
45
49
  copy.copyAdapterComposables(adapterComposablesDir, targetComposablesDir);
46
50
  console.log('[web-adapter] 已复制适配器 composables 到 websfc');
47
51
 
48
52
  // 3) 写入 web 项目模板(index.html、package.json、vite.config、postcss、tailwind、App.vue)
49
- templates.createWebPlatformFiles(webDir);
53
+ templates.createWebPlatformFiles(webDir, { framework });
50
54
  console.log('[web-adapter] 已生成 web 平台模板与配置');
51
55
 
52
- // 4) 生成路由(根据 pages 目录)
53
- const routerDirPath = path.join(webDir, 'src', 'router');
54
- if (!fs.existsSync(routerDirPath)) {
55
- fs.mkdirSync(routerDirPath, { recursive: true });
56
+ if (framework === 'vue') {
57
+ const routerDirPath = path.join(webDir, 'src', 'router');
58
+ if (!fs.existsSync(routerDirPath)) {
59
+ fs.mkdirSync(routerDirPath, { recursive: true });
60
+ }
61
+ const routerContent = routerGen.buildRouterFromPages(webDir);
62
+ fs.writeFileSync(path.join(routerDirPath, 'index.ts'), routerContent, 'utf8');
63
+ console.log('[web-adapter] 已创建路由配置');
64
+ appPatch.patchAppForWeb(webDir);
65
+ } else if (framework === 'angular') {
66
+ appPatch.patchAngularAppForWeb(webDir);
56
67
  }
57
- const routerContent = routerGen.buildRouterFromPages(webDir);
58
- fs.writeFileSync(path.join(routerDirPath, 'index.ts'), routerContent, 'utf8');
59
- console.log('[web-adapter] 已创建路由配置');
60
-
61
- // 5) 修补 app.ts:注册所有 Web 适配组件并挂载应用
62
- appPatch.patchAppForWeb(webDir);
63
68
  console.log('[web-adapter] 正在安装依赖项...');
64
69
 
65
70
  // 如需安装依赖,可在此调用(默认跳过以加快生成速度)
@@ -67,6 +72,23 @@ function createWebPlatform(options = {}) {
67
72
  } catch (err) {
68
73
  console.error('[web-adapter] 生成 web 平台时出错:', err);
69
74
  }
75
+ return { framework, webDir };
76
+ }
77
+
78
+ function detectFramework(projectRoot) {
79
+ try {
80
+ const pkgPath = path.join(projectRoot, 'package.json');
81
+ if (fs.existsSync(pkgPath)) {
82
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
83
+ const configured = pkg?.nsWeb?.framework;
84
+ if (configured === 'vue' || configured === 'angular') return configured;
85
+
86
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}), ...(pkg.peerDependencies || {}) };
87
+ if (deps['@nativescript/angular']) return 'angular';
88
+ if (deps['nativescript-vue']) return 'vue';
89
+ }
90
+ } catch {}
91
+ return 'vue';
70
92
  }
71
93
 
72
94
  module.exports = { createWebPlatform };
@@ -24,4 +24,128 @@ function patchAppForWeb(webDir) {
24
24
  }
25
25
  }
26
26
 
27
- module.exports = { patchAppForWeb };
27
+ function patchAngularAppForWeb(webDir) {
28
+ try {
29
+ const srcDir = path.join(webDir, 'src');
30
+ const appDir = path.join(srcDir, 'app');
31
+ if (!fs.existsSync(appDir)) {
32
+ console.warn('[web-adapter] 未找到 src/app,跳过 Angular 修补');
33
+ return;
34
+ }
35
+
36
+ const appHtml = path.join(appDir, 'app.component.html');
37
+ if (fs.existsSync(appHtml)) {
38
+ const html = fs.readFileSync(appHtml, 'utf8');
39
+ const next = html.replace(/<\s*page-router-outlet\s*><\s*\/\s*page-router-outlet\s*>/g, '<router-outlet></router-outlet>');
40
+ if (next !== html) fs.writeFileSync(appHtml, next, 'utf8');
41
+ }
42
+
43
+ const componentFiles = [];
44
+ walkFiles(appDir, (p) => {
45
+ if (p.endsWith('.component.ts')) componentFiles.push(p);
46
+ });
47
+
48
+ for (const filePath of componentFiles) {
49
+ let content = fs.readFileSync(filePath, 'utf8');
50
+
51
+ content = content.replace(/import\s+\{\s*PageRouterOutlet\s*\}\s+from\s+['"]@nativescript\/angular['"]\s*;?/g, "import { RouterOutlet } from '@angular/router';");
52
+ content = content.replace(/\bPageRouterOutlet\b/g, 'RouterOutlet');
53
+ content = content.replace(/<\s*page-router-outlet\b/g, '<router-outlet');
54
+ content = content.replace(/<\/\s*page-router-outlet\s*>/g, '</router-outlet>');
55
+
56
+ content = removeImportsFromModule(content, '@nativescript/angular');
57
+
58
+ if (/\bRouterOutlet\b/.test(content) && !/import\s+\{[^}]*\bRouterOutlet\b[^}]*\}\s+from\s+['"]@angular\/router['"]/.test(content)) {
59
+ content = `import { RouterOutlet } from '@angular/router';\n` + content;
60
+ }
61
+
62
+ if (/\bNativeScriptCommonModule\b/.test(content)) {
63
+ if (!/from\s+['"]@angular\/common['"]/.test(content)) {
64
+ content = `import { CommonModule } from '@angular/common';\n` + content;
65
+ }
66
+ content = content.replace(/\bNativeScriptCommonModule\b/g, 'CommonModule');
67
+ }
68
+
69
+ if (/\bNativeScriptRouterModule\b/.test(content)) {
70
+ if (!/from\s+['"]@angular\/router['"]/.test(content)) {
71
+ content = `import { RouterModule } from '@angular/router';\n` + content;
72
+ }
73
+ content = content.replace(/\bNativeScriptRouterModule\b/g, 'RouterModule');
74
+ }
75
+
76
+ const rel = toPosix(path.relative(path.dirname(filePath), path.join(srcDir, 'components', 'websfc')));
77
+ const importPath = rel.startsWith('.') ? rel : `./${rel}`;
78
+ if (!/nsWebAdapterImports/.test(content)) {
79
+ content = `import { imports as nsWebAdapterImports } from '${importPath}';\n` + content;
80
+ }
81
+
82
+ if (/imports\s*:\s*\[/.test(content)) {
83
+ content = content.replace(/imports\s*:\s*\[([\s\S]*?)\]/m, (m, inner) => {
84
+ if (/\.\.\.\s*nsWebAdapterImports/.test(inner)) return m;
85
+ const trimmed = inner.trim();
86
+ const suffix = trimmed.endsWith(',') || trimmed.length === 0 ? '' : ',';
87
+ return `imports: [${inner}${suffix} ...nsWebAdapterImports]`;
88
+ });
89
+ } else {
90
+ content = content.replace(/schemas\s*:\s*\[/m, `imports: [...nsWebAdapterImports],\n schemas: [`);
91
+ if (!/imports\s*:\s*\[/.test(content)) {
92
+ content = content.replace(/templateUrl\s*:\s*['"][^'"]+['"]\s*,/m, (m) => `${m}\n imports: [...nsWebAdapterImports],`);
93
+ }
94
+ }
95
+
96
+ if (/@Component\(\{[\s\S]*?\bimports\s*:/.test(content) && !/@Component\(\{[\s\S]*?\bstandalone\s*:/.test(content)) {
97
+ if (/\bselector\s*:/.test(content)) {
98
+ content = content.replace(/(\bselector\s*:\s*['"][^'"]+['"]\s*,)/, '$1\n standalone: true,');
99
+ } else {
100
+ content = content.replace(/@Component\(\{\s*\n?/, (m) => `${m} standalone: true,\n`);
101
+ }
102
+ }
103
+
104
+ fs.writeFileSync(filePath, content, 'utf8');
105
+ }
106
+ } catch (err) {
107
+ console.error('[web-adapter] 修补 Angular 应用时出错:', err);
108
+ }
109
+ }
110
+
111
+ function removeImportsFromModule(code, moduleName) {
112
+ const lines = String(code || '').split(/\r?\n/);
113
+ const out = [];
114
+ for (let i = 0; i < lines.length; i++) {
115
+ const line = lines[i];
116
+ if (/^\s*import\b/.test(line)) {
117
+ const stmtLines = [line];
118
+ while (i < lines.length - 1 && !/;\s*$/.test(lines[i])) {
119
+ i++;
120
+ stmtLines.push(lines[i]);
121
+ }
122
+ const stmt = stmtLines.join('\n');
123
+ if (new RegExp(`from\\s+['"]${escapeRegExp(moduleName)}['"]`).test(stmt)) {
124
+ continue;
125
+ }
126
+ out.push(...stmtLines);
127
+ continue;
128
+ }
129
+ out.push(line);
130
+ }
131
+ return out.join('\n');
132
+ }
133
+
134
+ function walkFiles(dir, onFile) {
135
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
136
+ for (const entry of entries) {
137
+ const p = path.join(dir, entry.name);
138
+ if (entry.isDirectory()) walkFiles(p, onFile);
139
+ else onFile(p);
140
+ }
141
+ }
142
+
143
+ function toPosix(p) {
144
+ return p.split(path.sep).join('/');
145
+ }
146
+
147
+ function escapeRegExp(str) {
148
+ return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
149
+ }
150
+
151
+ module.exports = { patchAppForWeb, patchAngularAppForWeb };
@@ -1,35 +1,134 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
 
4
- function createWebPlatformFiles(webDir) {
4
+ function createWebPlatformFiles(webDir, options = {}) {
5
+ const framework = options.framework || 'vue';
5
6
  const projectRoot = path.dirname(path.dirname(webDir));
6
- // 创建或更新 package.json
7
- fs.writeFileSync(path.join(webDir, 'package.json'), JSON.stringify({
8
- "name": "web-platform",
9
- "version": "1.0.0",
10
- "private": true,
11
- "scripts": {
12
- "dev": "vite",
13
- "build": "vue-tsc --noEmit && vite build",
14
- "preview": "vite preview"
7
+
8
+ const srcDir = path.join(webDir, 'src');
9
+ if (!fs.existsSync(srcDir)) fs.mkdirSync(srcDir, { recursive: true });
10
+
11
+ const packageJson = buildPackageJson(framework);
12
+ fs.writeFileSync(path.join(webDir, 'package.json'), JSON.stringify(packageJson, null, 2));
13
+
14
+ const indexHtml = buildIndexHtml(framework);
15
+ fs.writeFileSync(path.join(webDir, 'index.html'), indexHtml);
16
+
17
+ const viteConfig = buildViteConfig(framework);
18
+ fs.writeFileSync(path.join(webDir, 'vite.config.ts'), viteConfig);
19
+
20
+ if (framework === 'angular') {
21
+ writeAngularTsconfigs(webDir);
22
+ }
23
+
24
+ copyOrWritePostcssAndTailwind(projectRoot, webDir, framework);
25
+
26
+ if (framework === 'vue') {
27
+ const appVueContent = `<template>
28
+ <router-view></router-view>
29
+ </template>`;
30
+ fs.writeFileSync(path.join(srcDir, 'App.vue'), appVueContent, 'utf8');
31
+ }
32
+ }
33
+
34
+ function writeAngularTsconfigs(webDir) {
35
+ const tsconfig = {
36
+ compilerOptions: {
37
+ target: 'ES2022',
38
+ useDefineForClassFields: true,
39
+ module: 'ESNext',
40
+ moduleResolution: 'Bundler',
41
+ experimentalDecorators: true,
42
+ strict: true,
43
+ skipLibCheck: true,
44
+ lib: ['ES2022', 'DOM'],
45
+ types: [],
15
46
  },
16
- "dependencies": {
17
- "vue": "^3.3.4",
18
- "vue-router": "^4.2.5"
47
+ angularCompilerOptions: {
48
+ strictTemplates: false,
49
+ strictInjectionParameters: false,
19
50
  },
20
- "devDependencies": {
21
- "@vitejs/plugin-vue": "^4.2.3",
22
- "typescript": "^5.0.2",
23
- "vite": "^4.4.5",
24
- "vue-tsc": "^1.8.5",
25
- "autoprefixer": "^10.4.16",
26
- "postcss": "^8.4.31",
27
- "tailwindcss": "^3.4.0"
28
- }
29
- }, null, 2));
51
+ };
52
+ fs.writeFileSync(path.join(webDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
53
+
54
+ const appTsconfig = {
55
+ extends: './tsconfig.json',
56
+ include: ['src/**/*.ts'],
57
+ };
58
+ fs.writeFileSync(path.join(webDir, 'tsconfig.app.json'), JSON.stringify(appTsconfig, null, 2));
59
+ }
60
+
61
+ function buildPackageJson(framework) {
62
+ if (framework === 'angular') {
63
+ return {
64
+ name: 'ns-angular-web-platform',
65
+ private: true,
66
+ scripts: {
67
+ dev: 'vite',
68
+ build: 'vite build',
69
+ preview: 'vite preview',
70
+ },
71
+ dependencies: {
72
+ '@angular/common': '^18.2.0',
73
+ '@angular/core': '^18.2.0',
74
+ '@angular/platform-browser': '^18.2.0',
75
+ '@angular/router': '^18.2.0',
76
+ rxjs: '^7.8.1',
77
+ 'zone.js': '^0.14.10',
78
+ },
79
+ devDependencies: {
80
+ '@angular/build': '^18.2.0',
81
+ '@analogjs/vite-plugin-angular': '^1.8.0',
82
+ typescript: '^5.0.0',
83
+ vite: '^6.0.0',
84
+ autoprefixer: '^10.4.16',
85
+ postcss: '^8.4.31',
86
+ tailwindcss: '^3.4.0',
87
+ },
88
+ };
89
+ }
90
+
91
+ return {
92
+ name: 'ns-vue-web-platform',
93
+ private: true,
94
+ scripts: {
95
+ dev: 'vite',
96
+ build: 'vue-tsc --noEmit && vite build',
97
+ preview: 'vite preview',
98
+ },
99
+ dependencies: {
100
+ vue: '^3.4.0',
101
+ 'vue-router': '^4.2.5',
102
+ },
103
+ devDependencies: {
104
+ '@vitejs/plugin-vue': '^5.0.0',
105
+ typescript: '^5.0.0',
106
+ vite: '^5.0.0',
107
+ 'vue-tsc': '^2.2.12',
108
+ autoprefixer: '^10.4.16',
109
+ postcss: '^8.4.31',
110
+ tailwindcss: '^3.4.0',
111
+ },
112
+ };
113
+ }
114
+
115
+ function buildIndexHtml(framework) {
116
+ if (framework === 'angular') {
117
+ return `<!DOCTYPE html>
118
+ <html lang="en">
119
+ <head>
120
+ <meta charset="UTF-8" />
121
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
122
+ <title>NS Angular Web</title>
123
+ </head>
124
+ <body>
125
+ <ns-app></ns-app>
126
+ <script type="module" src="/src/main.ts"></script>
127
+ </body>
128
+ </html>`;
129
+ }
30
130
 
31
- // index.html
32
- fs.writeFileSync(path.join(webDir, 'index.html'), `<!DOCTYPE html>
131
+ return `<!DOCTYPE html>
33
132
  <html lang="en">
34
133
  <head>
35
134
  <meta charset="UTF-8" />
@@ -40,34 +139,32 @@ function createWebPlatformFiles(webDir) {
40
139
  <div id="app"></div>
41
140
  <script type="module" src="/src/app.ts"></script>
42
141
  </body>
43
- </html>`);
142
+ </html>`;
143
+ }
44
144
 
45
- // vite.config.ts
46
- const packageJson = {
47
- name: "ns-vue-web-platform",
48
- private: true,
49
- scripts: {
50
- dev: "vite",
51
- build: "vite build"
52
- },
53
- dependencies: {
54
- "vue": "^3.4.0",
55
- "vue-router": "^4.2.5"
56
- },
57
- devDependencies: {
58
- "@vitejs/plugin-vue": "^5.0.0",
59
- "typescript": "^5.0.0",
60
- "vite": "^5.0.0",
61
- "autoprefixer": "^10.4.16",
62
- "postcss": "^8.4.31",
63
- "tailwindcss": "^3.4.0"
145
+ function buildViteConfig(framework) {
146
+ if (framework === 'angular') {
147
+ return `import { defineConfig } from 'vite';
148
+ import path from 'path';
149
+
150
+ export default defineConfig(async () => {
151
+ const { default: angular } = await import('@analogjs/vite-plugin-angular');
152
+ return {
153
+ plugins: [angular()],
154
+ resolve: {
155
+ alias: {
156
+ '@': path.resolve(__dirname, './src')
64
157
  }
158
+ },
159
+ server: {
160
+ port: 3006,
161
+ strictPort: false
162
+ }
65
163
  };
66
- fs.writeFileSync(
67
- path.join(webDir, 'package.json'),
68
- JSON.stringify(packageJson, null, 2)
69
- );
70
- const viteConfig = `import { defineConfig } from 'vite';
164
+ });`;
165
+ }
166
+
167
+ return `import { defineConfig } from 'vite';
71
168
  import vue from '@vitejs/plugin-vue';
72
169
  import path from 'path';
73
170
 
@@ -83,48 +180,28 @@ export default defineConfig({
83
180
  strictPort: false
84
181
  }
85
182
  });`;
86
- fs.writeFileSync(path.join(webDir, 'vite.config.ts'), viteConfig);
183
+ }
87
184
 
88
- // postcss & tailwind configs: 复用根配置(如果存在),否则写默认
185
+ function copyOrWritePostcssAndTailwind(projectRoot, webDir, framework) {
89
186
  const rootPostcss = path.join(projectRoot, 'postcss.config.js');
90
187
  const rootTailwind = path.join(projectRoot, 'tailwind.config.js');
188
+
189
+ const defaultPostcss = `module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};`;
190
+ const defaultTailwind = `/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n content: [\n \"./index.html\",\n \"./src/**/*.{${framework === 'angular' ? 'html,ts,js,jsx,tsx' : 'vue,js,ts,jsx,tsx'}}\",\n ],\n theme: {\n extend: {},\n },\n plugins: [],\n};`;
191
+
91
192
  try {
92
- if (fs.existsSync(rootPostcss)) {
93
- fs.copyFileSync(rootPostcss, path.join(webDir, 'postcss.config.js'));
94
- } else {
95
- const postcssConfig = `module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};`;
96
- fs.writeFileSync(path.join(webDir, 'postcss.config.js'), postcssConfig);
97
- }
193
+ if (fs.existsSync(rootPostcss)) fs.copyFileSync(rootPostcss, path.join(webDir, 'postcss.config.js'));
194
+ else fs.writeFileSync(path.join(webDir, 'postcss.config.js'), defaultPostcss);
98
195
  } catch (e) {
99
- console.warn('[web-adapter] 复制 postcss 配置失败,使用默认:', e?.message);
100
- const postcssConfig = `module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};`;
101
- fs.writeFileSync(path.join(webDir, 'postcss.config.js'), postcssConfig);
196
+ fs.writeFileSync(path.join(webDir, 'postcss.config.js'), defaultPostcss);
102
197
  }
198
+
103
199
  try {
104
- if (fs.existsSync(rootTailwind)) {
105
- fs.copyFileSync(rootTailwind, path.join(webDir, 'tailwind.config.js'));
106
- } else {
107
- const tailwindConfig = `/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n content: [\n "./index.html",\n "./src/**/*.{vue,js,ts,jsx,tsx}",\n ],\n theme: {\n extend: {},\n },\n plugins: [],\n};`;
108
- fs.writeFileSync(path.join(webDir, 'tailwind.config.js'), tailwindConfig);
109
- }
200
+ if (fs.existsSync(rootTailwind)) fs.copyFileSync(rootTailwind, path.join(webDir, 'tailwind.config.js'));
201
+ else fs.writeFileSync(path.join(webDir, 'tailwind.config.js'), defaultTailwind);
110
202
  } catch (e) {
111
- console.warn('[web-adapter] 复制 tailwind 配置失败,使用默认:', e?.message);
112
- const tailwindConfig = `/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n content: [\n "./index.html",\n "./src/**/*.{vue,js,ts,jsx,tsx}",\n ],\n theme: {\n extend: {},\n },\n plugins: [],\n};`;
113
- fs.writeFileSync(path.join(webDir, 'tailwind.config.js'), tailwindConfig);
203
+ fs.writeFileSync(path.join(webDir, 'tailwind.config.js'), defaultTailwind);
114
204
  }
115
-
116
- // App.vue(根组件占位,仅渲染 <router-view/>)
117
- const appVueContent = `<template>
118
- <router-view></router-view>
119
- </template>
120
-
121
- <script setup lang="ts">
122
- // App root component
123
- </script>`;
124
- const appVuePath = path.join(webDir, 'src', 'App.vue');
125
- const srcDir = path.join(webDir, 'src');
126
- if (!fs.existsSync(srcDir)) fs.mkdirSync(srcDir, { recursive: true });
127
- fs.writeFileSync(appVuePath, appVueContent, 'utf8');
128
205
  }
129
206
 
130
- module.exports = { createWebPlatformFiles };
207
+ module.exports = { createWebPlatformFiles };