@microsoft/webui 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +92 -19
  2. package/bin/webui +0 -0
  3. package/dist/index.d.ts +37 -36
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +89 -80
  6. package/dist/index.js.map +1 -1
  7. package/dist/projection/adapters/esbuild.d.ts +14 -0
  8. package/dist/projection/adapters/esbuild.d.ts.map +1 -0
  9. package/dist/projection/adapters/esbuild.js +426 -0
  10. package/dist/projection/adapters/esbuild.js.map +1 -0
  11. package/dist/projection/compiler.d.ts +12 -0
  12. package/dist/projection/compiler.d.ts.map +1 -0
  13. package/dist/projection/compiler.js +1166 -0
  14. package/dist/projection/compiler.js.map +1 -0
  15. package/dist/projection/diagnostics.d.ts +153 -0
  16. package/dist/projection/diagnostics.d.ts.map +1 -0
  17. package/dist/projection/diagnostics.js +210 -0
  18. package/dist/projection/diagnostics.js.map +1 -0
  19. package/dist/projection/fixtures/conformance.d.ts +40 -0
  20. package/dist/projection/fixtures/conformance.d.ts.map +1 -0
  21. package/dist/projection/fixtures/conformance.js +602 -0
  22. package/dist/projection/fixtures/conformance.js.map +1 -0
  23. package/dist/projection/graph.d.ts +144 -0
  24. package/dist/projection/graph.d.ts.map +1 -0
  25. package/dist/projection/graph.js +4 -0
  26. package/dist/projection/graph.js.map +1 -0
  27. package/dist/projection/index.d.ts +37 -0
  28. package/dist/projection/index.d.ts.map +1 -0
  29. package/dist/projection/index.js +17 -0
  30. package/dist/projection/index.js.map +1 -0
  31. package/dist/projection/loader.d.ts +10 -0
  32. package/dist/projection/loader.d.ts.map +1 -0
  33. package/dist/projection/loader.js +45 -0
  34. package/dist/projection/loader.js.map +1 -0
  35. package/dist/projection/manifest.d.ts +97 -0
  36. package/dist/projection/manifest.d.ts.map +1 -0
  37. package/dist/projection/manifest.js +340 -0
  38. package/dist/projection/manifest.js.map +1 -0
  39. package/package.json +33 -11
@@ -0,0 +1,1166 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ /**
4
+ * Bundler-neutral TypeScript AST semantic compiler for WebUI state
5
+ * projection.
6
+ *
7
+ * `compileProjection()` consumes only the adapter-resolved `AdapterContext`
8
+ * (see `graph.ts`) — it never performs filesystem or package resolution.
9
+ * It parses supported source kinds with the TypeScript compiler API, builds a
10
+ * deterministic per-file symbol graph, resolves `observable`/`attr`/
11
+ * `WebUIElement` identities through imports/aliases/namespaces/re-exports,
12
+ * associates component tags through the two supported `define()` forms,
13
+ * proves exact reactive key sets by walking inheritance iteratively, and
14
+ * emits a deterministic `ProjectionManifest`.
15
+ *
16
+ * See DESIGN.md §"Bundler-Neutral State Projection Compiler" for the
17
+ * authoritative specification this module implements.
18
+ */
19
+ import * as path from "node:path";
20
+ import { createRequire } from "node:module";
21
+ import * as ts from "typescript";
22
+ import { MANIFEST_SCHEMA, VIRTUAL_HASH, compareUtf8, computeBuildId, hashContent, } from "./manifest.js";
23
+ import { ProjectionError, createDiagnostic } from "./diagnostics.js";
24
+ /** The well-known framework package specifier recognized by literal text. */
25
+ const FRAMEWORK_SPECIFIER = "@microsoft/webui-framework";
26
+ /**
27
+ * Sentinel "module id" representing the framework package. Never a key in
28
+ * `ctx.graph.modules`; recognized structurally wherever a specifier equals
29
+ * `FRAMEWORK_SPECIFIER`, independent of the adapter-resolved graph.
30
+ */
31
+ const FRAMEWORK_SENTINEL = "\0framework:@microsoft/webui-framework";
32
+ const SUPPORTED_EXTENSIONS = new Set([
33
+ ".ts",
34
+ ".tsx",
35
+ ".mts",
36
+ ".cts",
37
+ ".js",
38
+ ".jsx",
39
+ ".mjs",
40
+ ".cjs",
41
+ ]);
42
+ // ---------------------------------------------------------------------------
43
+ // Public entry point
44
+ // ---------------------------------------------------------------------------
45
+ /**
46
+ * Compiles the projection manifest for a single bundler invocation.
47
+ *
48
+ * Pure function of `ctx`: never touches the filesystem, never executes user
49
+ * modules. Throws `ProjectionError` carrying every hard diagnostic found
50
+ * across the whole graph when any candidate's exact keys cannot be proven or
51
+ * any `define()` association is invalid.
52
+ */
53
+ export function compileProjection(ctx) {
54
+ const profile = process.env["WEBUI_PROJECTION_PROFILE"] === "1";
55
+ const started = profile ? performance.now() : 0;
56
+ const diagnostics = [];
57
+ validateTypeScriptVersion(diagnostics);
58
+ validateAdapterContext(ctx, diagnostics);
59
+ if (diagnostics.length > 0)
60
+ throw new ProjectionError(diagnostics);
61
+ const analyses = buildAnalyses(ctx, diagnostics);
62
+ const parsed = profile ? performance.now() : 0;
63
+ const candidates = compileDefinedComponents(ctx, analyses, diagnostics);
64
+ const analyzed = profile ? performance.now() : 0;
65
+ if (diagnostics.length > 0) {
66
+ throw new ProjectionError(diagnostics);
67
+ }
68
+ const manifest = buildManifest(ctx, candidates);
69
+ if (profile) {
70
+ const finished = performance.now();
71
+ console.error(`[webui-projection-compiler] parse=${(parsed - started).toFixed(1)}ms semantics=${(analyzed - parsed).toFixed(1)}ms manifest=${(finished - analyzed).toFixed(1)}ms graphModules=${ctx.graph.modules.size} parsedModules=${analyses.size} components=${Object.keys(manifest.components).length}`);
72
+ }
73
+ return manifest;
74
+ }
75
+ function validateTypeScriptVersion(diagnostics) {
76
+ const parts = parseVersion(ts.version);
77
+ const supported = parts !== undefined &&
78
+ parts.major === 6 &&
79
+ (parts.minor > 0 ||
80
+ (parts.minor === 0 && parts.patch >= 3));
81
+ if (!supported) {
82
+ diagnostics.push(createDiagnostic("PROJ-P001", {
83
+ help: `Install a supported TypeScript peer (^6.0.3); found ${ts.version}.`,
84
+ }));
85
+ }
86
+ }
87
+ function parseVersion(value) {
88
+ const parts = value.split(".");
89
+ if (parts.length < 3)
90
+ return undefined;
91
+ const major = Number(parts[0]);
92
+ const minor = Number(parts[1]);
93
+ let patchEnd = 0;
94
+ const patchText = parts[2];
95
+ while (patchEnd < patchText.length &&
96
+ patchText.charCodeAt(patchEnd) >= 48 &&
97
+ patchText.charCodeAt(patchEnd) <= 57) {
98
+ patchEnd++;
99
+ }
100
+ if (patchEnd === 0)
101
+ return undefined;
102
+ const patch = Number(patchText.slice(0, patchEnd));
103
+ return Number.isInteger(major) &&
104
+ Number.isInteger(minor) &&
105
+ Number.isInteger(patch)
106
+ ? { major, minor, patch }
107
+ : undefined;
108
+ }
109
+ function validateAdapterContext(ctx, diagnostics) {
110
+ if (!path.isAbsolute(ctx.rootDir) || !path.isAbsolute(ctx.manifestPath)) {
111
+ diagnostics.push(createDiagnostic("PROJ-C013", {
112
+ help: "Adapters must provide absolute rootDir and manifestPath values.",
113
+ }));
114
+ }
115
+ for (const entry of ctx.graph.entries) {
116
+ if (!ctx.graph.modules.has(entry)) {
117
+ diagnostics.push(createDiagnostic("PROJ-C013", {
118
+ location: entry,
119
+ help: "Every graph entry must identify a module present in graph.modules.",
120
+ }));
121
+ }
122
+ }
123
+ for (const [moduleId, node] of ctx.graph.modules) {
124
+ if (moduleId !== node.id) {
125
+ diagnostics.push(createDiagnostic("PROJ-C013", {
126
+ location: moduleId,
127
+ help: "Each graph map key must exactly equal ModuleNode.id.",
128
+ }));
129
+ }
130
+ if (node.kind === "file" && node.source === undefined) {
131
+ diagnostics.push(createDiagnostic("PROJ-C013", {
132
+ location: moduleId,
133
+ help: "Adapters must provide raw UTF-8 source for every physical file module.",
134
+ }));
135
+ }
136
+ for (const edge of node.imports) {
137
+ if (!edge.external &&
138
+ edge.resolvedId !== undefined &&
139
+ !ctx.graph.modules.has(edge.resolvedId)) {
140
+ diagnostics.push(createDiagnostic("PROJ-C013", {
141
+ location: moduleId,
142
+ help: `The resolved target for "${edge.specifier}" must be present in graph.modules.`,
143
+ }));
144
+ }
145
+ if (!edge.external && edge.resolvedId === undefined) {
146
+ diagnostics.push(createDiagnostic("PROJ-C013", {
147
+ location: moduleId,
148
+ help: `The non-external edge for "${edge.specifier}" is missing resolvedId.`,
149
+ }));
150
+ }
151
+ }
152
+ }
153
+ for (const [outputId, members] of ctx.membership.outputs) {
154
+ for (const moduleId of members) {
155
+ if (!ctx.graph.modules.has(moduleId)) {
156
+ diagnostics.push(createDiagnostic("PROJ-C013", {
157
+ location: outputId,
158
+ help: `Output membership references unknown module "${moduleId}".`,
159
+ }));
160
+ }
161
+ }
162
+ if (!isVirtualId(outputId) && !ctx.outputContents.has(outputId)) {
163
+ diagnostics.push(createDiagnostic("PROJ-C014", {
164
+ location: outputId,
165
+ help: "Provide exact emitted bytes so disk manifests cannot bypass stale-output validation.",
166
+ }));
167
+ }
168
+ }
169
+ }
170
+ // ---------------------------------------------------------------------------
171
+ // Pass 1 — parse modules, build per-file scope/export tables, find define()s
172
+ // ---------------------------------------------------------------------------
173
+ function buildAnalyses(ctx, diagnostics) {
174
+ const analyses = new AnalysisRegistry(ctx, diagnostics);
175
+ for (const [moduleId, node] of ctx.graph.modules) {
176
+ if (node.source !== undefined &&
177
+ SUPPORTED_EXTENSIONS.has(getExtension(moduleId)) &&
178
+ containsPotentialDefineCall(node)) {
179
+ analyses.get(moduleId);
180
+ }
181
+ }
182
+ return analyses;
183
+ }
184
+ class AnalysisRegistry {
185
+ ctx;
186
+ diagnostics;
187
+ analyses = new Map();
188
+ attempted = new Set();
189
+ constructor(ctx, diagnostics) {
190
+ this.ctx = ctx;
191
+ this.diagnostics = diagnostics;
192
+ }
193
+ get size() {
194
+ return this.analyses.size;
195
+ }
196
+ values() {
197
+ return this.analyses.values();
198
+ }
199
+ get(moduleId) {
200
+ const existing = this.analyses.get(moduleId);
201
+ if (existing || this.attempted.has(moduleId))
202
+ return existing;
203
+ this.attempted.add(moduleId);
204
+ const node = this.ctx.graph.modules.get(moduleId);
205
+ if (node?.source === undefined ||
206
+ !SUPPORTED_EXTENSIONS.has(getExtension(moduleId))) {
207
+ return undefined;
208
+ }
209
+ const sourceFile = parseModule(moduleId, node);
210
+ if (getParseDiagnostics(sourceFile).length > 0) {
211
+ this.diagnostics.push(createDiagnostic("PROJ-C001", {
212
+ location: moduleId,
213
+ help: "Fix the TypeScript/JavaScript syntax error reported by the parser.",
214
+ }));
215
+ return undefined;
216
+ }
217
+ const analysis = analyzeModule(moduleId, sourceFile);
218
+ this.analyses.set(moduleId, analysis);
219
+ if (process.env["WEBUI_PROJECTION_PROFILE_DETAIL"] === "1") {
220
+ console.error(`[webui-projection-module] ${moduleId}`);
221
+ }
222
+ return analysis;
223
+ }
224
+ }
225
+ function containsPotentialDefineCall(node) {
226
+ const source = node.source ?? "";
227
+ const hasFrameworkEdge = node.imports.some((edge) => edge.packageName === FRAMEWORK_SPECIFIER);
228
+ let offset = 0;
229
+ while (offset < source.length) {
230
+ const found = source.indexOf("define", offset);
231
+ if (found < 0)
232
+ return false;
233
+ const before = found === 0 ? -1 : source.charCodeAt(found - 1);
234
+ const afterIndex = found + "define".length;
235
+ const after = afterIndex === source.length
236
+ ? -1
237
+ : source.charCodeAt(afterIndex);
238
+ if (!isIdentifierCode(before) && !isIdentifierCode(after)) {
239
+ let dot = found;
240
+ while (dot > 0 && isWhitespaceCode(source.charCodeAt(dot - 1)))
241
+ dot--;
242
+ if (dot > 0 && source.charCodeAt(dot - 1) === 46) {
243
+ let open = afterIndex;
244
+ while (open < source.length &&
245
+ isWhitespaceCode(source.charCodeAt(open))) {
246
+ open++;
247
+ }
248
+ if (source.charCodeAt(open) === 40) {
249
+ let argument = open + 1;
250
+ while (argument < source.length &&
251
+ isWhitespaceCode(source.charCodeAt(argument))) {
252
+ argument++;
253
+ }
254
+ const argumentCode = source.charCodeAt(argument);
255
+ if (argumentCode === 34 ||
256
+ argumentCode === 39 ||
257
+ hasFrameworkEdge ||
258
+ receiverIdentifier(source, dot - 1) === "customElements") {
259
+ return true;
260
+ }
261
+ }
262
+ }
263
+ }
264
+ offset = found + "define".length;
265
+ }
266
+ return false;
267
+ }
268
+ function receiverIdentifier(source, dotIndex) {
269
+ let end = dotIndex;
270
+ while (end > 0 && isWhitespaceCode(source.charCodeAt(end - 1)))
271
+ end--;
272
+ let start = end;
273
+ while (start > 0 && isIdentifierCode(source.charCodeAt(start - 1)))
274
+ start--;
275
+ return source.slice(start, end);
276
+ }
277
+ function isWhitespaceCode(code) {
278
+ return (code === 9 ||
279
+ code === 10 ||
280
+ code === 13 ||
281
+ code === 32);
282
+ }
283
+ function isIdentifierCode(code) {
284
+ return ((code >= 48 && code <= 57) ||
285
+ (code >= 65 && code <= 90) ||
286
+ (code >= 97 && code <= 122) ||
287
+ code === 36 ||
288
+ code === 95);
289
+ }
290
+ function parseModule(moduleId, node) {
291
+ return ts.createSourceFile(moduleId, node.source ?? "", ts.ScriptTarget.Latest,
292
+ /* setParentNodes */ false, scriptKindForExtension(getExtension(moduleId)));
293
+ }
294
+ function getParseDiagnostics(sourceFile) {
295
+ // The TS parser is error-tolerant; syntax errors are recorded on the
296
+ // source file rather than thrown. `parseDiagnostics` is an established
297
+ // (if internal) property of the parser result used by tooling that only
298
+ // needs a syntax check without building a full `ts.Program`.
299
+ const withDiagnostics = sourceFile;
300
+ return withDiagnostics.parseDiagnostics ?? [];
301
+ }
302
+ function getExtension(moduleId) {
303
+ const base = moduleId.slice(moduleId.lastIndexOf("/") + 1);
304
+ const dot = base.lastIndexOf(".");
305
+ return dot === -1 ? "" : base.slice(dot);
306
+ }
307
+ function scriptKindForExtension(ext) {
308
+ switch (ext) {
309
+ case ".tsx":
310
+ return ts.ScriptKind.TSX;
311
+ case ".jsx":
312
+ return ts.ScriptKind.JSX;
313
+ case ".js":
314
+ case ".mjs":
315
+ case ".cjs":
316
+ return ts.ScriptKind.JS;
317
+ default:
318
+ return ts.ScriptKind.TS;
319
+ }
320
+ }
321
+ /** Builds the scope/export tables and collects `define()` call sites for one module. */
322
+ function analyzeModule(moduleId, sourceFile) {
323
+ const scope = new Map();
324
+ const exports = new Map();
325
+ const starReexports = [];
326
+ for (const statement of sourceFile.statements) {
327
+ if (ts.isImportDeclaration(statement)) {
328
+ analyzeImportDeclaration(statement, scope);
329
+ }
330
+ else if (ts.isExportDeclaration(statement)) {
331
+ analyzeExportDeclaration(statement, scope, exports, starReexports);
332
+ }
333
+ else if (ts.isClassDeclaration(statement)) {
334
+ analyzeClassDeclaration(statement, scope, exports);
335
+ }
336
+ else if (ts.isFunctionDeclaration(statement)) {
337
+ analyzeNamedOtherDeclaration(statement.name?.text, isExported(statement), isDefaultExport(statement), scope, exports);
338
+ }
339
+ else if (ts.isVariableStatement(statement)) {
340
+ analyzeVariableStatement(statement, scope, exports);
341
+ }
342
+ else if (ts.isExportAssignment(statement)) {
343
+ analyzeExportAssignment(statement, exports);
344
+ }
345
+ }
346
+ const defineCalls = findDefineCalls(moduleId, sourceFile);
347
+ return { moduleId, scope, exports, starReexports, defineCalls };
348
+ }
349
+ function isExported(node) {
350
+ const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
351
+ return (modifiers ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
352
+ }
353
+ function isDefaultExport(node) {
354
+ const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
355
+ return (modifiers ?? []).some((m) => m.kind === ts.SyntaxKind.DefaultKeyword);
356
+ }
357
+ function analyzeImportDeclaration(decl, scope) {
358
+ if (!ts.isStringLiteral(decl.moduleSpecifier))
359
+ return;
360
+ const specifier = decl.moduleSpecifier.text;
361
+ const clause = decl.importClause;
362
+ if (!clause)
363
+ return;
364
+ if (clause.name) {
365
+ // Default import: `import Foo from '...'`.
366
+ scope.set(clause.name.text, {
367
+ kind: "import",
368
+ specifier,
369
+ importedName: "default",
370
+ });
371
+ }
372
+ const bindings = clause.namedBindings;
373
+ if (!bindings)
374
+ return;
375
+ if (ts.isNamespaceImport(bindings)) {
376
+ scope.set(bindings.name.text, { kind: "namespaceImport", specifier });
377
+ return;
378
+ }
379
+ if (ts.isNamedImports(bindings)) {
380
+ for (const spec of bindings.elements) {
381
+ const importedName = spec.propertyName?.text ?? spec.name.text;
382
+ const localName = spec.name.text;
383
+ scope.set(localName, { kind: "import", specifier, importedName });
384
+ }
385
+ }
386
+ }
387
+ function analyzeExportDeclaration(decl, scope, exports, starReexports) {
388
+ const specifierNode = decl.moduleSpecifier;
389
+ const hasFrom = specifierNode !== undefined && ts.isStringLiteral(specifierNode);
390
+ const specifier = hasFrom ? specifierNode.text : undefined;
391
+ if (!decl.exportClause) {
392
+ // `export * from '...'` (only meaningful with a module specifier).
393
+ if (specifier === undefined)
394
+ return;
395
+ starReexports.push({ specifier });
396
+ return;
397
+ }
398
+ if (ts.isNamespaceExport(decl.exportClause)) {
399
+ const ns = decl.exportClause.name.text;
400
+ if (specifier === undefined)
401
+ return; // `export * as ns;` without `from` is not valid syntax.
402
+ exports.set(ns, { kind: "namespace", specifier });
403
+ // `export * as ns from '...'` also introduces a local binding usable in this file.
404
+ scope.set(ns, { kind: "namespaceImport", specifier });
405
+ return;
406
+ }
407
+ // NamedExports: `export { a, b as c } [from '...']`.
408
+ for (const spec of decl.exportClause.elements) {
409
+ const exportedName = spec.name.text;
410
+ const sourceName = spec.propertyName?.text ?? spec.name.text;
411
+ if (specifier === undefined) {
412
+ exports.set(exportedName, { kind: "localRef", localName: sourceName });
413
+ }
414
+ else {
415
+ exports.set(exportedName, { kind: "reexport", specifier, importedName: sourceName });
416
+ }
417
+ }
418
+ }
419
+ function analyzeClassDeclaration(decl, scope, exports) {
420
+ if (!decl.name) {
421
+ if (isDefaultExport(decl)) {
422
+ exports.set("default", { kind: "class", node: decl });
423
+ }
424
+ return;
425
+ }
426
+ const name = decl.name.text;
427
+ scope.set(name, { kind: "localClass", node: decl });
428
+ if (isDefaultExport(decl)) {
429
+ exports.set("default", { kind: "localRef", localName: name });
430
+ }
431
+ else if (isExported(decl)) {
432
+ exports.set(name, { kind: "localRef", localName: name });
433
+ }
434
+ }
435
+ function analyzeNamedOtherDeclaration(name, exported, isDefault, scope, exports) {
436
+ if (!name)
437
+ return;
438
+ scope.set(name, { kind: "localOther" });
439
+ if (isDefault)
440
+ exports.set("default", { kind: "localRef", localName: name });
441
+ else if (exported)
442
+ exports.set(name, { kind: "localRef", localName: name });
443
+ }
444
+ function analyzeVariableStatement(stmt, scope, exports) {
445
+ const exported = isExported(stmt);
446
+ for (const decl of stmt.declarationList.declarations) {
447
+ if (!ts.isIdentifier(decl.name))
448
+ continue;
449
+ if (decl.initializer && ts.isClassExpression(decl.initializer)) {
450
+ scope.set(decl.name.text, {
451
+ kind: "localClass",
452
+ node: decl.initializer,
453
+ });
454
+ if (exported) {
455
+ exports.set(decl.name.text, {
456
+ kind: "localRef",
457
+ localName: decl.name.text,
458
+ });
459
+ }
460
+ }
461
+ else {
462
+ analyzeNamedOtherDeclaration(decl.name.text, exported, false, scope, exports);
463
+ }
464
+ }
465
+ }
466
+ function analyzeExportAssignment(statement, exports) {
467
+ if (statement.isExportEquals)
468
+ return;
469
+ if (ts.isIdentifier(statement.expression)) {
470
+ exports.set("default", {
471
+ kind: "localRef",
472
+ localName: statement.expression.text,
473
+ });
474
+ }
475
+ else if (ts.isClassExpression(statement.expression)) {
476
+ exports.set("default", {
477
+ kind: "class",
478
+ node: statement.expression,
479
+ });
480
+ }
481
+ }
482
+ // ---------------------------------------------------------------------------
483
+ // define() call site discovery (full-tree, iterative, explicit stack)
484
+ // ---------------------------------------------------------------------------
485
+ function findDefineCalls(moduleId, sourceFile) {
486
+ const sites = [];
487
+ const stack = [sourceFile];
488
+ while (stack.length > 0) {
489
+ const node = stack.pop();
490
+ const site = tryParseDefineCall(moduleId, node);
491
+ if (site)
492
+ sites.push(site);
493
+ const children = [];
494
+ ts.forEachChild(node, (child) => {
495
+ children.push(child);
496
+ });
497
+ for (let i = children.length - 1; i >= 0; i--)
498
+ stack.push(children[i]);
499
+ }
500
+ return sites;
501
+ }
502
+ function tryParseDefineCall(moduleId, node) {
503
+ if (!ts.isCallExpression(node))
504
+ return undefined;
505
+ const callee = node.expression;
506
+ if (!ts.isPropertyAccessExpression(callee) || callee.name.text !== "define")
507
+ return undefined;
508
+ const isCustomElements = ts.isIdentifier(callee.expression) && callee.expression.text === "customElements";
509
+ const args = node.arguments;
510
+ if (isCustomElements) {
511
+ return {
512
+ moduleId,
513
+ form: "customElements",
514
+ node,
515
+ argCountOk: args.length === 2,
516
+ tagArg: args[0],
517
+ classArg: args[1] ?? callee.expression,
518
+ };
519
+ }
520
+ return {
521
+ moduleId,
522
+ form: "class",
523
+ node,
524
+ argCountOk: args.length === 1,
525
+ tagArg: args[0],
526
+ classArg: callee.expression,
527
+ };
528
+ }
529
+ // ---------------------------------------------------------------------------
530
+ // Pass 2 — associate define() calls with tags and classes
531
+ // ---------------------------------------------------------------------------
532
+ function compileDefinedComponents(ctx, analyses, diagnostics) {
533
+ const result = new Map();
534
+ const seenTags = new Set();
535
+ const allSites = [];
536
+ for (const analysis of analyses.values())
537
+ allSites.push(...analysis.defineCalls);
538
+ for (const site of allSites) {
539
+ const analysis = analyses.get(site.moduleId);
540
+ if (site.form === "customElements" &&
541
+ analysis?.scope.has("customElements")) {
542
+ continue;
543
+ }
544
+ const classResolution = resolveClassReferenceExpression(ctx, analyses, site.moduleId, site.classArg);
545
+ if (classResolution.kind !== "class") {
546
+ // `.define()` is a common API outside Web Components. Unknown receivers
547
+ // are ignored here; strict WebUI build coverage catches any real
548
+ // scripted component omitted from the manifest without penalizing
549
+ // unrelated libraries.
550
+ continue;
551
+ }
552
+ if (ctx.graph.modules.get(classResolution.moduleId)?.kind ===
553
+ "virtual") {
554
+ diagnostics.push(createDiagnostic("PROJ-C006", {
555
+ location: classResolution.moduleId,
556
+ help: "Disk projection manifests require a physical source module for every shipped WebUI component.",
557
+ }));
558
+ continue;
559
+ }
560
+ if (!isPotentialWebUIClass(ctx, analyses, classResolution.moduleId, classResolution.node)) {
561
+ continue;
562
+ }
563
+ if (!site.argCountOk) {
564
+ diagnostics.push(createDiagnostic("PROJ-C011", {
565
+ location: site.moduleId,
566
+ help: site.form === "customElements"
567
+ ? "customElements.define(tag, Class) requires exactly two arguments."
568
+ : "Class.define(tag) requires exactly one argument.",
569
+ }));
570
+ continue;
571
+ }
572
+ if (site.tagArg === undefined || !ts.isStringLiteral(site.tagArg)) {
573
+ diagnostics.push(createDiagnostic("PROJ-C008", {
574
+ location: site.moduleId,
575
+ help: "Use a string literal tag name, e.g. MyElement.define(\"my-element\").",
576
+ }));
577
+ continue;
578
+ }
579
+ const tag = site.tagArg.text;
580
+ if (seenTags.has(tag)) {
581
+ diagnostics.push(createDiagnostic("PROJ-C010", {
582
+ location: site.moduleId,
583
+ help: `Tag "${tag}" is already defined elsewhere. Each custom element tag must be defined exactly once.`,
584
+ }));
585
+ continue;
586
+ }
587
+ const stateKeys = computeExactKeys(ctx, analyses, classResolution.moduleId, classResolution.node, diagnostics);
588
+ if (stateKeys === undefined)
589
+ continue;
590
+ seenTags.add(tag);
591
+ result.set(tag, {
592
+ moduleId: classResolution.moduleId,
593
+ stateKeys,
594
+ });
595
+ }
596
+ return result;
597
+ }
598
+ function isPotentialWebUIClass(ctx, analyses, startModuleId, startNode) {
599
+ const stack = [{ moduleId: startModuleId, node: startNode }];
600
+ const visited = new Set();
601
+ let hasFrameworkDecorator = false;
602
+ while (stack.length > 0) {
603
+ const current = stack.pop();
604
+ const key = `${current.moduleId}:${current.node.pos}:${current.node.end}`;
605
+ if (visited.has(key))
606
+ continue;
607
+ visited.add(key);
608
+ for (const member of current.node.members) {
609
+ if (!ts.isPropertyDeclaration(member))
610
+ continue;
611
+ const decorators = ts.canHaveDecorators(member)
612
+ ? ts.getDecorators(member) ?? []
613
+ : [];
614
+ for (const decorator of decorators) {
615
+ const parsed = parseDecoratorExpression(decorator.expression);
616
+ if (parsed.kind === "unsupported")
617
+ continue;
618
+ const identity = resolveDecoratorTarget(ctx, analyses, current.moduleId, parsed.calleeExpr);
619
+ if (identity.kind === "frameworkObservable" ||
620
+ identity.kind === "frameworkAttr") {
621
+ hasFrameworkDecorator = true;
622
+ }
623
+ }
624
+ }
625
+ const base = getExtendsExpression(current.node);
626
+ if (!base)
627
+ continue;
628
+ const resolved = resolveDecoratorTarget(ctx, analyses, current.moduleId, base);
629
+ if (resolved.kind === "frameworkElement")
630
+ return true;
631
+ if (resolved.kind === "class") {
632
+ stack.push({ moduleId: resolved.moduleId, node: resolved.node });
633
+ }
634
+ }
635
+ return hasFrameworkDecorator;
636
+ }
637
+ function resolveClassReferenceExpression(ctx, analyses, moduleId, expr) {
638
+ if (ts.isClassExpression(expr)) {
639
+ return { kind: "class", moduleId, node: expr };
640
+ }
641
+ if (ts.isIdentifier(expr)) {
642
+ return resolveIdentity(ctx, analyses, moduleId, expr.text, "local");
643
+ }
644
+ if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.expression)) {
645
+ const ns = resolveIdentity(ctx, analyses, moduleId, expr.expression.text, "local");
646
+ if (ns.kind !== "namespace")
647
+ return { kind: "unresolved", reason: "no-export" };
648
+ return resolveIdentity(ctx, analyses, ns.moduleId, expr.name.text, "export");
649
+ }
650
+ return { kind: "unresolved", reason: "no-export" };
651
+ }
652
+ // ---------------------------------------------------------------------------
653
+ // Iterative symbol resolution (no recursion; explicit stack)
654
+ // ---------------------------------------------------------------------------
655
+ function resolveSpecifierToModuleId(ctx, currentModuleId, specifier) {
656
+ const node = ctx.graph.modules.get(currentModuleId);
657
+ if (!node)
658
+ return undefined;
659
+ let match;
660
+ for (const edge of node.imports) {
661
+ if (edge.kind !== "static" || edge.specifier !== specifier)
662
+ continue;
663
+ if (match !== undefined &&
664
+ (match.resolvedId !== edge.resolvedId ||
665
+ match.external !== edge.external ||
666
+ match.packageName !== edge.packageName)) {
667
+ return undefined;
668
+ }
669
+ match = edge;
670
+ }
671
+ if (!match)
672
+ return undefined;
673
+ if (match.packageName === FRAMEWORK_SPECIFIER) {
674
+ return FRAMEWORK_SENTINEL;
675
+ }
676
+ return match.external ? undefined : match.resolvedId;
677
+ }
678
+ /**
679
+ * Resolves a name in a module's local scope (`mode: "local"`) or export table
680
+ * (`mode: "export"`) to its ultimate origin, following import/re-export/
681
+ * star-forward chains with an explicit stack instead of recursive calls.
682
+ * Cycles are detected via a visited-state set and reported as `"circular"`.
683
+ */
684
+ function resolveIdentity(ctx, analyses, startModuleId, startName, startMode) {
685
+ const stack = [{ mode: startMode, moduleId: startModuleId, name: startName }];
686
+ const visited = new Set();
687
+ let hadCircular = false;
688
+ let hadNoSource = false;
689
+ let hadAnyModule = false;
690
+ while (stack.length > 0) {
691
+ const cur = stack.pop();
692
+ const key = `${cur.mode[0]}:${cur.moduleId}:${cur.name}`;
693
+ if (visited.has(key)) {
694
+ hadCircular = true;
695
+ continue;
696
+ }
697
+ visited.add(key);
698
+ if (cur.moduleId === FRAMEWORK_SENTINEL) {
699
+ const resolved = resolveFrameworkName(cur.name);
700
+ if (resolved)
701
+ return resolved;
702
+ continue;
703
+ }
704
+ if (!ctx.graph.modules.has(cur.moduleId))
705
+ continue;
706
+ hadAnyModule = true;
707
+ const analysis = analyses.get(cur.moduleId);
708
+ if (!analysis) {
709
+ hadNoSource = true;
710
+ continue;
711
+ }
712
+ const next = cur.mode === "local"
713
+ ? stepLocal(ctx, analysis, cur, stack)
714
+ : stepExport(ctx, analysis, cur, stack);
715
+ if (next)
716
+ return next;
717
+ }
718
+ if (hadCircular)
719
+ return { kind: "unresolved", reason: "circular" };
720
+ if (hadNoSource)
721
+ return { kind: "unresolved", reason: "no-source" };
722
+ if (hadAnyModule)
723
+ return { kind: "unresolved", reason: "no-export" };
724
+ return { kind: "unresolved", reason: "no-module" };
725
+ }
726
+ function resolveFrameworkName(name) {
727
+ if (name === "observable")
728
+ return { kind: "frameworkObservable" };
729
+ if (name === "attr")
730
+ return { kind: "frameworkAttr" };
731
+ if (name === "WebUIElement")
732
+ return { kind: "frameworkElement" };
733
+ return undefined;
734
+ }
735
+ function stepLocal(ctx, analysis, cur, stack) {
736
+ const binding = analysis.scope.get(cur.name);
737
+ if (!binding)
738
+ return undefined;
739
+ switch (binding.kind) {
740
+ case "localClass":
741
+ return { kind: "class", moduleId: cur.moduleId, node: binding.node };
742
+ case "localOther":
743
+ return { kind: "other" };
744
+ case "import": {
745
+ const target = resolveSpecifierToModuleId(ctx, cur.moduleId, binding.specifier);
746
+ if (target !== undefined)
747
+ stack.push({ mode: "export", moduleId: target, name: binding.importedName });
748
+ return undefined;
749
+ }
750
+ case "namespaceImport": {
751
+ const target = resolveSpecifierToModuleId(ctx, cur.moduleId, binding.specifier);
752
+ if (target !== undefined)
753
+ return { kind: "namespace", moduleId: target };
754
+ return undefined;
755
+ }
756
+ }
757
+ }
758
+ function stepExport(ctx, analysis, cur, stack) {
759
+ const exp = analysis.exports.get(cur.name);
760
+ if (exp) {
761
+ switch (exp.kind) {
762
+ case "class":
763
+ return { kind: "class", moduleId: cur.moduleId, node: exp.node };
764
+ case "localRef":
765
+ stack.push({ mode: "local", moduleId: cur.moduleId, name: exp.localName });
766
+ return undefined;
767
+ case "reexport": {
768
+ const target = resolveSpecifierToModuleId(ctx, cur.moduleId, exp.specifier);
769
+ if (target !== undefined)
770
+ stack.push({ mode: "export", moduleId: target, name: exp.importedName });
771
+ return undefined;
772
+ }
773
+ case "namespace": {
774
+ const target = resolveSpecifierToModuleId(ctx, cur.moduleId, exp.specifier);
775
+ if (target !== undefined)
776
+ return { kind: "namespace", moduleId: target };
777
+ return undefined;
778
+ }
779
+ }
780
+ }
781
+ for (const star of analysis.starReexports) {
782
+ const target = resolveSpecifierToModuleId(ctx, cur.moduleId, star.specifier);
783
+ if (target !== undefined) {
784
+ stack.push({ mode: "export", moduleId: target, name: cur.name });
785
+ }
786
+ }
787
+ return undefined;
788
+ }
789
+ // ---------------------------------------------------------------------------
790
+ // Pass 3 — exact key derivation (decorators + iterative inheritance walk)
791
+ // ---------------------------------------------------------------------------
792
+ /** Returns exact initial/navigation surfaces, or `undefined` after diagnostics. */
793
+ function computeExactKeys(ctx, analyses, startModuleId, startNode, diagnostics) {
794
+ const hydrationKeys = new Set();
795
+ const navigationKeys = new Set();
796
+ const stack = [
797
+ { moduleId: startModuleId, node: startNode },
798
+ ];
799
+ const visitedClasses = new Set();
800
+ let ok = true;
801
+ while (stack.length > 0) {
802
+ const { moduleId, node } = stack.pop();
803
+ const classKey = `${moduleId}:${node.pos}:${node.end}`;
804
+ if (visitedClasses.has(classKey))
805
+ continue;
806
+ visitedClasses.add(classKey);
807
+ ok =
808
+ collectOwnKeys(ctx, analyses, moduleId, node, hydrationKeys, navigationKeys, diagnostics) && ok;
809
+ ok = climbBaseClass(ctx, analyses, moduleId, node, stack, diagnostics) && ok;
810
+ }
811
+ return ok
812
+ ? {
813
+ hydrationKeys: [...hydrationKeys].sort(compareUtf8),
814
+ navigationKeys: [...navigationKeys].sort(compareUtf8),
815
+ }
816
+ : undefined;
817
+ }
818
+ function collectOwnKeys(ctx, analyses, moduleId, node, hydrationKeys, navigationKeys, diagnostics) {
819
+ let ok = true;
820
+ for (const member of node.members) {
821
+ if (!ts.isPropertyDeclaration(member))
822
+ continue;
823
+ const decorators = ts.canHaveDecorators(member) ? (ts.getDecorators(member) ?? []) : [];
824
+ for (const decorator of decorators) {
825
+ ok =
826
+ applyDecorator(ctx, analyses, moduleId, member, decorator, hydrationKeys, navigationKeys, diagnostics) && ok;
827
+ }
828
+ }
829
+ return ok;
830
+ }
831
+ function applyDecorator(ctx, analyses, moduleId, member, decorator, hydrationKeys, navigationKeys, diagnostics) {
832
+ if (!ts.isIdentifier(member.name)) {
833
+ diagnostics.push(createDiagnostic("PROJ-C007", {
834
+ location: moduleId,
835
+ help: "Decorated reactive properties must use a plain identifier property name.",
836
+ }));
837
+ return false;
838
+ }
839
+ const propertyName = member.name.text;
840
+ const parsed = parseDecoratorExpression(decorator.expression);
841
+ if (parsed.kind === "unsupported") {
842
+ diagnostics.push(createDiagnostic("PROJ-C007", {
843
+ location: moduleId,
844
+ help: "Only a bare @observable/@attr identifier, a namespaced member, or @attr({ attribute }) is supported.",
845
+ }));
846
+ return false;
847
+ }
848
+ const identity = resolveDecoratorTarget(ctx, analyses, moduleId, parsed.calleeExpr);
849
+ if (identity.kind === "frameworkObservable") {
850
+ if (parsed.kind === "call") {
851
+ diagnostics.push(createDiagnostic("PROJ-C007", {
852
+ location: moduleId,
853
+ help: "@observable does not accept factory-call arguments.",
854
+ }));
855
+ return false;
856
+ }
857
+ hydrationKeys.add(propertyName);
858
+ navigationKeys.add(propertyName);
859
+ return true;
860
+ }
861
+ if (identity.kind === "frameworkAttr") {
862
+ // SSR host attributes win when present. Bootstrap state remains necessary
863
+ // for authored @attr values that are not materialized on the host.
864
+ hydrationKeys.add(propertyName);
865
+ navigationKeys.add(propertyName);
866
+ return true;
867
+ }
868
+ if (identity.kind === "other")
869
+ return true;
870
+ diagnostics.push(createDiagnostic(identity.kind === "unresolved" && identity.reason === "circular"
871
+ ? "PROJ-C012"
872
+ : identity.kind === "unresolved" && identity.reason === "no-source"
873
+ ? "PROJ-C006"
874
+ : "PROJ-C004", {
875
+ location: moduleId,
876
+ help: "Decorators must resolve to observable/attr exported by @microsoft/webui-framework.",
877
+ }));
878
+ return false;
879
+ }
880
+ function parseDecoratorExpression(expr) {
881
+ if (ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr)) {
882
+ return { kind: "bare", calleeExpr: expr };
883
+ }
884
+ if (ts.isCallExpression(expr)) {
885
+ const callee = expr.expression;
886
+ if (ts.isIdentifier(callee) || ts.isPropertyAccessExpression(callee)) {
887
+ return { kind: "call", calleeExpr: callee, call: expr };
888
+ }
889
+ return { kind: "unsupported" };
890
+ }
891
+ return { kind: "unsupported" };
892
+ }
893
+ function resolveDecoratorTarget(ctx, analyses, moduleId, expr) {
894
+ if (ts.isIdentifier(expr)) {
895
+ return resolveIdentity(ctx, analyses, moduleId, expr.text, "local");
896
+ }
897
+ if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.expression)) {
898
+ const ns = resolveIdentity(ctx, analyses, moduleId, expr.expression.text, "local");
899
+ if (ns.kind !== "namespace")
900
+ return { kind: "unresolved", reason: "no-export" };
901
+ return resolveIdentity(ctx, analyses, ns.moduleId, expr.name.text, "export");
902
+ }
903
+ return { kind: "unresolved", reason: "no-export" };
904
+ }
905
+ function climbBaseClass(ctx, analyses, moduleId, node, stack, diagnostics) {
906
+ const heritage = getExtendsExpression(node);
907
+ if (!heritage)
908
+ return true;
909
+ const resolved = resolveDecoratorTarget(ctx, analyses, moduleId, heritage);
910
+ if (resolved.kind === "frameworkElement")
911
+ return true;
912
+ if (resolved.kind === "class") {
913
+ stack.push({ moduleId: resolved.moduleId, node: resolved.node });
914
+ return true;
915
+ }
916
+ const isSourceUnavailable = resolved.kind === "unresolved" && resolved.reason === "no-source";
917
+ const isCircular = resolved.kind === "unresolved" && resolved.reason === "circular";
918
+ diagnostics.push(createDiagnostic(isCircular
919
+ ? "PROJ-C012"
920
+ : isSourceUnavailable
921
+ ? "PROJ-C006"
922
+ : "PROJ-C005", {
923
+ location: moduleId,
924
+ help: isSourceUnavailable
925
+ ? "The base class's module has no available source (external/virtual); its keys cannot be proven."
926
+ : "Ensure the base class is a statically resolvable class declaration reachable from the adapter graph.",
927
+ }));
928
+ return false;
929
+ }
930
+ function getExtendsExpression(node) {
931
+ const heritageClauses = node.heritageClauses ?? [];
932
+ for (const clause of heritageClauses) {
933
+ if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
934
+ return clause.types[0]?.expression;
935
+ }
936
+ }
937
+ return undefined;
938
+ }
939
+ // ---------------------------------------------------------------------------
940
+ // Pass 4/5 — output membership filter, path normalization, manifest assembly
941
+ // ---------------------------------------------------------------------------
942
+ function buildManifest(ctx, candidates) {
943
+ const root = manifestRoot(ctx);
944
+ const components = {};
945
+ for (const tag of [...candidates.keys()].sort(compareUtf8)) {
946
+ const { moduleId, stateKeys } = candidates.get(tag);
947
+ const outputs = findOutputsContaining(ctx, moduleId)
948
+ .map((outputId) => canonicalOutputId(ctx, outputId))
949
+ .sort(compareUtf8);
950
+ if (outputs.length === 0)
951
+ continue; // Tree-shaken: silently excluded, no diagnostic.
952
+ components[tag] = {
953
+ module: canonicalModuleId(ctx, moduleId),
954
+ outputs,
955
+ hydrationKeys: stateKeys.hydrationKeys,
956
+ navigationKeys: stateKeys.navigationKeys,
957
+ };
958
+ }
959
+ const inputs = buildInputsMap(ctx);
960
+ const outputs = buildOutputsMap(ctx);
961
+ const analysisHash = computeAnalysisHash(ctx);
962
+ const producerVersion = readProducerVersion();
963
+ const buildId = computeBuildId({
964
+ producerName: "@microsoft/webui/projection.js",
965
+ producerVersion,
966
+ adapterName: ctx.bundlerName,
967
+ adapterBundler: `${ctx.bundlerName}@${ctx.bundlerVersion}`,
968
+ root,
969
+ analysisHash,
970
+ sortedInputs: Object.entries(inputs),
971
+ sortedOutputs: Object.entries(outputs),
972
+ sortedComponents: Object.keys(components)
973
+ .sort(compareUtf8)
974
+ .map((tag) => [
975
+ tag,
976
+ components[tag].module,
977
+ components[tag].outputs,
978
+ components[tag].hydrationKeys,
979
+ components[tag].navigationKeys,
980
+ ]),
981
+ });
982
+ return {
983
+ schema: MANIFEST_SCHEMA,
984
+ producer: { name: "@microsoft/webui/projection.js", version: producerVersion },
985
+ adapter: { name: ctx.bundlerName, bundler: `${ctx.bundlerName}@${ctx.bundlerVersion}` },
986
+ root,
987
+ analysisHash,
988
+ buildId,
989
+ outputs,
990
+ inputs,
991
+ components,
992
+ };
993
+ }
994
+ function findOutputsContaining(ctx, moduleId) {
995
+ const found = [];
996
+ for (const [outputPath, moduleIds] of ctx.membership.outputs) {
997
+ if (moduleIds.has(moduleId))
998
+ found.push(outputPath);
999
+ }
1000
+ return found;
1001
+ }
1002
+ function buildInputsMap(ctx) {
1003
+ const entries = [];
1004
+ for (const [moduleId, node] of ctx.graph.modules) {
1005
+ const hash = node.kind === "virtual"
1006
+ ? VIRTUAL_HASH
1007
+ : hashContent(node.source ?? "");
1008
+ entries.push([canonicalModuleId(ctx, moduleId), hash]);
1009
+ }
1010
+ entries.sort((left, right) => compareUtf8(left[0], right[0]));
1011
+ return Object.fromEntries(entries);
1012
+ }
1013
+ function buildOutputsMap(ctx) {
1014
+ const entries = [];
1015
+ for (const outputId of ctx.membership.outputs.keys()) {
1016
+ const hash = isVirtualId(outputId)
1017
+ ? VIRTUAL_HASH
1018
+ : hashContent(ctx.outputContents.get(outputId));
1019
+ entries.push([canonicalOutputId(ctx, outputId), hash]);
1020
+ }
1021
+ entries.sort((left, right) => compareUtf8(left[0], right[0]));
1022
+ return Object.fromEntries(entries);
1023
+ }
1024
+ function manifestRoot(ctx) {
1025
+ const rootDir = path.resolve(ctx.rootDir);
1026
+ const manifestPath = path.resolve(ctx.manifestPath);
1027
+ ensureWithinRoot(rootDir, manifestPath);
1028
+ const relative = toPortableSeparators(path.relative(path.dirname(manifestPath), rootDir));
1029
+ return relative.length === 0 ? "." : relative;
1030
+ }
1031
+ function canonicalModuleId(ctx, moduleId) {
1032
+ return isVirtualId(moduleId)
1033
+ ? encodeVirtualId(moduleId)
1034
+ : canonicalPhysicalPath(ctx.rootDir, moduleId);
1035
+ }
1036
+ function canonicalOutputId(ctx, outputId) {
1037
+ return isVirtualId(outputId)
1038
+ ? encodeVirtualId(outputId)
1039
+ : canonicalPhysicalPath(ctx.rootDir, outputId);
1040
+ }
1041
+ function canonicalPhysicalPath(rootDir, targetPath) {
1042
+ const root = path.resolve(rootDir);
1043
+ const target = path.resolve(targetPath);
1044
+ ensureWithinRoot(root, target);
1045
+ const relative = toPortableSeparators(path.relative(root, target));
1046
+ if (relative.length === 0) {
1047
+ throw new ProjectionError([
1048
+ createDiagnostic("PROJ-S003", {
1049
+ location: targetPath,
1050
+ help: "Manifest file entries must identify a file below the build root.",
1051
+ }),
1052
+ ]);
1053
+ }
1054
+ return relative;
1055
+ }
1056
+ function ensureWithinRoot(root, target) {
1057
+ const relative = path.relative(root, target);
1058
+ if (relative.length === 0 ||
1059
+ (relative !== ".." &&
1060
+ !relative.startsWith(`..${path.sep}`) &&
1061
+ !path.isAbsolute(relative))) {
1062
+ return;
1063
+ }
1064
+ throw new ProjectionError([
1065
+ createDiagnostic("PROJ-S003", {
1066
+ location: target,
1067
+ help: `Choose a build root that contains the manifest and every physical input/output (${root}).`,
1068
+ }),
1069
+ ]);
1070
+ }
1071
+ function toPortableSeparators(value) {
1072
+ return value.split(path.sep).join("/");
1073
+ }
1074
+ function isVirtualId(value) {
1075
+ return value.charCodeAt(0) === 0;
1076
+ }
1077
+ function encodeVirtualId(value) {
1078
+ const bytes = Buffer.from(value.slice(1), "utf8");
1079
+ let encoded = "virtual:";
1080
+ for (const byte of bytes)
1081
+ encoded += byte.toString(16).padStart(2, "0");
1082
+ return encoded;
1083
+ }
1084
+ function computeAnalysisHash(ctx) {
1085
+ const records = [];
1086
+ appendAnalysisRecord(records, "entries", [
1087
+ String(ctx.graph.entries.length),
1088
+ ]);
1089
+ const entries = [...ctx.graph.entries]
1090
+ .map((entry) => canonicalModuleId(ctx, entry))
1091
+ .sort(compareUtf8);
1092
+ for (const entry of entries) {
1093
+ appendAnalysisRecord(records, "entry", [entry]);
1094
+ }
1095
+ const modules = [...ctx.graph.modules.values()].sort((left, right) => compareUtf8(canonicalModuleId(ctx, left.id), canonicalModuleId(ctx, right.id)));
1096
+ appendAnalysisRecord(records, "modules", [String(modules.length)]);
1097
+ for (const node of modules) {
1098
+ const imports = [...node.imports].sort(compareImportEdges);
1099
+ const fields = [
1100
+ canonicalModuleId(ctx, node.id),
1101
+ node.kind,
1102
+ node.packageName ?? "",
1103
+ node.source === undefined ? VIRTUAL_HASH : hashContent(node.source),
1104
+ String(imports.length),
1105
+ ];
1106
+ for (const edge of imports) {
1107
+ fields.push(edge.specifier, edge.resolvedId === undefined
1108
+ ? ""
1109
+ : canonicalModuleId(ctx, edge.resolvedId), edge.external ? "external" : "internal", edge.kind, edge.packageName ?? "");
1110
+ }
1111
+ appendAnalysisRecord(records, "module", fields);
1112
+ }
1113
+ const outputs = [...ctx.membership.outputs.entries()].sort((left, right) => compareUtf8(canonicalOutputId(ctx, left[0]), canonicalOutputId(ctx, right[0])));
1114
+ appendAnalysisRecord(records, "memberships", [String(outputs.length)]);
1115
+ for (const [outputId, memberIds] of outputs) {
1116
+ const members = [...memberIds]
1117
+ .map((memberId) => canonicalModuleId(ctx, memberId))
1118
+ .sort(compareUtf8);
1119
+ appendAnalysisRecord(records, "membership", [
1120
+ canonicalOutputId(ctx, outputId),
1121
+ String(members.length),
1122
+ ...members,
1123
+ ]);
1124
+ }
1125
+ return hashContent(records.join(""));
1126
+ }
1127
+ function compareImportEdges(left, right) {
1128
+ const leftKey = `${left.specifier}\0${left.resolvedId ?? ""}\0${left.external ? "1" : "0"}\0${left.kind}\0${left.packageName ?? ""}`;
1129
+ const rightKey = `${right.specifier}\0${right.resolvedId ?? ""}\0${right.external ? "1" : "0"}\0${right.kind}\0${right.packageName ?? ""}`;
1130
+ return compareUtf8(leftKey, rightKey);
1131
+ }
1132
+ function appendAnalysisRecord(destination, label, fields) {
1133
+ let record = label;
1134
+ for (const field of fields) {
1135
+ record += `${Buffer.byteLength(field, "utf8")}:${field}`;
1136
+ }
1137
+ destination.push(`${record}\n`);
1138
+ }
1139
+ let cachedProducerVersion;
1140
+ /**
1141
+ * Reads this package's own `package.json` `version` field once, for the
1142
+ * manifest `producer.version` field. This never resolves user modules or
1143
+ * bundler-owned specifiers; it only reads this package's own metadata via a
1144
+ * synchronous CommonJS-style `require` created for this ESM module.
1145
+ */
1146
+ function readProducerVersion() {
1147
+ if (cachedProducerVersion !== undefined)
1148
+ return cachedProducerVersion;
1149
+ try {
1150
+ const require = createRequire(import.meta.url);
1151
+ const pkg = require("../../package.json");
1152
+ if (typeof pkg.version !== "string" || pkg.version.length === 0) {
1153
+ throw new Error("package version is missing");
1154
+ }
1155
+ cachedProducerVersion = pkg.version;
1156
+ }
1157
+ catch (error) {
1158
+ throw new ProjectionError([
1159
+ createDiagnostic("PROJ-C013", {
1160
+ help: `Unable to read @microsoft/webui package version: ${error instanceof Error ? error.message : String(error)}`,
1161
+ }),
1162
+ ]);
1163
+ }
1164
+ return cachedProducerVersion;
1165
+ }
1166
+ //# sourceMappingURL=compiler.js.map