nativescript-web-adapter 0.1.2 → 0.1.5
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 +220 -244
- package/core/components/AbsoluteLayout.vue +11 -0
- package/core/components/ActionBar.vue +11 -0
- package/core/components/ActionItem.vue +11 -0
- package/core/components/ActivityIndicator.vue +15 -0
- package/core/components/Button.vue +41 -0
- package/core/components/DatePicker.vue +27 -0
- package/core/components/DockLayout.vue +23 -0
- package/core/components/FlexboxLayout.vue +11 -0
- package/core/components/Frame.vue +11 -0
- package/core/components/GridLayout.vue +85 -0
- package/core/components/HtmlView.vue +13 -0
- package/core/components/Image.vue +12 -0
- package/core/components/ImageCacheIt.vue +12 -0
- package/core/components/Label.vue +15 -0
- package/core/components/ListPicker.vue +21 -0
- package/core/components/ListView.vue +12 -0
- package/core/components/NavigationButton.vue +28 -0
- package/core/components/Page.vue +18 -0
- package/core/components/Placeholder.vue +11 -0
- package/core/components/Progress.vue +12 -0
- package/core/components/RootLayout.vue +11 -0
- package/core/components/ScrollView.vue +11 -0
- package/core/components/SearchBar.vue +22 -0
- package/core/components/SegmentedBar.vue +50 -0
- package/core/components/SegmentedBarItem.vue +21 -0
- package/core/components/Slider.vue +18 -0
- package/core/components/StackLayout.vue +11 -0
- package/core/components/Switch.vue +26 -0
- package/core/components/TabView.vue +48 -0
- package/core/components/TabViewItem.vue +27 -0
- package/core/components/TextField.vue +15 -0
- package/core/components/TextView.vue +18 -0
- package/core/components/TimePicker.vue +21 -0
- package/core/components/WebView.vue +13 -0
- package/core/components/WrapLayout.vue +11 -0
- package/core/components/index.js +3 -0
- package/core/components/index.ts +35 -0
- package/core/composables/dialogs.ts +31 -0
- package/core/composables/index.js +3 -0
- package/core/composables/index.ts +4 -0
- package/core/composables/useActionBar.js +7 -0
- package/core/composables/useActionBar.ts +19 -0
- package/core/composables/useFrame.js +8 -0
- package/core/composables/useFrame.ts +25 -0
- package/core/composables/usePage.js +8 -0
- package/core/composables/usePage.ts +25 -0
- package/core/env.d.ts +7 -0
- package/core/index.js +4 -0
- package/core/index.ts +85 -0
- package/core/types.ts +12 -0
- package/dist/nativescript-web-adapter.es.js +83 -0
- package/dist/nativescript-web-adapter.umd.js +1 -0
- package/dist/style.css +1 -0
- package/package.json +35 -49
- package/tools/cli.cjs +59 -0
- package/tools/create-nuxt-platform.cjs +57 -0
- package/tools/create-web-platform.cjs +76 -0
- package/tools/create-web-platform.js +196 -0
- package/tools/modules/appPatch.cjs +27 -0
- package/tools/modules/copy.cjs +84 -0
- package/tools/modules/router.cjs +46 -0
- package/tools/modules/templates-nuxt.cjs +63 -0
- package/tools/modules/templates.cjs +130 -0
- package/tools/modules/transform-nuxt.cjs +59 -0
- package/tools/modules/transform.cjs +93 -0
- package/dist/core.cjs +0 -3
- package/dist/core.cjs.map +0 -1
- package/dist/core.js +0 -2
- package/dist/core.js.map +0 -1
- package/dist/index.cjs +0 -377
- package/dist/index.cjs.map +0 -1
- package/dist/index.css +0 -172
- package/dist/index.js +0 -361
- package/dist/index.js.map +0 -1
- package/dist/types/core/index.d.ts +0 -8
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.types.d.ts +0 -1
- package/dist/types/vue/components/ActionBar.d.ts +0 -5
- package/dist/types/vue/components/ActionItem.d.ts +0 -15
- package/dist/types/vue/components/Button.d.ts +0 -26
- package/dist/types/vue/components/ContentView.d.ts +0 -3
- package/dist/types/vue/components/FlexboxLayout.d.ts +0 -35
- package/dist/types/vue/components/Frame.d.ts +0 -3
- package/dist/types/vue/components/GridLayout.d.ts +0 -26
- package/dist/types/vue/components/ImageCacheIt.d.ts +0 -23
- package/dist/types/vue/components/Label.d.ts +0 -26
- package/dist/types/vue/components/ListView.d.ts +0 -24
- package/dist/types/vue/components/Page.d.ts +0 -5
- package/dist/types/vue/components/StackLayout.d.ts +0 -5
- package/dist/types/vue/components/TabView.d.ts +0 -26
- package/dist/types/vue/components/TabViewItem.d.ts +0 -24
- package/dist/types/vue/index.d.ts +0 -18
- package/dist/types/vue/index.types.d.ts +0 -17
- package/dist/types/vue.d.ts +0 -266
- package/dist/vue.cjs +0 -377
- package/dist/vue.cjs.map +0 -1
- package/dist/vue.css +0 -172
- package/dist/vue.js +0 -361
- package/dist/vue.js.map +0 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// 文件扩展名应为 .cjs 以兼容 CommonJS require
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// CommonJS export will be set at the end of the file
|
|
7
|
+
function createWebPlatform(options = {}) {
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
const platformsDir = path.join(cwd, 'platforms');
|
|
10
|
+
const webDir = path.join(platformsDir, 'web');
|
|
11
|
+
const srcDir = path.join(cwd, 'src');
|
|
12
|
+
|
|
13
|
+
// 确保platforms/web目录存在
|
|
14
|
+
if (!fs.existsSync(webDir)) {
|
|
15
|
+
fs.mkdirSync(webDir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 复制源代码到web平台
|
|
19
|
+
copySourceFiles(srcDir, path.join(webDir, 'src'));
|
|
20
|
+
// 复制适配器的 composables 到 web 项目的 src/composables/websfc
|
|
21
|
+
try {
|
|
22
|
+
const adapterComposablesDir = path.join(__dirname, '..', 'core', 'composables');
|
|
23
|
+
const targetComposablesDir = path.join(webDir, 'src', 'composables', 'websfc');
|
|
24
|
+
copyAdapterDir(adapterComposablesDir, targetComposablesDir);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.warn('[web-adapter] 复制适配器 composables 失败:', err);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 创建web平台特定的文件
|
|
30
|
+
createWebPlatformFiles(webDir);
|
|
31
|
+
|
|
32
|
+
// 安装依赖
|
|
33
|
+
installDependencies(webDir);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function copyAdapterDir(srcDir, destDir) {
|
|
37
|
+
if (!fs.existsSync(srcDir)) {
|
|
38
|
+
// no composables to copy
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (!fs.existsSync(destDir)) {
|
|
42
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
47
|
+
const destPath = path.join(destDir, entry.name);
|
|
48
|
+
if (entry.isDirectory()) {
|
|
49
|
+
copyAdapterDir(srcPath, destPath);
|
|
50
|
+
} else {
|
|
51
|
+
try {
|
|
52
|
+
fs.copyFileSync(srcPath, destPath);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error('[web-adapter] 复制文件失败:', srcPath, err);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function copySourceFiles(src, dest) {
|
|
61
|
+
if (!fs.existsSync(dest)) {
|
|
62
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
66
|
+
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
const srcPath = path.join(src, entry.name);
|
|
69
|
+
const destPath = path.join(dest, entry.name);
|
|
70
|
+
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
copySourceFiles(srcPath, destPath);
|
|
73
|
+
} else {
|
|
74
|
+
// 转换并复制文件
|
|
75
|
+
let content = fs.readFileSync(srcPath, 'utf8');
|
|
76
|
+
content = transformContent(content);
|
|
77
|
+
fs.writeFileSync(destPath, content);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function transformContent(content) {
|
|
83
|
+
// 将nativescript特定的导入替换为web版本
|
|
84
|
+
content = content.replace(/from\s+['"](nativescript-vue)['"]/g, "from 'vue'");
|
|
85
|
+
content = content.replace(/from\s+['"]([@]nativescript\/core)['"]/g, "from 'nativescript-web-adapter'");
|
|
86
|
+
|
|
87
|
+
// 移除registerElement调用
|
|
88
|
+
content = content.replace(/registerElement\([^)]+\);?\n?/g, '// registerElement removed for web\n');
|
|
89
|
+
|
|
90
|
+
// 移除平台特定代码
|
|
91
|
+
if (content.includes('__ANDROID__') || content.includes('Application.launchEvent')) {
|
|
92
|
+
content = content.replace(/Application\.on[\s\S]*?}\);/g, '');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return content;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function createWebPlatformFiles(webDir) {
|
|
99
|
+
// 创建index.html
|
|
100
|
+
const indexHtml = `<!DOCTYPE html>
|
|
101
|
+
<html lang="en">
|
|
102
|
+
<head>
|
|
103
|
+
<meta charset="UTF-8" />
|
|
104
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
105
|
+
<title>NS Vue Web</title>
|
|
106
|
+
</head>
|
|
107
|
+
<body>
|
|
108
|
+
<div id="app"></div>
|
|
109
|
+
<script type="module" src="/src/app.ts"></script>
|
|
110
|
+
</body>
|
|
111
|
+
</html>`;
|
|
112
|
+
|
|
113
|
+
fs.writeFileSync(path.join(webDir, 'index.html'), indexHtml);
|
|
114
|
+
|
|
115
|
+
// 创建web平台的package.json
|
|
116
|
+
const packageJson = {
|
|
117
|
+
name: "ns-vue-web-platform",
|
|
118
|
+
private: true,
|
|
119
|
+
scripts: {
|
|
120
|
+
dev: "vite",
|
|
121
|
+
build: "vite build"
|
|
122
|
+
},
|
|
123
|
+
dependencies: {
|
|
124
|
+
"vue": "^3.4.0",
|
|
125
|
+
"nativescript-web-adapter": "file:../../nativescript-web-adapter"
|
|
126
|
+
},
|
|
127
|
+
devDependencies: {
|
|
128
|
+
"@vitejs/plugin-vue": "^5.0.0",
|
|
129
|
+
"typescript": "^5.0.0",
|
|
130
|
+
"vite": "^5.0.0",
|
|
131
|
+
"autoprefixer": "^10.4.16",
|
|
132
|
+
"postcss": "^8.4.31",
|
|
133
|
+
"tailwindcss": "^3.4.0"
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
fs.writeFileSync(
|
|
138
|
+
path.join(webDir, 'package.json'),
|
|
139
|
+
JSON.stringify(packageJson, null, 2)
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// 创建vite.config.ts(设置 dev server 端口为 3005,避免冲突)
|
|
143
|
+
const viteConfig = `import { defineConfig } from 'vite';
|
|
144
|
+
import vue from '@vitejs/plugin-vue';
|
|
145
|
+
import path from 'path';
|
|
146
|
+
|
|
147
|
+
export default defineConfig({
|
|
148
|
+
plugins: [vue()],
|
|
149
|
+
resolve: {
|
|
150
|
+
alias: {
|
|
151
|
+
'@': path.resolve(__dirname, './src'),
|
|
152
|
+
'@nativescript/core': 'nativescript-web-adapter'
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
server: {
|
|
156
|
+
port: 3005,
|
|
157
|
+
strictPort: true
|
|
158
|
+
}
|
|
159
|
+
});`;
|
|
160
|
+
|
|
161
|
+
fs.writeFileSync(path.join(webDir, 'vite.config.ts'), viteConfig);
|
|
162
|
+
|
|
163
|
+
// 创建postcss.config.js
|
|
164
|
+
const postcssConfig = `module.exports = {
|
|
165
|
+
plugins: {
|
|
166
|
+
tailwindcss: {},
|
|
167
|
+
autoprefixer: {},
|
|
168
|
+
},
|
|
169
|
+
};`;
|
|
170
|
+
|
|
171
|
+
fs.writeFileSync(path.join(webDir, 'postcss.config.js'), postcssConfig);
|
|
172
|
+
|
|
173
|
+
// 创建tailwind.config.js
|
|
174
|
+
const tailwindConfig = `/** @type {import('tailwindcss').Config} */
|
|
175
|
+
module.exports = {
|
|
176
|
+
content: [
|
|
177
|
+
"./index.html",
|
|
178
|
+
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
|
179
|
+
],
|
|
180
|
+
theme: {
|
|
181
|
+
extend: {},
|
|
182
|
+
},
|
|
183
|
+
plugins: [],
|
|
184
|
+
};`;
|
|
185
|
+
|
|
186
|
+
fs.writeFileSync(path.join(webDir, 'tailwind.config.js'), tailwindConfig);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function installDependencies(webDir) {
|
|
190
|
+
const { execSync } = require('child_process');
|
|
191
|
+
execSync('npm install', { cwd: webDir, stdio: 'inherit' });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = {
|
|
195
|
+
createWebPlatform
|
|
196
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function patchAppForWeb(webDir) {
|
|
5
|
+
try {
|
|
6
|
+
const appPath = path.join(webDir, 'src', 'app.ts');
|
|
7
|
+
if (!fs.existsSync(appPath)) {
|
|
8
|
+
console.warn('[web-adapter] 未找到 app.ts,跳过 app 修补');
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 动态扫描 websfc 组件目录,生成 import 与注册语句
|
|
13
|
+
const websfcDir = path.join(webDir, 'src', 'components', 'websfc');
|
|
14
|
+
const entries = fs.existsSync(websfcDir) ? fs.readdirSync(websfcDir) : [];
|
|
15
|
+
const vueFiles = entries.filter(f => f.endsWith('.vue'));
|
|
16
|
+
const imports = vueFiles.map(f => `import ${path.basename(f, '.vue')} from './components/websfc/${f}';`).join('\n');
|
|
17
|
+
const registers = vueFiles.map(f => `app.component("${path.basename(f, '.vue')}", ${path.basename(f, '.vue')});`).join('\n');
|
|
18
|
+
const content = `${imports}\nimport * as ns from './composables/websfc/index.ts';\nimport { createApp } from 'vue';\nimport { initGlobals } from "./globals";\nimport router from './router';\nimport App from './App.vue';\nimport './app.css';\n\ninitGlobals();\n\nconst app = createApp(App);\n\n// Register web components\n${registers}\n\n// Register global composables for dialogs, frame, page, etc.\napp.config.globalProperties.$ns = ns;\n\napp.use(router);\napp.mount('#app');`;
|
|
19
|
+
|
|
20
|
+
fs.writeFileSync(appPath, content, 'utf8');
|
|
21
|
+
console.log('[web-adapter] 已修补 app.ts');
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.error('[web-adapter] 修补 app.ts 时出错:', err);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = { patchAppForWeb };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function copyAdapterComponents(srcDir, destDir) {
|
|
5
|
+
if (!fs.existsSync(srcDir)) {
|
|
6
|
+
console.warn('[web-adapter] 适配器组件目录不存在:', srcDir);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (!fs.existsSync(destDir)) {
|
|
10
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
13
|
+
for (const entry of entries) {
|
|
14
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
15
|
+
const destPath = path.join(destDir, entry.name);
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
if (!fs.existsSync(destPath)) fs.mkdirSync(destPath, { recursive: true });
|
|
18
|
+
copyAdapterComponents(srcPath, destPath);
|
|
19
|
+
} else {
|
|
20
|
+
try {
|
|
21
|
+
fs.copyFileSync(srcPath, destPath);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.error('[web-adapter] 复制组件失败:', srcPath, err);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function copyAdapterComposables(srcDir, destDir) {
|
|
30
|
+
if (!fs.existsSync(srcDir)) {
|
|
31
|
+
console.warn('[web-adapter] 适配器 composables 目录不存在:', srcDir);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (!fs.existsSync(destDir)) {
|
|
35
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
40
|
+
const destPath = path.join(destDir, entry.name);
|
|
41
|
+
if (entry.isDirectory()) {
|
|
42
|
+
if (!fs.existsSync(destPath)) fs.mkdirSync(destPath, { recursive: true });
|
|
43
|
+
copyAdapterComposables(srcPath, destPath);
|
|
44
|
+
} else {
|
|
45
|
+
try {
|
|
46
|
+
fs.copyFileSync(srcPath, destPath);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error('[web-adapter] 复制 composable 文件失败:', srcPath, err);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function copySourceFiles(src, dest, transformContent) {
|
|
55
|
+
if (!fs.existsSync(src)) {
|
|
56
|
+
console.warn('[web-adapter] 源代码目录不存在:', src);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!fs.existsSync(dest)) {
|
|
60
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
const srcPath = path.join(src, entry.name);
|
|
65
|
+
const destPath = path.join(dest, entry.name);
|
|
66
|
+
if (entry.isDirectory()) {
|
|
67
|
+
copySourceFiles(srcPath, destPath, transformContent);
|
|
68
|
+
} else {
|
|
69
|
+
try {
|
|
70
|
+
let content = fs.readFileSync(srcPath, 'utf8');
|
|
71
|
+
content = transformContent ? transformContent(content, srcPath) : content;
|
|
72
|
+
fs.writeFileSync(destPath, content);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error('[web-adapter] 复制文件失败:', srcPath, err);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
copyAdapterComponents,
|
|
82
|
+
copyAdapterComposables,
|
|
83
|
+
copySourceFiles,
|
|
84
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function buildRouterFromPages(webDir) {
|
|
5
|
+
const pagesDir = path.join(webDir, 'src', 'pages');
|
|
6
|
+
const lines = [];
|
|
7
|
+
lines.push(`import { createRouter, createWebHistory } from 'vue-router'`);
|
|
8
|
+
let routes = [];
|
|
9
|
+
if (fs.existsSync(pagesDir)) {
|
|
10
|
+
const entries = fs.readdirSync(pagesDir, { withFileTypes: true });
|
|
11
|
+
for (const entry of entries) {
|
|
12
|
+
if (!entry.isFile()) continue;
|
|
13
|
+
if (!/\.vue$/i.test(entry.name)) continue;
|
|
14
|
+
const base = entry.name.replace(/\.vue$/i, '');
|
|
15
|
+
const importName = toIdentifier(base);
|
|
16
|
+
lines.push(`import ${importName} from '../pages/${entry.name}'`);
|
|
17
|
+
const routePath = base.toLowerCase() === 'index' ? '/' : `/${base}`;
|
|
18
|
+
routes.push(` { path: '${routePath}', name: '${base}', component: ${importName} }`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (routes.length === 0) {
|
|
22
|
+
lines.push(`import Home from '../components/Home.vue'`);
|
|
23
|
+
routes.push(` { path: '/', name: 'Home', component: Home }`);
|
|
24
|
+
}
|
|
25
|
+
lines.push('');
|
|
26
|
+
lines.push('const routes = [');
|
|
27
|
+
lines.push(routes.join(',\n'));
|
|
28
|
+
lines.push(']');
|
|
29
|
+
lines.push('');
|
|
30
|
+
lines.push('const router = createRouter({');
|
|
31
|
+
lines.push(' history: createWebHistory(),');
|
|
32
|
+
lines.push(' routes');
|
|
33
|
+
lines.push('})');
|
|
34
|
+
lines.push('');
|
|
35
|
+
lines.push('export default router');
|
|
36
|
+
return lines.join('\n');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function toIdentifier(name) {
|
|
40
|
+
let id = name.replace(/[^a-zA-Z0-9_$]/g, '_');
|
|
41
|
+
if (/^[0-9]/.test(id)) id = '_' + id;
|
|
42
|
+
id = id.replace(/(^|_)([a-z])/g, (m, p1, p2) => p1 + p2.toUpperCase());
|
|
43
|
+
return id;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { buildRouterFromPages, toIdentifier };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function createNuxtPlatformFiles(nuxtDir) {
|
|
5
|
+
const projectRoot = path.dirname(path.dirname(nuxtDir));
|
|
6
|
+
|
|
7
|
+
const pkg = {
|
|
8
|
+
name: 'ns-nuxt-platform',
|
|
9
|
+
private: true,
|
|
10
|
+
scripts: {
|
|
11
|
+
dev: 'nuxt dev',
|
|
12
|
+
build: 'nuxt build',
|
|
13
|
+
preview: 'nuxt preview'
|
|
14
|
+
},
|
|
15
|
+
dependencies: {
|
|
16
|
+
nuxt: '^3.12.0'
|
|
17
|
+
},
|
|
18
|
+
devDependencies: {
|
|
19
|
+
typescript: '^5.0.0',
|
|
20
|
+
tailwindcss: '^3.4.0',
|
|
21
|
+
postcss: '^8.4.31',
|
|
22
|
+
autoprefixer: '^10.4.16'
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
fs.writeFileSync(path.join(nuxtDir, 'package.json'), JSON.stringify(pkg, null, 2));
|
|
26
|
+
|
|
27
|
+
const appVue = `<template>\n <NuxtPage />\n</template>`;
|
|
28
|
+
fs.writeFileSync(path.join(nuxtDir, 'app.vue'), appVue);
|
|
29
|
+
|
|
30
|
+
const nuxtConfig = `import { defineNuxtConfig } from 'nuxt/config'
|
|
31
|
+
export default defineNuxtConfig({
|
|
32
|
+
components: [{ path: '~/components/websfc', global: true }],
|
|
33
|
+
css: ['~/assets/app.css']
|
|
34
|
+
})`;
|
|
35
|
+
fs.writeFileSync(path.join(nuxtDir, 'nuxt.config.ts'), nuxtConfig);
|
|
36
|
+
|
|
37
|
+
const pluginsDir = path.join(nuxtDir, 'plugins');
|
|
38
|
+
if (!fs.existsSync(pluginsDir)) fs.mkdirSync(pluginsDir, { recursive: true });
|
|
39
|
+
const pluginContent = `import { initGlobals } from '~/globals'
|
|
40
|
+
import * as ns from '~/composables/websfc/index.ts'
|
|
41
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
42
|
+
nuxtApp.vueApp.config.globalProperties.$ns = ns
|
|
43
|
+
initGlobals()
|
|
44
|
+
})`;
|
|
45
|
+
fs.writeFileSync(path.join(pluginsDir, 'ns-web.ts'), pluginContent);
|
|
46
|
+
|
|
47
|
+
const rootPostcss = path.join(projectRoot, 'postcss.config.js');
|
|
48
|
+
const rootTailwind = path.join(projectRoot, 'tailwind.config.js');
|
|
49
|
+
if (fs.existsSync(rootPostcss)) {
|
|
50
|
+
fs.copyFileSync(rootPostcss, path.join(nuxtDir, 'postcss.config.js'));
|
|
51
|
+
} else {
|
|
52
|
+
const postcssConfig = `module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};`;
|
|
53
|
+
fs.writeFileSync(path.join(nuxtDir, 'postcss.config.js'), postcssConfig);
|
|
54
|
+
}
|
|
55
|
+
if (fs.existsSync(rootTailwind)) {
|
|
56
|
+
fs.copyFileSync(rootTailwind, path.join(nuxtDir, 'tailwind.config.js'));
|
|
57
|
+
} else {
|
|
58
|
+
const tailwindConfig = `/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n content: [\n './app.vue',\n './components/**/*.{vue,js,ts}',\n './pages/**/*.{vue,js,ts}',\n ],\n theme: {\n extend: {},\n },\n plugins: [],\n};`;
|
|
59
|
+
fs.writeFileSync(path.join(nuxtDir, 'tailwind.config.js'), tailwindConfig);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { createNuxtPlatformFiles };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function createWebPlatformFiles(webDir) {
|
|
5
|
+
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"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"vue": "^3.3.4",
|
|
18
|
+
"vue-router": "^4.2.5"
|
|
19
|
+
},
|
|
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));
|
|
30
|
+
|
|
31
|
+
// index.html
|
|
32
|
+
fs.writeFileSync(path.join(webDir, 'index.html'), `<!DOCTYPE html>
|
|
33
|
+
<html lang="en">
|
|
34
|
+
<head>
|
|
35
|
+
<meta charset="UTF-8" />
|
|
36
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
37
|
+
<title>NS Vue Web</title>
|
|
38
|
+
</head>
|
|
39
|
+
<body>
|
|
40
|
+
<div id="app"></div>
|
|
41
|
+
<script type="module" src="/src/app.ts"></script>
|
|
42
|
+
</body>
|
|
43
|
+
</html>`);
|
|
44
|
+
|
|
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"
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
fs.writeFileSync(
|
|
67
|
+
path.join(webDir, 'package.json'),
|
|
68
|
+
JSON.stringify(packageJson, null, 2)
|
|
69
|
+
);
|
|
70
|
+
const viteConfig = `import { defineConfig } from 'vite';
|
|
71
|
+
import vue from '@vitejs/plugin-vue';
|
|
72
|
+
import path from 'path';
|
|
73
|
+
|
|
74
|
+
export default defineConfig({
|
|
75
|
+
plugins: [vue()],
|
|
76
|
+
resolve: {
|
|
77
|
+
alias: {
|
|
78
|
+
'@': path.resolve(__dirname, './src')
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
server: {
|
|
82
|
+
port: 3005,
|
|
83
|
+
strictPort: false
|
|
84
|
+
}
|
|
85
|
+
});`;
|
|
86
|
+
fs.writeFileSync(path.join(webDir, 'vite.config.ts'), viteConfig);
|
|
87
|
+
|
|
88
|
+
// postcss & tailwind configs: 复用根配置(如果存在),否则写默认
|
|
89
|
+
const rootPostcss = path.join(projectRoot, 'postcss.config.js');
|
|
90
|
+
const rootTailwind = path.join(projectRoot, 'tailwind.config.js');
|
|
91
|
+
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
|
+
}
|
|
98
|
+
} 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);
|
|
102
|
+
}
|
|
103
|
+
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
|
+
}
|
|
110
|
+
} 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);
|
|
114
|
+
}
|
|
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
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = { createWebPlatformFiles };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function transformNuxtContent(content, srcPath) {
|
|
2
|
+
if (srcPath && /globals\.ts$/.test(srcPath)) {
|
|
3
|
+
return "export function initGlobals() {}\n";
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
content = content.replace(/from\s+['\"](nativescript-vue)['\"]/g, "from 'vue'");
|
|
7
|
+
content = content.replace(/import\s+.*@nativescript\/core.*;?\n?/g, '');
|
|
8
|
+
content = content.replace(/import\s+.*nativescript-web-adapter.*;?\n?/g, '');
|
|
9
|
+
content = content.replace(/declare\s+var\s+com[:\s\w]*;?\n?/g, '');
|
|
10
|
+
|
|
11
|
+
content = content.replace(/function\s+enterNow\([^\)]*\)\s*\{[\s\S]*?\}\n?/g, "function enterNow() { window.open(\"https://viteconf.amsterdam\", '_blank'); }\n");
|
|
12
|
+
content = content.replace(/function\s+enterNow\([^\)]*\)\s*\{[\s\S]*?\}\s*[\s\S]*?let interval/g, 'function enterNow() { window.open("https://viteconf.amsterdam", "_blank"); }\n\nlet interval');
|
|
13
|
+
|
|
14
|
+
if (srcPath && /\.vue$/i.test(srcPath)) {
|
|
15
|
+
const hasNavigateTo = /\$navigateTo\(\s*[A-Za-z_][A-Za-z0-9_]*\s*\)/.test(content);
|
|
16
|
+
const hasNavigateBack = /\$navigateBack\(\s*\)/.test(content);
|
|
17
|
+
const hasFrameNav = /Frame\.(navigate|goBack)\(/.test(content);
|
|
18
|
+
const hasFrameTopmost = /Frame\.topmost\(\)\.(navigate|goBack)\(/.test(content);
|
|
19
|
+
if (hasNavigateTo || hasNavigateBack || hasFrameNav || hasFrameTopmost) {
|
|
20
|
+
content = content.replace(/(?:this\.|[A-Za-z_$]+\?\.|[A-Za-z_$]+\.)*\$navigateTo\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)/g, "router.push('/$1')");
|
|
21
|
+
content = content.replace(/(?:this\.|[A-Za-z_$]+\?\.|[A-Za-z_$]+\.)*\$navigateBack\(\s*\)/g, 'router.go(-1)');
|
|
22
|
+
content = content.replace(/Frame\.goBack\(\s*\)/g, 'router.go(-1)');
|
|
23
|
+
content = content.replace(/Frame\.navigate\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)/g, "router.push('/$1')");
|
|
24
|
+
content = content.replace(/Frame\.navigate\(\s*['\"]([^'\"]+)['\"]\s*\)/g, (m, p1) => {
|
|
25
|
+
const base = String(p1).split('/')?.pop()?.replace(/\.vue$/i, '') || p1;
|
|
26
|
+
return `router.push('/${base}')`;
|
|
27
|
+
});
|
|
28
|
+
content = content.replace(/Frame\.navigate\(\s*\{[\s\S]*?moduleName\s*:\s*['\"]([^'\"]+)['\"][\s\S]*?\}\s*\)/g, (m, p1) => {
|
|
29
|
+
const base = String(p1).split('/')?.pop()?.replace(/\.vue$/i, '') || p1;
|
|
30
|
+
return `router.push('/${base}')`;
|
|
31
|
+
});
|
|
32
|
+
content = content.replace(/Frame\.topmost\(\)\.goBack\(\s*\)/g, 'router.go(-1)');
|
|
33
|
+
content = content.replace(/Frame\.topmost\(\)\.navigate\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)/g, "router.push('/$1')");
|
|
34
|
+
content = content.replace(/Frame\.topmost\(\)\.navigate\(\s*['\"]([^'\"]+)['\"]\s*\)/g, (m, p1) => {
|
|
35
|
+
const base = String(p1).split('/')?.pop()?.replace(/\.vue$/i, '') || p1;
|
|
36
|
+
return `router.push('/${base}')`;
|
|
37
|
+
});
|
|
38
|
+
content = content.replace(/Frame\.topmost\(\)\.navigate\(\s*\{[\s\S]*?moduleName\s*:\s*['\"]([^'\"]+)['\"][\s\S]*?\}\s*\)/g, (m, p1) => {
|
|
39
|
+
const base = String(p1).split('/')?.pop()?.replace(/\.vue$/i, '') || p1;
|
|
40
|
+
return `router.push('/${base}')`;
|
|
41
|
+
});
|
|
42
|
+
if (!/useRouter\s*\(/.test(content)) {
|
|
43
|
+
content = content.replace(/(<script[^>]*>)/i, "$1\nimport { useRouter } from 'vue-router';\nconst router = useRouter();\n");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
content = content.replace(/.*(nativeApp|android\.|UIApplication|NSURL|NSDictionary|com\.tns|intent|startActivity|android\.content).*/g, '');
|
|
49
|
+
content = content.replace(/else\s*\{[\s\S]*?\}/g, '');
|
|
50
|
+
content = content.replace(/\/\s+\/>/g, '\/>');
|
|
51
|
+
content = content.replace(/registerElement\([^)]+\);?\n?/g, '');
|
|
52
|
+
if (content.includes('__ANDROID__') || content.includes('Application.launchEvent')) {
|
|
53
|
+
content = content.replace(/Application\.on[\s\S]*?}\);/g, '');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return content;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { transformNuxtContent };
|