@kuratchi/js 0.0.14 → 0.0.16
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 +135 -68
- package/dist/cli.js +80 -47
- package/dist/compiler/api-route-pipeline.d.ts +8 -0
- package/dist/compiler/api-route-pipeline.js +23 -0
- package/dist/compiler/asset-pipeline.d.ts +7 -0
- package/dist/compiler/asset-pipeline.js +33 -0
- package/dist/compiler/client-module-pipeline.d.ts +25 -0
- package/dist/compiler/client-module-pipeline.js +257 -0
- package/dist/compiler/compiler-shared.d.ts +55 -0
- package/dist/compiler/compiler-shared.js +4 -0
- package/dist/compiler/component-pipeline.d.ts +15 -0
- package/dist/compiler/component-pipeline.js +163 -0
- package/dist/compiler/config-reading.d.ts +11 -0
- package/dist/compiler/config-reading.js +323 -0
- package/dist/compiler/convention-discovery.d.ts +9 -0
- package/dist/compiler/convention-discovery.js +83 -0
- package/dist/compiler/durable-object-pipeline.d.ts +9 -0
- package/dist/compiler/durable-object-pipeline.js +255 -0
- package/dist/compiler/error-page-pipeline.d.ts +1 -0
- package/dist/compiler/error-page-pipeline.js +16 -0
- package/dist/compiler/import-linking.d.ts +36 -0
- package/dist/compiler/import-linking.js +139 -0
- package/dist/compiler/index.d.ts +3 -3
- package/dist/compiler/index.js +137 -3265
- package/dist/compiler/layout-pipeline.d.ts +31 -0
- package/dist/compiler/layout-pipeline.js +155 -0
- package/dist/compiler/page-route-pipeline.d.ts +16 -0
- package/dist/compiler/page-route-pipeline.js +62 -0
- package/dist/compiler/parser.d.ts +4 -0
- package/dist/compiler/parser.js +433 -51
- package/dist/compiler/root-layout-pipeline.d.ts +10 -0
- package/dist/compiler/root-layout-pipeline.js +517 -0
- package/dist/compiler/route-discovery.d.ts +7 -0
- package/dist/compiler/route-discovery.js +87 -0
- package/dist/compiler/route-pipeline.d.ts +57 -0
- package/dist/compiler/route-pipeline.js +296 -0
- package/dist/compiler/route-state-pipeline.d.ts +25 -0
- package/dist/compiler/route-state-pipeline.js +139 -0
- package/dist/compiler/routes-module-feature-blocks.d.ts +2 -0
- package/dist/compiler/routes-module-feature-blocks.js +330 -0
- package/dist/compiler/routes-module-pipeline.d.ts +2 -0
- package/dist/compiler/routes-module-pipeline.js +6 -0
- package/dist/compiler/routes-module-runtime-shell.d.ts +2 -0
- package/dist/compiler/routes-module-runtime-shell.js +81 -0
- package/dist/compiler/routes-module-types.d.ts +44 -0
- package/dist/compiler/routes-module-types.js +1 -0
- package/dist/compiler/script-transform.d.ts +16 -0
- package/dist/compiler/script-transform.js +218 -0
- package/dist/compiler/server-module-pipeline.d.ts +13 -0
- package/dist/compiler/server-module-pipeline.js +124 -0
- package/dist/compiler/template.d.ts +13 -1
- package/dist/compiler/template.js +323 -60
- package/dist/compiler/worker-output-pipeline.d.ts +13 -0
- package/dist/compiler/worker-output-pipeline.js +37 -0
- package/dist/compiler/wrangler-sync.d.ts +14 -0
- package/dist/compiler/wrangler-sync.js +185 -0
- package/dist/runtime/app.js +15 -3
- package/dist/runtime/generated-worker.d.ts +33 -0
- package/dist/runtime/generated-worker.js +412 -0
- package/dist/runtime/index.d.ts +2 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/router.d.ts +2 -1
- package/dist/runtime/router.js +12 -3
- package/dist/runtime/types.d.ts +8 -2
- package/package.json +5 -1
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { collectReferencedIdentifiers, parseImportStatement } from './import-linking.js';
|
|
5
|
+
import { transpileTypeScript } from './transpile.js';
|
|
6
|
+
function resolveExistingModuleFile(absBase) {
|
|
7
|
+
const candidates = [
|
|
8
|
+
absBase,
|
|
9
|
+
absBase + '.ts',
|
|
10
|
+
absBase + '.js',
|
|
11
|
+
absBase + '.mjs',
|
|
12
|
+
absBase + '.cjs',
|
|
13
|
+
path.join(absBase, 'index.ts'),
|
|
14
|
+
path.join(absBase, 'index.js'),
|
|
15
|
+
path.join(absBase, 'index.mjs'),
|
|
16
|
+
path.join(absBase, 'index.cjs'),
|
|
17
|
+
];
|
|
18
|
+
for (const candidate of candidates) {
|
|
19
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile())
|
|
20
|
+
return candidate;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function buildAsset(name, content) {
|
|
25
|
+
return {
|
|
26
|
+
name,
|
|
27
|
+
content,
|
|
28
|
+
mime: 'text/javascript; charset=utf-8',
|
|
29
|
+
etag: '"' + crypto.createHash('md5').update(content).digest('hex').slice(0, 12) + '"',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function toRelativeSpecifier(fromAssetName, toAssetName) {
|
|
33
|
+
let rel = path.posix.relative(path.posix.dirname(fromAssetName), toAssetName);
|
|
34
|
+
if (!rel.startsWith('.'))
|
|
35
|
+
rel = './' + rel;
|
|
36
|
+
return rel;
|
|
37
|
+
}
|
|
38
|
+
function rewriteImportSpecifiers(source, rewriteSpecifier) {
|
|
39
|
+
let rewritten = source.replace(/(from\s+)(['"])([^'"]+)\2/g, (_match, prefix, quote, spec) => {
|
|
40
|
+
return `${prefix}${quote}${rewriteSpecifier(spec)}${quote}`;
|
|
41
|
+
});
|
|
42
|
+
rewritten = rewritten.replace(/(import\s*\(\s*)(['"])([^'"]+)\2(\s*\))/g, (_match, prefix, quote, spec, suffix) => {
|
|
43
|
+
return `${prefix}${quote}${rewriteSpecifier(spec)}${quote}${suffix}`;
|
|
44
|
+
});
|
|
45
|
+
return rewritten;
|
|
46
|
+
}
|
|
47
|
+
function resolveClientImportTarget(srcDir, importerAbs, spec) {
|
|
48
|
+
if (spec.startsWith('$')) {
|
|
49
|
+
const slashIdx = spec.indexOf('/');
|
|
50
|
+
const folder = slashIdx === -1 ? spec.slice(1) : spec.slice(1, slashIdx);
|
|
51
|
+
const rest = slashIdx === -1 ? '' : spec.slice(slashIdx + 1);
|
|
52
|
+
if (folder !== 'client' && folder !== 'shared') {
|
|
53
|
+
throw new Error(`[kuratchi compiler] Unsupported browser import realm "${spec}". Only $client/* and $shared/* may be loaded in the browser.`);
|
|
54
|
+
}
|
|
55
|
+
const abs = path.join(srcDir, folder, rest);
|
|
56
|
+
const resolved = resolveExistingModuleFile(abs);
|
|
57
|
+
if (!resolved) {
|
|
58
|
+
throw new Error(`[kuratchi compiler] Browser import not found: ${spec}`);
|
|
59
|
+
}
|
|
60
|
+
return resolved;
|
|
61
|
+
}
|
|
62
|
+
if (spec.startsWith('.')) {
|
|
63
|
+
const abs = path.resolve(path.dirname(importerAbs), spec);
|
|
64
|
+
const resolved = resolveExistingModuleFile(abs);
|
|
65
|
+
if (!resolved) {
|
|
66
|
+
throw new Error(`[kuratchi compiler] Browser import not found: ${spec}`);
|
|
67
|
+
}
|
|
68
|
+
return resolved;
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`[kuratchi compiler] Browser modules currently only support project-local imports ($client, $shared, or relative). Unsupported import: ${spec}`);
|
|
71
|
+
}
|
|
72
|
+
class CompilerBackedClientRouteRegistry {
|
|
73
|
+
compiler;
|
|
74
|
+
bindingMap = new Map();
|
|
75
|
+
clientOnlyBindings = new Set();
|
|
76
|
+
handlerByKey = new Map();
|
|
77
|
+
routeId;
|
|
78
|
+
constructor(compiler, scopeId, importEntries) {
|
|
79
|
+
this.compiler = compiler;
|
|
80
|
+
this.routeId = scopeId;
|
|
81
|
+
for (const entry of importEntries) {
|
|
82
|
+
const parsed = parseImportStatement(entry.line);
|
|
83
|
+
if (!parsed.moduleSpecifier)
|
|
84
|
+
continue;
|
|
85
|
+
const isClient = parsed.moduleSpecifier.startsWith('$client/');
|
|
86
|
+
const isShared = parsed.moduleSpecifier.startsWith('$shared/');
|
|
87
|
+
if (!isClient && !isShared)
|
|
88
|
+
continue;
|
|
89
|
+
for (const binding of parsed.bindings) {
|
|
90
|
+
this.bindingMap.set(binding.local, {
|
|
91
|
+
importLine: entry.line,
|
|
92
|
+
importerDir: entry.importerDir,
|
|
93
|
+
localName: binding.local,
|
|
94
|
+
moduleSpecifier: parsed.moduleSpecifier,
|
|
95
|
+
});
|
|
96
|
+
if (isClient)
|
|
97
|
+
this.clientOnlyBindings.add(binding.local);
|
|
98
|
+
}
|
|
99
|
+
if (parsed.namespaceImport) {
|
|
100
|
+
this.bindingMap.set(parsed.namespaceImport, {
|
|
101
|
+
importLine: entry.line,
|
|
102
|
+
importerDir: entry.importerDir,
|
|
103
|
+
localName: parsed.namespaceImport,
|
|
104
|
+
moduleSpecifier: parsed.moduleSpecifier,
|
|
105
|
+
});
|
|
106
|
+
if (isClient)
|
|
107
|
+
this.clientOnlyBindings.add(parsed.namespaceImport);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
hasBindings() {
|
|
112
|
+
return this.bindingMap.size > 0;
|
|
113
|
+
}
|
|
114
|
+
hasBindingReference(expression) {
|
|
115
|
+
const refs = collectReferencedIdentifiers(expression);
|
|
116
|
+
for (const ref of refs) {
|
|
117
|
+
if (this.bindingMap.has(ref))
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
registerEventHandler(_eventName, expression) {
|
|
123
|
+
const parsed = this.parseClientExpression(expression);
|
|
124
|
+
if (!parsed)
|
|
125
|
+
return null;
|
|
126
|
+
const binding = this.bindingMap.get(parsed.rootBinding);
|
|
127
|
+
if (!binding)
|
|
128
|
+
return null;
|
|
129
|
+
if (parsed.argsExpr) {
|
|
130
|
+
const argRefs = collectReferencedIdentifiers(parsed.argsExpr);
|
|
131
|
+
const leakedClientRefs = Array.from(argRefs).filter((ref) => this.clientOnlyBindings.has(ref));
|
|
132
|
+
if (leakedClientRefs.length > 0) {
|
|
133
|
+
throw new Error(`[kuratchi compiler] Client event arguments cannot depend on $client bindings: ${leakedClientRefs.join(', ')}.\n` +
|
|
134
|
+
`Only server/shared values can be serialized into event handler arguments.`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const key = `${parsed.calleeExpr}::${parsed.argsExpr || ''}`;
|
|
138
|
+
let existing = this.handlerByKey.get(key);
|
|
139
|
+
if (!existing) {
|
|
140
|
+
existing = {
|
|
141
|
+
id: `h${this.handlerByKey.size}`,
|
|
142
|
+
calleeExpr: parsed.calleeExpr,
|
|
143
|
+
rootBinding: parsed.rootBinding,
|
|
144
|
+
};
|
|
145
|
+
this.handlerByKey.set(key, existing);
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
routeId: this.routeId,
|
|
149
|
+
handlerId: existing.id,
|
|
150
|
+
argsExpr: parsed.argsExpr,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
buildEntryAsset() {
|
|
154
|
+
if (this.handlerByKey.size === 0)
|
|
155
|
+
return null;
|
|
156
|
+
const usedImportLines = new Map();
|
|
157
|
+
for (const record of this.handlerByKey.values()) {
|
|
158
|
+
const binding = this.bindingMap.get(record.rootBinding);
|
|
159
|
+
if (!binding)
|
|
160
|
+
continue;
|
|
161
|
+
if (!usedImportLines.has(binding.importLine)) {
|
|
162
|
+
usedImportLines.set(binding.importLine, {
|
|
163
|
+
line: binding.importLine,
|
|
164
|
+
importerDir: binding.importerDir,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const assetName = `__kuratchi/client/routes/${this.routeId}.js`;
|
|
169
|
+
const importLines = [];
|
|
170
|
+
for (const entry of usedImportLines.values()) {
|
|
171
|
+
const parsed = parseImportStatement(entry.line);
|
|
172
|
+
if (!parsed.moduleSpecifier)
|
|
173
|
+
continue;
|
|
174
|
+
const targetAbs = resolveClientImportTarget(this.compiler.srcDir, path.join(entry.importerDir, '__route__.ts'), parsed.moduleSpecifier);
|
|
175
|
+
const targetAssetName = this.compiler.transformClientModule(targetAbs);
|
|
176
|
+
const relSpecifier = toRelativeSpecifier(assetName, targetAssetName);
|
|
177
|
+
importLines.push(entry.line.replace(parsed.moduleSpecifier, relSpecifier));
|
|
178
|
+
}
|
|
179
|
+
const registrationEntries = Array.from(this.handlerByKey.values()).map((record) => {
|
|
180
|
+
return `${JSON.stringify(record.id)}: (args, event, element) => ${record.calleeExpr}(...args, event, element)`;
|
|
181
|
+
});
|
|
182
|
+
const source = transpileTypeScript([
|
|
183
|
+
...importLines,
|
|
184
|
+
`window.__kuratchiClient?.register(${JSON.stringify(this.routeId)}, {`,
|
|
185
|
+
registrationEntries.map((entry) => ` ${entry},`).join('\n'),
|
|
186
|
+
`});`,
|
|
187
|
+
].join('\n'), `client-route:${this.routeId}.ts`);
|
|
188
|
+
const asset = buildAsset(assetName, source);
|
|
189
|
+
this.compiler.registerAsset(asset);
|
|
190
|
+
return { assetName, asset };
|
|
191
|
+
}
|
|
192
|
+
parseClientExpression(expression) {
|
|
193
|
+
const trimmed = expression.trim();
|
|
194
|
+
if (!trimmed)
|
|
195
|
+
return null;
|
|
196
|
+
const callMatch = trimmed.match(/^([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\(([\s\S]*)\)$/);
|
|
197
|
+
if (callMatch) {
|
|
198
|
+
const calleeExpr = callMatch[1];
|
|
199
|
+
const rootBinding = calleeExpr.split('.')[0];
|
|
200
|
+
const argsExpr = (callMatch[2] || '').trim();
|
|
201
|
+
return { calleeExpr, rootBinding, argsExpr: argsExpr || null };
|
|
202
|
+
}
|
|
203
|
+
const refMatch = trimmed.match(/^([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)$/);
|
|
204
|
+
if (!refMatch)
|
|
205
|
+
return null;
|
|
206
|
+
const calleeExpr = refMatch[1];
|
|
207
|
+
const rootBinding = calleeExpr.split('.')[0];
|
|
208
|
+
return { calleeExpr, rootBinding, argsExpr: null };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
class CompilerBackedClientModuleCompiler {
|
|
212
|
+
projectDir;
|
|
213
|
+
srcDir;
|
|
214
|
+
compiledAssets = new Map();
|
|
215
|
+
transformedModules = new Map();
|
|
216
|
+
constructor(projectDir, srcDir) {
|
|
217
|
+
this.projectDir = projectDir;
|
|
218
|
+
this.srcDir = srcDir;
|
|
219
|
+
}
|
|
220
|
+
createRegistry(scopeId, importEntries) {
|
|
221
|
+
return new CompilerBackedClientRouteRegistry(this, scopeId, importEntries);
|
|
222
|
+
}
|
|
223
|
+
createRouteRegistry(routeIndex, importEntries) {
|
|
224
|
+
return this.createRegistry(`route_${routeIndex}`, importEntries);
|
|
225
|
+
}
|
|
226
|
+
getCompiledAssets() {
|
|
227
|
+
return Array.from(this.compiledAssets.values());
|
|
228
|
+
}
|
|
229
|
+
registerAsset(asset) {
|
|
230
|
+
this.compiledAssets.set(asset.name, asset);
|
|
231
|
+
}
|
|
232
|
+
transformClientModule(entryAbsPath) {
|
|
233
|
+
const resolved = resolveExistingModuleFile(entryAbsPath) ?? entryAbsPath;
|
|
234
|
+
const normalized = resolved.replace(/\\/g, '/');
|
|
235
|
+
const cached = this.transformedModules.get(normalized);
|
|
236
|
+
if (cached)
|
|
237
|
+
return cached;
|
|
238
|
+
const relFromSrc = path.relative(this.srcDir, resolved).replace(/\\/g, '/');
|
|
239
|
+
const assetName = `__kuratchi/client/modules/${relFromSrc.replace(/\.(ts|js|mjs|cjs)$/i, '.js')}`;
|
|
240
|
+
this.transformedModules.set(normalized, assetName);
|
|
241
|
+
if (!fs.existsSync(resolved)) {
|
|
242
|
+
throw new Error(`[kuratchi compiler] Browser module not found: ${resolved}`);
|
|
243
|
+
}
|
|
244
|
+
const source = fs.readFileSync(resolved, 'utf-8');
|
|
245
|
+
let rewritten = transpileTypeScript(source, `client-module:${relFromSrc}`);
|
|
246
|
+
rewritten = rewriteImportSpecifiers(rewritten, (spec) => {
|
|
247
|
+
const targetAbs = resolveClientImportTarget(this.srcDir, resolved, spec);
|
|
248
|
+
const targetAssetName = this.transformClientModule(targetAbs);
|
|
249
|
+
return toRelativeSpecifier(assetName, targetAssetName);
|
|
250
|
+
});
|
|
251
|
+
this.registerAsset(buildAsset(assetName, rewritten));
|
|
252
|
+
return assetName;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
export function createClientModuleCompiler(opts) {
|
|
256
|
+
return new CompilerBackedClientModuleCompiler(opts.projectDir, opts.srcDir);
|
|
257
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface OrmDatabaseEntry {
|
|
2
|
+
binding: string;
|
|
3
|
+
schemaImportPath: string;
|
|
4
|
+
schemaExportName: string;
|
|
5
|
+
skipMigrations: boolean;
|
|
6
|
+
type: 'd1' | 'do';
|
|
7
|
+
}
|
|
8
|
+
export interface AuthConfigEntry {
|
|
9
|
+
cookieName: string;
|
|
10
|
+
secretEnvKey: string;
|
|
11
|
+
sessionEnabled: boolean;
|
|
12
|
+
hasCredentials: boolean;
|
|
13
|
+
hasActivity: boolean;
|
|
14
|
+
hasRoles: boolean;
|
|
15
|
+
hasOAuth: boolean;
|
|
16
|
+
hasGuards: boolean;
|
|
17
|
+
hasRateLimit: boolean;
|
|
18
|
+
hasTurnstile: boolean;
|
|
19
|
+
hasOrganization: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface DoConfigEntry {
|
|
22
|
+
binding: string;
|
|
23
|
+
className: string;
|
|
24
|
+
stubId?: string;
|
|
25
|
+
files?: string[];
|
|
26
|
+
}
|
|
27
|
+
export interface WorkerClassConfigEntry {
|
|
28
|
+
binding: string;
|
|
29
|
+
className: string;
|
|
30
|
+
file: string;
|
|
31
|
+
exportKind: 'named' | 'default';
|
|
32
|
+
}
|
|
33
|
+
export interface ConventionClassEntry {
|
|
34
|
+
className: string;
|
|
35
|
+
file: string;
|
|
36
|
+
exportKind: 'named' | 'default';
|
|
37
|
+
}
|
|
38
|
+
export interface DoClassMethodEntry {
|
|
39
|
+
name: string;
|
|
40
|
+
visibility: 'public' | 'private' | 'protected';
|
|
41
|
+
isStatic: boolean;
|
|
42
|
+
isAsync: boolean;
|
|
43
|
+
hasWorkerContextCalls: boolean;
|
|
44
|
+
callsThisMethods: string[];
|
|
45
|
+
}
|
|
46
|
+
export interface DoHandlerEntry {
|
|
47
|
+
fileName: string;
|
|
48
|
+
absPath: string;
|
|
49
|
+
binding: string;
|
|
50
|
+
mode: 'class' | 'function';
|
|
51
|
+
className?: string;
|
|
52
|
+
classMethods: DoClassMethodEntry[];
|
|
53
|
+
exportedFunctions: string[];
|
|
54
|
+
}
|
|
55
|
+
export declare function toSafeIdentifier(input: string): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ComponentCompiler {
|
|
2
|
+
ensureCompiled(fileName: string): string | null;
|
|
3
|
+
collectComponentMap(componentImports: Record<string, string>): Map<string, string>;
|
|
4
|
+
getActionPropNames(fileName: string): Set<string>;
|
|
5
|
+
collectStyles(componentNames: Map<string, string>): string[];
|
|
6
|
+
resolveActionProps(template: string, componentNames: Map<string, string>, shouldInclude?: (fnName: string) => boolean): Set<string>;
|
|
7
|
+
getCompiledComponents(): string[];
|
|
8
|
+
}
|
|
9
|
+
interface CreateComponentCompilerOptions {
|
|
10
|
+
projectDir: string;
|
|
11
|
+
srcDir: string;
|
|
12
|
+
isDev: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function createComponentCompiler(options: CreateComponentCompilerOptions): ComponentCompiler;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { parseFile, stripTopLevelImports } from './parser.js';
|
|
5
|
+
import { compileTemplate } from './template.js';
|
|
6
|
+
import { transpileTypeScript } from './transpile.js';
|
|
7
|
+
import { buildDevAliasDeclarations } from './script-transform.js';
|
|
8
|
+
function resolvePackageComponent(projectDir, pkgName, componentFile) {
|
|
9
|
+
const nmPath = path.join(projectDir, 'node_modules', pkgName, 'src', 'lib', componentFile + '.html');
|
|
10
|
+
if (fs.existsSync(nmPath))
|
|
11
|
+
return nmPath;
|
|
12
|
+
const pkgDirName = pkgName.replace(/^@/, '').replace(/\//g, '-');
|
|
13
|
+
const workspaceRoot = path.resolve(projectDir, '../..');
|
|
14
|
+
const wsPath = path.join(workspaceRoot, 'packages', pkgDirName, 'src', 'lib', componentFile + '.html');
|
|
15
|
+
if (fs.existsSync(wsPath))
|
|
16
|
+
return wsPath;
|
|
17
|
+
const rootNmPath = path.join(workspaceRoot, 'node_modules', pkgName, 'src', 'lib', componentFile + '.html');
|
|
18
|
+
if (fs.existsSync(rootNmPath))
|
|
19
|
+
return rootNmPath;
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
function escapeTemplateLiteral(source) {
|
|
23
|
+
return source.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
24
|
+
}
|
|
25
|
+
function scopeComponentCss(source, scopeHash) {
|
|
26
|
+
return source.replace(/([^{}]+)\{/g, (_match, selectors) => {
|
|
27
|
+
const scoped = selectors
|
|
28
|
+
.split(',')
|
|
29
|
+
.map((selector) => `.${scopeHash} ${selector.trim()}`)
|
|
30
|
+
.join(', ');
|
|
31
|
+
return scoped + ' {';
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export function createComponentCompiler(options) {
|
|
35
|
+
const { projectDir, srcDir, isDev } = options;
|
|
36
|
+
const libDir = path.join(srcDir, 'lib');
|
|
37
|
+
const compiledComponentCache = new Map();
|
|
38
|
+
const componentStyleCache = new Map();
|
|
39
|
+
const componentActionCache = new Map();
|
|
40
|
+
function ensureCompiled(fileName) {
|
|
41
|
+
if (compiledComponentCache.has(fileName))
|
|
42
|
+
return compiledComponentCache.get(fileName);
|
|
43
|
+
let filePath;
|
|
44
|
+
let funcName;
|
|
45
|
+
const pkgMatch = fileName.match(/^(@[^:]+):(.+)$/);
|
|
46
|
+
if (pkgMatch) {
|
|
47
|
+
const pkgName = pkgMatch[1];
|
|
48
|
+
const componentFile = pkgMatch[2];
|
|
49
|
+
funcName = '__c_' + componentFile.replace(/[\/\-]/g, '_');
|
|
50
|
+
filePath = resolvePackageComponent(projectDir, pkgName, componentFile);
|
|
51
|
+
if (!filePath || !fs.existsSync(filePath))
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
funcName = '__c_' + fileName.replace(/[\/\-]/g, '_');
|
|
56
|
+
filePath = path.join(libDir, fileName + '.html');
|
|
57
|
+
if (!fs.existsSync(filePath))
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const scopeHash = 'dz-' + crypto.createHash('md5').update(fileName).digest('hex').slice(0, 6);
|
|
61
|
+
const rawSource = fs.readFileSync(filePath, 'utf-8');
|
|
62
|
+
const parsed = parseFile(rawSource, { kind: 'component', filePath });
|
|
63
|
+
const propsCode = parsed.script ? stripTopLevelImports(parsed.script) : '';
|
|
64
|
+
const devDecls = buildDevAliasDeclarations(parsed.devAliases, isDev);
|
|
65
|
+
const effectivePropsCode = [devDecls, propsCode].filter(Boolean).join('\n');
|
|
66
|
+
const transpiledPropsCode = propsCode
|
|
67
|
+
? transpileTypeScript(effectivePropsCode, `component-script:${fileName}.ts`)
|
|
68
|
+
: devDecls
|
|
69
|
+
? transpileTypeScript(devDecls, `component-script:${fileName}.ts`)
|
|
70
|
+
: '';
|
|
71
|
+
let source = parsed.template;
|
|
72
|
+
let styleBlock = '';
|
|
73
|
+
const styleMatch = source.match(/<style[\s>][\s\S]*?<\/style>/i);
|
|
74
|
+
if (styleMatch) {
|
|
75
|
+
styleBlock = styleMatch[0];
|
|
76
|
+
source = source.replace(styleMatch[0], '').trim();
|
|
77
|
+
}
|
|
78
|
+
let scopedStyle = '';
|
|
79
|
+
if (styleBlock) {
|
|
80
|
+
const cssContent = styleBlock.replace(/<style[^>]*>/i, '').replace(/<\/style>/i, '').trim();
|
|
81
|
+
scopedStyle = `<style>${scopeComponentCss(cssContent, scopeHash)}</style>`;
|
|
82
|
+
}
|
|
83
|
+
componentStyleCache.set(fileName, scopedStyle ? escapeTemplateLiteral(scopedStyle) : '');
|
|
84
|
+
source = source.replace(/<slot\s*><\/slot>/g, '{@raw props.children || ""}');
|
|
85
|
+
source = source.replace(/<slot\s*\/>/g, '{@raw props.children || ""}');
|
|
86
|
+
const subComponentNames = collectComponentMap(parsed.componentImports);
|
|
87
|
+
for (const subFileName of subComponentNames.values()) {
|
|
88
|
+
const subStyle = componentStyleCache.get(subFileName);
|
|
89
|
+
if (subStyle) {
|
|
90
|
+
const existing = componentStyleCache.get(fileName) || '';
|
|
91
|
+
if (!existing.includes(subStyle)) {
|
|
92
|
+
componentStyleCache.set(fileName, existing + subStyle);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const actionPropNames = new Set();
|
|
97
|
+
for (const match of source.matchAll(/\baction=\{([A-Za-z_$][\w$]*)\}/g)) {
|
|
98
|
+
actionPropNames.add(match[1]);
|
|
99
|
+
}
|
|
100
|
+
componentActionCache.set(fileName, actionPropNames);
|
|
101
|
+
const body = compileTemplate(source, subComponentNames, undefined, undefined);
|
|
102
|
+
const scopeOpen = `__html += '<div class="${scopeHash}">';`;
|
|
103
|
+
const scopeClose = `__html += '</div>';`;
|
|
104
|
+
const bodyLines = body.split('\n');
|
|
105
|
+
const scopedBody = [bodyLines[0], scopeOpen, ...bodyLines.slice(1), scopeClose].join('\n');
|
|
106
|
+
const fnBody = transpiledPropsCode ? `${transpiledPropsCode}\n ${scopedBody}` : scopedBody;
|
|
107
|
+
const compiled = `function ${funcName}(props, __esc) {\n ${fnBody}\n return __html;\n}`;
|
|
108
|
+
compiledComponentCache.set(fileName, compiled);
|
|
109
|
+
return compiled;
|
|
110
|
+
}
|
|
111
|
+
function collectComponentMap(componentImports) {
|
|
112
|
+
const componentNames = new Map();
|
|
113
|
+
for (const [pascalName, fileName] of Object.entries(componentImports)) {
|
|
114
|
+
ensureCompiled(fileName);
|
|
115
|
+
componentNames.set(pascalName, fileName);
|
|
116
|
+
}
|
|
117
|
+
return componentNames;
|
|
118
|
+
}
|
|
119
|
+
function getActionPropNames(fileName) {
|
|
120
|
+
return componentActionCache.get(fileName) ?? new Set();
|
|
121
|
+
}
|
|
122
|
+
function collectStyles(componentNames) {
|
|
123
|
+
const styles = [];
|
|
124
|
+
for (const fileName of componentNames.values()) {
|
|
125
|
+
const css = componentStyleCache.get(fileName);
|
|
126
|
+
if (css)
|
|
127
|
+
styles.push(css);
|
|
128
|
+
}
|
|
129
|
+
return styles;
|
|
130
|
+
}
|
|
131
|
+
function resolveActionProps(template, componentNames, shouldInclude) {
|
|
132
|
+
const names = new Set();
|
|
133
|
+
for (const [pascalName, compFileName] of componentNames.entries()) {
|
|
134
|
+
const actionPropNames = getActionPropNames(compFileName);
|
|
135
|
+
const compTagRegex = new RegExp(`<${pascalName}\\b([\\s\\S]*?)(?:/?)>`, 'g');
|
|
136
|
+
for (const tagMatch of template.matchAll(compTagRegex)) {
|
|
137
|
+
const attrs = tagMatch[1];
|
|
138
|
+
for (const propName of actionPropNames) {
|
|
139
|
+
const propRegex = new RegExp(`\\b${propName}=\\{([A-Za-z_$][\\w$]*)\\}`);
|
|
140
|
+
const propMatch = attrs.match(propRegex);
|
|
141
|
+
if (!propMatch)
|
|
142
|
+
continue;
|
|
143
|
+
const fnName = propMatch[1];
|
|
144
|
+
if (!shouldInclude || shouldInclude(fnName)) {
|
|
145
|
+
names.add(fnName);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return names;
|
|
151
|
+
}
|
|
152
|
+
function getCompiledComponents() {
|
|
153
|
+
return Array.from(compiledComponentCache.values());
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
ensureCompiled,
|
|
157
|
+
collectComponentMap,
|
|
158
|
+
getActionPropNames,
|
|
159
|
+
collectStyles,
|
|
160
|
+
resolveActionProps,
|
|
161
|
+
getCompiledComponents,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type AuthConfigEntry, type DoConfigEntry, type OrmDatabaseEntry, type WorkerClassConfigEntry } from './compiler-shared.js';
|
|
2
|
+
export declare function readUiTheme(projectDir: string): string | null;
|
|
3
|
+
export declare function readUiConfigValues(projectDir: string): {
|
|
4
|
+
theme: string;
|
|
5
|
+
radius: string;
|
|
6
|
+
} | null;
|
|
7
|
+
export declare function readOrmConfig(projectDir: string): OrmDatabaseEntry[];
|
|
8
|
+
export declare function readAuthConfig(projectDir: string): AuthConfigEntry | null;
|
|
9
|
+
export declare function readDoConfig(projectDir: string): DoConfigEntry[];
|
|
10
|
+
export declare function readWorkerClassConfig(projectDir: string, key: 'containers' | 'workflows'): WorkerClassConfigEntry[];
|
|
11
|
+
export declare function readAssetsPrefix(projectDir: string): string;
|