transone-cli 0.6.3 → 0.10.0
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 -3
- package/README.zh-CN.md +31 -0
- package/dist/app/analyze.d.ts +2 -1
- package/dist/app/components.d.ts +4 -0
- package/dist/app/types.d.ts +3 -0
- package/dist/{build-mp-vk5ap2cw.js → build-mp-y5pwahsy.js} +824 -271
- package/dist/build-mp-y5pwahsy.js.map +24 -0
- package/dist/build.d.ts +3 -1
- package/dist/bundle-inputs.d.ts +6 -0
- package/dist/cli.d.ts +4 -2
- package/dist/cli.js +49 -16
- package/dist/cli.js.map +3 -3
- package/dist/components/discovery.d.ts +5 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/report.d.ts +48 -0
- package/dist/components/resolution.d.ts +7 -0
- package/dist/components/types.d.ts +56 -0
- package/dist/components/web.d.ts +11 -0
- package/dist/config-inputs.d.ts +4 -0
- package/dist/create.d.ts +4 -4
- package/dist/document-styles.d.ts +7 -0
- package/dist/index-2g3e5sjt.js +3388 -0
- package/dist/index-2g3e5sjt.js.map +32 -0
- package/dist/index-d51tp4y9.js +1402 -0
- package/dist/index-d51tp4y9.js.map +11 -0
- package/dist/index-j4h0wd27.js +489 -0
- package/dist/index-j4h0wd27.js.map +14 -0
- package/dist/index-xgn9fbc6.js +298 -0
- package/dist/index-xgn9fbc6.js.map +13 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +12 -4
- package/dist/index.js.map +2 -2
- package/dist/mp/inheritance.d.ts +17 -0
- package/dist/mp/inline.d.ts +22 -0
- package/dist/mp/methods.d.ts +1 -0
- package/dist/mp/source.d.ts +11 -0
- package/dist/mp/types.d.ts +8 -0
- package/dist/output-transaction.d.ts +19 -0
- package/dist/{project-ybacfze8.js → project-6tft5e77.js} +38 -6
- package/dist/{project-ybacfze8.js.map → project-6tft5e77.js.map} +3 -3
- package/dist/project.d.ts +2 -1
- package/dist/safe-path.d.ts +1 -0
- package/dist/scaffold.d.ts +6 -0
- package/dist/target/types.d.ts +7 -6
- package/dist/target/web-target.d.ts +1 -0
- package/dist/types.d.ts +13 -1
- package/dist/web-bundle.d.ts +17 -0
- package/dist/web-styles.d.ts +9 -0
- package/package.json +2 -2
- package/dist/build-mp-vk5ap2cw.js.map +0 -22
- package/dist/index-8k9x2k6m.js +0 -2288
- package/dist/index-8k9x2k6m.js.map +0 -27
- package/dist/index-k1qfvp0y.js +0 -19
- package/dist/index-k1qfvp0y.js.map +0 -10
- package/dist/index-yb2wsc7f.js +0 -7
- package/dist/index-yb2wsc7f.js.map +0 -9
|
@@ -0,0 +1,3388 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
createWebComponentContext,
|
|
4
|
+
createWebStylesPlugin
|
|
5
|
+
} from "./index-d51tp4y9.js";
|
|
6
|
+
import {
|
|
7
|
+
assertSafeSubdirectory,
|
|
8
|
+
assertSafeSubdirectoryDoesNotContain,
|
|
9
|
+
buildInputPaths,
|
|
10
|
+
buildOutputPath,
|
|
11
|
+
loadTypescript,
|
|
12
|
+
withBuildOutput
|
|
13
|
+
} from "./index-xgn9fbc6.js";
|
|
14
|
+
import {
|
|
15
|
+
PLATFORM_DEFINITIONS,
|
|
16
|
+
__require,
|
|
17
|
+
canonicalPath,
|
|
18
|
+
createComponentIndex,
|
|
19
|
+
getPlatformDefinition,
|
|
20
|
+
normalizePlatform
|
|
21
|
+
} from "./index-j4h0wd27.js";
|
|
22
|
+
|
|
23
|
+
// src/target/types.ts
|
|
24
|
+
var TARGET_TYPES = [
|
|
25
|
+
...new Set(PLATFORM_DEFINITIONS.map((definition) => definition.target))
|
|
26
|
+
];
|
|
27
|
+
function isTargetType(value) {
|
|
28
|
+
return typeof value === "string" && TARGET_TYPES.includes(value);
|
|
29
|
+
}
|
|
30
|
+
var MP_TARGET_TYPES = TARGET_TYPES.filter((target) => target.startsWith("mp-"));
|
|
31
|
+
var APP_TARGET_TYPES = TARGET_TYPES.filter((target) => target.startsWith("app-"));
|
|
32
|
+
function isAppTargetType(value) {
|
|
33
|
+
return typeof value === "string" && APP_TARGET_TYPES.includes(value);
|
|
34
|
+
}
|
|
35
|
+
function isMpTargetType(value) {
|
|
36
|
+
return typeof value === "string" && MP_TARGET_TYPES.includes(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/config.ts
|
|
40
|
+
import { existsSync } from "fs";
|
|
41
|
+
import { resolve } from "path";
|
|
42
|
+
var DEFAULT_CONFIG = {
|
|
43
|
+
entry: "src/main.ts",
|
|
44
|
+
server: {
|
|
45
|
+
host: "127.0.0.1",
|
|
46
|
+
port: 52211,
|
|
47
|
+
proxy: {}
|
|
48
|
+
},
|
|
49
|
+
build: {
|
|
50
|
+
outDir: "dist/build/h5",
|
|
51
|
+
basePath: "",
|
|
52
|
+
directoryPages: false
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
function defineConfig(config) {
|
|
56
|
+
return config;
|
|
57
|
+
}
|
|
58
|
+
async function resolveConfig(options = {}) {
|
|
59
|
+
const root = resolve(options.root ?? process.cwd());
|
|
60
|
+
const configFile = resolve(root, "transone.config.ts");
|
|
61
|
+
const hasInlineConfig = options.config !== undefined;
|
|
62
|
+
const loadedConfig = hasInlineConfig ? { exists: false } : await loadConfigFile(configFile);
|
|
63
|
+
let fileConfig = {};
|
|
64
|
+
if (loadedConfig.exists) {
|
|
65
|
+
const config2 = loadedConfig.config;
|
|
66
|
+
validateUserConfig(config2);
|
|
67
|
+
fileConfig = config2;
|
|
68
|
+
}
|
|
69
|
+
if (hasInlineConfig) {
|
|
70
|
+
validateUserConfig(options.config);
|
|
71
|
+
}
|
|
72
|
+
const inlineOverrides = toInlineOverrides(options);
|
|
73
|
+
validateUserConfig(inlineOverrides);
|
|
74
|
+
const config = mergeConfig(fileConfig, options.config ?? {}, inlineOverrides);
|
|
75
|
+
const entry = resolve(root, config.entry);
|
|
76
|
+
if (!existsSync(entry) && !config.library) {
|
|
77
|
+
throw new Error(`Entry file does not exist: ${entry}`);
|
|
78
|
+
}
|
|
79
|
+
const pages = await resolvePages(root, entry, config.pages);
|
|
80
|
+
const platform = resolvePlatform(options.target ?? config.target);
|
|
81
|
+
const target = getPlatformDefinition(platform).target;
|
|
82
|
+
const explicitOutDir = options.outDir ?? options.config?.build?.outDir ?? fileConfig.build?.outDir;
|
|
83
|
+
const webOutDir = explicitOutDir ?? (platform === "web-mobile" /* WebMobile */ || platform === "web-pc" /* WebPc */ ? `dist/build/${platform}` : config.build.outDir);
|
|
84
|
+
const mpConfig = pickMpConfig(config.mp, target);
|
|
85
|
+
const mpPages = mpConfig?.pages ? await resolvePages(root, entry, mpConfig.pages, {
|
|
86
|
+
includeEntry: false,
|
|
87
|
+
allowRoot: true
|
|
88
|
+
}) : pages;
|
|
89
|
+
const mpEnabled = config.mp !== undefined || isMpTargetType(target);
|
|
90
|
+
const appConfig = pickAppConfig(config.app, target);
|
|
91
|
+
return {
|
|
92
|
+
root,
|
|
93
|
+
...loadedConfig.exists ? { configFile } : {},
|
|
94
|
+
entry,
|
|
95
|
+
pages,
|
|
96
|
+
target,
|
|
97
|
+
platform,
|
|
98
|
+
...config.components ? { components: config.components } : {},
|
|
99
|
+
server: {
|
|
100
|
+
host: config.server.host,
|
|
101
|
+
port: config.server.port,
|
|
102
|
+
proxy: config.server.proxy
|
|
103
|
+
},
|
|
104
|
+
build: {
|
|
105
|
+
outDir: resolve(root, webOutDir),
|
|
106
|
+
basePath: normalizeBasePath(config.build.basePath),
|
|
107
|
+
directoryPages: config.build.directoryPages
|
|
108
|
+
},
|
|
109
|
+
...config.library ? { library: resolveLibrary(root, config.library) } : {},
|
|
110
|
+
...mpEnabled ? {
|
|
111
|
+
mp: resolveMiniProgram(root, options.mpOutDir ?? options.outDir, mpConfig ?? {}, mpPages, target)
|
|
112
|
+
} : {},
|
|
113
|
+
...isAppTargetType(target) ? {
|
|
114
|
+
app: await resolveApp(root, options.outDir, appConfig ?? {}, target, pages)
|
|
115
|
+
} : {}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function isAppConfigMap(value) {
|
|
119
|
+
return isRecord(value) && APP_TARGET_TYPES.some((platform) => (platform in value));
|
|
120
|
+
}
|
|
121
|
+
function pickAppConfig(app, target) {
|
|
122
|
+
if (app === undefined) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (isAppConfigMap(app)) {
|
|
126
|
+
return {
|
|
127
|
+
...app.host !== undefined ? { host: app.host } : {},
|
|
128
|
+
...isAppTargetType(target) ? app[target] : {}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return app;
|
|
132
|
+
}
|
|
133
|
+
async function resolveApp(root, outDirOverride, app, target, fallbackPages) {
|
|
134
|
+
return {
|
|
135
|
+
...app.host !== undefined ? { host: app.host } : {},
|
|
136
|
+
appName: app.appName ?? "TransOne",
|
|
137
|
+
bundleId: app.bundleId ?? "com.transone.app",
|
|
138
|
+
outDir: resolve(root, outDirOverride ?? app.outDir ?? `dist/build/${target}`),
|
|
139
|
+
minPlatformVersion: app.minPlatformVersion ?? "16.0",
|
|
140
|
+
publicDir: resolve(root, app.publicDir ?? "public"),
|
|
141
|
+
pages: app.pages ? await resolvePages(root, resolve(root, app.pages["/"] ?? "src/main.ts"), app.pages, {
|
|
142
|
+
includeEntry: false,
|
|
143
|
+
allowRoot: true
|
|
144
|
+
}) : fallbackPages
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
var MP_DEFAULT_OUT_DIRS = {
|
|
148
|
+
"mp-weixin": "dist/build/mp-weixin",
|
|
149
|
+
"mp-alipay": "dist/build/mp-alipay",
|
|
150
|
+
"mp-bytedance": "dist/build/mp-bytedance",
|
|
151
|
+
"mp-xiaohongshu": "dist/build/mp-xiaohongshu"
|
|
152
|
+
};
|
|
153
|
+
function pickMpConfig(mp, target) {
|
|
154
|
+
if (mp === undefined) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const common = Object.fromEntries(Object.entries(mp).filter(([key]) => !MP_TARGET_TYPES.includes(key)));
|
|
158
|
+
const platform = isMpTargetType(target) ? mp[target] : undefined;
|
|
159
|
+
return mergeMpRecords(common, { ...platform });
|
|
160
|
+
}
|
|
161
|
+
function mergeMpRecords(common, overrides) {
|
|
162
|
+
const merged = { ...common };
|
|
163
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
164
|
+
if (value === undefined)
|
|
165
|
+
continue;
|
|
166
|
+
const previous = Object.prototype.hasOwnProperty.call(common, key) ? common[key] : undefined;
|
|
167
|
+
const next = isRecord(previous) && isRecord(value) ? mergeMpRecords(previous, value) : value;
|
|
168
|
+
Object.defineProperty(merged, key, {
|
|
169
|
+
value: next,
|
|
170
|
+
enumerable: true,
|
|
171
|
+
configurable: true,
|
|
172
|
+
writable: true
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return merged;
|
|
176
|
+
}
|
|
177
|
+
function resolveMiniProgram(root, outDirOverride, mp, pages, target) {
|
|
178
|
+
return {
|
|
179
|
+
...mp.host !== undefined ? { host: mp.host } : {},
|
|
180
|
+
appId: mp.appId ?? "touristappid",
|
|
181
|
+
outDir: resolve(root, outDirOverride ?? mp.outDir ?? MP_DEFAULT_OUT_DIRS[target] ?? "dist/build/mp-weixin"),
|
|
182
|
+
navigationBarTitleText: mp.navigationBarTitleText ?? "TransOne",
|
|
183
|
+
pages,
|
|
184
|
+
...mp.window !== undefined ? { window: mp.window } : {},
|
|
185
|
+
...mp.tabBar !== undefined ? { tabBar: mp.tabBar } : {},
|
|
186
|
+
...mp.appExtra !== undefined ? { appExtra: mp.appExtra } : {},
|
|
187
|
+
...mp.pageExtra !== undefined ? { pageExtra: mp.pageExtra } : {},
|
|
188
|
+
lengthUnit: mp.lengthUnit ?? "px",
|
|
189
|
+
publicDir: resolve(root, mp.publicDir ?? "public"),
|
|
190
|
+
...mp.globalData !== undefined ? { globalData: mp.globalData } : {}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function resolveLibrary(root, library) {
|
|
194
|
+
return {
|
|
195
|
+
entry: resolve(root, library.entry ?? "src/index.ts"),
|
|
196
|
+
outDir: resolve(root, library.outDir ?? "dist"),
|
|
197
|
+
external: library.external ?? [],
|
|
198
|
+
tsconfigs: (library.tsconfigs ?? ["tsconfig.build.json"]).map((path) => resolve(root, path)),
|
|
199
|
+
dts: library.dts ?? true,
|
|
200
|
+
splitting: library.splitting ?? true,
|
|
201
|
+
sourcemap: library.sourcemap ?? true,
|
|
202
|
+
minify: library.minify
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function normalizeBasePath(base) {
|
|
206
|
+
const trimmed = (base ?? "").trim();
|
|
207
|
+
if (!trimmed || trimmed === "/") {
|
|
208
|
+
return "";
|
|
209
|
+
}
|
|
210
|
+
const withLeading = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
211
|
+
return withLeading.endsWith("/") ? withLeading.slice(0, -1) : withLeading;
|
|
212
|
+
}
|
|
213
|
+
function resolvePlatform(target) {
|
|
214
|
+
const platform = normalizePlatform(target ?? "web" /* Web */);
|
|
215
|
+
if (platform !== undefined)
|
|
216
|
+
return platform;
|
|
217
|
+
throw new Error(`Unknown build target "${String(target)}". ` + `Available targets: ${PLATFORM_DEFINITIONS.map((definition) => definition.platform).join(", ")}.`);
|
|
218
|
+
}
|
|
219
|
+
async function resolvePages(root, entry, pages, options = {}) {
|
|
220
|
+
const resolved = options.includeEntry === false ? {} : { "/": entry };
|
|
221
|
+
for (const [route, pageEntry] of Object.entries(pages ?? {})) {
|
|
222
|
+
const normalized = normalizePageRoute(route, options.allowRoot);
|
|
223
|
+
const absolute = resolve(root, pageEntry);
|
|
224
|
+
if (!existsSync(absolute)) {
|
|
225
|
+
throw new Error(`Page entry file does not exist for "${normalized}": ${absolute}`);
|
|
226
|
+
}
|
|
227
|
+
resolved[normalized] = absolute;
|
|
228
|
+
}
|
|
229
|
+
return resolved;
|
|
230
|
+
}
|
|
231
|
+
function normalizePageRoute(route, allowRoot = false) {
|
|
232
|
+
if (route === "/") {
|
|
233
|
+
if (allowRoot) {
|
|
234
|
+
return "/";
|
|
235
|
+
}
|
|
236
|
+
throw new Error('Config pages must not redefine the root page "/"; use config.entry instead');
|
|
237
|
+
}
|
|
238
|
+
const normalized = route.replace(/\/+$/, "");
|
|
239
|
+
if (normalized === "") {
|
|
240
|
+
throw new Error(`Page route must not be empty: ${route}`);
|
|
241
|
+
}
|
|
242
|
+
return normalized;
|
|
243
|
+
}
|
|
244
|
+
var configLoadSequence = 0;
|
|
245
|
+
async function loadConfigFile(configFile) {
|
|
246
|
+
if (!existsSync(configFile)) {
|
|
247
|
+
return { exists: false };
|
|
248
|
+
}
|
|
249
|
+
configLoadSequence += 1;
|
|
250
|
+
const module = await import(`${configFile}?transone_config=${configLoadSequence}`);
|
|
251
|
+
return { exists: true, config: module.default };
|
|
252
|
+
}
|
|
253
|
+
function toInlineOverrides(options) {
|
|
254
|
+
const server = {};
|
|
255
|
+
const build = {};
|
|
256
|
+
if (options.host !== undefined) {
|
|
257
|
+
server.host = options.host;
|
|
258
|
+
}
|
|
259
|
+
if (options.port !== undefined) {
|
|
260
|
+
server.port = options.port;
|
|
261
|
+
}
|
|
262
|
+
if (options.outDir !== undefined) {
|
|
263
|
+
build.outDir = options.outDir;
|
|
264
|
+
}
|
|
265
|
+
if (options.base !== undefined) {
|
|
266
|
+
build.basePath = options.base;
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
...Object.keys(server).length > 0 ? { server } : {},
|
|
270
|
+
...Object.keys(build).length > 0 ? { build } : {}
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function mergeConfig(...configs) {
|
|
274
|
+
return configs.reduce((merged, config) => {
|
|
275
|
+
const server = config.server;
|
|
276
|
+
const build = config.build;
|
|
277
|
+
return {
|
|
278
|
+
target: config.target ?? merged.target,
|
|
279
|
+
components: config.components ? { ...merged.components, ...config.components } : merged.components,
|
|
280
|
+
entry: config.entry ?? merged.entry,
|
|
281
|
+
pages: config.pages ?? merged.pages,
|
|
282
|
+
server: {
|
|
283
|
+
host: server?.host ?? merged.server.host,
|
|
284
|
+
port: server?.port ?? merged.server.port,
|
|
285
|
+
proxy: {
|
|
286
|
+
...merged.server.proxy,
|
|
287
|
+
...server?.proxy
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
build: {
|
|
291
|
+
outDir: build?.outDir ?? merged.build.outDir,
|
|
292
|
+
basePath: build?.basePath ?? merged.build.basePath,
|
|
293
|
+
directoryPages: build?.directoryPages ?? merged.build.directoryPages
|
|
294
|
+
},
|
|
295
|
+
library: config.library ?? merged.library,
|
|
296
|
+
mp: config.mp ?? merged.mp,
|
|
297
|
+
app: config.app ?? merged.app
|
|
298
|
+
};
|
|
299
|
+
}, DEFAULT_CONFIG);
|
|
300
|
+
}
|
|
301
|
+
function validateUserConfig(config) {
|
|
302
|
+
assertRecord(config, "Config");
|
|
303
|
+
if (config.target !== undefined && normalizePlatform(config.target) === undefined) {
|
|
304
|
+
throw new Error(`Config target is invalid: ${String(config.target)}`);
|
|
305
|
+
}
|
|
306
|
+
if (config.components !== undefined) {
|
|
307
|
+
assertRecord(config.components, "Config components");
|
|
308
|
+
for (const key of ["include", "exclude"]) {
|
|
309
|
+
if (config.components[key] !== undefined)
|
|
310
|
+
assertStringArray(config.components[key], `Config components.${key}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (config.entry !== undefined && typeof config.entry !== "string") {
|
|
314
|
+
throw new Error("Config entry must be a string");
|
|
315
|
+
}
|
|
316
|
+
if (config.pages !== undefined) {
|
|
317
|
+
validatePagesConfig(config.pages);
|
|
318
|
+
}
|
|
319
|
+
if (config.server !== undefined) {
|
|
320
|
+
validateServerConfig(config.server);
|
|
321
|
+
}
|
|
322
|
+
if (config.build !== undefined) {
|
|
323
|
+
validateBuildConfig(config.build);
|
|
324
|
+
}
|
|
325
|
+
if (config.library !== undefined) {
|
|
326
|
+
validateLibraryConfig(config.library);
|
|
327
|
+
}
|
|
328
|
+
if (config.mp !== undefined) {
|
|
329
|
+
validateMpConfig(config.mp);
|
|
330
|
+
}
|
|
331
|
+
if (config.app !== undefined) {
|
|
332
|
+
validateAppConfig(config.app);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function validateAppConfig(app) {
|
|
336
|
+
assertRecord(app, "Config app");
|
|
337
|
+
validateApiHost(app.host, "Config app.host");
|
|
338
|
+
if (isAppConfigMap(app)) {
|
|
339
|
+
const flatKeys = [
|
|
340
|
+
"appName",
|
|
341
|
+
"bundleId",
|
|
342
|
+
"outDir",
|
|
343
|
+
"minPlatformVersion",
|
|
344
|
+
"publicDir",
|
|
345
|
+
"pages"
|
|
346
|
+
];
|
|
347
|
+
if (flatKeys.some((key) => (key in app))) {
|
|
348
|
+
throw new Error("Config app cannot mix flat fields with platform keys");
|
|
349
|
+
}
|
|
350
|
+
for (const platform of APP_TARGET_TYPES) {
|
|
351
|
+
if (app[platform] !== undefined) {
|
|
352
|
+
validateSingleAppConfig(app[platform], `Config app.${platform}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
validateSingleAppConfig(app, "Config app");
|
|
358
|
+
}
|
|
359
|
+
function validateSingleAppConfig(app, label) {
|
|
360
|
+
assertRecord(app, label);
|
|
361
|
+
validateApiHost(app.host, `${label}.host`);
|
|
362
|
+
for (const key of [
|
|
363
|
+
"appName",
|
|
364
|
+
"bundleId",
|
|
365
|
+
"outDir",
|
|
366
|
+
"minPlatformVersion",
|
|
367
|
+
"publicDir"
|
|
368
|
+
]) {
|
|
369
|
+
if (app[key] !== undefined && typeof app[key] !== "string") {
|
|
370
|
+
throw new Error(`${label}.${key} must be a string`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (app.pages !== undefined) {
|
|
374
|
+
validatePagesConfig(app.pages, `${label}.pages`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function validateMpConfig(mp) {
|
|
378
|
+
assertRecord(mp, "Config mp");
|
|
379
|
+
validateMiniProgramConfig(mp, "Config mp");
|
|
380
|
+
for (const platform of MP_TARGET_TYPES) {
|
|
381
|
+
if (mp[platform] !== undefined) {
|
|
382
|
+
validateMiniProgramConfig(mp[platform], `Config mp.${platform}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function validateMiniProgramConfig(mp, label) {
|
|
387
|
+
assertRecord(mp, label);
|
|
388
|
+
validateApiHost(mp.host, `${label}.host`);
|
|
389
|
+
for (const key of [
|
|
390
|
+
"appId",
|
|
391
|
+
"outDir",
|
|
392
|
+
"navigationBarTitleText",
|
|
393
|
+
"publicDir"
|
|
394
|
+
]) {
|
|
395
|
+
if (mp[key] !== undefined && typeof mp[key] !== "string") {
|
|
396
|
+
throw new Error(`${label}.${key} must be a string`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (mp.pages !== undefined) {
|
|
400
|
+
validatePagesConfig(mp.pages, `${label}.pages`);
|
|
401
|
+
}
|
|
402
|
+
for (const key of ["window", "tabBar", "appExtra", "globalData"]) {
|
|
403
|
+
if (mp[key] !== undefined) {
|
|
404
|
+
assertRecord(mp[key], `${label}.${key}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (mp.pageExtra !== undefined) {
|
|
408
|
+
assertRecord(mp.pageExtra, `${label}.pageExtra`);
|
|
409
|
+
for (const [route, extra] of Object.entries(mp.pageExtra)) {
|
|
410
|
+
assertRecord(extra, `${label}.pageExtra.${route}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (mp.lengthUnit !== undefined && mp.lengthUnit !== "px" && mp.lengthUnit !== "rpx") {
|
|
414
|
+
throw new Error(`${label}.lengthUnit must be 'px' or 'rpx'`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function validateLibraryConfig(library) {
|
|
418
|
+
assertRecord(library, "Config library");
|
|
419
|
+
if (library.entry !== undefined && typeof library.entry !== "string") {
|
|
420
|
+
throw new Error("Config library.entry must be a string");
|
|
421
|
+
}
|
|
422
|
+
if (library.outDir !== undefined && typeof library.outDir !== "string") {
|
|
423
|
+
throw new Error("Config library.outDir must be a string");
|
|
424
|
+
}
|
|
425
|
+
if (library.external !== undefined) {
|
|
426
|
+
assertStringArray(library.external, "Config library.external");
|
|
427
|
+
}
|
|
428
|
+
if (library.tsconfigs !== undefined) {
|
|
429
|
+
assertStringArray(library.tsconfigs, "Config library.tsconfigs");
|
|
430
|
+
}
|
|
431
|
+
for (const key of ["dts", "splitting", "sourcemap", "minify"]) {
|
|
432
|
+
if (library[key] !== undefined && typeof library[key] !== "boolean") {
|
|
433
|
+
throw new Error(`Config library.${key} must be a boolean`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
function assertStringArray(value, name) {
|
|
438
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
439
|
+
throw new Error(`${name} must be an array of strings`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function validatePagesConfig(pages, label = "Config pages") {
|
|
443
|
+
assertRecord(pages, label);
|
|
444
|
+
for (const [route, entry] of Object.entries(pages)) {
|
|
445
|
+
if (!route.startsWith("/")) {
|
|
446
|
+
throw new Error(`Page route must start with "/": ${route}`);
|
|
447
|
+
}
|
|
448
|
+
if (typeof entry !== "string") {
|
|
449
|
+
throw new Error(`Page entry for "${route}" must be a string`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function validateServerConfig(server) {
|
|
454
|
+
assertRecord(server, "Config server");
|
|
455
|
+
if (server.host !== undefined && typeof server.host !== "string") {
|
|
456
|
+
throw new Error("Config server.host must be a string");
|
|
457
|
+
}
|
|
458
|
+
const port = server.port;
|
|
459
|
+
if (port !== undefined && (typeof port !== "number" || !Number.isInteger(port) || port < 0 || port > 65535)) {
|
|
460
|
+
throw new Error("Config server.port must be an integer between 0 and 65535");
|
|
461
|
+
}
|
|
462
|
+
if (server.proxy === undefined) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
assertRecord(server.proxy, "Config server.proxy");
|
|
466
|
+
for (const [prefix, value] of Object.entries(server.proxy)) {
|
|
467
|
+
if (!prefix.startsWith("/")) {
|
|
468
|
+
throw new Error(`Proxy prefix must start with "/": ${prefix}`);
|
|
469
|
+
}
|
|
470
|
+
const target = getProxyTarget(value);
|
|
471
|
+
validateProxyTarget(target);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function validateBuildConfig(build) {
|
|
475
|
+
assertRecord(build, "Config build");
|
|
476
|
+
if (build.outDir !== undefined && typeof build.outDir !== "string") {
|
|
477
|
+
throw new Error("Config build.outDir must be a string");
|
|
478
|
+
}
|
|
479
|
+
if (build.basePath !== undefined && typeof build.basePath !== "string") {
|
|
480
|
+
throw new Error("Config build.basePath must be a string");
|
|
481
|
+
}
|
|
482
|
+
if (build.directoryPages !== undefined && typeof build.directoryPages !== "boolean") {
|
|
483
|
+
throw new Error("Config build.directoryPages must be a boolean");
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function getProxyTarget(value) {
|
|
487
|
+
if (typeof value === "string") {
|
|
488
|
+
return value;
|
|
489
|
+
}
|
|
490
|
+
assertRecord(value, "Proxy options");
|
|
491
|
+
if (typeof value.target !== "string") {
|
|
492
|
+
throw new Error("Proxy target must be a string");
|
|
493
|
+
}
|
|
494
|
+
if (value.changeOrigin !== undefined && typeof value.changeOrigin !== "boolean") {
|
|
495
|
+
throw new Error("Proxy changeOrigin must be a boolean");
|
|
496
|
+
}
|
|
497
|
+
if (value.rewrite !== undefined && typeof value.rewrite !== "function") {
|
|
498
|
+
throw new Error("Proxy rewrite must be a function");
|
|
499
|
+
}
|
|
500
|
+
return value.target;
|
|
501
|
+
}
|
|
502
|
+
function validateProxyTarget(target) {
|
|
503
|
+
try {
|
|
504
|
+
const url = new URL(target);
|
|
505
|
+
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
} catch {}
|
|
509
|
+
throw new Error(`Proxy target must use http or https: ${target}`);
|
|
510
|
+
}
|
|
511
|
+
function assertRecord(value, name) {
|
|
512
|
+
if (!isRecord(value)) {
|
|
513
|
+
throw new Error(`${name} must be an object`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function isRecord(value) {
|
|
517
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
518
|
+
}
|
|
519
|
+
function validateApiHost(host, label) {
|
|
520
|
+
if (host === undefined)
|
|
521
|
+
return;
|
|
522
|
+
if (typeof host !== "string")
|
|
523
|
+
throw new Error(`${label} must be a string`);
|
|
524
|
+
try {
|
|
525
|
+
const url = new URL(host);
|
|
526
|
+
if (url.protocol === "http:" || url.protocol === "https:")
|
|
527
|
+
return;
|
|
528
|
+
} catch {}
|
|
529
|
+
throw new Error(`${label} must use http or https`);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// src/target/web-target.ts
|
|
533
|
+
import { mkdir, writeFile as writeFile2 } from "fs/promises";
|
|
534
|
+
import { dirname as dirname2, isAbsolute, relative, resolve as resolve4, sep } from "path";
|
|
535
|
+
|
|
536
|
+
// src/build-output.ts
|
|
537
|
+
function isEntryJavaScriptOutput(output) {
|
|
538
|
+
return output.kind === "entry-point" && isJavaScriptPath(output.path);
|
|
539
|
+
}
|
|
540
|
+
function isStylesheetOutput(output) {
|
|
541
|
+
return /\.css$/i.test(output.path);
|
|
542
|
+
}
|
|
543
|
+
function isJavaScriptPath(path) {
|
|
544
|
+
return /\.(?:[cm]?js)$/i.test(path);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// src/document-styles.ts
|
|
548
|
+
import { createHash, randomUUID } from "crypto";
|
|
549
|
+
import { rename, rm, writeFile } from "fs/promises";
|
|
550
|
+
import { resolve as resolve2 } from "path";
|
|
551
|
+
async function emitDocumentStyles(html, outDir, assetUrl, stylesheets, componentStylesheets = []) {
|
|
552
|
+
const styles = [];
|
|
553
|
+
const linked = new Set;
|
|
554
|
+
let hasHead = false;
|
|
555
|
+
let hasHtml = false;
|
|
556
|
+
let current;
|
|
557
|
+
new HTMLRewriter().on("html", {
|
|
558
|
+
element() {
|
|
559
|
+
hasHtml = true;
|
|
560
|
+
}
|
|
561
|
+
}).on("head", {
|
|
562
|
+
element() {
|
|
563
|
+
hasHead = true;
|
|
564
|
+
}
|
|
565
|
+
}).on("head link", {
|
|
566
|
+
element(element) {
|
|
567
|
+
if (!element.getAttribute("rel")?.toLowerCase().split(/\s+/).includes("stylesheet"))
|
|
568
|
+
return;
|
|
569
|
+
const href = element.getAttribute("href");
|
|
570
|
+
if (href !== null)
|
|
571
|
+
linked.add(href);
|
|
572
|
+
}
|
|
573
|
+
}).on("head style", {
|
|
574
|
+
element(element) {
|
|
575
|
+
const attributes = [...element.attributes];
|
|
576
|
+
const type = element.getAttribute("type")?.trim().toLowerCase();
|
|
577
|
+
current = {
|
|
578
|
+
css: "",
|
|
579
|
+
attributes,
|
|
580
|
+
eligible: (!type || type === "text/css") && attributes.every(([name]) => ["type", "media", "title", "nonce"].includes(name))
|
|
581
|
+
};
|
|
582
|
+
styles.push(current);
|
|
583
|
+
},
|
|
584
|
+
text(chunk) {
|
|
585
|
+
if (current)
|
|
586
|
+
current.css += chunk.text;
|
|
587
|
+
}
|
|
588
|
+
}).transform(html);
|
|
589
|
+
const replacements = new Map;
|
|
590
|
+
const assets = new Set;
|
|
591
|
+
for (const [index2, style] of styles.entries()) {
|
|
592
|
+
if (!style.eligible || !style.css.trim() || hasRelativeResources(style.css))
|
|
593
|
+
continue;
|
|
594
|
+
const hash = createHash("sha256").update(style.css).digest("hex").slice(0, 16);
|
|
595
|
+
const path = resolve2(outDir, `document-${hash}.css`);
|
|
596
|
+
await writeStylesheet(path, style.css);
|
|
597
|
+
assets.add(path);
|
|
598
|
+
const attributes = style.attributes.map(([name, value]) => ` ${name}="${escapeAttribute(value)}"`).join("");
|
|
599
|
+
replacements.set(index2, `<link rel="stylesheet" href="${escapeAttribute(assetUrl(path))}"${attributes}>`);
|
|
600
|
+
}
|
|
601
|
+
const missing = [...new Set(stylesheets)].filter((href) => !linked.has(href));
|
|
602
|
+
const componentLinks = renderStylesheetLinks([...new Set(componentStylesheets)].filter((href) => !linked.has(href)));
|
|
603
|
+
const importedLinks = renderStylesheetLinks(missing);
|
|
604
|
+
if (!hasHead && (importedLinks || componentLinks)) {
|
|
605
|
+
const head = `<head>${importedLinks}${componentLinks}</head>`;
|
|
606
|
+
const output2 = hasHtml ? new HTMLRewriter().on("html", {
|
|
607
|
+
element(element) {
|
|
608
|
+
element.prepend(head, { html: true });
|
|
609
|
+
}
|
|
610
|
+
}).transform(html) : html.replace(/^(\s*<!doctype[^>]*>)?/i, (doctype) => doctype + head);
|
|
611
|
+
return { html: output2, assets: [...assets] };
|
|
612
|
+
}
|
|
613
|
+
let index = 0;
|
|
614
|
+
const output = new HTMLRewriter().on("head", {
|
|
615
|
+
element(element) {
|
|
616
|
+
element.prepend(importedLinks, { html: true });
|
|
617
|
+
element.append(componentLinks, { html: true });
|
|
618
|
+
}
|
|
619
|
+
}).on("head style", {
|
|
620
|
+
element(element) {
|
|
621
|
+
const replacement = replacements.get(index++);
|
|
622
|
+
if (replacement !== undefined)
|
|
623
|
+
element.replace(replacement, { html: true });
|
|
624
|
+
}
|
|
625
|
+
}).transform(html);
|
|
626
|
+
return { html: output, assets: [...assets] };
|
|
627
|
+
}
|
|
628
|
+
function renderStylesheetLinks(hrefs) {
|
|
629
|
+
return hrefs.map((href) => `<link rel="stylesheet" href="${escapeAttribute(href)}">`).join("");
|
|
630
|
+
}
|
|
631
|
+
async function emitComponentStyles(styles, outDir) {
|
|
632
|
+
const css = [...styles.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, value]) => value).join(`
|
|
633
|
+
`);
|
|
634
|
+
if (!css)
|
|
635
|
+
return [];
|
|
636
|
+
const hash = createHash("sha256").update(css).digest("hex").slice(0, 16);
|
|
637
|
+
const path = resolve2(outDir, `components-${hash}.css`);
|
|
638
|
+
await writeStylesheet(path, css);
|
|
639
|
+
return [path];
|
|
640
|
+
}
|
|
641
|
+
function escapeAttribute(value) {
|
|
642
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
643
|
+
}
|
|
644
|
+
function hasRelativeResources(css) {
|
|
645
|
+
if (/\\|@import\b|(?:-webkit-)?image(?:-set)?\(/i.test(css))
|
|
646
|
+
return true;
|
|
647
|
+
for (const match of css.matchAll(/url\(\s*(['"]?)(.*?)\1\s*\)/gi)) {
|
|
648
|
+
const url = match[2].trim();
|
|
649
|
+
if (url && !/^(?:\/|[a-z][a-z\d+.-]*:)/i.test(url))
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
async function writeStylesheet(path, css) {
|
|
655
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
656
|
+
try {
|
|
657
|
+
await writeFile(temporary, css);
|
|
658
|
+
await rename(temporary, path);
|
|
659
|
+
} finally {
|
|
660
|
+
await rm(temporary, { force: true });
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/bundle-inputs.ts
|
|
665
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
666
|
+
import { dirname, resolve as resolve3 } from "path";
|
|
667
|
+
function createBundleInputTracker() {
|
|
668
|
+
const inputs = new Set;
|
|
669
|
+
const plugin = {
|
|
670
|
+
name: "transone-build-inputs",
|
|
671
|
+
setup(build) {
|
|
672
|
+
build.onLoad({ filter: /.*/, namespace: "file" }, ({ path }) => {
|
|
673
|
+
inputs.add(resolve3(path));
|
|
674
|
+
return;
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
return {
|
|
679
|
+
plugin,
|
|
680
|
+
paths(result) {
|
|
681
|
+
for (const [path, input] of Object.entries(result.metafile?.inputs ?? {})) {
|
|
682
|
+
const file = resolve3(path);
|
|
683
|
+
if (!existsSync2(file))
|
|
684
|
+
continue;
|
|
685
|
+
inputs.add(file);
|
|
686
|
+
for (const dependency of input.imports) {
|
|
687
|
+
const resolved = resolve3(dependency.path);
|
|
688
|
+
if (existsSync2(resolved))
|
|
689
|
+
inputs.add(resolved);
|
|
690
|
+
try {
|
|
691
|
+
const imported = Bun.resolveSync(dependency.original ?? dependency.path, dirname(file));
|
|
692
|
+
if (existsSync2(imported))
|
|
693
|
+
inputs.add(imported);
|
|
694
|
+
} catch {}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (!result.metafile) {
|
|
698
|
+
for (const file of [...inputs]) {
|
|
699
|
+
if (!/\.[cm]?[jt]sx?$/i.test(file))
|
|
700
|
+
continue;
|
|
701
|
+
const loader = file.endsWith(".tsx") ? "tsx" : file.endsWith(".jsx") ? "jsx" : /\.[cm]?ts$/.test(file) ? "ts" : "js";
|
|
702
|
+
try {
|
|
703
|
+
for (const dependency of new Bun.Transpiler({ loader }).scanImports(readFileSync(file, "utf8"))) {
|
|
704
|
+
try {
|
|
705
|
+
const path = Bun.resolveSync(dependency.path, dirname(file));
|
|
706
|
+
if (existsSync2(path))
|
|
707
|
+
inputs.add(path);
|
|
708
|
+
} catch {}
|
|
709
|
+
}
|
|
710
|
+
} catch {}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return [...inputs];
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/web-bundle.ts
|
|
719
|
+
async function buildWebBundle(options, components) {
|
|
720
|
+
const { outdir, ...inMemoryOptions } = options;
|
|
721
|
+
const inputs = createBundleInputTracker();
|
|
722
|
+
let stylesPlugin = createWebStylesPlugin(new Set, components?.transform);
|
|
723
|
+
const buildOptions = {
|
|
724
|
+
...inMemoryOptions,
|
|
725
|
+
metafile: true,
|
|
726
|
+
...components?.active ? { entrypoints: [components.entrypoint] } : {}
|
|
727
|
+
};
|
|
728
|
+
const plugins = () => [
|
|
729
|
+
inputs.plugin,
|
|
730
|
+
...inMemoryOptions.plugins ?? [],
|
|
731
|
+
...components?.active ? [components.plugin] : [],
|
|
732
|
+
stylesPlugin
|
|
733
|
+
];
|
|
734
|
+
let result = await Bun.build({ ...buildOptions, plugins: plugins() });
|
|
735
|
+
if (result.success && stylesPlugin.styles.size && stylesPlugin.runtimeFiles.size) {
|
|
736
|
+
stylesPlugin = createWebStylesPlugin(stylesPlugin.runtimeFiles, components?.transform);
|
|
737
|
+
result = await Bun.build({ ...buildOptions, plugins: plugins() });
|
|
738
|
+
}
|
|
739
|
+
const outputs = [];
|
|
740
|
+
if (result.success) {
|
|
741
|
+
for (const output of result.outputs) {
|
|
742
|
+
const path = buildOutputPath(outdir, output.path);
|
|
743
|
+
const contents = await output.arrayBuffer();
|
|
744
|
+
await Bun.write(path, contents);
|
|
745
|
+
outputs.push({ path, kind: output.kind, type: output.type });
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return {
|
|
749
|
+
result: { success: result.success, logs: result.logs, outputs },
|
|
750
|
+
styles: stylesPlugin.styles,
|
|
751
|
+
inputPaths: inputs.paths(result)
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// src/target/web-target.ts
|
|
756
|
+
var INVALID_OUTPUT_DIRECTORY_MESSAGE = "Build output must be a subdirectory of the project root";
|
|
757
|
+
|
|
758
|
+
class WebTarget {
|
|
759
|
+
type = "web";
|
|
760
|
+
label = "\u9759\u6001\u7AD9\u70B9 / SPA\uFF08\u590D\u7528 TSone \u7B56\u7565\u5316\u6E32\u67D3\uFF09";
|
|
761
|
+
async build(config, _options) {
|
|
762
|
+
const entries = [...new Set(Object.values(config.pages))];
|
|
763
|
+
for (const entry of entries) {
|
|
764
|
+
await assertSafeSubdirectoryDoesNotContain(config.root, config.build.outDir, entry, INVALID_OUTPUT_DIRECTORY_MESSAGE);
|
|
765
|
+
}
|
|
766
|
+
const contexts = new Map(entries.map((entry) => [entry, createWebComponentContext(config, entry)]));
|
|
767
|
+
const protectedPaths = buildInputPaths(config, entries, [...contexts.values()].flatMap((context) => context.sourcePaths));
|
|
768
|
+
return withBuildOutput({
|
|
769
|
+
root: config.root,
|
|
770
|
+
outDir: config.build.outDir,
|
|
771
|
+
protectedPaths,
|
|
772
|
+
requireProjectSubdirectory: true
|
|
773
|
+
}, (outDir) => this.buildOutput({ ...config, build: { ...config.build, outDir } }, contexts, protectedPaths));
|
|
774
|
+
}
|
|
775
|
+
async buildOutput(config, contexts, protectedPaths) {
|
|
776
|
+
const entries = [...new Set(Object.values(config.pages))];
|
|
777
|
+
const { renderProjectHtml } = await import("./project-6tft5e77.js");
|
|
778
|
+
for (const [route, entry] of Object.entries(config.pages)) {
|
|
779
|
+
await renderProjectHtml(config, {}, entry, route, contexts.get(entry));
|
|
780
|
+
}
|
|
781
|
+
const builtByEntry = new Map;
|
|
782
|
+
const failures = [];
|
|
783
|
+
for (const entry of entries) {
|
|
784
|
+
const built = await buildPage(config, entry, contexts.get(entry), protectedPaths);
|
|
785
|
+
if (typeof built === "string") {
|
|
786
|
+
failures.push(built);
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
builtByEntry.set(entry, built);
|
|
790
|
+
}
|
|
791
|
+
if (failures.length > 0) {
|
|
792
|
+
throw buildFailure(failures);
|
|
793
|
+
}
|
|
794
|
+
const assetsBuilt = [];
|
|
795
|
+
for (const [route, entry] of Object.entries(config.pages)) {
|
|
796
|
+
const page = builtByEntry.get(entry);
|
|
797
|
+
if (!page) {
|
|
798
|
+
throw new Error(`Missing built assets for entry: ${entry}`);
|
|
799
|
+
}
|
|
800
|
+
const pageHtmlPath = htmlPath(config, route);
|
|
801
|
+
const rendered = await renderProjectHtml(config, {
|
|
802
|
+
head: page.stylesheetAssets.map((asset) => ({
|
|
803
|
+
tag: "link",
|
|
804
|
+
attributes: {
|
|
805
|
+
rel: "stylesheet",
|
|
806
|
+
href: toAssetUrl(config.build.outDir, pageHtmlPath, asset)
|
|
807
|
+
}
|
|
808
|
+
})),
|
|
809
|
+
scripts: page.javascriptAssets.map((asset) => ({
|
|
810
|
+
type: "module",
|
|
811
|
+
src: toAssetUrl(config.build.outDir, pageHtmlPath, asset)
|
|
812
|
+
}))
|
|
813
|
+
}, page.entry, route, contexts.get(entry));
|
|
814
|
+
const { html, assets: documentStyles } = await emitDocumentStyles(rendered, config.build.outDir, (asset) => toAssetUrl(config.build.outDir, pageHtmlPath, asset), page.stylesheetAssets.map((asset) => toAssetUrl(config.build.outDir, pageHtmlPath, asset)), page.componentStylesheetAssets.map((asset) => toAssetUrl(config.build.outDir, pageHtmlPath, asset)));
|
|
815
|
+
await mkdir(dirname2(pageHtmlPath), { recursive: true });
|
|
816
|
+
await writeFile2(pageHtmlPath, html);
|
|
817
|
+
assetsBuilt.push(...page.assets, ...documentStyles, pageHtmlPath);
|
|
818
|
+
}
|
|
819
|
+
return {
|
|
820
|
+
root: config.root,
|
|
821
|
+
outDir: config.build.outDir,
|
|
822
|
+
assetsBuilt: [...new Set(assetsBuilt)]
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
async function buildPage(config, entry, components, protectedPaths = []) {
|
|
827
|
+
let result;
|
|
828
|
+
let styles;
|
|
829
|
+
try {
|
|
830
|
+
const bundle = await buildWebBundle({
|
|
831
|
+
entrypoints: [entry],
|
|
832
|
+
outdir: config.build.outDir,
|
|
833
|
+
target: "browser",
|
|
834
|
+
format: "esm",
|
|
835
|
+
minify: process.env.TRANSONE_MINIFY !== "0",
|
|
836
|
+
naming: { entry: "[name]-[hash].[ext]", chunk: "[name]-[hash].[ext]" },
|
|
837
|
+
throw: false
|
|
838
|
+
}, components);
|
|
839
|
+
protectedPaths.push(...bundle.inputPaths);
|
|
840
|
+
result = bundle.result;
|
|
841
|
+
styles = bundle.styles;
|
|
842
|
+
} catch (error) {
|
|
843
|
+
return pageFailureReason(entry, [errorMessage(error)]);
|
|
844
|
+
}
|
|
845
|
+
const assets = result.outputs.map((output) => resolve4(output.path));
|
|
846
|
+
const javascriptAssets = result.outputs.filter(isEntryJavaScriptOutput).map((output) => resolve4(output.path));
|
|
847
|
+
const stylesheetAssets = result.outputs.filter(isStylesheetOutput).map((output) => resolve4(output.path));
|
|
848
|
+
if (!result.success || assets.length === 0 || javascriptAssets.length === 0) {
|
|
849
|
+
return pageFailureReason(entry, result.logs.map((log) => log.message), {
|
|
850
|
+
success: result.success,
|
|
851
|
+
hasOutput: assets.length > 0,
|
|
852
|
+
hasJavaScript: javascriptAssets.length > 0
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
const componentStylesheetAssets = await emitComponentStyles(styles, config.build.outDir);
|
|
856
|
+
return {
|
|
857
|
+
entry,
|
|
858
|
+
assets: [...assets, ...componentStylesheetAssets],
|
|
859
|
+
javascriptAssets,
|
|
860
|
+
stylesheetAssets,
|
|
861
|
+
componentStylesheetAssets
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
function pageFailureReason(entry, logs, state) {
|
|
865
|
+
const reasons = [
|
|
866
|
+
state && !state.success ? "Bun build reported failure" : "",
|
|
867
|
+
state && !state.hasOutput ? "Bun emitted no output files" : "",
|
|
868
|
+
state && !state.hasJavaScript ? "Bun emitted no JavaScript output" : "",
|
|
869
|
+
...logs
|
|
870
|
+
].filter((reason) => reason !== "");
|
|
871
|
+
return `${entry}: ${reasons.join(`
|
|
872
|
+
`)}`;
|
|
873
|
+
}
|
|
874
|
+
function buildFailure(failures) {
|
|
875
|
+
if (failures.length === 1) {
|
|
876
|
+
return new Error(`Failed to build TransOne application: ${failures[0]}`);
|
|
877
|
+
}
|
|
878
|
+
return new Error(`Failed to build TransOne application:
|
|
879
|
+
${failures.map((failure) => `- ${failure}`).join(`
|
|
880
|
+
`)}`);
|
|
881
|
+
}
|
|
882
|
+
function htmlPath(config, route) {
|
|
883
|
+
if (route === "/") {
|
|
884
|
+
return buildOutputPath(config.build.outDir, "index.html");
|
|
885
|
+
}
|
|
886
|
+
const relativeRoute = route.replace(/^\/+/, "");
|
|
887
|
+
if (config.build.directoryPages) {
|
|
888
|
+
return buildOutputPath(config.build.outDir, `${relativeRoute}/index.html`);
|
|
889
|
+
}
|
|
890
|
+
return buildOutputPath(config.build.outDir, `${relativeRoute}.html`);
|
|
891
|
+
}
|
|
892
|
+
function toAssetUrl(outDir, fromHtmlFile, asset) {
|
|
893
|
+
const fromOutDir = relative(outDir, asset).split(sep).join("/");
|
|
894
|
+
if (fromOutDir === "" || fromOutDir === ".." || fromOutDir.startsWith("../") || isAbsolute(fromOutDir)) {
|
|
895
|
+
throw new Error(`Build asset must be inside the output directory: ${asset}`);
|
|
896
|
+
}
|
|
897
|
+
const fromHtmlDir = dirname2(fromHtmlFile);
|
|
898
|
+
const assetPath = relative(fromHtmlDir, asset).split(sep).join("/");
|
|
899
|
+
return `./${assetPath}`;
|
|
900
|
+
}
|
|
901
|
+
function errorMessage(error) {
|
|
902
|
+
return error instanceof Error ? error.message : String(error);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// src/mp/dialect.ts
|
|
906
|
+
function pascalEvent(eventName) {
|
|
907
|
+
return eventName.charAt(0).toUpperCase() + eventName.slice(1);
|
|
908
|
+
}
|
|
909
|
+
var WEIXIN_PROJECT_SETTING = {
|
|
910
|
+
es6: true,
|
|
911
|
+
postcss: true,
|
|
912
|
+
minified: true,
|
|
913
|
+
urlCheck: false
|
|
914
|
+
};
|
|
915
|
+
var MP_WEIXIN_DIALECT = {
|
|
916
|
+
id: "mp-weixin",
|
|
917
|
+
label: "\u5FAE\u4FE1\u5C0F\u7A0B\u5E8F\u539F\u751F\u5DE5\u7A0B\uFF08WXML / WXSS / JS \u9759\u6001\u8F6C\u6362\uFF09",
|
|
918
|
+
templateExt: "wxml",
|
|
919
|
+
styleExt: "wxss",
|
|
920
|
+
appStyleFile: "app.wxss",
|
|
921
|
+
directivePrefix: "wx:",
|
|
922
|
+
bindEvent: (event) => `bind${event}`,
|
|
923
|
+
catchEvent: (event) => `catch${event}`,
|
|
924
|
+
projectConfigFile: "project.config.json",
|
|
925
|
+
buildProjectConfig: (mp, projectName) => ({
|
|
926
|
+
appid: mp.appId,
|
|
927
|
+
projectname: projectName,
|
|
928
|
+
compileType: "miniprogram",
|
|
929
|
+
setting: { ...WEIXIN_PROJECT_SETTING }
|
|
930
|
+
}),
|
|
931
|
+
windowDefaults: (title) => ({
|
|
932
|
+
navigationBarTitleText: title,
|
|
933
|
+
navigationBarBackgroundColor: "#ffffff",
|
|
934
|
+
navigationBarTextStyle: "black",
|
|
935
|
+
backgroundTextStyle: "light"
|
|
936
|
+
}),
|
|
937
|
+
appJsonExtras: () => ({
|
|
938
|
+
lazyCodeLoading: "requiredComponents",
|
|
939
|
+
style: "v2",
|
|
940
|
+
sitemapLocation: "sitemap.json"
|
|
941
|
+
}),
|
|
942
|
+
sitemap: true,
|
|
943
|
+
componentJson: (usingComponents) => ({
|
|
944
|
+
component: true,
|
|
945
|
+
multipleSlots: true,
|
|
946
|
+
usingComponents
|
|
947
|
+
})
|
|
948
|
+
};
|
|
949
|
+
var MP_ALIPAY_DIALECT = {
|
|
950
|
+
id: "mp-alipay",
|
|
951
|
+
label: "\u963F\u91CC\u5C0F\u7A0B\u5E8F\u539F\u751F\u5DE5\u7A0B\uFF08AXML / ACSS / JS \u9759\u6001\u8F6C\u6362\uFF09",
|
|
952
|
+
templateExt: "axml",
|
|
953
|
+
styleExt: "acss",
|
|
954
|
+
appStyleFile: "app.acss",
|
|
955
|
+
directivePrefix: "a:",
|
|
956
|
+
bindEvent: (event) => `on${pascalEvent(event)}`,
|
|
957
|
+
catchEvent: (event) => `catch${pascalEvent(event)}`,
|
|
958
|
+
projectConfigFile: "mini.project.json",
|
|
959
|
+
buildProjectConfig: () => ({
|
|
960
|
+
miniprogramRoot: "./",
|
|
961
|
+
component2: true,
|
|
962
|
+
enableAppxNg: true,
|
|
963
|
+
compileType: "miniprogram"
|
|
964
|
+
}),
|
|
965
|
+
windowDefaults: (title) => ({
|
|
966
|
+
defaultTitle: title,
|
|
967
|
+
titleBarColor: "#ffffff"
|
|
968
|
+
}),
|
|
969
|
+
appJsonExtras: () => ({}),
|
|
970
|
+
sitemap: false,
|
|
971
|
+
componentJson: (usingComponents) => ({
|
|
972
|
+
component: true,
|
|
973
|
+
usingComponents
|
|
974
|
+
})
|
|
975
|
+
};
|
|
976
|
+
var MP_BYTEDANCE_DIALECT = {
|
|
977
|
+
id: "mp-bytedance",
|
|
978
|
+
label: "\u5B57\u8282\u5C0F\u7A0B\u5E8F\u539F\u751F\u5DE5\u7A0B\uFF08TTML / TTSS / JS \u9759\u6001\u8F6C\u6362\uFF09",
|
|
979
|
+
templateExt: "ttml",
|
|
980
|
+
styleExt: "ttss",
|
|
981
|
+
appStyleFile: "app.ttss",
|
|
982
|
+
directivePrefix: "tt:",
|
|
983
|
+
bindEvent: (event) => `bind${event}`,
|
|
984
|
+
catchEvent: (event) => `catch${event}`,
|
|
985
|
+
projectConfigFile: "project.config.json",
|
|
986
|
+
buildProjectConfig: (mp, projectName) => ({
|
|
987
|
+
miniprogramRoot: "./",
|
|
988
|
+
compileType: "miniprogram",
|
|
989
|
+
projectname: projectName,
|
|
990
|
+
appid: mp.appId,
|
|
991
|
+
setting: { ...WEIXIN_PROJECT_SETTING }
|
|
992
|
+
}),
|
|
993
|
+
windowDefaults: (title) => ({
|
|
994
|
+
navigationBarTitleText: title,
|
|
995
|
+
navigationBarBackgroundColor: "#ffffff",
|
|
996
|
+
navigationBarTextStyle: "black",
|
|
997
|
+
backgroundTextStyle: "light"
|
|
998
|
+
}),
|
|
999
|
+
appJsonExtras: () => ({}),
|
|
1000
|
+
sitemap: false,
|
|
1001
|
+
componentJson: (usingComponents) => ({
|
|
1002
|
+
component: true,
|
|
1003
|
+
usingComponents
|
|
1004
|
+
})
|
|
1005
|
+
};
|
|
1006
|
+
var MP_XIAOHONGSHU_DIALECT = {
|
|
1007
|
+
id: "mp-xiaohongshu",
|
|
1008
|
+
label: "\u5C0F\u7EA2\u4E66\u5C0F\u7A0B\u5E8F\u539F\u751F\u5DE5\u7A0B\uFF08XHSML / CSS / JS \u9759\u6001\u8F6C\u6362\uFF09",
|
|
1009
|
+
templateExt: "xhsml",
|
|
1010
|
+
styleExt: "css",
|
|
1011
|
+
appStyleFile: "app.css",
|
|
1012
|
+
directivePrefix: "xhs:",
|
|
1013
|
+
bindEvent: (event) => `bind${event}`,
|
|
1014
|
+
catchEvent: (event) => `catch${event}`,
|
|
1015
|
+
projectConfigFile: "project.config.json",
|
|
1016
|
+
buildProjectConfig: (mp, projectName) => ({
|
|
1017
|
+
appid: mp.appId,
|
|
1018
|
+
projectname: projectName,
|
|
1019
|
+
compileType: "miniprogram"
|
|
1020
|
+
}),
|
|
1021
|
+
windowDefaults: (title) => ({
|
|
1022
|
+
navigationBarTitleText: title,
|
|
1023
|
+
navigationBarBackgroundColor: "#ffffff",
|
|
1024
|
+
navigationBarTextStyle: "black",
|
|
1025
|
+
backgroundTextStyle: "light"
|
|
1026
|
+
}),
|
|
1027
|
+
appJsonExtras: () => ({}),
|
|
1028
|
+
sitemap: false,
|
|
1029
|
+
componentJson: (usingComponents) => ({
|
|
1030
|
+
component: true,
|
|
1031
|
+
multipleSlots: true,
|
|
1032
|
+
usingComponents
|
|
1033
|
+
})
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
// src/target/mp-target.ts
|
|
1037
|
+
class MpTarget {
|
|
1038
|
+
dialect;
|
|
1039
|
+
constructor(dialect) {
|
|
1040
|
+
this.dialect = dialect;
|
|
1041
|
+
}
|
|
1042
|
+
get type() {
|
|
1043
|
+
return this.dialect.id;
|
|
1044
|
+
}
|
|
1045
|
+
get label() {
|
|
1046
|
+
return this.dialect.label;
|
|
1047
|
+
}
|
|
1048
|
+
async build(config, _options) {
|
|
1049
|
+
if (!config.mp) {
|
|
1050
|
+
throw new Error("Mini program build requires config.mp\uFF08\u8BF7\u5728 transone.config.ts \u914D\u7F6E mp \u5B57\u6BB5\uFF0C" + "\u5982 appId / pages / outDir\uFF09");
|
|
1051
|
+
}
|
|
1052
|
+
const { buildMiniProgram } = await import("./build-mp-y5pwahsy.js");
|
|
1053
|
+
return buildMiniProgram(config, this.dialect);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
var mpWeixinTarget = new MpTarget(MP_WEIXIN_DIALECT);
|
|
1057
|
+
var mpAlipayTarget = new MpTarget(MP_ALIPAY_DIALECT);
|
|
1058
|
+
var mpBytedanceTarget = new MpTarget(MP_BYTEDANCE_DIALECT);
|
|
1059
|
+
var mpXiaohongshuTarget = new MpTarget(MP_XIAOHONGSHU_DIALECT);
|
|
1060
|
+
|
|
1061
|
+
// src/app/build-app.ts
|
|
1062
|
+
import { cp, mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
|
|
1063
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1064
|
+
import { dirname as dirname3, resolve as resolve5 } from "path";
|
|
1065
|
+
|
|
1066
|
+
// src/app/errors.ts
|
|
1067
|
+
import { basename } from "path";
|
|
1068
|
+
function nativeError(filePath, message) {
|
|
1069
|
+
return new Error(`[transone app] ${basename(filePath)}: ${message}`);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// src/app/analyze.ts
|
|
1073
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1074
|
+
|
|
1075
|
+
// src/app/components.ts
|
|
1076
|
+
import ts from "typescript";
|
|
1077
|
+
import { realpathSync } from "fs";
|
|
1078
|
+
var CONTAINERS = new Set(["view", "main", "div"]);
|
|
1079
|
+
var TEXT = new Set(["text", "span", "p", "h1"]);
|
|
1080
|
+
var STYLES = new Set([
|
|
1081
|
+
"color",
|
|
1082
|
+
"backgroundColor",
|
|
1083
|
+
"fontSize",
|
|
1084
|
+
"fontWeight",
|
|
1085
|
+
"padding",
|
|
1086
|
+
"margin",
|
|
1087
|
+
"width",
|
|
1088
|
+
"height",
|
|
1089
|
+
"borderRadius",
|
|
1090
|
+
"textAlign"
|
|
1091
|
+
]);
|
|
1092
|
+
function fail(node, message) {
|
|
1093
|
+
const source = node.getSourceFile();
|
|
1094
|
+
const point = source.getLineAndCharacterOfPosition(node.getStart());
|
|
1095
|
+
throw nativeError(`${source.fileName}`, `${point.line + 1}:${point.character + 1} ${message}`);
|
|
1096
|
+
}
|
|
1097
|
+
function unwrap(node) {
|
|
1098
|
+
while (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isNonNullExpression(node) || ts.isSatisfiesExpression(node))
|
|
1099
|
+
node = node.expression;
|
|
1100
|
+
return node;
|
|
1101
|
+
}
|
|
1102
|
+
function name(node) {
|
|
1103
|
+
return ts.isIdentifier(node) || ts.isStringLiteralLike(node) || ts.isNumericLiteral(node) ? node.text : fail(node, "\u53EA\u652F\u6301\u9759\u6001\u5C5E\u6027\u540D");
|
|
1104
|
+
}
|
|
1105
|
+
function property(node, key) {
|
|
1106
|
+
const item = node.properties.find((p) => !ts.isSpreadAssignment(p) && name(p.name) === key);
|
|
1107
|
+
if (!item)
|
|
1108
|
+
return;
|
|
1109
|
+
if (ts.isPropertyAssignment(item))
|
|
1110
|
+
return item.initializer;
|
|
1111
|
+
if (ts.isShorthandPropertyAssignment(item))
|
|
1112
|
+
return item.name;
|
|
1113
|
+
return fail(item, "\u53EA\u652F\u6301\u9759\u6001\u5BF9\u8C61\u5C5E\u6027");
|
|
1114
|
+
}
|
|
1115
|
+
function object(node, label) {
|
|
1116
|
+
if (!node || !ts.isObjectLiteralExpression(unwrap(node))) {
|
|
1117
|
+
if (node)
|
|
1118
|
+
fail(node, `${label} \u5FC5\u987B\u662F\u9759\u6001\u5BF9\u8C61`);
|
|
1119
|
+
throw new Error(`[transone app] \u7F3A\u5C11 ${label}`);
|
|
1120
|
+
}
|
|
1121
|
+
return unwrap(node);
|
|
1122
|
+
}
|
|
1123
|
+
function returnValue(member) {
|
|
1124
|
+
const declaration = member.declaration;
|
|
1125
|
+
if (!ts.isMethodDeclaration(declaration) || !declaration.body || declaration.parameters.length !== 0 || declaration.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) || declaration.body.statements.length !== 1 || !ts.isReturnStatement(declaration.body.statements[0]) || !declaration.body.statements[0].expression)
|
|
1126
|
+
fail(declaration, `${declaration.name?.getText()} \u53EA\u652F\u6301\u5355\u4E2A\u9759\u6001 return \u8868\u8FBE\u5F0F`);
|
|
1127
|
+
return declaration.body.statements[0].expression;
|
|
1128
|
+
}
|
|
1129
|
+
function isTopLevelStartup(call) {
|
|
1130
|
+
function outer(node) {
|
|
1131
|
+
let value = node;
|
|
1132
|
+
while (value.parent && (ts.isParenthesizedExpression(value.parent) || ts.isAsExpression(value.parent) || ts.isNonNullExpression(value.parent) || ts.isSatisfiesExpression(value.parent)))
|
|
1133
|
+
value = value.parent;
|
|
1134
|
+
return value;
|
|
1135
|
+
}
|
|
1136
|
+
let expression = outer(call);
|
|
1137
|
+
const access = expression.parent;
|
|
1138
|
+
if (ts.isPropertyAccessExpression(access) && access.name.text === "mount" && ts.isCallExpression(access.parent) && access.parent.expression === access && !access.parent.arguments.length)
|
|
1139
|
+
expression = outer(access.parent);
|
|
1140
|
+
const parent = expression.parent;
|
|
1141
|
+
if (ts.isExpressionStatement(parent) || ts.isExportAssignment(parent))
|
|
1142
|
+
return ts.isSourceFile(parent.parent);
|
|
1143
|
+
if (ts.isVariableDeclaration(parent) && parent.initializer === expression && ts.isIdentifier(parent.name) && ts.isVariableDeclarationList(parent.parent)) {
|
|
1144
|
+
const statement = parent.parent.parent;
|
|
1145
|
+
return ts.isVariableStatement(statement) && ts.isSourceFile(statement.parent);
|
|
1146
|
+
}
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1149
|
+
function validateStaticMetadata(expression) {
|
|
1150
|
+
const node = unwrap(expression);
|
|
1151
|
+
if (ts.isStringLiteralLike(node) || ts.isNumericLiteral(node) || node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword || node.kind === ts.SyntaxKind.NullKeyword)
|
|
1152
|
+
return;
|
|
1153
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
1154
|
+
const seen = new Set;
|
|
1155
|
+
for (const item of node.properties) {
|
|
1156
|
+
if (!ts.isPropertyAssignment(item))
|
|
1157
|
+
fail(item, "\u539F\u751F createApp \u6587\u6863\u914D\u7F6E\u53EA\u652F\u6301\u9759\u6001\u5B57\u9762\u91CF\u5C5E\u6027");
|
|
1158
|
+
const key = name(item.name);
|
|
1159
|
+
if (seen.has(key))
|
|
1160
|
+
fail(item, `\u539F\u751F createApp \u6587\u6863\u914D\u7F6E\u5B58\u5728\u91CD\u590D\u5C5E\u6027 ${key}`);
|
|
1161
|
+
seen.add(key);
|
|
1162
|
+
validateStaticMetadata(item.initializer);
|
|
1163
|
+
}
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
1167
|
+
for (const value of node.elements)
|
|
1168
|
+
validateStaticMetadata(value);
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
fail(node, "\u539F\u751F createApp \u6587\u6863\u914D\u7F6E\u53EA\u652F\u6301\u9759\u6001\u5B57\u9762\u91CF\u8868\u8FBE\u5F0F");
|
|
1172
|
+
}
|
|
1173
|
+
function composeNativeScreen(entry, config, onSourcePaths) {
|
|
1174
|
+
const index = createComponentIndex({
|
|
1175
|
+
root: config.root,
|
|
1176
|
+
entrypoints: [entry],
|
|
1177
|
+
platform: config.platform,
|
|
1178
|
+
...config.components
|
|
1179
|
+
});
|
|
1180
|
+
onSourcePaths?.(index.sources.map((source2) => source2.fileName));
|
|
1181
|
+
const source = index.sources.find((s) => realpathSync(s.fileName) === realpathSync(entry));
|
|
1182
|
+
if (!source)
|
|
1183
|
+
throw nativeError(entry, "\u6E90\u6587\u4EF6\u4E0D\u5B58\u5728");
|
|
1184
|
+
const compiler = new NativeComposer(index);
|
|
1185
|
+
const roots = [];
|
|
1186
|
+
const startupCalls = [];
|
|
1187
|
+
function findStartup(node) {
|
|
1188
|
+
if (ts.isCallExpression(node) && compiler.frameworkCall(node, "createApp"))
|
|
1189
|
+
startupCalls.push(node);
|
|
1190
|
+
ts.forEachChild(node, findStartup);
|
|
1191
|
+
}
|
|
1192
|
+
findStartup(source);
|
|
1193
|
+
function createdApp(expression, seen = new Set) {
|
|
1194
|
+
const value = unwrap(expression);
|
|
1195
|
+
if (seen.has(value))
|
|
1196
|
+
return false;
|
|
1197
|
+
seen.add(value);
|
|
1198
|
+
if (ts.isCallExpression(value))
|
|
1199
|
+
return compiler.frameworkCall(value, "createApp");
|
|
1200
|
+
const declaration = index.symbolAt(value)?.valueDeclaration;
|
|
1201
|
+
return !!declaration && ts.isVariableDeclaration(declaration) && !!declaration.initializer && createdApp(declaration.initializer, seen);
|
|
1202
|
+
}
|
|
1203
|
+
function rejectInstalledPlugins(node) {
|
|
1204
|
+
if (ts.isFunctionLike(node) || ts.isClassLike(node))
|
|
1205
|
+
return;
|
|
1206
|
+
if (ts.isCallExpression(node)) {
|
|
1207
|
+
const callee = unwrap(node.expression);
|
|
1208
|
+
const receiver = ts.isPropertyAccessExpression(callee) ? callee.expression : ts.isElementAccessExpression(callee) ? callee.expression : undefined;
|
|
1209
|
+
const method = ts.isPropertyAccessExpression(callee) ? callee.name.text : ts.isElementAccessExpression(callee) && ts.isStringLiteralLike(callee.argumentExpression) ? callee.argumentExpression.text : undefined;
|
|
1210
|
+
if (method === "use" && receiver && (createdApp(receiver) || compiler.frameworkCall(node, "use")))
|
|
1211
|
+
fail(node, "\u539F\u751F App \u4E0D\u652F\u6301\u8DEF\u7531\u6216\u63D2\u4EF6\u5B89\u88C5 app.use()");
|
|
1212
|
+
}
|
|
1213
|
+
ts.forEachChild(node, rejectInstalledPlugins);
|
|
1214
|
+
}
|
|
1215
|
+
rejectInstalledPlugins(source);
|
|
1216
|
+
for (const call of startupCalls) {
|
|
1217
|
+
if (!isTopLevelStartup(call))
|
|
1218
|
+
fail(call, "\u539F\u751F createApp \u5FC5\u987B\u76F4\u63A5\u4F4D\u4E8E\u9876\u5C42\u542F\u52A8\u8BED\u53E5\uFF1B\u4E0D\u652F\u6301\u6761\u4EF6\u3001\u51FD\u6570\u6216\u52A8\u6001\u8868\u8FBE\u5F0F");
|
|
1219
|
+
if (call.arguments.length !== 1)
|
|
1220
|
+
fail(call, "\u539F\u751F createApp \u53EA\u652F\u6301\u4E00\u4E2A\u9759\u6001 options \u5BF9\u8C61");
|
|
1221
|
+
const options = object(call.arguments[0], "createApp options");
|
|
1222
|
+
const seen = new Set;
|
|
1223
|
+
for (const option of options.properties) {
|
|
1224
|
+
if (ts.isSpreadAssignment(option))
|
|
1225
|
+
fail(option, "\u539F\u751F createApp options \u4E0D\u652F\u6301\u5BF9\u8C61\u5C55\u5F00");
|
|
1226
|
+
if (!ts.isPropertyAssignment(option) && !ts.isShorthandPropertyAssignment(option))
|
|
1227
|
+
fail(option, "\u539F\u751F createApp options \u53EA\u652F\u6301\u9759\u6001\u5C5E\u6027");
|
|
1228
|
+
const key = name(option.name);
|
|
1229
|
+
if (seen.has(key))
|
|
1230
|
+
fail(option, `\u539F\u751F createApp options \u5B58\u5728\u91CD\u590D\u5C5E\u6027 ${key}`);
|
|
1231
|
+
seen.add(key);
|
|
1232
|
+
if (!["root", "rootProps", "document", "rootElement"].includes(key))
|
|
1233
|
+
fail(option, `\u539F\u751F createApp \u4E0D\u652F\u6301 options.${key}`);
|
|
1234
|
+
if (key === "document" || key === "rootElement")
|
|
1235
|
+
validateStaticMetadata(ts.isPropertyAssignment(option) ? option.initializer : option.name);
|
|
1236
|
+
}
|
|
1237
|
+
const root = property(options, "root");
|
|
1238
|
+
if (!root)
|
|
1239
|
+
fail(call, "createApp \u7F3A\u5C11 root");
|
|
1240
|
+
roots.push({ reference: root, props: property(options, "rootProps") });
|
|
1241
|
+
}
|
|
1242
|
+
if (roots.length > 1)
|
|
1243
|
+
fail(roots[1].reference, "\u539F\u751F\u9875\u9762\u53EA\u652F\u6301\u4E00\u4E2A createApp \u6839\u7EC4\u4EF6");
|
|
1244
|
+
let reference = roots[0] && index.resolve(roots[0].reference, source);
|
|
1245
|
+
if (roots.length && (!reference || !index.isComponent(reference)))
|
|
1246
|
+
fail(roots[0].reference, "root \u5FC5\u987B\u662F\u53EF\u89E3\u6790\u7684\u9759\u6001\u7EC4\u4EF6\u5F15\u7528");
|
|
1247
|
+
if (!reference) {
|
|
1248
|
+
const local = index.classes.filter((c) => c.source === source && index.isComponent(c));
|
|
1249
|
+
const logical = local.filter((c) => index.metadata(c).platforms.length === 0);
|
|
1250
|
+
const candidates = logical.length ? logical : local;
|
|
1251
|
+
if (candidates.length > 1)
|
|
1252
|
+
fail(candidates[1].declaration, "\u591A\u4E2A\u6839\u7EC4\u4EF6\uFF0C\u8BF7\u4F7F\u7528 createApp({ root })");
|
|
1253
|
+
reference = candidates[0];
|
|
1254
|
+
}
|
|
1255
|
+
return reference ? compiler.screen(reference, roots[0]?.props) : undefined;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
class NativeComposer {
|
|
1259
|
+
index;
|
|
1260
|
+
nextInstance = 0;
|
|
1261
|
+
state = [];
|
|
1262
|
+
stack = [];
|
|
1263
|
+
constructor(index) {
|
|
1264
|
+
this.index = index;
|
|
1265
|
+
}
|
|
1266
|
+
frameworkCall(call, expected) {
|
|
1267
|
+
const symbol = this.index.symbolAt(call.expression);
|
|
1268
|
+
return symbol?.name === expected && !!symbol.declarations?.some((d) => /(?:^|\/)transone\/(?:lib|dist)\//.test(d.getSourceFile().fileName.replace(/\\/g, "/")));
|
|
1269
|
+
}
|
|
1270
|
+
screen(reference, props) {
|
|
1271
|
+
const bindings = new Map;
|
|
1272
|
+
if (props)
|
|
1273
|
+
for (const p of object(props, "rootProps").properties) {
|
|
1274
|
+
if (!ts.isPropertyAssignment(p))
|
|
1275
|
+
fail(p, "rootProps \u53EA\u652F\u6301\u9759\u6001\u5C5E\u6027");
|
|
1276
|
+
bindings.set(name(p.name), {
|
|
1277
|
+
kind: "literal",
|
|
1278
|
+
value: this.literal(p.initializer)
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
const root = this.instance(reference, bindings);
|
|
1282
|
+
if (root.kind !== "container")
|
|
1283
|
+
fail(reference.declaration, "\u6839\u8282\u70B9\u5FC5\u987B\u662F\u5BB9\u5668\u8282\u70B9");
|
|
1284
|
+
return { name: reference.className, state: this.state, root };
|
|
1285
|
+
}
|
|
1286
|
+
member(reference, key) {
|
|
1287
|
+
return this.index.members(reference).find((m) => m.declaration.name && name(m.declaration.name) === key && !(ts.canHaveModifiers(m.declaration) && ts.getModifiers(m.declaration)?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword)));
|
|
1288
|
+
}
|
|
1289
|
+
parent(member, key) {
|
|
1290
|
+
const parent = this.index.metadata(member.owner).parent;
|
|
1291
|
+
const result = parent && this.member(parent, key);
|
|
1292
|
+
if (!result)
|
|
1293
|
+
fail(member.declaration, `\u65E0\u6CD5\u89E3\u6790 super.${key}`);
|
|
1294
|
+
return result;
|
|
1295
|
+
}
|
|
1296
|
+
literal(expression, seen = new Set) {
|
|
1297
|
+
const node = unwrap(expression);
|
|
1298
|
+
if (ts.isStringLiteralLike(node))
|
|
1299
|
+
return node.text;
|
|
1300
|
+
if (ts.isNumericLiteral(node))
|
|
1301
|
+
return Number(node.text);
|
|
1302
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword)
|
|
1303
|
+
return true;
|
|
1304
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword)
|
|
1305
|
+
return false;
|
|
1306
|
+
if (ts.isPrefixUnaryExpression(node) && (node.operator === ts.SyntaxKind.MinusToken || node.operator === ts.SyntaxKind.PlusToken)) {
|
|
1307
|
+
const value = this.literal(node.operand, seen);
|
|
1308
|
+
if (typeof value === "number")
|
|
1309
|
+
return node.operator === ts.SyntaxKind.MinusToken ? -value : value;
|
|
1310
|
+
}
|
|
1311
|
+
if (ts.isIdentifier(node) || ts.isPropertyAccessExpression(node)) {
|
|
1312
|
+
const declaration = this.index.symbolAt(node)?.valueDeclaration;
|
|
1313
|
+
if (declaration && ts.isVariableDeclaration(declaration) && declaration.initializer && ts.isVariableDeclarationList(declaration.parent) && declaration.parent.flags & ts.NodeFlags.Const) {
|
|
1314
|
+
if (seen.has(declaration))
|
|
1315
|
+
fail(node, "\u5E38\u91CF\u5F15\u7528\u5FAA\u73AF");
|
|
1316
|
+
return this.literal(declaration.initializer, new Set([...seen, declaration]));
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
return fail(node, `\u4E0D\u652F\u6301\u7684\u539F\u751F\u8868\u8FBE\u5F0F: ${node.getText()}`);
|
|
1320
|
+
}
|
|
1321
|
+
initialState(member, active = new Set) {
|
|
1322
|
+
if (active.has(member.declaration))
|
|
1323
|
+
fail(member.declaration, "initState \u5FAA\u73AF");
|
|
1324
|
+
active.add(member.declaration);
|
|
1325
|
+
const value = object(returnValue(member), "initState()");
|
|
1326
|
+
const values = new Map;
|
|
1327
|
+
for (const p of value.properties) {
|
|
1328
|
+
if (ts.isSpreadAssignment(p)) {
|
|
1329
|
+
const call = unwrap(p.expression);
|
|
1330
|
+
if (!ts.isCallExpression(call) || !ts.isPropertyAccessExpression(call.expression) || call.expression.expression.kind !== ts.SyntaxKind.SuperKeyword || call.expression.name.text !== "initState" || call.arguments.length)
|
|
1331
|
+
fail(p, "state \u5C55\u5F00\u53EA\u652F\u6301 super.initState()");
|
|
1332
|
+
for (const [key, value2] of this.initialState(this.parent(member, "initState"), active))
|
|
1333
|
+
values.set(key, value2);
|
|
1334
|
+
} else if (ts.isPropertyAssignment(p))
|
|
1335
|
+
values.set(name(p.name), this.literal(p.initializer));
|
|
1336
|
+
else
|
|
1337
|
+
fail(p, "state \u5FC5\u987B\u4F7F\u7528\u9759\u6001\u5C5E\u6027");
|
|
1338
|
+
}
|
|
1339
|
+
active.delete(member.declaration);
|
|
1340
|
+
return values;
|
|
1341
|
+
}
|
|
1342
|
+
instance(logical, props) {
|
|
1343
|
+
const reference = this.index.select(logical);
|
|
1344
|
+
if (this.stack.includes(reference.id))
|
|
1345
|
+
fail(reference.declaration, `\u9759\u6001\u7EC4\u4EF6\u7EC4\u5408\u5FAA\u73AF: ${reference.className}`);
|
|
1346
|
+
this.stack.push(reference.id);
|
|
1347
|
+
this.validateInstance(reference);
|
|
1348
|
+
const instance = {
|
|
1349
|
+
reference,
|
|
1350
|
+
prefix: `c${this.nextInstance++}_`,
|
|
1351
|
+
state: new Map,
|
|
1352
|
+
props
|
|
1353
|
+
};
|
|
1354
|
+
const init = this.member(reference, "initState");
|
|
1355
|
+
if (init)
|
|
1356
|
+
for (const [key, value] of this.initialState(init)) {
|
|
1357
|
+
const scoped = `${instance.prefix}s${instance.state.size}_${key.replace(/[^A-Za-z0-9_]/g, "_")}`;
|
|
1358
|
+
instance.state.set(key, scoped);
|
|
1359
|
+
this.state.push({ key: scoped, value });
|
|
1360
|
+
}
|
|
1361
|
+
const styles = this.member(reference, "initStyles");
|
|
1362
|
+
if (styles)
|
|
1363
|
+
this.validateStyles(styles);
|
|
1364
|
+
const render = this.member(reference, "render");
|
|
1365
|
+
if (!render)
|
|
1366
|
+
fail(reference.declaration, "\u7F3A\u5C11 render()");
|
|
1367
|
+
const root = this.node(returnValue(render), instance, render);
|
|
1368
|
+
this.stack.pop();
|
|
1369
|
+
return root;
|
|
1370
|
+
}
|
|
1371
|
+
validateInstance(reference) {
|
|
1372
|
+
for (const owner of this.index.hierarchy(reference))
|
|
1373
|
+
for (const member of owner.declaration.members) {
|
|
1374
|
+
if (ts.isConstructorDeclaration(member))
|
|
1375
|
+
fail(member, "\u539F\u751F\u7EC4\u4EF6\u4E0D\u652F\u6301\u81EA\u5B9A\u4E49 constructor");
|
|
1376
|
+
if (ts.isPropertyDeclaration(member) && member.initializer && !member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword))
|
|
1377
|
+
fail(member, "\u539F\u751F\u7EC4\u4EF6\u4E0D\u652F\u6301\u5B9E\u4F8B\u5B57\u6BB5\u521D\u59CB\u5316\uFF1B\u8BF7\u4F7F\u7528 initState");
|
|
1378
|
+
}
|
|
1379
|
+
for (const key of [
|
|
1380
|
+
"beforeMount",
|
|
1381
|
+
"onMounted",
|
|
1382
|
+
"beforeUpdate",
|
|
1383
|
+
"onUpdated",
|
|
1384
|
+
"onPropsChange",
|
|
1385
|
+
"beforeUnmount",
|
|
1386
|
+
"onUnmounted",
|
|
1387
|
+
"onErrorCaptured"
|
|
1388
|
+
]) {
|
|
1389
|
+
const member = this.member(reference, key);
|
|
1390
|
+
if (member && (!ts.isMethodDeclaration(member.declaration) || member.declaration.body?.statements.length))
|
|
1391
|
+
fail(member.declaration, `\u539F\u751F\u7EC4\u4EF6\u4E0D\u652F\u6301\u751F\u547D\u5468\u671F ${key} \u4E2D\u7684 JavaScript`);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
validateStyles(member) {
|
|
1395
|
+
const declaration = member.declaration;
|
|
1396
|
+
if (!ts.isMethodDeclaration(declaration) || !declaration.body)
|
|
1397
|
+
fail(declaration, "\u539F\u751F initStyles \u5FC5\u987B\u5177\u6709\u9759\u6001\u65B9\u6CD5\u4F53");
|
|
1398
|
+
for (const statement of declaration.body.statements) {
|
|
1399
|
+
if (ts.isExpressionStatement(statement) && ts.isCallExpression(statement.expression) && ts.isPropertyAccessExpression(statement.expression.expression) && statement.expression.expression.expression.kind === ts.SyntaxKind.SuperKeyword && statement.expression.expression.name.text === "initStyles" && !statement.expression.arguments.length)
|
|
1400
|
+
this.validateStyles(this.parent(member, "initStyles"));
|
|
1401
|
+
else
|
|
1402
|
+
fail(statement, "\u539F\u751F initStyles \u4E0D\u652F\u6301 CSS \u89C4\u5219\uFF1B\u8BF7\u4F7F\u7528 VNode \u5185\u8054 style \u6216\u8986\u76D6\u4E3A\u7A7A\u65B9\u6CD5");
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
binding(expression, instance) {
|
|
1406
|
+
const node = unwrap(expression);
|
|
1407
|
+
if (ts.isPropertyAccessExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
1408
|
+
const group = node.expression.name.text;
|
|
1409
|
+
const key = node.name.text;
|
|
1410
|
+
if (group === "state") {
|
|
1411
|
+
const scoped = instance.state.get(key);
|
|
1412
|
+
if (!scoped)
|
|
1413
|
+
fail(node, `\u672A\u58F0\u660E\u7684 state.${key}`);
|
|
1414
|
+
return { kind: "state", key: scoped };
|
|
1415
|
+
}
|
|
1416
|
+
if (group === "props") {
|
|
1417
|
+
const binding = instance.props.get(key);
|
|
1418
|
+
if (!binding)
|
|
1419
|
+
fail(node, `\u672A\u63D0\u4F9B\u7684 props.${key}`);
|
|
1420
|
+
return binding;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
return { kind: "literal", value: this.literal(node) };
|
|
1424
|
+
}
|
|
1425
|
+
text(expression, instance) {
|
|
1426
|
+
const value = this.binding(expression, instance);
|
|
1427
|
+
if (value.kind === "state")
|
|
1428
|
+
return [value];
|
|
1429
|
+
if (typeof value.value !== "string")
|
|
1430
|
+
return [{ kind: "literal", value: String(value.value) }];
|
|
1431
|
+
const result = [];
|
|
1432
|
+
const matcher = /{{([^}]+)}}/g;
|
|
1433
|
+
let cursor = 0;
|
|
1434
|
+
let match;
|
|
1435
|
+
while (match = matcher.exec(value.value)) {
|
|
1436
|
+
if (match.index > cursor)
|
|
1437
|
+
result.push({
|
|
1438
|
+
kind: "literal",
|
|
1439
|
+
value: value.value.slice(cursor, match.index)
|
|
1440
|
+
});
|
|
1441
|
+
const key = match[1].trim();
|
|
1442
|
+
const scoped = instance.state.get(key);
|
|
1443
|
+
const prop = instance.props.get(key);
|
|
1444
|
+
if (scoped)
|
|
1445
|
+
result.push({ kind: "state", key: scoped });
|
|
1446
|
+
else if (prop)
|
|
1447
|
+
result.push(prop.kind === "state" ? prop : { kind: "literal", value: String(prop.value) });
|
|
1448
|
+
else
|
|
1449
|
+
fail(expression, `\u53EA\u652F\u6301\u5DF2\u58F0\u660E state/props \u7684 {{key}} \u7ED1\u5B9A: ${key}`);
|
|
1450
|
+
cursor = matcher.lastIndex;
|
|
1451
|
+
}
|
|
1452
|
+
if (cursor < value.value.length || !result.length)
|
|
1453
|
+
result.push({ kind: "literal", value: value.value.slice(cursor) });
|
|
1454
|
+
return result;
|
|
1455
|
+
}
|
|
1456
|
+
props(node, instance) {
|
|
1457
|
+
const result = new Map;
|
|
1458
|
+
if (!node)
|
|
1459
|
+
return result;
|
|
1460
|
+
for (const p of object(node, "\u7EC4\u4EF6 props").properties) {
|
|
1461
|
+
if (!ts.isPropertyAssignment(p))
|
|
1462
|
+
fail(p, "\u7EC4\u4EF6 props \u53EA\u652F\u6301\u9759\u6001\u5C5E\u6027");
|
|
1463
|
+
result.set(name(p.name), this.binding(p.initializer, instance));
|
|
1464
|
+
}
|
|
1465
|
+
return result;
|
|
1466
|
+
}
|
|
1467
|
+
style(props) {
|
|
1468
|
+
if (!props)
|
|
1469
|
+
return;
|
|
1470
|
+
const p = object(props, "VNode props");
|
|
1471
|
+
for (const field of p.properties)
|
|
1472
|
+
if (ts.isSpreadAssignment(field) || name(field.name) !== "style")
|
|
1473
|
+
fail(field, "\u539F\u751F VNode props \u53EA\u652F\u6301 style");
|
|
1474
|
+
const style = property(p, "style");
|
|
1475
|
+
if (!style)
|
|
1476
|
+
return;
|
|
1477
|
+
const out = {};
|
|
1478
|
+
for (const item of object(style, "style").properties) {
|
|
1479
|
+
if (!ts.isPropertyAssignment(item) || !STYLES.has(name(item.name)))
|
|
1480
|
+
fail(item, "\u4E0D\u652F\u6301\u7684\u539F\u751F\u6837\u5F0F\u5C5E\u6027");
|
|
1481
|
+
const key = name(item.name), value = this.literal(item.initializer);
|
|
1482
|
+
if (["color", "backgroundColor", "fontWeight", "textAlign"].includes(key) && (typeof value === "string" || typeof value === "number"))
|
|
1483
|
+
out[key] = value;
|
|
1484
|
+
else if (typeof value === "number")
|
|
1485
|
+
out[key] = value;
|
|
1486
|
+
else if (typeof value === "string" && /^\d+(\.\d+)?px$/.test(value))
|
|
1487
|
+
out[key] = parseFloat(value);
|
|
1488
|
+
else
|
|
1489
|
+
fail(item, `\u6837\u5F0F ${key} \u53EA\u652F\u6301\u6570\u5B57\u6216 px`);
|
|
1490
|
+
}
|
|
1491
|
+
return out;
|
|
1492
|
+
}
|
|
1493
|
+
node(expression, instance, owner) {
|
|
1494
|
+
const node = unwrap(expression);
|
|
1495
|
+
let tag, props, children, listeners;
|
|
1496
|
+
if (ts.isCallExpression(node) && this.frameworkCall(node, "createComponent")) {
|
|
1497
|
+
const first = node.arguments[0];
|
|
1498
|
+
if (!first)
|
|
1499
|
+
fail(node, "createComponent \u7F3A\u5C11\u9759\u6001\u7EC4\u4EF6");
|
|
1500
|
+
if (ts.isObjectLiteralExpression(unwrap(first))) {
|
|
1501
|
+
if (node.arguments.length !== 1)
|
|
1502
|
+
fail(node, "createComponent options \u4E0D\u652F\u6301\u989D\u5916\u53C2\u6570/slots");
|
|
1503
|
+
return this.componentNode(unwrap(first), instance);
|
|
1504
|
+
}
|
|
1505
|
+
if (node.arguments.length > 2)
|
|
1506
|
+
fail(node, "\u539F\u751F\u7EC4\u4EF6\u4E0D\u652F\u6301 children/slots \u6216\u989D\u5916\u53C2\u6570");
|
|
1507
|
+
const reference = this.index.resolve(first, first.getSourceFile());
|
|
1508
|
+
if (!reference || !this.index.isComponent(reference))
|
|
1509
|
+
fail(node, "createComponent \u5FC5\u987B\u5F15\u7528\u9759\u6001\u7EC4\u4EF6");
|
|
1510
|
+
return this.instance(reference, this.props(node.arguments[1], instance));
|
|
1511
|
+
} else if (ts.isCallExpression(node) && this.frameworkCall(node, "h")) {
|
|
1512
|
+
if (node.arguments.length > 4)
|
|
1513
|
+
fail(node, "h \u4E0D\u652F\u6301\u989D\u5916\u53C2\u6570/slots");
|
|
1514
|
+
[tag, props, children, listeners] = node.arguments;
|
|
1515
|
+
} else if (ts.isObjectLiteralExpression(node)) {
|
|
1516
|
+
if (property(node, "component"))
|
|
1517
|
+
return this.componentNode(node, instance);
|
|
1518
|
+
for (const p of node.properties)
|
|
1519
|
+
if (ts.isSpreadAssignment(p) || !["tag", "props", "children", "listeners"].includes(name(p.name)))
|
|
1520
|
+
fail(p, "\u4E0D\u652F\u6301\u7684\u539F\u751F VNode \u5C5E\u6027/slots");
|
|
1521
|
+
tag = property(node, "tag");
|
|
1522
|
+
props = property(node, "props");
|
|
1523
|
+
children = property(node, "children");
|
|
1524
|
+
listeners = property(node, "listeners");
|
|
1525
|
+
} else if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.expression.kind === ts.SyntaxKind.SuperKeyword && node.expression.name.text === "render" && !node.arguments.length) {
|
|
1526
|
+
const parent = this.parent(owner, "render");
|
|
1527
|
+
return this.node(returnValue(parent), instance, parent);
|
|
1528
|
+
} else
|
|
1529
|
+
return fail(node, "\u53EA\u652F\u6301\u9759\u6001 h/createComponent/VNode \u8868\u8FBE\u5F0F");
|
|
1530
|
+
if (!tag)
|
|
1531
|
+
fail(node, "VNode \u7F3A\u5C11 tag");
|
|
1532
|
+
const tagName = this.literal(tag);
|
|
1533
|
+
if (typeof tagName !== "string")
|
|
1534
|
+
fail(tag, "VNode tag \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
1535
|
+
const style = this.style(props);
|
|
1536
|
+
let values = [];
|
|
1537
|
+
if (children) {
|
|
1538
|
+
const array = unwrap(children);
|
|
1539
|
+
if (!ts.isArrayLiteralExpression(array))
|
|
1540
|
+
fail(children, "children \u5FC5\u987B\u662F\u9759\u6001\u6570\u7EC4");
|
|
1541
|
+
values = array.elements;
|
|
1542
|
+
}
|
|
1543
|
+
if (CONTAINERS.has(tagName)) {
|
|
1544
|
+
if (listeners)
|
|
1545
|
+
fail(listeners, "\u539F\u751F\u5BB9\u5668\u4E0D\u652F\u6301 listeners");
|
|
1546
|
+
return {
|
|
1547
|
+
kind: "container",
|
|
1548
|
+
children: values.map((child) => this.node(child, instance, owner)),
|
|
1549
|
+
...style ? { style } : {}
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
if (!TEXT.has(tagName) && tagName !== "button")
|
|
1553
|
+
fail(tag, `\u4E0D\u652F\u6301\u7684\u539F\u751F\u8282\u70B9: ${tagName}`);
|
|
1554
|
+
const parts = values.flatMap((value) => this.text(value, instance));
|
|
1555
|
+
if (tagName === "button")
|
|
1556
|
+
return {
|
|
1557
|
+
kind: "button",
|
|
1558
|
+
parts,
|
|
1559
|
+
action: this.click(listeners, instance),
|
|
1560
|
+
...style ? { style } : {}
|
|
1561
|
+
};
|
|
1562
|
+
if (listeners)
|
|
1563
|
+
fail(listeners, "\u539F\u751F\u6587\u672C\u4E0D\u652F\u6301 listeners");
|
|
1564
|
+
return { kind: "text", parts, ...style ? { style } : {} };
|
|
1565
|
+
}
|
|
1566
|
+
componentNode(options, instance) {
|
|
1567
|
+
for (const p of options.properties)
|
|
1568
|
+
if (ts.isSpreadAssignment(p) || !["component", "props"].includes(name(p.name)))
|
|
1569
|
+
fail(p, "\u539F\u751F\u7EC4\u4EF6\u4E0D\u652F\u6301 slots\u3001emitters \u6216\u52A8\u6001 options");
|
|
1570
|
+
const component = property(options, "component");
|
|
1571
|
+
const reference = component && this.index.resolve(component, component.getSourceFile());
|
|
1572
|
+
if (!reference || !this.index.isComponent(reference))
|
|
1573
|
+
fail(options, "createComponent \u5FC5\u987B\u5F15\u7528\u9759\u6001\u7EC4\u4EF6");
|
|
1574
|
+
return this.instance(reference, this.props(property(options, "props"), instance));
|
|
1575
|
+
}
|
|
1576
|
+
click(listeners, instance) {
|
|
1577
|
+
const options = object(listeners, "button listeners.click");
|
|
1578
|
+
for (const p of options.properties)
|
|
1579
|
+
if (ts.isSpreadAssignment(p) || name(p.name) !== "click")
|
|
1580
|
+
fail(p, "\u53EA\u652F\u6301 listeners.click");
|
|
1581
|
+
const handler = property(options, "click");
|
|
1582
|
+
if (!handler || !ts.isArrowFunction(handler) || handler.parameters.length)
|
|
1583
|
+
fail(options, "click \u53EA\u652F\u6301 () => this.method()");
|
|
1584
|
+
const body = handler.body;
|
|
1585
|
+
const call = ts.isBlock(body) && body.statements.length === 1 && ts.isExpressionStatement(body.statements[0]) ? body.statements[0].expression : body;
|
|
1586
|
+
if (!ts.isCallExpression(call) || !ts.isPropertyAccessExpression(call.expression) || call.expression.expression.kind !== ts.SyntaxKind.ThisKeyword || call.arguments.length)
|
|
1587
|
+
fail(handler, "click \u53EA\u652F\u6301\u8C03\u7528 this.method()");
|
|
1588
|
+
const member = this.member(instance.reference, call.expression.name.text);
|
|
1589
|
+
if (!member)
|
|
1590
|
+
fail(call, "click \u65B9\u6CD5\u4E0D\u5B58\u5728");
|
|
1591
|
+
return this.action(member, instance, new Set);
|
|
1592
|
+
}
|
|
1593
|
+
action(member, instance, active) {
|
|
1594
|
+
if (active.has(member.declaration))
|
|
1595
|
+
fail(member.declaration, "\u539F\u751F\u65B9\u6CD5\u8C03\u7528\u5FAA\u73AF");
|
|
1596
|
+
const declaration = member.declaration;
|
|
1597
|
+
if (!ts.isMethodDeclaration(declaration) || !declaration.body || declaration.parameters.length || declaration.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword))
|
|
1598
|
+
fail(declaration, "\u539F\u751F\u52A8\u4F5C\u53EA\u652F\u6301\u540C\u6B65\u65E0\u53C2\u6570\u65B9\u6CD5");
|
|
1599
|
+
const next = new Set([...active, declaration]);
|
|
1600
|
+
const actions = [];
|
|
1601
|
+
for (const statement of declaration.body.statements) {
|
|
1602
|
+
if (!ts.isExpressionStatement(statement))
|
|
1603
|
+
fail(statement, "\u65B9\u6CD5\u4E0D\u652F\u6301\u539F\u751F\u7F16\u8BD1");
|
|
1604
|
+
const expression = unwrap(statement.expression);
|
|
1605
|
+
if (ts.isCallExpression(expression) && ts.isPropertyAccessExpression(expression.expression) && !expression.arguments.length) {
|
|
1606
|
+
const target = expression.expression;
|
|
1607
|
+
let called;
|
|
1608
|
+
if (target.expression.kind === ts.SyntaxKind.SuperKeyword)
|
|
1609
|
+
called = this.parent(member, target.name.text);
|
|
1610
|
+
else if (target.expression.kind === ts.SyntaxKind.ThisKeyword)
|
|
1611
|
+
called = this.member(instance.reference, target.name.text);
|
|
1612
|
+
if (!called)
|
|
1613
|
+
fail(expression, "\u65B9\u6CD5\u4E0D\u652F\u6301\u539F\u751F\u7F16\u8BD1");
|
|
1614
|
+
actions.push(this.action(called, instance, next));
|
|
1615
|
+
continue;
|
|
1616
|
+
}
|
|
1617
|
+
if (ts.isBinaryExpression(expression) && ts.isPropertyAccessExpression(expression.left) && ts.isPropertyAccessExpression(expression.left.expression) && expression.left.expression.expression.kind === ts.SyntaxKind.ThisKeyword && expression.left.expression.name.text === "state") {
|
|
1618
|
+
const key = instance.state.get(expression.left.name.text);
|
|
1619
|
+
if (!key)
|
|
1620
|
+
fail(expression.left, "\u52A8\u4F5C\u5F15\u7528\u672A\u58F0\u660E state");
|
|
1621
|
+
const value = this.literal(expression.right);
|
|
1622
|
+
const operator = expression.operatorToken.kind;
|
|
1623
|
+
const initial = this.state.find((s) => s.key === key).value;
|
|
1624
|
+
if ((operator === ts.SyntaxKind.PlusEqualsToken || operator === ts.SyntaxKind.MinusEqualsToken) && typeof value === "number" && typeof initial === "number")
|
|
1625
|
+
actions.push({
|
|
1626
|
+
kind: "increment",
|
|
1627
|
+
key,
|
|
1628
|
+
by: operator === ts.SyntaxKind.MinusEqualsToken ? -value : value
|
|
1629
|
+
});
|
|
1630
|
+
else if (operator === ts.SyntaxKind.EqualsToken && typeof value === typeof initial)
|
|
1631
|
+
actions.push({ kind: "set-literal", key, value });
|
|
1632
|
+
else
|
|
1633
|
+
fail(expression, "\u539F\u751F\u52A8\u4F5C\u53EA\u652F\u6301\u540C\u7C7B\u578B\u8D4B\u503C\u6216\u6570\u503C\u589E\u51CF");
|
|
1634
|
+
continue;
|
|
1635
|
+
}
|
|
1636
|
+
fail(expression, "\u65B9\u6CD5\u4E0D\u652F\u6301\u539F\u751F\u7F16\u8BD1");
|
|
1637
|
+
}
|
|
1638
|
+
if (!actions.length)
|
|
1639
|
+
fail(declaration, "\u539F\u751F\u52A8\u4F5C\u4E0D\u80FD\u4E3A\u7A7A");
|
|
1640
|
+
return actions.length === 1 ? actions[0] : { kind: "sequence", actions };
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
// src/app/analyze.ts
|
|
1645
|
+
var CONTAINERS2 = new Set(["view", "main", "div"]);
|
|
1646
|
+
var TEXT2 = new Set(["text", "span", "p", "h1"]);
|
|
1647
|
+
var STYLE_KEYS = new Set(["color", "backgroundColor", "fontSize", "fontWeight", "padding", "margin", "width", "height", "borderRadius", "textAlign"]);
|
|
1648
|
+
async function analyzeNativeScreen(entry, config, onSourcePaths) {
|
|
1649
|
+
if (!existsSync3(entry))
|
|
1650
|
+
throw nativeError(entry, "\u6E90\u6587\u4EF6\u4E0D\u5B58\u5728");
|
|
1651
|
+
if (config) {
|
|
1652
|
+
const screen = composeNativeScreen(entry, config, onSourcePaths);
|
|
1653
|
+
if (screen)
|
|
1654
|
+
return screen;
|
|
1655
|
+
}
|
|
1656
|
+
const ts2 = await loadTypescript();
|
|
1657
|
+
const source = ts2.createSourceFile(entry, readFileSync2(entry, "utf8"), ts2.ScriptTarget.Latest, true);
|
|
1658
|
+
const component = source.statements.find((s) => ts2.isClassDeclaration(s));
|
|
1659
|
+
if (!component)
|
|
1660
|
+
throw nativeError(entry, "\u672A\u627E\u5230 Component \u7C7B");
|
|
1661
|
+
const state = readState(ts2, component, entry);
|
|
1662
|
+
const actions = new Map;
|
|
1663
|
+
for (const member of component.members)
|
|
1664
|
+
if (ts2.isMethodDeclaration(member) && ts2.isIdentifier(member.name)) {
|
|
1665
|
+
const action = readAction(ts2, member, entry);
|
|
1666
|
+
if (action)
|
|
1667
|
+
actions.set(member.name.text, action);
|
|
1668
|
+
}
|
|
1669
|
+
const render = component.members.find((m) => ts2.isMethodDeclaration(m) && m.name.getText(source) === "render");
|
|
1670
|
+
const value = render?.body?.statements.find(ts2.isReturnStatement)?.expression;
|
|
1671
|
+
if (!value || !ts2.isObjectLiteralExpression(value))
|
|
1672
|
+
throw nativeError(entry, "render() \u5FC5\u987B\u8FD4\u56DE\u9759\u6001 VNode \u5BF9\u8C61");
|
|
1673
|
+
const root = readNode(ts2, value, entry, new Set(state.map((item) => item.key)), actions);
|
|
1674
|
+
if (root.kind !== "container")
|
|
1675
|
+
throw nativeError(entry, "\u6839\u8282\u70B9\u5FC5\u987B\u662F\u5BB9\u5668\u8282\u70B9");
|
|
1676
|
+
return { name: component.name?.text ?? "App", state, root };
|
|
1677
|
+
}
|
|
1678
|
+
function readState(ts2, component, file) {
|
|
1679
|
+
const method = component.members.find((m) => ts2.isMethodDeclaration(m) && m.name.getText() === "initState");
|
|
1680
|
+
const value = method?.body?.statements.find(ts2.isReturnStatement)?.expression;
|
|
1681
|
+
if (!value)
|
|
1682
|
+
return [];
|
|
1683
|
+
if (!ts2.isObjectLiteralExpression(value))
|
|
1684
|
+
throw nativeError(file, "initState() \u5FC5\u987B\u8FD4\u56DE\u5BF9\u8C61\u5B57\u9762\u91CF");
|
|
1685
|
+
return value.properties.map((item) => {
|
|
1686
|
+
if (!ts2.isPropertyAssignment(item) || !ts2.isIdentifier(item.name))
|
|
1687
|
+
throw nativeError(file, "state \u5FC5\u987B\u4F7F\u7528\u6807\u8BC6\u7B26\u5C5E\u6027");
|
|
1688
|
+
const literal = literalValue(ts2, item.initializer);
|
|
1689
|
+
if (literal === undefined)
|
|
1690
|
+
throw nativeError(file, `state.${item.name.text} \u5FC5\u987B\u662F\u5B57\u9762\u91CF`);
|
|
1691
|
+
return { key: item.name.text, value: literal };
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
function readAction(ts2, method, file) {
|
|
1695
|
+
const expression = method.body?.statements[0];
|
|
1696
|
+
if (!expression || !ts2.isExpressionStatement(expression) || !ts2.isBinaryExpression(expression.expression))
|
|
1697
|
+
return;
|
|
1698
|
+
const binary = expression.expression;
|
|
1699
|
+
if (!ts2.isPropertyAccessExpression(binary.left) || !ts2.isPropertyAccessExpression(binary.left.expression) || binary.left.expression.expression.kind !== ts2.SyntaxKind.ThisKeyword || binary.left.expression.name.text !== "state")
|
|
1700
|
+
return;
|
|
1701
|
+
const value = literalValue(ts2, binary.right);
|
|
1702
|
+
const key = binary.left.name.text;
|
|
1703
|
+
if ((binary.operatorToken.kind === ts2.SyntaxKind.PlusEqualsToken || binary.operatorToken.kind === ts2.SyntaxKind.MinusEqualsToken) && typeof value === "number")
|
|
1704
|
+
return { kind: "increment", key, by: binary.operatorToken.kind === ts2.SyntaxKind.MinusEqualsToken ? -value : value };
|
|
1705
|
+
if (binary.operatorToken.kind === ts2.SyntaxKind.EqualsToken && value !== undefined)
|
|
1706
|
+
return { kind: "set-literal", key, value };
|
|
1707
|
+
throw nativeError(file, `\u65B9\u6CD5 ${method.name.getText()} \u4E0D\u652F\u6301\u539F\u751F\u7F16\u8BD1`);
|
|
1708
|
+
}
|
|
1709
|
+
function readNode(ts2, node, file, state, actions) {
|
|
1710
|
+
const tag = stringValue(ts2, get(ts2, node, "tag"), file, "VNode tag");
|
|
1711
|
+
const style = readStyle(ts2, get(ts2, node, "props"), file);
|
|
1712
|
+
const children = get(ts2, node, "children");
|
|
1713
|
+
const childNodes = children && ts2.isArrayLiteralExpression(children) ? [...children.elements].filter(ts2.isExpression) : [];
|
|
1714
|
+
if (CONTAINERS2.has(tag))
|
|
1715
|
+
return { kind: "container", children: childNodes.map((child) => {
|
|
1716
|
+
if (!ts2.isObjectLiteralExpression(child))
|
|
1717
|
+
throw nativeError(file, "\u5BB9\u5668 children \u53EA\u652F\u6301\u9759\u6001 VNode");
|
|
1718
|
+
return readNode(ts2, child, file, state, actions);
|
|
1719
|
+
}), ...style ? { style } : {} };
|
|
1720
|
+
const parts = childNodes.flatMap((child) => readText(ts2, child, file, state));
|
|
1721
|
+
if (TEXT2.has(tag))
|
|
1722
|
+
return { kind: "text", parts, ...style ? { style } : {} };
|
|
1723
|
+
if (tag === "button")
|
|
1724
|
+
return { kind: "button", parts, action: readClick(ts2, get(ts2, node, "listeners"), file, actions), ...style ? { style } : {} };
|
|
1725
|
+
throw nativeError(file, `\u4E0D\u652F\u6301\u7684\u539F\u751F\u8282\u70B9: ${tag}`);
|
|
1726
|
+
}
|
|
1727
|
+
function readText(ts2, expression, file, state) {
|
|
1728
|
+
const value = literalValue(ts2, expression);
|
|
1729
|
+
if (typeof value !== "string")
|
|
1730
|
+
throw nativeError(file, "\u6587\u672C children \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u5B57\u9762\u91CF");
|
|
1731
|
+
const result = [];
|
|
1732
|
+
const matcher = /{{([^}]+)}}/g;
|
|
1733
|
+
let cursor = 0;
|
|
1734
|
+
let match;
|
|
1735
|
+
while (match = matcher.exec(value)) {
|
|
1736
|
+
if (match.index > cursor)
|
|
1737
|
+
result.push({ kind: "literal", value: value.slice(cursor, match.index) });
|
|
1738
|
+
const key = match[1];
|
|
1739
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(key) || !state.has(key))
|
|
1740
|
+
throw nativeError(file, "\u53EA\u652F\u6301\u5DF2\u58F0\u660E state \u7684 {{key}} \u7ED1\u5B9A");
|
|
1741
|
+
result.push({ kind: "state", key });
|
|
1742
|
+
cursor = matcher.lastIndex;
|
|
1743
|
+
}
|
|
1744
|
+
if (cursor < value.length || result.length === 0)
|
|
1745
|
+
result.push({ kind: "literal", value: value.slice(cursor) });
|
|
1746
|
+
return result;
|
|
1747
|
+
}
|
|
1748
|
+
function readClick(ts2, listeners, file, actions) {
|
|
1749
|
+
if (!listeners || !ts2.isObjectLiteralExpression(listeners))
|
|
1750
|
+
throw nativeError(file, "button \u5FC5\u987B\u58F0\u660E listeners.click");
|
|
1751
|
+
const handler = get(ts2, listeners, "click");
|
|
1752
|
+
const body = handler && ts2.isArrowFunction(handler) ? handler.body : undefined;
|
|
1753
|
+
const expression = body && ts2.isCallExpression(body) ? body : body && ts2.isBlock(body) && ts2.isExpressionStatement(body.statements[0]) ? body.statements[0].expression : undefined;
|
|
1754
|
+
if (!expression || !ts2.isCallExpression(expression) || !ts2.isPropertyAccessExpression(expression.expression) || expression.expression.expression.kind !== ts2.SyntaxKind.ThisKeyword)
|
|
1755
|
+
throw nativeError(file, "click \u53EA\u652F\u6301\u8C03\u7528 this.method()");
|
|
1756
|
+
const action = actions.get(expression.expression.name.text);
|
|
1757
|
+
if (!action)
|
|
1758
|
+
throw nativeError(file, `click \u65B9\u6CD5 ${expression.expression.name.text} \u4E0D\u652F\u6301\u539F\u751F\u7F16\u8BD1`);
|
|
1759
|
+
return action;
|
|
1760
|
+
}
|
|
1761
|
+
function readStyle(ts2, props, file) {
|
|
1762
|
+
if (!props || !ts2.isObjectLiteralExpression(props))
|
|
1763
|
+
return;
|
|
1764
|
+
const style = get(ts2, props, "style");
|
|
1765
|
+
if (!style || !ts2.isObjectLiteralExpression(style))
|
|
1766
|
+
return;
|
|
1767
|
+
const out = {};
|
|
1768
|
+
for (const item of style.properties) {
|
|
1769
|
+
if (!ts2.isPropertyAssignment(item) || !ts2.isIdentifier(item.name) || !STYLE_KEYS.has(item.name.text))
|
|
1770
|
+
throw nativeError(file, "\u4E0D\u652F\u6301\u7684\u539F\u751F\u6837\u5F0F\u5C5E\u6027");
|
|
1771
|
+
const value = literalValue(ts2, item.initializer);
|
|
1772
|
+
const keyword = item.name.text === "color" || item.name.text === "backgroundColor" || item.name.text === "fontWeight" || item.name.text === "textAlign";
|
|
1773
|
+
if (keyword && (typeof value === "string" || typeof value === "number")) {
|
|
1774
|
+
out[item.name.text] = value;
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1777
|
+
if (typeof value !== "number" && !(typeof value === "string" && /^\d+(\.\d+)?px$/.test(value)))
|
|
1778
|
+
throw nativeError(file, `\u6837\u5F0F ${item.name.text} \u53EA\u652F\u6301\u6570\u5B57\u6216 px`);
|
|
1779
|
+
out[item.name.text] = typeof value === "string" ? Number.parseFloat(value) : value;
|
|
1780
|
+
}
|
|
1781
|
+
return out;
|
|
1782
|
+
}
|
|
1783
|
+
function get(ts2, node, name2) {
|
|
1784
|
+
const item = node.properties.find((p) => ts2.isPropertyAssignment(p) && p.name.getText().replace(/['"]/g, "") === name2);
|
|
1785
|
+
return item && ts2.isPropertyAssignment(item) ? item.initializer : undefined;
|
|
1786
|
+
}
|
|
1787
|
+
function stringValue(ts2, expression, file, label) {
|
|
1788
|
+
const value = expression && literalValue(ts2, expression);
|
|
1789
|
+
if (typeof value !== "string")
|
|
1790
|
+
throw nativeError(file, `${label} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
|
|
1791
|
+
return value;
|
|
1792
|
+
}
|
|
1793
|
+
function literalValue(ts2, value) {
|
|
1794
|
+
if (ts2.isStringLiteral(value) || ts2.isNoSubstitutionTemplateLiteral(value))
|
|
1795
|
+
return value.text;
|
|
1796
|
+
if (ts2.isNumericLiteral(value))
|
|
1797
|
+
return Number(value.text);
|
|
1798
|
+
if (value.kind === ts2.SyntaxKind.TrueKeyword)
|
|
1799
|
+
return true;
|
|
1800
|
+
if (value.kind === ts2.SyntaxKind.FalseKeyword)
|
|
1801
|
+
return false;
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
// src/app/build-app.ts
|
|
1806
|
+
async function buildNativeApp(config, dialect) {
|
|
1807
|
+
if (!config.app)
|
|
1808
|
+
throw new Error("Native app build requires config.app");
|
|
1809
|
+
const entries = Object.values(config.app.pages);
|
|
1810
|
+
if (entries.length !== 1)
|
|
1811
|
+
throw nativeError(config.entry, "\u9996\u671F\u539F\u751F App \u53EA\u652F\u6301\u4E00\u4E2A\u9875\u9762");
|
|
1812
|
+
const sourcePaths = [];
|
|
1813
|
+
const screen = await analyzeNativeScreen(entries[0], config, (paths) => sourcePaths.push(...paths));
|
|
1814
|
+
return withBuildOutput({
|
|
1815
|
+
root: config.root,
|
|
1816
|
+
outDir: config.app.outDir,
|
|
1817
|
+
protectedPaths: buildInputPaths(config, entries, [
|
|
1818
|
+
config.app.publicDir,
|
|
1819
|
+
...sourcePaths
|
|
1820
|
+
])
|
|
1821
|
+
}, (outDir) => generateNativeApp(config, dialect, outDir, screen));
|
|
1822
|
+
}
|
|
1823
|
+
async function generateNativeApp(config, dialect, outDir, screen) {
|
|
1824
|
+
const app = config.app;
|
|
1825
|
+
const files = dialect.generateProject(screen, app);
|
|
1826
|
+
if (app.host !== undefined) {
|
|
1827
|
+
files[`${dialect.resourceDirectory}/transone.config.json`] = `${JSON.stringify({ host: app.host }, null, 2)}
|
|
1828
|
+
`;
|
|
1829
|
+
}
|
|
1830
|
+
const assetsBuilt = [];
|
|
1831
|
+
for (const [relative2, content] of Object.entries(files).sort(([a], [b]) => a.localeCompare(b))) {
|
|
1832
|
+
const target = buildOutputPath(outDir, relative2);
|
|
1833
|
+
await mkdir2(dirname3(target), { recursive: true });
|
|
1834
|
+
await writeFile3(target, content, "utf8");
|
|
1835
|
+
assetsBuilt.push(target);
|
|
1836
|
+
}
|
|
1837
|
+
if (existsSync4(app.publicDir))
|
|
1838
|
+
await cp(app.publicDir, buildOutputPath(outDir, dialect.resourceDirectory), { recursive: true });
|
|
1839
|
+
return { root: config.root, outDir: resolve5(outDir), assetsBuilt };
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
// src/target/app-target.ts
|
|
1843
|
+
class AppTarget {
|
|
1844
|
+
dialect;
|
|
1845
|
+
constructor(dialect) {
|
|
1846
|
+
this.dialect = dialect;
|
|
1847
|
+
}
|
|
1848
|
+
get type() {
|
|
1849
|
+
return this.dialect.id;
|
|
1850
|
+
}
|
|
1851
|
+
get label() {
|
|
1852
|
+
return this.dialect.label;
|
|
1853
|
+
}
|
|
1854
|
+
build(config, _options) {
|
|
1855
|
+
return buildNativeApp(config, this.dialect);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
// src/app/dialects.ts
|
|
1860
|
+
function text(parts, language) {
|
|
1861
|
+
if (language === "swift") {
|
|
1862
|
+
const value = parts.map((part) => part.kind === "literal" ? part.value : `\\(${part.key})`).join("");
|
|
1863
|
+
return `"${value.replace(/"/g, "\\\"")}"`;
|
|
1864
|
+
}
|
|
1865
|
+
if (language === "kotlin") {
|
|
1866
|
+
return JSON.stringify(parts.map((part) => part.kind === "literal" ? part.value : `$${part.key}`).join(""));
|
|
1867
|
+
}
|
|
1868
|
+
return parts.map((part) => part.kind === "literal" ? JSON.stringify(part.value) : `this.${part.key}`).join(" + ") || '""';
|
|
1869
|
+
}
|
|
1870
|
+
function action(node, language) {
|
|
1871
|
+
return nativeAction(node.action, language);
|
|
1872
|
+
}
|
|
1873
|
+
function nativeAction(action2, language) {
|
|
1874
|
+
if (action2.kind === "sequence")
|
|
1875
|
+
return action2.actions.map((item) => nativeAction(item, language)).join("; ");
|
|
1876
|
+
const target = language === "ark" ? `this.${action2.key}` : action2.key;
|
|
1877
|
+
return action2.kind === "increment" ? `${target} ${action2.by >= 0 ? "+=" : "-="} ${Math.abs(action2.by)}` : `${target} = ${typeof action2.value === "string" ? JSON.stringify(action2.value) : action2.value}`;
|
|
1878
|
+
}
|
|
1879
|
+
function swiftNode(node, indent = " ") {
|
|
1880
|
+
if (node.kind === "container")
|
|
1881
|
+
return `${indent}VStack {
|
|
1882
|
+
${node.children.map((child) => swiftNode(child, `${indent} `)).join(`
|
|
1883
|
+
`)}
|
|
1884
|
+
${indent}}`;
|
|
1885
|
+
if (node.kind === "text")
|
|
1886
|
+
return `${indent}Text(${text(node.parts, "swift")})`;
|
|
1887
|
+
return `${indent}Button(${text(node.parts, "swift")}) { ${action(node, "swift")} }`;
|
|
1888
|
+
}
|
|
1889
|
+
function kotlinNode(node, indent = " ") {
|
|
1890
|
+
if (node.kind === "container")
|
|
1891
|
+
return `${indent}Column {
|
|
1892
|
+
${node.children.map((child) => kotlinNode(child, `${indent} `)).join(`
|
|
1893
|
+
`)}
|
|
1894
|
+
${indent}}`;
|
|
1895
|
+
if (node.kind === "text")
|
|
1896
|
+
return `${indent}Text(${text(node.parts, "kotlin")})`;
|
|
1897
|
+
return `${indent}Button(onClick = { ${action(node, "kotlin")} }) { Text(${text(node.parts, "kotlin")}) }`;
|
|
1898
|
+
}
|
|
1899
|
+
function arkNode(node, indent = " ") {
|
|
1900
|
+
if (node.kind === "container")
|
|
1901
|
+
return `${indent}Column() {
|
|
1902
|
+
${node.children.map((child) => arkNode(child, `${indent} `)).join(`
|
|
1903
|
+
`)}
|
|
1904
|
+
${indent}}`;
|
|
1905
|
+
if (node.kind === "text")
|
|
1906
|
+
return `${indent}Text(${text(node.parts, "ark")})`;
|
|
1907
|
+
return `${indent}Button(${text(node.parts, "ark")}).onClick(() => { ${action(node, "ark")} })`;
|
|
1908
|
+
}
|
|
1909
|
+
function swiftState(screen) {
|
|
1910
|
+
return screen.state.map((item) => ` @State private var ${item.key}: ${typeof item.value === "number" ? "Int" : typeof item.value === "boolean" ? "Bool" : "String"} = ${JSON.stringify(item.value)}`).join(`
|
|
1911
|
+
`);
|
|
1912
|
+
}
|
|
1913
|
+
function kotlinState(screen) {
|
|
1914
|
+
return screen.state.map((item) => ` var ${item.key} by remember { mutableStateOf(${JSON.stringify(item.value)}) }`).join(`
|
|
1915
|
+
`);
|
|
1916
|
+
}
|
|
1917
|
+
function arkState(screen) {
|
|
1918
|
+
return screen.state.map((item) => ` @State ${item.key}: ${typeof item.value === "number" ? "number" : typeof item.value === "boolean" ? "boolean" : "string"} = ${JSON.stringify(item.value)}`).join(`
|
|
1919
|
+
`);
|
|
1920
|
+
}
|
|
1921
|
+
var IOS_DIALECT = {
|
|
1922
|
+
id: "app-ios",
|
|
1923
|
+
label: "iOS \u539F\u751F App\uFF08SwiftUI\uFF09",
|
|
1924
|
+
resourceDirectory: "TransOneApp/Assets",
|
|
1925
|
+
generateProject(screen, config) {
|
|
1926
|
+
return {
|
|
1927
|
+
"TransOneApp/TransOneApp.swift": `import SwiftUI
|
|
1928
|
+
|
|
1929
|
+
@main struct TransOneApp: App { var body: some Scene { WindowGroup { ContentView() } } }
|
|
1930
|
+
`,
|
|
1931
|
+
"TransOneApp/ContentView.swift": `import SwiftUI
|
|
1932
|
+
|
|
1933
|
+
struct ContentView: View {
|
|
1934
|
+
${swiftState(screen)}
|
|
1935
|
+
var body: some View {
|
|
1936
|
+
${swiftNode(screen.root)}
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
`,
|
|
1940
|
+
"TransOneApp.xcodeproj/project.pbxproj": xcodeProject(config),
|
|
1941
|
+
"TransOneApp/Assets.xcassets/Contents.json": `{"info":{"author":"xcode","version":1}}
|
|
1942
|
+
`
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
};
|
|
1946
|
+
function xcodeProject(config) {
|
|
1947
|
+
const escapedName = config.appName.replace(/"/g, "\\\"");
|
|
1948
|
+
return `// !$*UTF8*$!
|
|
1949
|
+
{
|
|
1950
|
+
archiveVersion = 1;
|
|
1951
|
+
classes = {};
|
|
1952
|
+
objectVersion = 56;
|
|
1953
|
+
objects = {
|
|
1954
|
+
|
|
1955
|
+
/* Begin PBXBuildFile section */
|
|
1956
|
+
A00100000000000000000001 /* TransOneApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00100000000000000000011 /* TransOneApp.swift */; };
|
|
1957
|
+
A00100000000000000000002 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00100000000000000000012 /* ContentView.swift */; };
|
|
1958
|
+
/* End PBXBuildFile section */
|
|
1959
|
+
|
|
1960
|
+
/* Begin PBXFileReference section */
|
|
1961
|
+
A00100000000000000000011 /* TransOneApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransOneApp.swift; sourceTree = "<group>"; };
|
|
1962
|
+
A00100000000000000000012 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
|
1963
|
+
A00100000000000000000013 /* TransOneApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TransOneApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
1964
|
+
/* End PBXFileReference section */
|
|
1965
|
+
|
|
1966
|
+
/* Begin PBXGroup section */
|
|
1967
|
+
A00100000000000000000021 = {isa = PBXGroup; children = (A00100000000000000000022 /* TransOneApp */, A00100000000000000000023 /* Products */, ); sourceTree = "<group>"; };
|
|
1968
|
+
A00100000000000000000022 /* TransOneApp */ = {isa = PBXGroup; children = (A00100000000000000000011 /* TransOneApp.swift */, A00100000000000000000012 /* ContentView.swift */, ); path = TransOneApp; sourceTree = "<group>"; };
|
|
1969
|
+
A00100000000000000000023 /* Products */ = {isa = PBXGroup; children = (A00100000000000000000013 /* TransOneApp.app */, ); name = Products; sourceTree = "<group>"; };
|
|
1970
|
+
/* End PBXGroup section */
|
|
1971
|
+
|
|
1972
|
+
/* Begin PBXNativeTarget section */
|
|
1973
|
+
A00100000000000000000031 /* TransOneApp */ = {isa = PBXNativeTarget; buildConfigurationList = A00100000000000000000041 /* Build configuration list for PBXNativeTarget "TransOneApp" */; buildPhases = (A00100000000000000000032 /* Sources */, A00100000000000000000033 /* Frameworks */, A00100000000000000000034 /* Resources */, ); buildRules = (); dependencies = (); name = TransOneApp; productName = TransOneApp; productReference = A00100000000000000000013 /* TransOneApp.app */; productType = "com.apple.product-type.application"; };
|
|
1974
|
+
/* End PBXNativeTarget section */
|
|
1975
|
+
|
|
1976
|
+
/* Begin PBXProject section */
|
|
1977
|
+
A00100000000000000000051 /* Project object */ = {isa = PBXProject; attributes = { LastUpgradeCheck = 2700; TargetAttributes = { A00100000000000000000031 = { CreatedOnToolsVersion = 27.0; }; }; }; buildConfigurationList = A00100000000000000000052 /* Build configuration list for PBXProject "TransOneApp" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = (en, Base, ); mainGroup = A00100000000000000000021; productRefGroup = A00100000000000000000023; projectDirPath = ""; projectRoot = ""; targets = (A00100000000000000000031 /* TransOneApp */, ); };
|
|
1978
|
+
/* End PBXProject section */
|
|
1979
|
+
|
|
1980
|
+
/* Begin PBXResourcesBuildPhase section */
|
|
1981
|
+
A00100000000000000000034 /* Resources */ = {isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; };
|
|
1982
|
+
/* End PBXResourcesBuildPhase section */
|
|
1983
|
+
|
|
1984
|
+
/* Begin PBXSourcesBuildPhase section */
|
|
1985
|
+
A00100000000000000000032 /* Sources */ = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (A00100000000000000000001 /* TransOneApp.swift in Sources */, A00100000000000000000002 /* ContentView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };
|
|
1986
|
+
/* End PBXSourcesBuildPhase section */
|
|
1987
|
+
|
|
1988
|
+
/* Begin PBXFrameworksBuildPhase section */
|
|
1989
|
+
A00100000000000000000033 /* Frameworks */ = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; };
|
|
1990
|
+
/* End PBXFrameworksBuildPhase section */
|
|
1991
|
+
|
|
1992
|
+
/* Begin XCBuildConfiguration section */
|
|
1993
|
+
A00100000000000000000061 /* Debug */ = {isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; SWIFT_VERSION = 5.0; }; name = Debug; };
|
|
1994
|
+
A00100000000000000000062 /* Release */ = {isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; SWIFT_VERSION = 5.0; }; name = Release; };
|
|
1995
|
+
A00100000000000000000063 /* Debug */ = {isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CODE_SIGNING_ALLOWED = NO; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = ${config.minPlatformVersion}; PRODUCT_BUNDLE_IDENTIFIER = ${config.bundleId}; PRODUCT_NAME = "${escapedName}"; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; };
|
|
1996
|
+
A00100000000000000000064 /* Release */ = {isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CODE_SIGNING_ALLOWED = NO; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = ${config.minPlatformVersion}; PRODUCT_BUNDLE_IDENTIFIER = ${config.bundleId}; PRODUCT_NAME = "${escapedName}"; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; };
|
|
1997
|
+
/* End XCBuildConfiguration section */
|
|
1998
|
+
|
|
1999
|
+
/* Begin XCConfigurationList section */
|
|
2000
|
+
A00100000000000000000041 /* Build configuration list for PBXNativeTarget "TransOneApp" */ = {isa = XCConfigurationList; buildConfigurations = (A00100000000000000000063 /* Debug */, A00100000000000000000064 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; };
|
|
2001
|
+
A00100000000000000000052 /* Build configuration list for PBXProject "TransOneApp" */ = {isa = XCConfigurationList; buildConfigurations = (A00100000000000000000061 /* Debug */, A00100000000000000000062 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; };
|
|
2002
|
+
/* End XCConfigurationList section */
|
|
2003
|
+
};
|
|
2004
|
+
rootObject = A00100000000000000000051 /* Project object */;
|
|
2005
|
+
}
|
|
2006
|
+
`;
|
|
2007
|
+
}
|
|
2008
|
+
var ANDROID_DIALECT = {
|
|
2009
|
+
id: "app-android",
|
|
2010
|
+
label: "Android \u539F\u751F App\uFF08Jetpack Compose\uFF09",
|
|
2011
|
+
resourceDirectory: "app/src/main/assets",
|
|
2012
|
+
generateProject(screen, config) {
|
|
2013
|
+
const pkg = config.bundleId.replace(/[^A-Za-z0-9_.]/g, "_");
|
|
2014
|
+
const path = pkg.replace(/\./g, "/");
|
|
2015
|
+
return {
|
|
2016
|
+
"settings.gradle.kts": `rootProject.name = "${config.appName}"
|
|
2017
|
+
include(":app")
|
|
2018
|
+
`,
|
|
2019
|
+
"build.gradle.kts": `plugins { }
|
|
2020
|
+
`,
|
|
2021
|
+
"app/build.gradle.kts": `plugins { id("com.android.application") version "8.5.0" apply false }
|
|
2022
|
+
`,
|
|
2023
|
+
"app/src/main/AndroidManifest.xml": `<manifest package="${pkg}" xmlns:android="http://schemas.android.com/apk/res/android"><application android:label="${config.appName}"><activity android:name=".MainActivity" android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter></activity></application></manifest>
|
|
2024
|
+
`,
|
|
2025
|
+
[`app/src/main/java/${path}/MainActivity.kt`]: `package ${pkg}
|
|
2026
|
+
|
|
2027
|
+
import androidx.activity.ComponentActivity
|
|
2028
|
+
import androidx.activity.compose.setContent
|
|
2029
|
+
import androidx.compose.runtime.*
|
|
2030
|
+
import androidx.compose.foundation.layout.Column
|
|
2031
|
+
import androidx.compose.material3.*
|
|
2032
|
+
|
|
2033
|
+
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: android.os.Bundle?) { super.onCreate(savedInstanceState); setContent {
|
|
2034
|
+
${kotlinState(screen)}
|
|
2035
|
+
${kotlinNode(screen.root)}
|
|
2036
|
+
} } }
|
|
2037
|
+
`
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
};
|
|
2041
|
+
var HARMONY_DIALECT = {
|
|
2042
|
+
id: "app-harmony",
|
|
2043
|
+
label: "HarmonyOS \u539F\u751F App\uFF08ArkUI\uFF09",
|
|
2044
|
+
resourceDirectory: "entry/src/main/resources/rawfile",
|
|
2045
|
+
generateProject(screen, config) {
|
|
2046
|
+
return {
|
|
2047
|
+
"AppScope/app.json5": `{ "app": { "bundleName": "${config.bundleId}", "vendor": "TransOne", "versionCode": 1, "versionName": "1.0.0" } }
|
|
2048
|
+
`,
|
|
2049
|
+
"AppScope/resources/base/element/string.json": `{ "string": [{ "name": "app_name", "value": "${config.appName}" }] }
|
|
2050
|
+
`,
|
|
2051
|
+
"entry/src/main/module.json5": `{ "module": { "name": "entry", "type": "entry", "mainElement": "EntryAbility" } }
|
|
2052
|
+
`,
|
|
2053
|
+
"entry/src/main/ets/entryability/EntryAbility.ets": `import UIAbility from '@ohos.app.ability.UIAbility';
|
|
2054
|
+
export default class EntryAbility extends UIAbility {}
|
|
2055
|
+
`,
|
|
2056
|
+
"entry/src/main/ets/pages/Index.ets": `@Entry
|
|
2057
|
+
@Component
|
|
2058
|
+
struct Index {
|
|
2059
|
+
${arkState(screen)}
|
|
2060
|
+
build() {
|
|
2061
|
+
${arkNode(screen.root)}
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
`
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
2068
|
+
|
|
2069
|
+
// src/target/registry.ts
|
|
2070
|
+
class TargetRegistry {
|
|
2071
|
+
targets = new Map;
|
|
2072
|
+
constructor() {
|
|
2073
|
+
this.register(new WebTarget);
|
|
2074
|
+
this.register(mpWeixinTarget);
|
|
2075
|
+
this.register(mpAlipayTarget);
|
|
2076
|
+
this.register(mpBytedanceTarget);
|
|
2077
|
+
this.register(mpXiaohongshuTarget);
|
|
2078
|
+
this.register(new AppTarget(IOS_DIALECT));
|
|
2079
|
+
this.register(new AppTarget(ANDROID_DIALECT));
|
|
2080
|
+
this.register(new AppTarget(HARMONY_DIALECT));
|
|
2081
|
+
}
|
|
2082
|
+
register(target) {
|
|
2083
|
+
this.targets.set(target.type, target);
|
|
2084
|
+
}
|
|
2085
|
+
resolve(type) {
|
|
2086
|
+
const target = this.targets.get(type);
|
|
2087
|
+
if (!target) {
|
|
2088
|
+
throw new Error(`Unknown build target: ${type}`);
|
|
2089
|
+
}
|
|
2090
|
+
return target;
|
|
2091
|
+
}
|
|
2092
|
+
getAvailableTargets() {
|
|
2093
|
+
return [...this.targets.values()];
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
var targetRegistry = new TargetRegistry;
|
|
2097
|
+
|
|
2098
|
+
// src/build.ts
|
|
2099
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
2100
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve6, sep as sep2 } from "path";
|
|
2101
|
+
async function build(options = {}) {
|
|
2102
|
+
return buildConfigured(await resolveConfig(options), options);
|
|
2103
|
+
}
|
|
2104
|
+
async function buildConfigured(config, options = {}) {
|
|
2105
|
+
if (options.library) {
|
|
2106
|
+
return buildLibrary(config);
|
|
2107
|
+
}
|
|
2108
|
+
const target = targetRegistry.resolve(config.target);
|
|
2109
|
+
return target.build(config, options);
|
|
2110
|
+
}
|
|
2111
|
+
async function buildLibrary(config) {
|
|
2112
|
+
const library = config.library;
|
|
2113
|
+
if (!library) {
|
|
2114
|
+
throw new Error("TransOne library build requires config.library");
|
|
2115
|
+
}
|
|
2116
|
+
const declarations = library.dts ? await Promise.all(library.tsconfigs.map(loadDeclarationProject)) : [];
|
|
2117
|
+
const protectedPaths = buildInputPaths(config, [library.entry], [...library.tsconfigs, ...declarations.flatMap((project) => project.inputs)]);
|
|
2118
|
+
return withBuildOutput({
|
|
2119
|
+
root: config.root,
|
|
2120
|
+
outDir: library.outDir,
|
|
2121
|
+
protectedPaths
|
|
2122
|
+
}, async (outDir) => {
|
|
2123
|
+
const declarationAssets = declarations.flatMap((project) => emitDeclarations(project, library.outDir, outDir));
|
|
2124
|
+
const minify = library.minify ?? process.env.TRANSONE_MINIFY !== "0";
|
|
2125
|
+
const inputs = createBundleInputTracker();
|
|
2126
|
+
const result = await Bun.build({
|
|
2127
|
+
entrypoints: [library.entry],
|
|
2128
|
+
outdir: outDir,
|
|
2129
|
+
metafile: true,
|
|
2130
|
+
plugins: [inputs.plugin],
|
|
2131
|
+
root: dirname4(library.entry),
|
|
2132
|
+
target: "browser",
|
|
2133
|
+
format: "esm",
|
|
2134
|
+
sourcemap: library.sourcemap ? "linked" : undefined,
|
|
2135
|
+
splitting: library.splitting,
|
|
2136
|
+
minify,
|
|
2137
|
+
external: library.external,
|
|
2138
|
+
throw: false
|
|
2139
|
+
});
|
|
2140
|
+
if (!result.success || result.outputs.length === 0) {
|
|
2141
|
+
throw new Error(`Failed to build TransOne library:
|
|
2142
|
+
${result.logs.map((log) => log.message).join(`
|
|
2143
|
+
`)}`);
|
|
2144
|
+
}
|
|
2145
|
+
protectedPaths.push(...inputs.paths(result));
|
|
2146
|
+
const assetsBuilt = [
|
|
2147
|
+
...declarationAssets,
|
|
2148
|
+
...result.outputs.map((output) => resolve6(output.path))
|
|
2149
|
+
];
|
|
2150
|
+
return {
|
|
2151
|
+
root: config.root,
|
|
2152
|
+
outDir,
|
|
2153
|
+
assetsBuilt: [...new Set(assetsBuilt)]
|
|
2154
|
+
};
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
async function loadDeclarationProject(configFile) {
|
|
2158
|
+
const ts2 = await loadTypescript();
|
|
2159
|
+
const inputs = new Set;
|
|
2160
|
+
const diagnostics = [];
|
|
2161
|
+
const parsed = ts2.getParsedCommandLineOfConfigFile(configFile, {}, {
|
|
2162
|
+
...ts2.sys,
|
|
2163
|
+
readFile: (path) => {
|
|
2164
|
+
inputs.add(resolve6(path));
|
|
2165
|
+
return ts2.sys.readFile(path);
|
|
2166
|
+
},
|
|
2167
|
+
onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
|
|
2168
|
+
diagnostics.push(diagnostic);
|
|
2169
|
+
}
|
|
2170
|
+
});
|
|
2171
|
+
if (!parsed)
|
|
2172
|
+
throw declarationError(ts2, configFile, diagnostics);
|
|
2173
|
+
const program = ts2.createProgram({
|
|
2174
|
+
rootNames: parsed.fileNames,
|
|
2175
|
+
options: parsed.options,
|
|
2176
|
+
projectReferences: parsed.projectReferences
|
|
2177
|
+
});
|
|
2178
|
+
for (const source of program.getSourceFiles())
|
|
2179
|
+
inputs.add(resolve6(source.fileName));
|
|
2180
|
+
return {
|
|
2181
|
+
ts: ts2,
|
|
2182
|
+
program,
|
|
2183
|
+
diagnostics: [...diagnostics, ...parsed.errors],
|
|
2184
|
+
inputs: [...inputs],
|
|
2185
|
+
configFile
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
function emitDeclarations(project, finalOutDir, staging) {
|
|
2189
|
+
const { ts: ts2, program, configFile } = project;
|
|
2190
|
+
const diagnostics = [
|
|
2191
|
+
...project.diagnostics,
|
|
2192
|
+
...ts2.getPreEmitDiagnostics(program)
|
|
2193
|
+
];
|
|
2194
|
+
if (diagnostics.some((diagnostic) => diagnostic.category === ts2.DiagnosticCategory.Error)) {
|
|
2195
|
+
throw declarationError(ts2, configFile, diagnostics);
|
|
2196
|
+
}
|
|
2197
|
+
const assets = [];
|
|
2198
|
+
const emitted = program.emit(undefined, (file, content, byteOrderMark) => {
|
|
2199
|
+
const path = relative2(finalOutDir, resolve6(file));
|
|
2200
|
+
if (path === "" || path === ".." || path.startsWith(`..${sep2}`) || isAbsolute2(path)) {
|
|
2201
|
+
throw new Error(`TypeScript output must be inside library.outDir for recoverable publication: ${file} (${configFile})`);
|
|
2202
|
+
}
|
|
2203
|
+
const target = resolve6(staging, path);
|
|
2204
|
+
mkdirSync(dirname4(target), { recursive: true });
|
|
2205
|
+
writeFileSync(target, `${byteOrderMark ? "\uFEFF" : ""}${content}`);
|
|
2206
|
+
assets.push(target);
|
|
2207
|
+
});
|
|
2208
|
+
if (emitted.emitSkipped || emitted.diagnostics.some((diagnostic) => diagnostic.category === ts2.DiagnosticCategory.Error)) {
|
|
2209
|
+
throw declarationError(ts2, configFile, emitted.diagnostics);
|
|
2210
|
+
}
|
|
2211
|
+
return assets;
|
|
2212
|
+
}
|
|
2213
|
+
function declarationError(ts2, configFile, diagnostics) {
|
|
2214
|
+
return new Error(`TypeScript compilation failed for ${configFile}:
|
|
2215
|
+
${ts2.formatDiagnostics(diagnostics, {
|
|
2216
|
+
getCanonicalFileName: (file) => file,
|
|
2217
|
+
getCurrentDirectory: () => dirname4(configFile),
|
|
2218
|
+
getNewLine: () => `
|
|
2219
|
+
`
|
|
2220
|
+
})}`);
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
// src/components/report.ts
|
|
2224
|
+
import { relative as relative3, sep as sep3 } from "path";
|
|
2225
|
+
async function reportComponents(options = {}) {
|
|
2226
|
+
return reportConfiguredComponents(await resolveConfig(options));
|
|
2227
|
+
}
|
|
2228
|
+
function reportConfiguredComponents(config) {
|
|
2229
|
+
const family = getPlatformDefinition(config.platform).family;
|
|
2230
|
+
const pages = family === "miniprogram" ? config.mp?.pages ?? config.pages : family === "app" ? config.app?.pages ?? config.pages : config.pages;
|
|
2231
|
+
const entries = [...new Set(Object.values(pages))];
|
|
2232
|
+
const contexts = family === "web" ? entries.map((entry) => [entry]) : [entries];
|
|
2233
|
+
const root = canonicalPath(config.root);
|
|
2234
|
+
const sourcePath = (path) => relative3(root, canonicalPath(path)).split(sep3).join("/");
|
|
2235
|
+
const location = (reference) => {
|
|
2236
|
+
const point = reference.source.getLineAndCharacterOfPosition(reference.declaration.getStart(reference.source));
|
|
2237
|
+
return {
|
|
2238
|
+
source: sourcePath(reference.filePath),
|
|
2239
|
+
className: reference.className,
|
|
2240
|
+
line: point.line + 1,
|
|
2241
|
+
column: point.character + 1
|
|
2242
|
+
};
|
|
2243
|
+
};
|
|
2244
|
+
const components = [];
|
|
2245
|
+
for (const context of contexts) {
|
|
2246
|
+
const index = createComponentIndex({
|
|
2247
|
+
root: config.root,
|
|
2248
|
+
entrypoints: context,
|
|
2249
|
+
platform: config.platform,
|
|
2250
|
+
include: config.components?.include,
|
|
2251
|
+
exclude: config.components?.exclude
|
|
2252
|
+
});
|
|
2253
|
+
const entrypoints = context.map(sourcePath).sort();
|
|
2254
|
+
for (const reference of index.classes) {
|
|
2255
|
+
if (!index.isComponent(reference) || index.metadata(reference).framework)
|
|
2256
|
+
continue;
|
|
2257
|
+
const decision = index.decision(reference);
|
|
2258
|
+
if (decision.reason === "explicit")
|
|
2259
|
+
continue;
|
|
2260
|
+
components.push({
|
|
2261
|
+
entrypoints,
|
|
2262
|
+
component: location(reference),
|
|
2263
|
+
implementation: decision.implementation ? location(decision.implementation) : null,
|
|
2264
|
+
reason: decision.reason,
|
|
2265
|
+
candidates: decision.candidates.map((candidate) => ({
|
|
2266
|
+
...location(candidate),
|
|
2267
|
+
platforms: [...index.metadata(candidate).platforms].sort()
|
|
2268
|
+
})).sort(compareLocations)
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
components.sort((left, right) => compareText(left.entrypoints.join("\x00"), right.entrypoints.join("\x00")) || compareLocations(left.component, right.component));
|
|
2273
|
+
return {
|
|
2274
|
+
platform: config.platform,
|
|
2275
|
+
scope: "discovered-logical-components",
|
|
2276
|
+
components
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
function compareText(left, right) {
|
|
2280
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
2281
|
+
}
|
|
2282
|
+
function compareLocations(left, right) {
|
|
2283
|
+
return compareText(left.source, right.source) || left.line - right.line || left.column - right.column || compareText(left.className, right.className);
|
|
2284
|
+
}
|
|
2285
|
+
function formatComponentSelectionReport(report) {
|
|
2286
|
+
const describe = (value) => `${value.className} (${value.source}:${value.line}:${value.column})`;
|
|
2287
|
+
return [
|
|
2288
|
+
`TransOne component selection: ${report.platform}`,
|
|
2289
|
+
"Scope: discovered logical component mappings; runtime constructor usage is not analyzed.",
|
|
2290
|
+
...report.components.length ? report.components.flatMap((row) => [
|
|
2291
|
+
`${describe(row.component)} -> ${row.implementation ? describe(row.implementation) : "(missing)"} [${row.reason}]`,
|
|
2292
|
+
` Entrypoints: ${row.entrypoints.join(", ")}`,
|
|
2293
|
+
...row.candidates.length ? [
|
|
2294
|
+
` Candidates: ${row.candidates.map((candidate) => `${describe(candidate)} [${candidate.platforms.join(", ")}]`).join("; ")}`
|
|
2295
|
+
] : []
|
|
2296
|
+
]) : ["(no logical components discovered)"]
|
|
2297
|
+
].join(`
|
|
2298
|
+
`);
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
// src/create.ts
|
|
2302
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2303
|
+
import { basename as basename2, dirname as dirname5, join } from "path";
|
|
2304
|
+
|
|
2305
|
+
// src/scaffold.ts
|
|
2306
|
+
var cardViews = [
|
|
2307
|
+
{
|
|
2308
|
+
className: "CardWebComponent",
|
|
2309
|
+
decorator: "Web",
|
|
2310
|
+
label: "Web Card",
|
|
2311
|
+
background: "#eff6ff"
|
|
2312
|
+
},
|
|
2313
|
+
{
|
|
2314
|
+
className: "CardMiniprogramComponent",
|
|
2315
|
+
decorator: "Miniprogram",
|
|
2316
|
+
label: "Miniprogram Card",
|
|
2317
|
+
background: "#f0fdf4"
|
|
2318
|
+
},
|
|
2319
|
+
{
|
|
2320
|
+
className: "CardAppComponent",
|
|
2321
|
+
decorator: "App",
|
|
2322
|
+
label: "App Card",
|
|
2323
|
+
background: "#f8fafc"
|
|
2324
|
+
},
|
|
2325
|
+
{
|
|
2326
|
+
className: "CardMobileComponent",
|
|
2327
|
+
decorator: "WebMobile",
|
|
2328
|
+
label: "Mobile Web Card",
|
|
2329
|
+
background: "#f0fdf4"
|
|
2330
|
+
},
|
|
2331
|
+
{
|
|
2332
|
+
className: "CardPcComponent",
|
|
2333
|
+
decorator: "WebPc",
|
|
2334
|
+
label: "PC Web Card",
|
|
2335
|
+
background: "#eef2ff"
|
|
2336
|
+
},
|
|
2337
|
+
{
|
|
2338
|
+
className: "CardWeixinComponent",
|
|
2339
|
+
decorator: "MiniprogramWeixin",
|
|
2340
|
+
label: "Weixin Card",
|
|
2341
|
+
background: "#f0fdf4"
|
|
2342
|
+
},
|
|
2343
|
+
{
|
|
2344
|
+
className: "CardAlipayComponent",
|
|
2345
|
+
decorator: "MiniprogramAlipay",
|
|
2346
|
+
label: "Alipay Card",
|
|
2347
|
+
background: "#eff6ff"
|
|
2348
|
+
},
|
|
2349
|
+
{
|
|
2350
|
+
className: "CardIosComponent",
|
|
2351
|
+
decorator: "AppIos",
|
|
2352
|
+
label: "iOS Card",
|
|
2353
|
+
background: "#f8fafc"
|
|
2354
|
+
},
|
|
2355
|
+
{
|
|
2356
|
+
className: "CardAndroidComponent",
|
|
2357
|
+
decorator: "AppAndroid",
|
|
2358
|
+
label: "Android Card",
|
|
2359
|
+
background: "#f0fdf4"
|
|
2360
|
+
}
|
|
2361
|
+
];
|
|
2362
|
+
function cardRender(label, background = "#ffffff") {
|
|
2363
|
+
return ` protected render(): VNode {
|
|
2364
|
+
return h('main', {
|
|
2365
|
+
style: { padding: '16px', borderRadius: '12px', backgroundColor: '${background}' },
|
|
2366
|
+
}, [
|
|
2367
|
+
h('h1', { style: { fontSize: '20px' } }, [this.props.title]),
|
|
2368
|
+
h('p', {}, ['${label}']),
|
|
2369
|
+
h('p', {}, ['Count: {{count}}']),
|
|
2370
|
+
h('button', {}, ['+1'], { click: () => this.increment() }),
|
|
2371
|
+
]);
|
|
2372
|
+
}`;
|
|
2373
|
+
}
|
|
2374
|
+
function scaffoldFiles(name2, frameworkVersion, cliVersion) {
|
|
2375
|
+
return [
|
|
2376
|
+
{
|
|
2377
|
+
path: "package.json",
|
|
2378
|
+
content: JSON.stringify({
|
|
2379
|
+
name: name2,
|
|
2380
|
+
private: true,
|
|
2381
|
+
type: "module",
|
|
2382
|
+
scripts: {
|
|
2383
|
+
dev: "transone dev",
|
|
2384
|
+
"dev:mobile": "transone dev --target web-mobile",
|
|
2385
|
+
"dev:pc": "transone dev --target web-pc",
|
|
2386
|
+
build: "transone build",
|
|
2387
|
+
"build:web": "transone build --target web",
|
|
2388
|
+
"build:mobile": "transone build --target web-mobile",
|
|
2389
|
+
"build:pc": "transone build --target web-pc",
|
|
2390
|
+
"build:weixin": "transone build --target miniprogram-weixin",
|
|
2391
|
+
"build:alipay": "transone build --target miniprogram-alipay",
|
|
2392
|
+
"build:bytedance": "transone build --target miniprogram-bytedance",
|
|
2393
|
+
"build:xiaohongshu": "transone build --target miniprogram-xiaohongshu",
|
|
2394
|
+
"build:ios": "transone build --target app-ios",
|
|
2395
|
+
"build:android": "transone build --target app-android",
|
|
2396
|
+
"build:harmony": "transone build --target app-harmony",
|
|
2397
|
+
typecheck: "bunx tsc --noEmit"
|
|
2398
|
+
},
|
|
2399
|
+
dependencies: { transone: `^${frameworkVersion}` },
|
|
2400
|
+
devDependencies: {
|
|
2401
|
+
"transone-cli": `^${cliVersion}`,
|
|
2402
|
+
typescript: "^5.3.3"
|
|
2403
|
+
}
|
|
2404
|
+
}, null, 2) + `
|
|
2405
|
+
`
|
|
2406
|
+
},
|
|
2407
|
+
{
|
|
2408
|
+
path: "transone.config.ts",
|
|
2409
|
+
content: `import { Platform } from 'transone';
|
|
2410
|
+
import { defineConfig } from 'transone-cli';
|
|
2411
|
+
|
|
2412
|
+
export default defineConfig({
|
|
2413
|
+
entry: 'src/main.ts',
|
|
2414
|
+
target: Platform.Web,
|
|
2415
|
+
});
|
|
2416
|
+
`
|
|
2417
|
+
},
|
|
2418
|
+
{
|
|
2419
|
+
path: "tsconfig.json",
|
|
2420
|
+
content: JSON.stringify({
|
|
2421
|
+
compilerOptions: {
|
|
2422
|
+
target: "ESNext",
|
|
2423
|
+
module: "ESNext",
|
|
2424
|
+
moduleResolution: "Bundler",
|
|
2425
|
+
lib: ["ESNext", "DOM", "DOM.Iterable"],
|
|
2426
|
+
strict: true,
|
|
2427
|
+
noEmit: true,
|
|
2428
|
+
skipLibCheck: true,
|
|
2429
|
+
verbatimModuleSyntax: true
|
|
2430
|
+
},
|
|
2431
|
+
include: ["src/**/*", "transone.config.ts"]
|
|
2432
|
+
}, null, 2) + `
|
|
2433
|
+
`
|
|
2434
|
+
},
|
|
2435
|
+
{ path: ".gitignore", content: `node_modules/
|
|
2436
|
+
dist/
|
|
2437
|
+
` },
|
|
2438
|
+
{
|
|
2439
|
+
path: "src/main.ts",
|
|
2440
|
+
content: `import { createApp } from 'transone';
|
|
2441
|
+
import { AppComponent } from './AppComponent';
|
|
2442
|
+
|
|
2443
|
+
export const app = createApp({
|
|
2444
|
+
root: AppComponent,
|
|
2445
|
+
document: { title: 'TransOne component variants' },
|
|
2446
|
+
});
|
|
2447
|
+
app.mount();
|
|
2448
|
+
`
|
|
2449
|
+
},
|
|
2450
|
+
{
|
|
2451
|
+
path: "src/AppComponent.ts",
|
|
2452
|
+
content: `import { BaseComponent, createComponent, h, type VNode } from 'transone';
|
|
2453
|
+
import { CardComponent } from './components/card/CardComponent';
|
|
2454
|
+
|
|
2455
|
+
export class AppComponent extends BaseComponent<object, object> {
|
|
2456
|
+
protected initState(): object { return {}; }
|
|
2457
|
+
protected initStyles(): void {}
|
|
2458
|
+
|
|
2459
|
+
protected render(): VNode {
|
|
2460
|
+
return h('main', { style: { padding: '16px' } }, [
|
|
2461
|
+
h('h1', {}, ['TransOne component variants']),
|
|
2462
|
+
createComponent({
|
|
2463
|
+
component: CardComponent,
|
|
2464
|
+
props: { title: 'Shared business, platform view' },
|
|
2465
|
+
}),
|
|
2466
|
+
]);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
`
|
|
2470
|
+
},
|
|
2471
|
+
{
|
|
2472
|
+
path: "src/components/card/CardComponent.ts",
|
|
2473
|
+
content: `import { BaseComponent, h, type VNode } from 'transone';
|
|
2474
|
+
|
|
2475
|
+
export interface CardProps { title: string; }
|
|
2476
|
+
export interface CardState { count: number; }
|
|
2477
|
+
|
|
2478
|
+
/** Shared state and business methods; pages only import this class. */
|
|
2479
|
+
export class CardComponent extends BaseComponent<CardProps, CardState> {
|
|
2480
|
+
protected initState(): CardState { return { count: 0 }; }
|
|
2481
|
+
protected initStyles(): void {}
|
|
2482
|
+
protected increment(): void { this.state.count += 1; }
|
|
2483
|
+
|
|
2484
|
+
${cardRender("Shared Card")}
|
|
2485
|
+
}
|
|
2486
|
+
`
|
|
2487
|
+
},
|
|
2488
|
+
...cardViews.map((view) => ({
|
|
2489
|
+
path: `src/components/card/${view.className}.ts`,
|
|
2490
|
+
content: `import { ${view.decorator}, h, type VNode } from 'transone';
|
|
2491
|
+
import { CardComponent } from './CardComponent';
|
|
2492
|
+
|
|
2493
|
+
@${view.decorator}()
|
|
2494
|
+
export class ${view.className} extends CardComponent {
|
|
2495
|
+
${cardRender(view.label, view.background)}
|
|
2496
|
+
}
|
|
2497
|
+
`
|
|
2498
|
+
}))
|
|
2499
|
+
];
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
// src/create.ts
|
|
2503
|
+
var TRANSONE_FRAMEWORK_VERSION = "0.9.0";
|
|
2504
|
+
var TRANSONE_CLI_VERSION = "0.10.0";
|
|
2505
|
+
function createProject(options = {}) {
|
|
2506
|
+
const root = options.root ?? process.cwd();
|
|
2507
|
+
const name2 = options.name ?? basename2(root);
|
|
2508
|
+
const files = scaffoldFiles(name2, TRANSONE_FRAMEWORK_VERSION, TRANSONE_CLI_VERSION);
|
|
2509
|
+
const existing = files.filter((file) => existsSync5(join(root, file.path)));
|
|
2510
|
+
if (existing.length > 0) {
|
|
2511
|
+
throw new Error(`Refusing to overwrite existing TransOne project files: ${existing.map((file) => file.path).join(", ")}`);
|
|
2512
|
+
}
|
|
2513
|
+
mkdirSync2(join(root, "src"), { recursive: true });
|
|
2514
|
+
const created = [];
|
|
2515
|
+
for (const file of files) {
|
|
2516
|
+
mkdirSync2(dirname5(join(root, file.path)), { recursive: true });
|
|
2517
|
+
writeFileSync2(join(root, file.path), file.content, {
|
|
2518
|
+
encoding: "utf8",
|
|
2519
|
+
flag: "wx"
|
|
2520
|
+
});
|
|
2521
|
+
created.push(file.path);
|
|
2522
|
+
}
|
|
2523
|
+
return { root, files: created };
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
// src/server.ts
|
|
2527
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2528
|
+
import { existsSync as existsSync6, realpathSync as realpathSync2, statSync } from "fs";
|
|
2529
|
+
import { rm as rm2 } from "fs/promises";
|
|
2530
|
+
import {
|
|
2531
|
+
basename as basename3,
|
|
2532
|
+
dirname as dirname6,
|
|
2533
|
+
extname,
|
|
2534
|
+
isAbsolute as isAbsolute4,
|
|
2535
|
+
relative as relative5,
|
|
2536
|
+
resolve as resolve7,
|
|
2537
|
+
sep as sep4
|
|
2538
|
+
} from "path";
|
|
2539
|
+
|
|
2540
|
+
// src/proxy.ts
|
|
2541
|
+
var HOP_BY_HOP_HEADERS = new Set([
|
|
2542
|
+
"connection",
|
|
2543
|
+
"keep-alive",
|
|
2544
|
+
"proxy-authenticate",
|
|
2545
|
+
"proxy-authorization",
|
|
2546
|
+
"te",
|
|
2547
|
+
"trailer",
|
|
2548
|
+
"transfer-encoding",
|
|
2549
|
+
"upgrade"
|
|
2550
|
+
]);
|
|
2551
|
+
function createProxyHandler(proxy) {
|
|
2552
|
+
const rules = Object.entries(proxy ?? {}).map(([prefix, options]) => toProxyRule(prefix, options)).sort((first, second) => second.prefix.length - first.prefix.length);
|
|
2553
|
+
return async (request) => {
|
|
2554
|
+
const sourceUrl = new URL(request.url);
|
|
2555
|
+
const rule = rules.find(({ prefix }) => sourceUrl.pathname.startsWith(prefix));
|
|
2556
|
+
if (!rule) {
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
const target = createTargetUrl(rule, sourceUrl);
|
|
2560
|
+
const headers = createRequestHeaders(request, sourceUrl, rule, target);
|
|
2561
|
+
const body = request.method === "GET" || request.method === "HEAD" ? undefined : request.body;
|
|
2562
|
+
try {
|
|
2563
|
+
const upstream = await fetch(target, {
|
|
2564
|
+
method: request.method,
|
|
2565
|
+
headers,
|
|
2566
|
+
body,
|
|
2567
|
+
signal: request.signal,
|
|
2568
|
+
redirect: "manual"
|
|
2569
|
+
});
|
|
2570
|
+
return new Response(upstream.body, {
|
|
2571
|
+
status: upstream.status,
|
|
2572
|
+
statusText: upstream.statusText,
|
|
2573
|
+
headers: filterHeaders(upstream.headers)
|
|
2574
|
+
});
|
|
2575
|
+
} catch {
|
|
2576
|
+
return new Response("Bad Gateway", { status: 502 });
|
|
2577
|
+
}
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
function toProxyRule(prefix, value) {
|
|
2581
|
+
const options = typeof value === "string" ? { target: value } : value;
|
|
2582
|
+
return {
|
|
2583
|
+
prefix,
|
|
2584
|
+
target: new URL(options.target),
|
|
2585
|
+
changeOrigin: options.changeOrigin ?? false,
|
|
2586
|
+
...options.rewrite === undefined ? {} : { rewrite: options.rewrite }
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
function createTargetUrl(rule, sourceUrl) {
|
|
2590
|
+
const target = new URL(rule.target);
|
|
2591
|
+
const pathname = rule.rewrite?.(sourceUrl.pathname) ?? sourceUrl.pathname;
|
|
2592
|
+
target.pathname = joinPathnames(target.pathname, pathname);
|
|
2593
|
+
target.search = sourceUrl.search;
|
|
2594
|
+
return target;
|
|
2595
|
+
}
|
|
2596
|
+
function joinPathnames(basePathname, pathname) {
|
|
2597
|
+
const base = basePathname === "/" ? "" : basePathname.replace(/\/+$/, "");
|
|
2598
|
+
const path = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
2599
|
+
return `${base}${path}` || "/";
|
|
2600
|
+
}
|
|
2601
|
+
function createRequestHeaders(request, sourceUrl, rule, target) {
|
|
2602
|
+
const headers = filterHeaders(request.headers);
|
|
2603
|
+
const incomingHost = request.headers.get("host") ?? sourceUrl.host;
|
|
2604
|
+
headers.set("host", rule.changeOrigin ? target.host : incomingHost);
|
|
2605
|
+
return headers;
|
|
2606
|
+
}
|
|
2607
|
+
function filterHeaders(source) {
|
|
2608
|
+
const headers = new Headers(source);
|
|
2609
|
+
const connection = headers.get("connection");
|
|
2610
|
+
if (connection) {
|
|
2611
|
+
connection.split(",").forEach((header) => {
|
|
2612
|
+
const name2 = header.trim();
|
|
2613
|
+
if (name2) {
|
|
2614
|
+
headers.delete(name2);
|
|
2615
|
+
}
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
HOP_BY_HOP_HEADERS.forEach((header) => headers.delete(header));
|
|
2619
|
+
return headers;
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
// src/watch.ts
|
|
2623
|
+
import { watch } from "fs";
|
|
2624
|
+
import { readdir, stat } from "fs/promises";
|
|
2625
|
+
import { isAbsolute as isAbsolute3, join as join2, relative as relative4 } from "path";
|
|
2626
|
+
var RECURSIVE_WATCH_UNSUPPORTED_ERROR = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
|
|
2627
|
+
var DEFAULT_DEBOUNCE_MS = 100;
|
|
2628
|
+
var DEFAULT_POLL_INTERVAL_MS = 300;
|
|
2629
|
+
function createFileWatcher(options) {
|
|
2630
|
+
const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
2631
|
+
let timer;
|
|
2632
|
+
const disposers = [];
|
|
2633
|
+
const scheduleChange = (relativePath) => {
|
|
2634
|
+
if (timer) {
|
|
2635
|
+
clearTimeout(timer);
|
|
2636
|
+
}
|
|
2637
|
+
timer = setTimeout(() => {
|
|
2638
|
+
timer = undefined;
|
|
2639
|
+
options.onChange(relativePath);
|
|
2640
|
+
}, debounceMs);
|
|
2641
|
+
};
|
|
2642
|
+
disposers.push(() => {
|
|
2643
|
+
if (timer) {
|
|
2644
|
+
clearTimeout(timer);
|
|
2645
|
+
}
|
|
2646
|
+
});
|
|
2647
|
+
const nativeWatcher = tryCreateNativeWatcher(options.root, options.isRelevant, scheduleChange);
|
|
2648
|
+
if (nativeWatcher) {
|
|
2649
|
+
disposers.push(nativeWatcher);
|
|
2650
|
+
} else {
|
|
2651
|
+
disposers.push(createPollingWatcher(options.root, options.isRelevant, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS, scheduleChange));
|
|
2652
|
+
}
|
|
2653
|
+
return {
|
|
2654
|
+
dispose: () => {
|
|
2655
|
+
disposers.splice(0).reverse().forEach((dispose) => dispose());
|
|
2656
|
+
}
|
|
2657
|
+
};
|
|
2658
|
+
}
|
|
2659
|
+
function tryCreateNativeWatcher(root, isRelevant, onRelevantChange) {
|
|
2660
|
+
let watcher;
|
|
2661
|
+
try {
|
|
2662
|
+
watcher = watch(root, { recursive: true }, (_event, filename) => {
|
|
2663
|
+
if (typeof filename !== "string") {
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
const relativePath = toRelativePath(root, filename);
|
|
2667
|
+
if (relativePath === undefined || !isRelevant(relativePath)) {
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2670
|
+
onRelevantChange(relativePath);
|
|
2671
|
+
});
|
|
2672
|
+
} catch (error) {
|
|
2673
|
+
if (isRecursiveWatchUnsupported(error)) {
|
|
2674
|
+
return;
|
|
2675
|
+
}
|
|
2676
|
+
throw error;
|
|
2677
|
+
}
|
|
2678
|
+
return () => watcher.close();
|
|
2679
|
+
}
|
|
2680
|
+
function createPollingWatcher(root, isRelevant, intervalMs, onRelevantChange) {
|
|
2681
|
+
let running = true;
|
|
2682
|
+
let snapshot = new Map;
|
|
2683
|
+
let timer;
|
|
2684
|
+
const scan = async () => {
|
|
2685
|
+
if (!running) {
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
const next = new Map;
|
|
2689
|
+
try {
|
|
2690
|
+
await collectRelevantFiles(root, isRelevant, next);
|
|
2691
|
+
} catch {
|
|
2692
|
+
return;
|
|
2693
|
+
}
|
|
2694
|
+
let changedPath;
|
|
2695
|
+
for (const [relativePath, snapshotEntry] of next) {
|
|
2696
|
+
const previous = snapshot.get(relativePath);
|
|
2697
|
+
if (!previous || previous.mtimeMs !== snapshotEntry.mtimeMs || previous.size !== snapshotEntry.size) {
|
|
2698
|
+
changedPath = relativePath;
|
|
2699
|
+
break;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
if (changedPath === undefined) {
|
|
2703
|
+
for (const relativePath of snapshot.keys()) {
|
|
2704
|
+
if (!next.has(relativePath)) {
|
|
2705
|
+
changedPath = relativePath;
|
|
2706
|
+
break;
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
snapshot = next;
|
|
2711
|
+
if (changedPath !== undefined) {
|
|
2712
|
+
onRelevantChange(changedPath);
|
|
2713
|
+
}
|
|
2714
|
+
};
|
|
2715
|
+
scan();
|
|
2716
|
+
timer = setInterval(() => {
|
|
2717
|
+
scan();
|
|
2718
|
+
}, intervalMs);
|
|
2719
|
+
if (typeof timer.unref === "function") {
|
|
2720
|
+
timer.unref();
|
|
2721
|
+
}
|
|
2722
|
+
return () => {
|
|
2723
|
+
running = false;
|
|
2724
|
+
if (timer) {
|
|
2725
|
+
clearInterval(timer);
|
|
2726
|
+
}
|
|
2727
|
+
};
|
|
2728
|
+
}
|
|
2729
|
+
async function collectRelevantFiles(root, isRelevant, output) {
|
|
2730
|
+
const pendingDirectories = [""];
|
|
2731
|
+
while (pendingDirectories.length > 0) {
|
|
2732
|
+
const relativeDirectory = pendingDirectories.pop();
|
|
2733
|
+
if (relativeDirectory === undefined) {
|
|
2734
|
+
break;
|
|
2735
|
+
}
|
|
2736
|
+
const absoluteDirectory = join2(root, relativeDirectory);
|
|
2737
|
+
let entries;
|
|
2738
|
+
try {
|
|
2739
|
+
entries = await readdir(absoluteDirectory, { withFileTypes: true });
|
|
2740
|
+
} catch {
|
|
2741
|
+
continue;
|
|
2742
|
+
}
|
|
2743
|
+
for (const entry of entries) {
|
|
2744
|
+
const relativePath = relativeDirectory === "" ? entry.name : `${relativeDirectory}/${entry.name}`;
|
|
2745
|
+
if (entry.isDirectory()) {
|
|
2746
|
+
pendingDirectories.push(relativePath);
|
|
2747
|
+
continue;
|
|
2748
|
+
}
|
|
2749
|
+
if (!entry.isFile() || !isRelevant(relativePath)) {
|
|
2750
|
+
continue;
|
|
2751
|
+
}
|
|
2752
|
+
try {
|
|
2753
|
+
const info = await stat(join2(root, relativePath));
|
|
2754
|
+
output.set(relativePath, { mtimeMs: info.mtimeMs, size: info.size });
|
|
2755
|
+
} catch {}
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
function toRelativePath(root, filename) {
|
|
2760
|
+
const relativePath = isAbsolute3(filename) ? relative4(root, filename) : filename.split("\\").join("/");
|
|
2761
|
+
if (relativePath === "" || relativePath === ".." || relativePath.startsWith("../")) {
|
|
2762
|
+
return;
|
|
2763
|
+
}
|
|
2764
|
+
return relativePath.split("\\").join("/");
|
|
2765
|
+
}
|
|
2766
|
+
function isRecursiveWatchUnsupported(error) {
|
|
2767
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === RECURSIVE_WATCH_UNSUPPORTED_ERROR;
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
// src/server.ts
|
|
2771
|
+
var DEVELOPMENT_URL_PREFIX = "/dev";
|
|
2772
|
+
var INVALID_DEVELOPMENT_DIRECTORY_MESSAGE = "Development output must be a subdirectory of the project root";
|
|
2773
|
+
var LIVE_RELOAD_PATH = "/__transone/reload";
|
|
2774
|
+
var DEVELOPMENT_OUTPUT_RETENTION_MS = 5000;
|
|
2775
|
+
var WATCHED_EXTENSIONS = new Set([
|
|
2776
|
+
".ts",
|
|
2777
|
+
".tsx",
|
|
2778
|
+
".js",
|
|
2779
|
+
".mjs",
|
|
2780
|
+
".cjs",
|
|
2781
|
+
".json",
|
|
2782
|
+
".css",
|
|
2783
|
+
".html"
|
|
2784
|
+
]);
|
|
2785
|
+
var IGNORED_WATCH_SEGMENTS = new Set([
|
|
2786
|
+
"dist",
|
|
2787
|
+
"node_modules",
|
|
2788
|
+
".git",
|
|
2789
|
+
".worktrees"
|
|
2790
|
+
]);
|
|
2791
|
+
var LIVE_RELOAD_CLIENT_SCRIPT = [
|
|
2792
|
+
'<script type="module">',
|
|
2793
|
+
"const transoneReloadSource=new EventSource(",
|
|
2794
|
+
JSON.stringify(LIVE_RELOAD_PATH),
|
|
2795
|
+
");",
|
|
2796
|
+
'transoneReloadSource.addEventListener("reload",()=>location.reload());',
|
|
2797
|
+
"</script>"
|
|
2798
|
+
].join("");
|
|
2799
|
+
async function startDevServer(options = {}) {
|
|
2800
|
+
if (options.watch) {
|
|
2801
|
+
return startWatchingDevServer(options);
|
|
2802
|
+
}
|
|
2803
|
+
const instance = await createServerInstance(options, false);
|
|
2804
|
+
return createServerHandle(() => instance, () => {
|
|
2805
|
+
return;
|
|
2806
|
+
});
|
|
2807
|
+
}
|
|
2808
|
+
async function startWatchingDevServer(options) {
|
|
2809
|
+
let instance = await createServerInstance(options, true);
|
|
2810
|
+
let watcher = createProjectWatcher(instance, onChange);
|
|
2811
|
+
async function onChange(changedPath) {
|
|
2812
|
+
const configFile = instance.config.configFile;
|
|
2813
|
+
const configRelativePath = configFile === undefined ? undefined : toRelativePath2(instance.config.root, configFile);
|
|
2814
|
+
if (configRelativePath === changedPath) {
|
|
2815
|
+
await restart();
|
|
2816
|
+
return;
|
|
2817
|
+
}
|
|
2818
|
+
await rebuildAndReload();
|
|
2819
|
+
}
|
|
2820
|
+
async function rebuildAndReload() {
|
|
2821
|
+
const entries = [...new Set(Object.values(instance.config.pages))];
|
|
2822
|
+
instance.markDirty(entries);
|
|
2823
|
+
for (const entry of entries) {
|
|
2824
|
+
const bundle = await instance.getBundle(entry);
|
|
2825
|
+
if (bundle instanceof Response) {
|
|
2826
|
+
console.error("TransOne rebuild failed; keeping the last working page.");
|
|
2827
|
+
return;
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
instance.liveReload?.broadcast();
|
|
2831
|
+
}
|
|
2832
|
+
async function restart() {
|
|
2833
|
+
watcher.dispose();
|
|
2834
|
+
await instance.server.stop(true);
|
|
2835
|
+
await instance.dispose();
|
|
2836
|
+
instance = await createServerInstance(options, true);
|
|
2837
|
+
watcher = createProjectWatcher(instance, onChange);
|
|
2838
|
+
console.log(`TransOne dev server restarted at http://${instance.server.hostname}:${instance.server.port}`);
|
|
2839
|
+
}
|
|
2840
|
+
return createServerHandle(() => instance, () => watcher.dispose());
|
|
2841
|
+
}
|
|
2842
|
+
function createProjectWatcher(instance, onChange) {
|
|
2843
|
+
const root = realpathSync2(instance.config.root);
|
|
2844
|
+
const watchers = [];
|
|
2845
|
+
try {
|
|
2846
|
+
watchers.push(createFileWatcher({
|
|
2847
|
+
root,
|
|
2848
|
+
isRelevant: (relativePath) => isWatchedFile(instance, relativePath),
|
|
2849
|
+
onChange: (changedPath) => {
|
|
2850
|
+
onChange(changedPath);
|
|
2851
|
+
}
|
|
2852
|
+
}));
|
|
2853
|
+
for (const group of externalComponentWatchRoots(instance.config, root)) {
|
|
2854
|
+
const excluded = (instance.config.components?.exclude ?? []).map((pattern) => new Bun.Glob(pattern.split(sep4).join("/")));
|
|
2855
|
+
watchers.push(createFileWatcher({
|
|
2856
|
+
root: group.root,
|
|
2857
|
+
isRelevant: (relativePath) => {
|
|
2858
|
+
const path = resolve7(group.root, relativePath);
|
|
2859
|
+
if (isWithinOrEqual(root, path) || ![".ts", ".tsx"].includes(extname(path).toLowerCase()) || path.endsWith(".d.ts"))
|
|
2860
|
+
return false;
|
|
2861
|
+
if (path.split(sep4).some((segment) => IGNORED_WATCH_SEGMENTS.has(segment)))
|
|
2862
|
+
return false;
|
|
2863
|
+
const portable = path.split(sep4).join("/");
|
|
2864
|
+
const fromApp = toRelativePath2(root, path);
|
|
2865
|
+
return group.patterns.some((pattern) => pattern.match(portable)) && !excluded.some((pattern) => pattern.match(fromApp) || pattern.match(portable));
|
|
2866
|
+
},
|
|
2867
|
+
onChange: (changedPath) => {
|
|
2868
|
+
onChange(toRelativePath2(root, resolve7(group.root, changedPath)));
|
|
2869
|
+
}
|
|
2870
|
+
}));
|
|
2871
|
+
}
|
|
2872
|
+
} catch (error) {
|
|
2873
|
+
watchers.forEach((watcher) => watcher.dispose());
|
|
2874
|
+
throw error;
|
|
2875
|
+
}
|
|
2876
|
+
return {
|
|
2877
|
+
dispose: () => {
|
|
2878
|
+
watchers.splice(0).reverse().forEach((watcher) => watcher.dispose());
|
|
2879
|
+
}
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2882
|
+
function externalComponentWatchRoots(config, appRoot) {
|
|
2883
|
+
const groups = [];
|
|
2884
|
+
for (const include of config.components?.include ?? []) {
|
|
2885
|
+
const absolute = resolve7(config.root, include);
|
|
2886
|
+
const wildcard = absolute.search(/[*?\[\]{}]/);
|
|
2887
|
+
const prefix = wildcard < 0 ? absolute : absolute.slice(0, wildcard);
|
|
2888
|
+
const directoryPattern = wildcard < 0 && (existsSync6(absolute) && statSync(absolute).isDirectory() || !extname(absolute));
|
|
2889
|
+
let directory = wildcard < 0 ? directoryPattern ? absolute : dirname6(absolute) : prefix.endsWith(sep4) ? prefix : dirname6(prefix);
|
|
2890
|
+
while (!existsSync6(directory)) {
|
|
2891
|
+
const parent2 = dirname6(directory);
|
|
2892
|
+
if (parent2 === directory)
|
|
2893
|
+
break;
|
|
2894
|
+
directory = parent2;
|
|
2895
|
+
}
|
|
2896
|
+
if (!existsSync6(directory) || !statSync(directory).isDirectory())
|
|
2897
|
+
continue;
|
|
2898
|
+
const canonical = realpathSync2(directory);
|
|
2899
|
+
if (isWithinOrEqual(appRoot, canonical))
|
|
2900
|
+
continue;
|
|
2901
|
+
const patternPath = directoryPattern ? resolve7(absolute, "**", "*") : absolute;
|
|
2902
|
+
const pattern = new Bun.Glob(resolve7(canonical, relative5(directory, patternPath)).split(sep4).join("/"));
|
|
2903
|
+
const parent = groups.find((group) => isWithinOrEqual(group.root, canonical));
|
|
2904
|
+
if (parent) {
|
|
2905
|
+
parent.patterns.push(pattern);
|
|
2906
|
+
continue;
|
|
2907
|
+
}
|
|
2908
|
+
const nested = groups.filter((group) => isWithinOrEqual(canonical, group.root));
|
|
2909
|
+
for (const group of nested)
|
|
2910
|
+
groups.splice(groups.indexOf(group), 1);
|
|
2911
|
+
groups.push({
|
|
2912
|
+
root: canonical,
|
|
2913
|
+
patterns: [pattern, ...nested.flatMap((group) => group.patterns)]
|
|
2914
|
+
});
|
|
2915
|
+
}
|
|
2916
|
+
return groups;
|
|
2917
|
+
}
|
|
2918
|
+
async function createServerInstance(options, watch2) {
|
|
2919
|
+
const config = await resolveConfig(options);
|
|
2920
|
+
if (config.target !== "web") {
|
|
2921
|
+
throw new Error("Development server only supports Web targets: web, web-mobile, web-pc");
|
|
2922
|
+
}
|
|
2923
|
+
const { renderProjectHtml } = await import("./project-6tft5e77.js");
|
|
2924
|
+
for (const [route, entry] of Object.entries(config.pages)) {
|
|
2925
|
+
await renderProjectHtml(config, {}, entry, route);
|
|
2926
|
+
}
|
|
2927
|
+
const developmentOutDir = resolve7(config.root, "dist", "dev", "h5");
|
|
2928
|
+
await assertSafeSubdirectory(config.root, developmentOutDir, INVALID_DEVELOPMENT_DIRECTORY_MESSAGE);
|
|
2929
|
+
const proxy = createProxyHandler(config.server.proxy);
|
|
2930
|
+
const sessionId = createGenerationId();
|
|
2931
|
+
const sessionOutDir = resolve7(developmentOutDir, sessionId);
|
|
2932
|
+
const artifacts = new Map;
|
|
2933
|
+
const buildProject = createDevelopmentBuilder(sessionId, sessionOutDir, artifacts, config);
|
|
2934
|
+
const liveReload = watch2 ? createLiveReloadHub() : undefined;
|
|
2935
|
+
const bundles = new Map;
|
|
2936
|
+
const dirtyEntries = new Set;
|
|
2937
|
+
const getBundle = async (entry, acquire = false) => {
|
|
2938
|
+
const cached = bundles.get(entry);
|
|
2939
|
+
if (watch2 && cached !== undefined && !dirtyEntries.has(entry)) {
|
|
2940
|
+
if (acquire)
|
|
2941
|
+
cached.activeRequests += 1;
|
|
2942
|
+
return cached;
|
|
2943
|
+
}
|
|
2944
|
+
const bundle = await buildProject.build(entry);
|
|
2945
|
+
if (bundle instanceof Response) {
|
|
2946
|
+
if (watch2) {
|
|
2947
|
+
dirtyEntries.add(entry);
|
|
2948
|
+
}
|
|
2949
|
+
return bundle;
|
|
2950
|
+
}
|
|
2951
|
+
if (watch2) {
|
|
2952
|
+
bundles.set(entry, bundle);
|
|
2953
|
+
dirtyEntries.delete(entry);
|
|
2954
|
+
}
|
|
2955
|
+
if (acquire)
|
|
2956
|
+
bundle.activeRequests += 1;
|
|
2957
|
+
return bundle;
|
|
2958
|
+
};
|
|
2959
|
+
const server = Bun.serve({
|
|
2960
|
+
hostname: config.server.host,
|
|
2961
|
+
port: config.server.port,
|
|
2962
|
+
fetch: async (request) => {
|
|
2963
|
+
const reloadResponse = liveReload?.handleRequest(request);
|
|
2964
|
+
if (reloadResponse) {
|
|
2965
|
+
return reloadResponse;
|
|
2966
|
+
}
|
|
2967
|
+
return await proxy(request) ?? serveProjectRequest(request, config, config.pages, getBundle, artifacts, liveReload);
|
|
2968
|
+
}
|
|
2969
|
+
});
|
|
2970
|
+
return {
|
|
2971
|
+
server,
|
|
2972
|
+
config,
|
|
2973
|
+
getBundle,
|
|
2974
|
+
markDirty: (entries) => {
|
|
2975
|
+
entries.forEach((entry) => dirtyEntries.add(entry));
|
|
2976
|
+
},
|
|
2977
|
+
dispose: buildProject.dispose,
|
|
2978
|
+
liveReload
|
|
2979
|
+
};
|
|
2980
|
+
}
|
|
2981
|
+
function createServerHandle(getInstance, disposeWatcher) {
|
|
2982
|
+
return new Proxy({}, {
|
|
2983
|
+
get(_target, property2) {
|
|
2984
|
+
if (property2 === "stop") {
|
|
2985
|
+
return async (closeActiveConnections) => {
|
|
2986
|
+
disposeWatcher();
|
|
2987
|
+
const instance = getInstance();
|
|
2988
|
+
await instance.server.stop(closeActiveConnections);
|
|
2989
|
+
await instance.dispose();
|
|
2990
|
+
};
|
|
2991
|
+
}
|
|
2992
|
+
const server = getInstance().server;
|
|
2993
|
+
const value = server[property2];
|
|
2994
|
+
return typeof value === "function" ? value.bind(server) : value;
|
|
2995
|
+
}
|
|
2996
|
+
});
|
|
2997
|
+
}
|
|
2998
|
+
async function serveProjectRequest(request, config, pages, getBundle, artifacts, liveReload) {
|
|
2999
|
+
const pathname = new URL(request.url).pathname;
|
|
3000
|
+
const route = pageRouteForPathname(pages, pathname);
|
|
3001
|
+
if (route !== undefined) {
|
|
3002
|
+
const entry = pages[route];
|
|
3003
|
+
const bundle = await getBundle(entry, true);
|
|
3004
|
+
if (bundle instanceof Response) {
|
|
3005
|
+
return bundle;
|
|
3006
|
+
}
|
|
3007
|
+
try {
|
|
3008
|
+
const { renderProjectHtml } = await import("./project-6tft5e77.js");
|
|
3009
|
+
const rendered = await renderProjectHtml(config, {
|
|
3010
|
+
head: bundle.stylesheets.map(({ pathname: href }) => ({
|
|
3011
|
+
tag: "link",
|
|
3012
|
+
attributes: { rel: "stylesheet", href }
|
|
3013
|
+
})),
|
|
3014
|
+
scripts: [{ type: "module", src: bundle.entry.pathname }]
|
|
3015
|
+
}, entry, route, bundle.components);
|
|
3016
|
+
const html = await emitDevelopmentDocumentStyles(rendered, bundle);
|
|
3017
|
+
publishDevelopmentBundle(bundle, artifacts);
|
|
3018
|
+
return new Response(liveReload ? injectLiveReloadScript(html) : html, {
|
|
3019
|
+
headers: {
|
|
3020
|
+
"content-type": "text/html; charset=utf-8",
|
|
3021
|
+
"cache-control": "no-store"
|
|
3022
|
+
}
|
|
3023
|
+
});
|
|
3024
|
+
} catch (error) {
|
|
3025
|
+
console.error(error);
|
|
3026
|
+
return buildFailureResponse();
|
|
3027
|
+
} finally {
|
|
3028
|
+
releaseDevelopmentBundle(bundle);
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
if (pathname === "/bundle.js") {
|
|
3032
|
+
const bundle = await getBundle(config.entry, true);
|
|
3033
|
+
if (bundle instanceof Response) {
|
|
3034
|
+
return bundle;
|
|
3035
|
+
}
|
|
3036
|
+
try {
|
|
3037
|
+
publishDevelopmentBundle(bundle, artifacts);
|
|
3038
|
+
return new Response(null, {
|
|
3039
|
+
status: 307,
|
|
3040
|
+
headers: {
|
|
3041
|
+
location: bundle.entry.pathname,
|
|
3042
|
+
"cache-control": "no-store"
|
|
3043
|
+
}
|
|
3044
|
+
});
|
|
3045
|
+
} catch (error) {
|
|
3046
|
+
console.error(error);
|
|
3047
|
+
return buildFailureResponse();
|
|
3048
|
+
} finally {
|
|
3049
|
+
releaseDevelopmentBundle(bundle);
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
const artifact = artifacts.get(pathname);
|
|
3053
|
+
if (artifact) {
|
|
3054
|
+
return new Response(Bun.file(artifact.filePath), {
|
|
3055
|
+
headers: {
|
|
3056
|
+
"content-type": artifact.type,
|
|
3057
|
+
"cache-control": "no-store"
|
|
3058
|
+
}
|
|
3059
|
+
});
|
|
3060
|
+
}
|
|
3061
|
+
if (pathname !== "/" && !pathname.startsWith("/api/") && !/\.[a-zA-Z0-9]+$/.test(pathname)) {
|
|
3062
|
+
const bundle = await getBundle(config.entry, true);
|
|
3063
|
+
if (!(bundle instanceof Response)) {
|
|
3064
|
+
try {
|
|
3065
|
+
const { renderProjectHtml } = await import("./project-6tft5e77.js");
|
|
3066
|
+
const rendered = await renderProjectHtml(config, {
|
|
3067
|
+
head: bundle.stylesheets.map(({ pathname: href }) => ({
|
|
3068
|
+
tag: "link",
|
|
3069
|
+
attributes: { rel: "stylesheet", href }
|
|
3070
|
+
})),
|
|
3071
|
+
scripts: [{ type: "module", src: bundle.entry.pathname }]
|
|
3072
|
+
}, config.entry, "/", bundle.components);
|
|
3073
|
+
const html = await emitDevelopmentDocumentStyles(rendered, bundle);
|
|
3074
|
+
publishDevelopmentBundle(bundle, artifacts);
|
|
3075
|
+
return new Response(liveReload ? injectLiveReloadScript(html) : html, {
|
|
3076
|
+
headers: {
|
|
3077
|
+
"content-type": "text/html; charset=utf-8",
|
|
3078
|
+
"cache-control": "no-store"
|
|
3079
|
+
}
|
|
3080
|
+
});
|
|
3081
|
+
} catch (error) {
|
|
3082
|
+
console.error(error);
|
|
3083
|
+
return buildFailureResponse();
|
|
3084
|
+
} finally {
|
|
3085
|
+
releaseDevelopmentBundle(bundle);
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
return new Response("Not found", {
|
|
3090
|
+
status: 404,
|
|
3091
|
+
headers: { "cache-control": "no-store" }
|
|
3092
|
+
});
|
|
3093
|
+
}
|
|
3094
|
+
function pageRouteForPathname(pages, pathname) {
|
|
3095
|
+
if (pages[pathname] !== undefined) {
|
|
3096
|
+
return pathname;
|
|
3097
|
+
}
|
|
3098
|
+
if (pathname === "/index.html") {
|
|
3099
|
+
return "/";
|
|
3100
|
+
}
|
|
3101
|
+
const candidate = pathname.length > 1 && pathname.endsWith("/") ? pathname.replace(/\/+$/, "") : pathname;
|
|
3102
|
+
if (pages[candidate] !== undefined) {
|
|
3103
|
+
return candidate;
|
|
3104
|
+
}
|
|
3105
|
+
if (candidate.endsWith("/index.html")) {
|
|
3106
|
+
const base = candidate.slice(0, candidate.length - "/index.html".length);
|
|
3107
|
+
if (base === "") {
|
|
3108
|
+
return "/";
|
|
3109
|
+
}
|
|
3110
|
+
if (pages[base] !== undefined) {
|
|
3111
|
+
return base;
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
return;
|
|
3115
|
+
}
|
|
3116
|
+
function injectLiveReloadScript(html) {
|
|
3117
|
+
if (html.includes(LIVE_RELOAD_PATH)) {
|
|
3118
|
+
return html;
|
|
3119
|
+
}
|
|
3120
|
+
if (html.includes("</body>")) {
|
|
3121
|
+
return html.replace("</body>", `${LIVE_RELOAD_CLIENT_SCRIPT}</body>`);
|
|
3122
|
+
}
|
|
3123
|
+
return html + LIVE_RELOAD_CLIENT_SCRIPT;
|
|
3124
|
+
}
|
|
3125
|
+
var reloadEncoder = new TextEncoder;
|
|
3126
|
+
function createLiveReloadHub() {
|
|
3127
|
+
const controllers = new Set;
|
|
3128
|
+
function handleRequest(request) {
|
|
3129
|
+
if (new URL(request.url).pathname !== LIVE_RELOAD_PATH) {
|
|
3130
|
+
return;
|
|
3131
|
+
}
|
|
3132
|
+
let streamController;
|
|
3133
|
+
const stream = new ReadableStream({
|
|
3134
|
+
start: (controller) => {
|
|
3135
|
+
streamController = controller;
|
|
3136
|
+
controllers.add(controller);
|
|
3137
|
+
try {
|
|
3138
|
+
controller.enqueue(reloadEncoder.encode(`: connected
|
|
3139
|
+
|
|
3140
|
+
`));
|
|
3141
|
+
} catch {
|
|
3142
|
+
controllers.delete(controller);
|
|
3143
|
+
}
|
|
3144
|
+
},
|
|
3145
|
+
cancel: () => {
|
|
3146
|
+
if (streamController) {
|
|
3147
|
+
controllers.delete(streamController);
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
});
|
|
3151
|
+
return new Response(stream, {
|
|
3152
|
+
headers: {
|
|
3153
|
+
"content-type": "text/event-stream",
|
|
3154
|
+
"cache-control": "no-store"
|
|
3155
|
+
}
|
|
3156
|
+
});
|
|
3157
|
+
}
|
|
3158
|
+
function broadcast() {
|
|
3159
|
+
const message = reloadEncoder.encode(`event: reload
|
|
3160
|
+
data: {}
|
|
3161
|
+
|
|
3162
|
+
`);
|
|
3163
|
+
for (const controller of [...controllers]) {
|
|
3164
|
+
try {
|
|
3165
|
+
controller.enqueue(message);
|
|
3166
|
+
} catch {
|
|
3167
|
+
controllers.delete(controller);
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
return { handleRequest, broadcast };
|
|
3172
|
+
}
|
|
3173
|
+
function isWatchedFile(instance, relativePath) {
|
|
3174
|
+
if (relativePath === "" || relativePath.startsWith("..")) {
|
|
3175
|
+
return false;
|
|
3176
|
+
}
|
|
3177
|
+
const segments = relativePath.split("/");
|
|
3178
|
+
if (segments.some((segment) => IGNORED_WATCH_SEGMENTS.has(segment))) {
|
|
3179
|
+
return false;
|
|
3180
|
+
}
|
|
3181
|
+
const absolutePath = resolve7(instance.config.root, relativePath);
|
|
3182
|
+
if (isWithinOrEqual(instance.config.build.outDir, absolutePath)) {
|
|
3183
|
+
return false;
|
|
3184
|
+
}
|
|
3185
|
+
const base = basename3(absolutePath);
|
|
3186
|
+
if (base.startsWith(".") && absolutePath !== instance.config.configFile) {
|
|
3187
|
+
return false;
|
|
3188
|
+
}
|
|
3189
|
+
return WATCHED_EXTENSIONS.has(extname(base).toLowerCase());
|
|
3190
|
+
}
|
|
3191
|
+
function toRelativePath2(root, absolutePath) {
|
|
3192
|
+
return relative5(root, absolutePath).split(sep4).join("/");
|
|
3193
|
+
}
|
|
3194
|
+
function isWithinOrEqual(parent, candidate) {
|
|
3195
|
+
const pathFromParent = relative5(parent, candidate);
|
|
3196
|
+
return pathFromParent === "" || pathFromParent !== ".." && !pathFromParent.startsWith(`..${sep4}`) && !isAbsolute4(pathFromParent);
|
|
3197
|
+
}
|
|
3198
|
+
async function emitDevelopmentDocumentStyles(rendered, bundle) {
|
|
3199
|
+
const generationUrl = bundle.generationUrl;
|
|
3200
|
+
const result = await emitDocumentStyles(rendered, bundle.outDir, (path) => `${generationUrl}/${basename3(path)}`, bundle.stylesheets.map((stylesheet) => stylesheet.pathname), bundle.componentStylesheets.map((stylesheet) => stylesheet.pathname));
|
|
3201
|
+
for (const path of result.assets) {
|
|
3202
|
+
const pathname = `${generationUrl}/${basename3(path)}`;
|
|
3203
|
+
bundle.artifacts.set(pathname, {
|
|
3204
|
+
filePath: path,
|
|
3205
|
+
pathname,
|
|
3206
|
+
type: "text/css; charset=utf-8"
|
|
3207
|
+
});
|
|
3208
|
+
}
|
|
3209
|
+
return result.html;
|
|
3210
|
+
}
|
|
3211
|
+
function createDevelopmentBuilder(sessionId, sessionOutDir, artifacts, config) {
|
|
3212
|
+
let queue = Promise.resolve();
|
|
3213
|
+
let disposed = false;
|
|
3214
|
+
let disposal;
|
|
3215
|
+
const latestBundles = new Map;
|
|
3216
|
+
const retirementTimers = new Map;
|
|
3217
|
+
function retire(bundle) {
|
|
3218
|
+
bundle.retire = () => {
|
|
3219
|
+
const previousTimer = retirementTimers.get(bundle.outDir);
|
|
3220
|
+
if (previousTimer)
|
|
3221
|
+
clearTimeout(previousTimer);
|
|
3222
|
+
retirementTimers.delete(bundle.outDir);
|
|
3223
|
+
if (disposed || bundle.activeRequests > 0)
|
|
3224
|
+
return;
|
|
3225
|
+
const timer = setTimeout(() => {
|
|
3226
|
+
retirementTimers.delete(bundle.outDir);
|
|
3227
|
+
removeDevelopmentBundle(bundle, artifacts).catch((error) => console.error(error));
|
|
3228
|
+
}, DEVELOPMENT_OUTPUT_RETENTION_MS);
|
|
3229
|
+
timer.unref();
|
|
3230
|
+
retirementTimers.set(bundle.outDir, timer);
|
|
3231
|
+
};
|
|
3232
|
+
bundle.retire();
|
|
3233
|
+
}
|
|
3234
|
+
function build2(entry) {
|
|
3235
|
+
const result = queue.then(async () => {
|
|
3236
|
+
if (disposed)
|
|
3237
|
+
return buildFailureResponse();
|
|
3238
|
+
const bundle = await buildProjectBundle(entry, sessionId, sessionOutDir, config);
|
|
3239
|
+
if (!(bundle instanceof Response)) {
|
|
3240
|
+
const previous = latestBundles.get(entry);
|
|
3241
|
+
latestBundles.set(entry, bundle);
|
|
3242
|
+
if (previous)
|
|
3243
|
+
retire(previous);
|
|
3244
|
+
}
|
|
3245
|
+
return bundle;
|
|
3246
|
+
});
|
|
3247
|
+
queue = result.then(() => {
|
|
3248
|
+
return;
|
|
3249
|
+
}, () => {
|
|
3250
|
+
return;
|
|
3251
|
+
});
|
|
3252
|
+
return result;
|
|
3253
|
+
}
|
|
3254
|
+
function dispose() {
|
|
3255
|
+
if (disposal)
|
|
3256
|
+
return disposal;
|
|
3257
|
+
disposed = true;
|
|
3258
|
+
retirementTimers.forEach((timer) => clearTimeout(timer));
|
|
3259
|
+
retirementTimers.clear();
|
|
3260
|
+
disposal = queue.then(async () => {
|
|
3261
|
+
await rm2(sessionOutDir, { recursive: true, force: true });
|
|
3262
|
+
latestBundles.clear();
|
|
3263
|
+
artifacts.clear();
|
|
3264
|
+
});
|
|
3265
|
+
return disposal;
|
|
3266
|
+
}
|
|
3267
|
+
return { build: build2, dispose };
|
|
3268
|
+
}
|
|
3269
|
+
function releaseDevelopmentBundle(bundle) {
|
|
3270
|
+
bundle.activeRequests -= 1;
|
|
3271
|
+
bundle.retire?.();
|
|
3272
|
+
}
|
|
3273
|
+
async function removeDevelopmentBundle(bundle, artifacts) {
|
|
3274
|
+
await rm2(bundle.outDir, { recursive: true, force: true });
|
|
3275
|
+
for (const pathname of bundle.artifacts.keys()) {
|
|
3276
|
+
artifacts.delete(pathname);
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
async function buildProjectBundle(entry, sessionId, sessionOutDir, config) {
|
|
3280
|
+
const generationId = createGenerationId();
|
|
3281
|
+
const generationOutDir = resolve7(sessionOutDir, generationId);
|
|
3282
|
+
const generationUrl = `${DEVELOPMENT_URL_PREFIX}/${sessionId}/${generationId}`;
|
|
3283
|
+
const buildOptions = {
|
|
3284
|
+
entrypoints: [entry],
|
|
3285
|
+
outdir: generationOutDir,
|
|
3286
|
+
target: "browser",
|
|
3287
|
+
format: "esm",
|
|
3288
|
+
sourcemap: "inline",
|
|
3289
|
+
throw: false,
|
|
3290
|
+
publicPath: `${generationUrl}/`,
|
|
3291
|
+
naming: {
|
|
3292
|
+
entry: "[name]-[hash].[ext]",
|
|
3293
|
+
chunk: "[name]-[hash].[ext]",
|
|
3294
|
+
asset: "assets/[name]-[hash].[ext]"
|
|
3295
|
+
}
|
|
3296
|
+
};
|
|
3297
|
+
let success = false;
|
|
3298
|
+
try {
|
|
3299
|
+
const components = createWebComponentContext(config, entry);
|
|
3300
|
+
const { result, styles } = await buildWebBundle(buildOptions, components);
|
|
3301
|
+
const entryArtifact = result.outputs.find(isEntryJavaScriptOutput);
|
|
3302
|
+
if (!result.success || !entryArtifact) {
|
|
3303
|
+
result.logs.forEach((log) => console.error(log));
|
|
3304
|
+
if (!entryArtifact) {
|
|
3305
|
+
console.error("Failed to build project bundle: no JavaScript output");
|
|
3306
|
+
}
|
|
3307
|
+
return buildFailureResponse();
|
|
3308
|
+
}
|
|
3309
|
+
const pendingArtifacts = new Map;
|
|
3310
|
+
const outputs = new Map;
|
|
3311
|
+
for (const output of result.outputs) {
|
|
3312
|
+
const developmentOutput = createDevelopmentOutput(generationOutDir, generationUrl, output);
|
|
3313
|
+
if (pendingArtifacts.has(developmentOutput.pathname)) {
|
|
3314
|
+
throw new Error(`Development output URL collision: ${developmentOutput.pathname}`);
|
|
3315
|
+
}
|
|
3316
|
+
pendingArtifacts.set(developmentOutput.pathname, developmentOutput);
|
|
3317
|
+
outputs.set(output, developmentOutput);
|
|
3318
|
+
}
|
|
3319
|
+
const entryOutput = outputs.get(entryArtifact);
|
|
3320
|
+
if (!entryOutput) {
|
|
3321
|
+
throw new Error("Development entry output was not indexed");
|
|
3322
|
+
}
|
|
3323
|
+
const componentStylesheets = (await emitComponentStyles(styles, generationOutDir)).map((filePath) => ({
|
|
3324
|
+
filePath,
|
|
3325
|
+
pathname: `${generationUrl}/${basename3(filePath)}`,
|
|
3326
|
+
type: "text/css; charset=utf-8"
|
|
3327
|
+
}));
|
|
3328
|
+
for (const output of componentStylesheets)
|
|
3329
|
+
pendingArtifacts.set(output.pathname, output);
|
|
3330
|
+
success = true;
|
|
3331
|
+
return {
|
|
3332
|
+
outDir: generationOutDir,
|
|
3333
|
+
components,
|
|
3334
|
+
generationUrl,
|
|
3335
|
+
activeRequests: 0,
|
|
3336
|
+
entry: entryOutput,
|
|
3337
|
+
componentStylesheets,
|
|
3338
|
+
stylesheets: result.outputs.filter(isStylesheetOutput).map((output) => outputs.get(output)).filter((output) => output !== undefined),
|
|
3339
|
+
artifacts: pendingArtifacts
|
|
3340
|
+
};
|
|
3341
|
+
} catch (error) {
|
|
3342
|
+
console.error(error);
|
|
3343
|
+
return buildFailureResponse();
|
|
3344
|
+
} finally {
|
|
3345
|
+
if (!success) {
|
|
3346
|
+
await rm2(generationOutDir, { recursive: true, force: true });
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
function publishDevelopmentBundle(bundle, artifacts) {
|
|
3351
|
+
for (const [pathname, output] of bundle.artifacts) {
|
|
3352
|
+
const existing = artifacts.get(pathname);
|
|
3353
|
+
if (existing) {
|
|
3354
|
+
if (existing.filePath === output.filePath) {
|
|
3355
|
+
continue;
|
|
3356
|
+
}
|
|
3357
|
+
throw new Error(`Development output URL collision: ${pathname}`);
|
|
3358
|
+
}
|
|
3359
|
+
artifacts.set(pathname, output);
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
function createDevelopmentOutput(generationOutDir, generationUrl, output) {
|
|
3363
|
+
const filePath = resolve7(output.path);
|
|
3364
|
+
const pathFromGeneration = relative5(generationOutDir, filePath);
|
|
3365
|
+
if (pathFromGeneration === "" || pathFromGeneration === ".." || pathFromGeneration.startsWith(`..${sep4}`) || isAbsolute4(pathFromGeneration)) {
|
|
3366
|
+
throw new Error(`Development output must not escape outside its generation directory: ${output.path}`);
|
|
3367
|
+
}
|
|
3368
|
+
const urlPath = pathFromGeneration.split(sep4).map((segment) => encodeURIComponent(segment)).join("/");
|
|
3369
|
+
return {
|
|
3370
|
+
filePath,
|
|
3371
|
+
pathname: `${generationUrl}/${urlPath}`,
|
|
3372
|
+
type: output.type
|
|
3373
|
+
};
|
|
3374
|
+
}
|
|
3375
|
+
function createGenerationId() {
|
|
3376
|
+
return randomUUID2().replace(/-/g, "");
|
|
3377
|
+
}
|
|
3378
|
+
function buildFailureResponse() {
|
|
3379
|
+
return new Response("Failed to build project bundle", {
|
|
3380
|
+
status: 500,
|
|
3381
|
+
headers: { "cache-control": "no-store" }
|
|
3382
|
+
});
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
export { TARGET_TYPES, isTargetType, MP_TARGET_TYPES, isMpTargetType, defineConfig, resolveConfig, WebTarget, MpTarget, mpWeixinTarget, mpAlipayTarget, mpBytedanceTarget, mpXiaohongshuTarget, TargetRegistry, targetRegistry, build, buildConfigured, reportComponents, reportConfiguredComponents, formatComponentSelectionReport, createProject, startDevServer };
|
|
3386
|
+
|
|
3387
|
+
//# debugId=2077B6EF8001292264756E2164756E21
|
|
3388
|
+
//# sourceMappingURL=index-2g3e5sjt.js.map
|