@fuzdev/fuz_ui 0.204.0 → 0.205.1

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 (123) hide show
  1. package/dist/ApiModule.svelte +14 -1
  2. package/dist/ApiModule.svelte.d.ts.map +1 -1
  3. package/dist/Breadcrumb.svelte +2 -2
  4. package/dist/Card.svelte +1 -7
  5. package/dist/ColorSchemeInput.svelte +1 -2
  6. package/dist/ContextmenuEntry.svelte +1 -3
  7. package/dist/ContextmenuLinkEntry.svelte +1 -4
  8. package/dist/ContextmenuSeparator.svelte +1 -1
  9. package/dist/ContextmenuSubmenu.svelte +1 -2
  10. package/dist/CopyToClipboard.svelte +5 -5
  11. package/dist/CopyToClipboard.svelte.d.ts.map +1 -1
  12. package/dist/DeclarationDetail.svelte +15 -4
  13. package/dist/DeclarationDetail.svelte.d.ts.map +1 -1
  14. package/dist/Dialog.svelte +1 -2
  15. package/dist/DialogContent.svelte +1 -1
  16. package/dist/DocsList.svelte +1 -1
  17. package/dist/DocsMenu.svelte +1 -1
  18. package/dist/DocsPageLinks.svelte +9 -8
  19. package/dist/DocsPageLinks.svelte.d.ts.map +1 -1
  20. package/dist/DocsPrimaryNav.svelte +1 -1
  21. package/dist/DocsSecondaryNav.svelte +1 -1
  22. package/dist/EcosystemLinks.svelte +1 -1
  23. package/dist/LibraryDetail.svelte +10 -7
  24. package/dist/LibraryDetail.svelte.d.ts.map +1 -1
  25. package/dist/LibrarySummary.svelte +1 -2
  26. package/dist/PendingAnimation.svelte +6 -4
  27. package/dist/PendingAnimation.svelte.d.ts.map +1 -1
  28. package/dist/PendingButton.svelte +1 -2
  29. package/dist/StyleVariableButton.svelte +1 -2
  30. package/dist/Svg.svelte +1 -1
  31. package/dist/ThemeInput.svelte +1 -2
  32. package/dist/TomeSectionHeader.svelte +1 -1
  33. package/package.json +14 -38
  34. package/dist/Mdz.svelte +0 -34
  35. package/dist/Mdz.svelte.d.ts +0 -11
  36. package/dist/Mdz.svelte.d.ts.map +0 -1
  37. package/dist/MdzNodeView.svelte +0 -107
  38. package/dist/MdzNodeView.svelte.d.ts +0 -9
  39. package/dist/MdzNodeView.svelte.d.ts.map +0 -1
  40. package/dist/MdzPrecompiled.svelte +0 -30
  41. package/dist/MdzPrecompiled.svelte.d.ts +0 -11
  42. package/dist/MdzPrecompiled.svelte.d.ts.map +0 -1
  43. package/dist/MdzRoot.svelte +0 -30
  44. package/dist/MdzRoot.svelte.d.ts +0 -12
  45. package/dist/MdzRoot.svelte.d.ts.map +0 -1
  46. package/dist/MdzStream.svelte +0 -32
  47. package/dist/MdzStream.svelte.d.ts +0 -12
  48. package/dist/MdzStream.svelte.d.ts.map +0 -1
  49. package/dist/MdzStreamNodeView.svelte +0 -106
  50. package/dist/MdzStreamNodeView.svelte.d.ts +0 -9
  51. package/dist/MdzStreamNodeView.svelte.d.ts.map +0 -1
  52. package/dist/mdz.d.ts +0 -112
  53. package/dist/mdz.d.ts.map +0 -1
  54. package/dist/mdz.js +0 -1186
  55. package/dist/mdz_components.d.ts +0 -63
  56. package/dist/mdz_components.d.ts.map +0 -1
  57. package/dist/mdz_components.js +0 -32
  58. package/dist/mdz_helpers.d.ts +0 -164
  59. package/dist/mdz_helpers.d.ts.map +0 -1
  60. package/dist/mdz_helpers.js +0 -424
  61. package/dist/mdz_lexer.d.ts +0 -93
  62. package/dist/mdz_lexer.d.ts.map +0 -1
  63. package/dist/mdz_lexer.js +0 -732
  64. package/dist/mdz_opcodes.d.ts +0 -174
  65. package/dist/mdz_opcodes.d.ts.map +0 -1
  66. package/dist/mdz_opcodes.js +0 -14
  67. package/dist/mdz_opcodes_to_nodes.d.ts +0 -20
  68. package/dist/mdz_opcodes_to_nodes.d.ts.map +0 -1
  69. package/dist/mdz_opcodes_to_nodes.js +0 -332
  70. package/dist/mdz_stream_parser.d.ts +0 -80
  71. package/dist/mdz_stream_parser.d.ts.map +0 -1
  72. package/dist/mdz_stream_parser.js +0 -354
  73. package/dist/mdz_stream_parser_block.d.ts +0 -76
  74. package/dist/mdz_stream_parser_block.d.ts.map +0 -1
  75. package/dist/mdz_stream_parser_block.js +0 -458
  76. package/dist/mdz_stream_parser_inline.d.ts +0 -52
  77. package/dist/mdz_stream_parser_inline.d.ts.map +0 -1
  78. package/dist/mdz_stream_parser_inline.js +0 -378
  79. package/dist/mdz_stream_parser_link.d.ts +0 -22
  80. package/dist/mdz_stream_parser_link.d.ts.map +0 -1
  81. package/dist/mdz_stream_parser_link.js +0 -215
  82. package/dist/mdz_stream_parser_state.d.ts +0 -187
  83. package/dist/mdz_stream_parser_state.d.ts.map +0 -1
  84. package/dist/mdz_stream_parser_state.js +0 -398
  85. package/dist/mdz_stream_parser_text.d.ts +0 -14
  86. package/dist/mdz_stream_parser_text.d.ts.map +0 -1
  87. package/dist/mdz_stream_parser_text.js +0 -50
  88. package/dist/mdz_stream_parser_url.d.ts +0 -60
  89. package/dist/mdz_stream_parser_url.d.ts.map +0 -1
  90. package/dist/mdz_stream_parser_url.js +0 -307
  91. package/dist/mdz_stream_state.svelte.d.ts +0 -44
  92. package/dist/mdz_stream_state.svelte.d.ts.map +0 -1
  93. package/dist/mdz_stream_state.svelte.js +0 -357
  94. package/dist/mdz_to_svelte.d.ts +0 -41
  95. package/dist/mdz_to_svelte.d.ts.map +0 -1
  96. package/dist/mdz_to_svelte.js +0 -100
  97. package/dist/mdz_token_parser.d.ts +0 -14
  98. package/dist/mdz_token_parser.d.ts.map +0 -1
  99. package/dist/mdz_token_parser.js +0 -344
  100. package/dist/svelte_preprocess_mdz.d.ts +0 -65
  101. package/dist/svelte_preprocess_mdz.d.ts.map +0 -1
  102. package/dist/svelte_preprocess_mdz.js +0 -529
  103. package/dist/tsdoc_mdz.d.ts +0 -45
  104. package/dist/tsdoc_mdz.d.ts.map +0 -1
  105. package/dist/tsdoc_mdz.js +0 -88
  106. package/src/lib/mdz.ts +0 -1532
  107. package/src/lib/mdz_components.ts +0 -64
  108. package/src/lib/mdz_helpers.ts +0 -449
  109. package/src/lib/mdz_lexer.ts +0 -1012
  110. package/src/lib/mdz_opcodes.ts +0 -205
  111. package/src/lib/mdz_opcodes_to_nodes.ts +0 -375
  112. package/src/lib/mdz_stream_parser.ts +0 -415
  113. package/src/lib/mdz_stream_parser_block.ts +0 -532
  114. package/src/lib/mdz_stream_parser_inline.ts +0 -414
  115. package/src/lib/mdz_stream_parser_link.ts +0 -271
  116. package/src/lib/mdz_stream_parser_state.ts +0 -539
  117. package/src/lib/mdz_stream_parser_text.ts +0 -77
  118. package/src/lib/mdz_stream_parser_url.ts +0 -365
  119. package/src/lib/mdz_stream_state.svelte.ts +0 -387
  120. package/src/lib/mdz_to_svelte.ts +0 -141
  121. package/src/lib/mdz_token_parser.ts +0 -434
  122. package/src/lib/svelte_preprocess_mdz.ts +0 -742
  123. package/src/lib/tsdoc_mdz.ts +0 -97
@@ -1,742 +0,0 @@
1
- /**
2
- * Svelte preprocessor that compiles static `Mdz` content to Svelte markup at build time.
3
- *
4
- * Detects `Mdz` components with static string `content` props, parses the mdz content,
5
- * renders each `MdzNode` to equivalent Svelte markup via `mdz_to_svelte`, and replaces
6
- * the `Mdz` with `MdzPrecompiled` containing pre-rendered children.
7
- *
8
- * Also handles ternary chains (`content={a ? 'x' : b ? 'y' : 'z'}`) where all leaf
9
- * values are statically resolvable strings, emitting `{#if a}markup_x{:else if b}markup_y{:else}markup_z{/if}`
10
- * as children of a single `MdzPrecompiled`.
11
- *
12
- * Truly dynamic `content` props are left untouched.
13
- *
14
- * @module
15
- */
16
-
17
- import type {ImportDeclaration, VariableDeclaration} from 'estree';
18
- import {parse, type PreprocessorGroup, type AST} from 'svelte/compiler';
19
- import MagicString from 'magic-string';
20
- import {walk} from 'zimmerframe';
21
- import {should_exclude_path} from '@fuzdev/fuz_util/path.js';
22
- import {
23
- find_attribute,
24
- extract_static_string,
25
- try_extract_conditional_chain,
26
- build_static_bindings,
27
- resolve_component_names,
28
- has_identifier_in_tree,
29
- find_import_insert_position,
30
- generate_import_lines,
31
- remove_variable_declaration,
32
- remove_import_declaration,
33
- remove_import_specifier,
34
- handle_preprocess_error,
35
- type PreprocessImportInfo,
36
- type ResolvedComponentImport,
37
- } from '@fuzdev/fuz_util/svelte_preprocess_helpers.js';
38
-
39
- import {mdz_parse} from './mdz.js';
40
- import {mdz_to_svelte} from './mdz_to_svelte.js';
41
-
42
- /**
43
- * An estree `ImportDeclaration` augmented with Svelte's position data.
44
- * Svelte's parser adds `start`/`end` to all AST nodes, but the estree
45
- * types don't declare them.
46
- */
47
- type PositionedImportDeclaration = ImportDeclaration & AST.BaseNode;
48
-
49
- /**
50
- * Options for `svelte_preprocess_mdz`.
51
- */
52
- export interface SveltePreprocessMdzOptions {
53
- /** File patterns to exclude. */
54
- exclude?: Array<string | RegExp>;
55
-
56
- /**
57
- * Component import mapping for mdz content.
58
- * Key: component name as used in mdz (e.g., 'Alert').
59
- * Value: import path (e.g., '$lib/Alert.svelte').
60
- *
61
- * If mdz content references a component not in this map,
62
- * that `Mdz` usage is skipped (left as runtime).
63
- */
64
- components?: Record<string, string>;
65
-
66
- /**
67
- * Allowed HTML elements in mdz content.
68
- * If mdz content references an element not in this list,
69
- * that `Mdz` usage is skipped (left as runtime).
70
- */
71
- elements?: Array<string>;
72
-
73
- /**
74
- * Import sources that resolve to the `Mdz` component.
75
- * Used to verify that `Mdz` in templates refers to fuz_ui's `Mdz.svelte`.
76
- *
77
- * @default ['@fuzdev/fuz_ui/Mdz.svelte']
78
- */
79
- component_imports?: Array<string>;
80
-
81
- /**
82
- * Import path for the precompiled wrapper component.
83
- *
84
- * @default '@fuzdev/fuz_ui/MdzPrecompiled.svelte'
85
- */
86
- compiled_component_import?: string;
87
-
88
- /**
89
- * How to handle errors during mdz parsing or rendering.
90
- *
91
- * @default 'throw' in CI, 'log' otherwise
92
- */
93
- on_error?: 'log' | 'throw';
94
- }
95
-
96
- const PRECOMPILED_NAME = 'MdzPrecompiled';
97
-
98
- /**
99
- * Creates a Svelte preprocessor that compiles static `Mdz` content at build time.
100
- *
101
- * @param options - configuration for component/element resolution and file filtering
102
- * @returns a Svelte `PreprocessorGroup` for use in `svelte.config.js`
103
- */
104
- export const svelte_preprocess_mdz = (
105
- options: SveltePreprocessMdzOptions = {},
106
- ): PreprocessorGroup => {
107
- const {
108
- exclude = [],
109
- components = {},
110
- elements: elements_array = [],
111
- component_imports = ['@fuzdev/fuz_ui/Mdz.svelte'],
112
- compiled_component_import = '@fuzdev/fuz_ui/MdzPrecompiled.svelte',
113
- on_error = process.env.CI === 'true' ? 'throw' : 'log',
114
- } = options;
115
- const elements = new Set(elements_array);
116
-
117
- return {
118
- name: 'fuz-mdz',
119
-
120
- markup: ({content, filename}) => {
121
- if (should_exclude_path(filename, exclude)) {
122
- return {code: content};
123
- }
124
-
125
- // Quick bail: does file mention any known Mdz import source?
126
- if (!component_imports.some((source) => content.includes(source))) {
127
- return {code: content};
128
- }
129
-
130
- const ast = parse(content, {filename, modern: true});
131
-
132
- // Resolve which local names map to the Mdz component
133
- const mdz_names = resolve_component_names(ast, component_imports);
134
- if (mdz_names.size === 0) {
135
- return {code: content};
136
- }
137
-
138
- // Check for MdzPrecompiled name collision
139
- if (has_name_collision(ast, compiled_component_import)) {
140
- return {code: content};
141
- }
142
-
143
- const s = new MagicString(content);
144
- const bindings = build_static_bindings(ast);
145
-
146
- // Find and transform Mdz usages with static content
147
- const {transformations, total_usages, transformed_usages} = find_mdz_usages(ast, mdz_names, {
148
- components,
149
- elements,
150
- filename,
151
- source: content,
152
- bindings,
153
- on_error,
154
- });
155
-
156
- if (transformations.length === 0) {
157
- return {code: content};
158
- }
159
-
160
- // Apply transformations
161
- for (const t of transformations) {
162
- s.overwrite(t.start, t.end, t.replacement);
163
- }
164
-
165
- // Remove dead const bindings that were consumed by transformations
166
- remove_dead_const_bindings(s, ast, transformations, content);
167
-
168
- // Determine which Mdz imports can be removed
169
- const removable_imports = find_removable_mdz_imports(
170
- ast,
171
- mdz_names,
172
- total_usages,
173
- transformed_usages,
174
- );
175
-
176
- // Add required imports and remove unused Mdz imports
177
- manage_imports(
178
- s,
179
- ast,
180
- transformations,
181
- removable_imports,
182
- compiled_component_import,
183
- content,
184
- );
185
-
186
- return {
187
- code: s.toString(),
188
- map: s.generateMap({hires: true}),
189
- };
190
- },
191
- };
192
- };
193
-
194
- interface MdzTransformation {
195
- start: number;
196
- end: number;
197
- replacement: string;
198
- required_imports: Map<string, PreprocessImportInfo>;
199
- /** Const binding names that were resolved to produce this transformation. */
200
- consumed_bindings: Set<string>;
201
- /** The original AST component node, used to skip during dead code analysis. */
202
- component_node: AST.Component;
203
- }
204
-
205
- interface FindMdzUsagesResult {
206
- transformations: Array<MdzTransformation>;
207
- /** Total template usages per `Mdz` local name. */
208
- total_usages: Map<string, number>;
209
- /** Successfully transformed usages per `Mdz` local name. */
210
- transformed_usages: Map<string, number>;
211
- }
212
-
213
- interface FindMdzUsagesContext {
214
- components: Record<string, string>;
215
- elements: ReadonlySet<string>;
216
- filename: string | undefined;
217
- source: string;
218
- bindings: ReadonlyMap<string, string>;
219
- on_error: 'log' | 'throw';
220
- }
221
-
222
- /**
223
- * Checks if `MdzPrecompiled` is already imported from a different source.
224
- * If so, the preprocessor bails to avoid name collisions.
225
- */
226
- const has_name_collision = (ast: AST.Root, compiled_component_import: string): boolean => {
227
- for (const script of [ast.instance, ast.module]) {
228
- if (!script) continue;
229
- for (const node of script.content.body) {
230
- if (node.type !== 'ImportDeclaration') continue;
231
- const source_path = node.source.value as string;
232
- for (const spec of node.specifiers) {
233
- if (spec.local.name === PRECOMPILED_NAME && source_path !== compiled_component_import) {
234
- return true;
235
- }
236
- }
237
- }
238
- }
239
- return false;
240
- };
241
-
242
- /**
243
- * Collects identifiers from an expression that resolved through bindings.
244
- * Only collects top-level Identifier nodes that appear in the bindings map.
245
- *
246
- * TODO: support transitive dead const removal — currently only directly-consumed
247
- * bindings (identifiers in the expression AST) are tracked. Transitive dependencies
248
- * (e.g., `const a = 'x'; const b = a; content={b}` — `a` is not removed) are not
249
- * traced. After removing a dead const, re-check whether identifiers in its
250
- * initializer became dead too.
251
- */
252
- const collect_consumed_bindings = (
253
- value: AST.Attribute['value'],
254
- bindings: ReadonlyMap<string, string>,
255
- ): Set<string> => {
256
- const consumed: Set<string> = new Set();
257
- if (value === true || Array.isArray(value)) return consumed;
258
- const collect_from_expr = (expr: {type: string; [key: string]: any}): void => {
259
- if (expr.type === 'Identifier' && bindings.has(expr.name)) {
260
- consumed.add(expr.name);
261
- } else if (expr.type === 'BinaryExpression' && expr.operator === '+') {
262
- collect_from_expr(expr.left);
263
- collect_from_expr(expr.right);
264
- } else if (expr.type === 'TemplateLiteral') {
265
- for (const e of expr.expressions) {
266
- collect_from_expr(e);
267
- }
268
- } else if (expr.type === 'ConditionalExpression') {
269
- collect_from_expr(expr.consequent);
270
- collect_from_expr(expr.alternate);
271
- }
272
- };
273
- collect_from_expr(value.expression);
274
- return consumed;
275
- };
276
-
277
- /**
278
- * Walks the AST to find `Mdz` component usages with static `content` props
279
- * and generates transformations to replace them with `MdzPrecompiled` children.
280
- */
281
- const find_mdz_usages = (
282
- ast: AST.Root,
283
- mdz_names: Map<string, ResolvedComponentImport>,
284
- context: FindMdzUsagesContext,
285
- ): FindMdzUsagesResult => {
286
- const transformations: Array<MdzTransformation> = [];
287
- const total_usages: Map<string, number> = new Map();
288
- const transformed_usages: Map<string, number> = new Map();
289
-
290
- // zimmerframe types visitors against {type: string}, requiring explicit annotations
291
- // on the callback parameters for Svelte-specific AST types like AST.Component
292
- walk(ast.fragment as any, null, {
293
- Component(node: AST.Component, ctx: {next: () => void}) {
294
- // Always recurse into children so nested Mdz components are found
295
- ctx.next();
296
-
297
- if (!mdz_names.has(node.name)) return;
298
-
299
- // Track total usages per name
300
- total_usages.set(node.name, (total_usages.get(node.name) ?? 0) + 1);
301
-
302
- // Skip if spread attributes present — can't determine content statically
303
- if (node.attributes.some((attr) => attr.type === 'SpreadAttribute')) return;
304
-
305
- const content_attr = find_attribute(node, 'content');
306
- if (!content_attr) return;
307
-
308
- // Extract optional static base prop for relative path resolution.
309
- // If base is present but dynamic, skip precompilation entirely —
310
- // MdzPrecompiled doesn't resolve relative paths at runtime,
311
- // so precompiling with unresolved relative links would be wrong.
312
- const base_attr = find_attribute(node, 'base');
313
- let base: string | undefined;
314
- if (base_attr) {
315
- const base_value = extract_static_string(base_attr.value, context.bindings);
316
- if (base_value === null) return; // dynamic base — fall back to runtime
317
- base = base_value;
318
- }
319
-
320
- // Collect attributes to exclude from precompiled output
321
- const exclude_attrs: Set<AST.Attribute> = new Set([content_attr]);
322
- if (base_attr) exclude_attrs.add(base_attr);
323
-
324
- // Extract static string value
325
- const content_value = extract_static_string(content_attr.value, context.bindings);
326
- if (content_value !== null) {
327
- // Parse mdz content and render to Svelte markup
328
- let result;
329
- try {
330
- const nodes = mdz_parse(content_value);
331
- result = mdz_to_svelte(nodes, context.components, context.elements, base);
332
- } catch (error) {
333
- handle_preprocess_error(error, '[fuz-mdz]', context.filename, context.on_error);
334
- return;
335
- }
336
-
337
- // If content has unconfigured tags, skip this usage (fall back to runtime)
338
- if (result.has_unconfigured_tags) return;
339
-
340
- const consumed = collect_consumed_bindings(content_attr.value, context.bindings);
341
- const replacement = build_replacement(node, exclude_attrs, result.markup, context.source);
342
- transformed_usages.set(node.name, (transformed_usages.get(node.name) ?? 0) + 1);
343
- transformations.push({
344
- start: node.start,
345
- end: node.end,
346
- replacement,
347
- required_imports: result.imports,
348
- consumed_bindings: consumed,
349
- component_node: node,
350
- });
351
- return;
352
- }
353
-
354
- // Try conditional chain (handles both simple and nested ternaries)
355
- const chain = try_extract_conditional_chain(
356
- content_attr.value,
357
- context.source,
358
- context.bindings,
359
- );
360
- if (chain === null) return;
361
-
362
- // Parse and render each branch
363
- const branch_results: Array<{markup: string; imports: Map<string, PreprocessImportInfo>}> =
364
- [];
365
- try {
366
- for (const branch of chain) {
367
- const nodes = mdz_parse(branch.value);
368
- const result = mdz_to_svelte(nodes, context.components, context.elements, base);
369
- if (result.has_unconfigured_tags) return;
370
- branch_results.push({markup: result.markup, imports: result.imports});
371
- }
372
- } catch (error) {
373
- handle_preprocess_error(error, '[fuz-mdz]', context.filename, context.on_error);
374
- return;
375
- }
376
-
377
- // Build {#if}/{:else if}/{:else} markup
378
- let children_markup = '';
379
- for (let i = 0; i < chain.length; i++) {
380
- const branch = chain[i]!;
381
- const result = branch_results[i]!;
382
- if (i === 0) {
383
- children_markup += `{#if ${branch.test_source}}${result.markup}`;
384
- } else if (branch.test_source !== null) {
385
- children_markup += `{:else if ${branch.test_source}}${result.markup}`;
386
- } else {
387
- children_markup += `{:else}${result.markup}`;
388
- }
389
- }
390
- children_markup += '{/if}';
391
-
392
- const replacement = build_replacement(node, exclude_attrs, children_markup, context.source);
393
-
394
- // Merge imports from all branches
395
- const merged_imports: Map<string, PreprocessImportInfo> = new Map();
396
- for (const result of branch_results) {
397
- for (const [name, info] of result.imports) {
398
- merged_imports.set(name, info);
399
- }
400
- }
401
-
402
- const consumed = collect_consumed_bindings(content_attr.value, context.bindings);
403
- transformed_usages.set(node.name, (transformed_usages.get(node.name) ?? 0) + 1);
404
- transformations.push({
405
- start: node.start,
406
- end: node.end,
407
- replacement,
408
- required_imports: merged_imports,
409
- consumed_bindings: consumed,
410
- component_node: node,
411
- });
412
- },
413
- });
414
-
415
- return {transformations, total_usages, transformed_usages};
416
- };
417
-
418
- /**
419
- * Removes dead `const` bindings from the instance script that were consumed
420
- * by transformations and are no longer referenced anywhere else in the file.
421
- *
422
- * Only handles single-declarator `const` statements. Skips `const a = 'x', b = 'y'`
423
- * and module script variables (which could be exported/imported by other files).
424
- */
425
- const remove_dead_const_bindings = (
426
- s: MagicString,
427
- ast: AST.Root,
428
- transformations: Array<MdzTransformation>,
429
- source: string,
430
- ): void => {
431
- // Collect all consumed binding names across transformations
432
- const all_consumed: Set<string> = new Set();
433
- for (const t of transformations) {
434
- for (const name of t.consumed_bindings) {
435
- all_consumed.add(name);
436
- }
437
- }
438
- if (all_consumed.size === 0) return;
439
-
440
- // Only remove from instance script (module script variables could be exported)
441
- const instance = ast.instance;
442
- if (!instance) return;
443
-
444
- // Build a skip set of transformed component nodes so their attribute
445
- // expressions (which still contain the old identifiers) don't false-match
446
- const skip: Set<unknown> = new Set();
447
- for (const t of transformations) {
448
- skip.add(t.component_node);
449
- }
450
-
451
- for (const name of all_consumed) {
452
- // Find the VariableDeclaration containing this binding in instance script
453
- let declaration_node: (VariableDeclaration & {start: number; end: number}) | null = null;
454
- for (const node of instance.content.body) {
455
- if (node.type !== 'VariableDeclaration' || node.kind !== 'const') continue;
456
- for (const decl of node.declarations) {
457
- if (decl.id.type === 'Identifier' && decl.id.name === name) {
458
- declaration_node = node as VariableDeclaration & {start: number; end: number};
459
- break;
460
- }
461
- }
462
- if (declaration_node) break;
463
- }
464
-
465
- if (!declaration_node) continue;
466
-
467
- // Only handle single-declarator statements
468
- if (declaration_node.declarations.length !== 1) continue;
469
-
470
- // Check if the identifier is referenced anywhere else
471
- const id_skip = new Set([...skip, declaration_node]);
472
-
473
- // Check instance script (excluding the declaration itself)
474
- if (has_identifier_in_tree(instance.content, name, id_skip)) continue;
475
-
476
- // Check module script
477
- if (ast.module?.content && has_identifier_in_tree(ast.module.content, name)) continue;
478
-
479
- // Check template — skip transformed component nodes whose attribute expressions
480
- // still contain the old identifier references in the AST
481
- if (has_identifier_in_tree(ast.fragment, name, id_skip)) continue;
482
-
483
- remove_variable_declaration(s, declaration_node, source);
484
- // Add to skip set so chained dead const checks don't find references to this removed node
485
- skip.add(declaration_node);
486
- }
487
- };
488
-
489
- /**
490
- * Builds the replacement string for a transformed `Mdz` component.
491
- *
492
- * Reconstructs the opening tag as `<MdzPrecompiled` with all attributes except `content`,
493
- * using source text slicing to preserve exact formatting and dynamic expressions.
494
- */
495
- const build_replacement = (
496
- node: AST.Component,
497
- exclude_attrs: ReadonlySet<AST.Attribute>,
498
- children_markup: string,
499
- source: string,
500
- ): string => {
501
- // Collect source ranges of all attributes except excluded ones (content, base when resolved)
502
- const other_attr_ranges: Array<{start: number; end: number}> = [];
503
- for (const attr of node.attributes) {
504
- if (exclude_attrs.has(attr as AST.Attribute)) continue;
505
- other_attr_ranges.push({start: attr.start, end: attr.end});
506
- }
507
-
508
- // Build opening tag with MdzPrecompiled name
509
- let opening = `<${PRECOMPILED_NAME}`;
510
- for (const range of other_attr_ranges) {
511
- opening += ' ' + source.slice(range.start, range.end);
512
- }
513
- opening += '>';
514
-
515
- return `${opening}${children_markup}</${PRECOMPILED_NAME}>`;
516
- };
517
-
518
- /** Result of import removability analysis for a single `Mdz` name. */
519
- interface ImportRemovalAction {
520
- /** The import declaration node. */
521
- node: PositionedImportDeclaration;
522
- /** 'full' removes the entire declaration; 'partial' removes only the `Mdz` specifier. */
523
- kind: 'full' | 'partial';
524
- /** For partial removal: the specifier to remove. */
525
- specifier_to_remove?: ImportDeclaration['specifiers'][number];
526
- }
527
-
528
- /**
529
- * Determines which `Mdz` import declarations can be safely removed or trimmed.
530
- *
531
- * An import is removable when:
532
- * 1. All template usages of that name were successfully transformed.
533
- * 2. The identifier is not referenced elsewhere in script or template expressions.
534
- *
535
- * For multi-specifier imports, only the `Mdz` specifier is removed (partial removal).
536
- * For single-specifier imports, the entire declaration is removed.
537
- */
538
- const find_removable_mdz_imports = (
539
- ast: AST.Root,
540
- mdz_names: Map<string, ResolvedComponentImport>,
541
- total_usages: Map<string, number>,
542
- transformed_usages: Map<string, number>,
543
- ): Map<string, ImportRemovalAction> => {
544
- const removable: Map<string, ImportRemovalAction> = new Map();
545
-
546
- for (const [name, {import_node, specifier}] of mdz_names) {
547
- const total = total_usages.get(name) ?? 0;
548
- const transformed = transformed_usages.get(name) ?? 0;
549
-
550
- // Only remove if ALL template usages were transformed
551
- if (total === 0 || transformed < total) continue;
552
-
553
- // Check if identifier is referenced elsewhere in the AST
554
- const skip: Set<unknown> = new Set([import_node]);
555
-
556
- // Check instance script body (excluding the import itself)
557
- if (ast.instance?.content && has_identifier_in_tree(ast.instance.content, name, skip)) {
558
- continue;
559
- }
560
-
561
- // Check module script body (excluding the import itself)
562
- if (ast.module?.content && has_identifier_in_tree(ast.module.content, name, skip)) {
563
- continue;
564
- }
565
-
566
- // Check template for expression references (Component.name is a string, not Identifier)
567
- if (has_identifier_in_tree(ast.fragment, name)) {
568
- continue;
569
- }
570
-
571
- const positioned_node = import_node as PositionedImportDeclaration;
572
- if (import_node.specifiers.length === 1) {
573
- removable.set(name, {node: positioned_node, kind: 'full'});
574
- } else {
575
- removable.set(name, {
576
- node: positioned_node,
577
- kind: 'partial',
578
- specifier_to_remove: specifier,
579
- });
580
- }
581
- }
582
-
583
- return removable;
584
- };
585
-
586
- /**
587
- * Manages import additions and removals.
588
- *
589
- * Adds the `MdzPrecompiled` import and other required imports (DocsLink, Code, resolve).
590
- * Removes `Mdz` import declarations that are no longer referenced.
591
- *
592
- * Handles both full removal (single-specifier imports) and partial removal
593
- * (multi-specifier imports where only the `Mdz` specifier is removed).
594
- *
595
- * To avoid MagicString boundary conflicts when the insertion position falls inside
596
- * a removal range, one removable `Mdz` import is overwritten with the `MdzPrecompiled`
597
- * import line instead of using separate remove + appendLeft.
598
- */
599
- const manage_imports = (
600
- s: MagicString,
601
- ast: AST.Root,
602
- transformations: Array<MdzTransformation>,
603
- removable_imports: Map<string, ImportRemovalAction>,
604
- compiled_component_import: string,
605
- source: string,
606
- ): void => {
607
- // Collect all required imports across transformations
608
- const required: Map<string, PreprocessImportInfo> = new Map();
609
- for (const t of transformations) {
610
- for (const [name, info] of t.required_imports) {
611
- required.set(name, info);
612
- }
613
- }
614
-
615
- // Always need MdzPrecompiled when transformations occur
616
- required.set(PRECOMPILED_NAME, {path: compiled_component_import, kind: 'default'});
617
-
618
- const script = ast.instance;
619
-
620
- // Separate full removals from partial removals
621
- const full_removals: Set<PositionedImportDeclaration> = new Set();
622
- const partial_removals: Array<ImportRemovalAction> = [];
623
- for (const [, action] of removable_imports) {
624
- if (action.kind === 'full') {
625
- full_removals.add(action.node);
626
- } else {
627
- partial_removals.push(action);
628
- }
629
- }
630
-
631
- if (!script) {
632
- // No instance script — removable_imports won't apply (imports are in module script if any)
633
- // Just add all required imports
634
- const lines = generate_import_lines(required);
635
- if (ast.module) {
636
- s.appendLeft(ast.module.end, `\n\n<script lang="ts">\n${lines}\n</script>`);
637
- } else {
638
- s.prepend(`<script lang="ts">\n${lines}\n</script>\n\n`);
639
- }
640
- // Remove Mdz imports from module script if removable
641
- for (const node of full_removals) {
642
- remove_import_declaration(s, node, source);
643
- }
644
- // Apply partial removals
645
- for (const action of partial_removals) {
646
- remove_import_specifier(s, action.node, action.specifier_to_remove!, source);
647
- }
648
- return;
649
- }
650
-
651
- // Check existing imports to avoid duplicates — tracks both name AND source path
652
- const existing: Map<string, string> = new Map();
653
- for (const node of script.content.body) {
654
- if (node.type === 'ImportDeclaration') {
655
- const source_path = node.source.value as string;
656
- for (const spec of node.specifiers) {
657
- existing.set(spec.local.name, source_path);
658
- }
659
- }
660
- }
661
-
662
- const to_add: Map<string, PreprocessImportInfo> = new Map();
663
- for (const [name, info] of required) {
664
- const existing_path = existing.get(name);
665
- if (existing_path === undefined) {
666
- to_add.set(name, info);
667
- }
668
- }
669
-
670
- // Strategy: if we're both adding MdzPrecompiled and removing an Mdz import,
671
- // overwrite one full-removable import with the MdzPrecompiled line to avoid
672
- // MagicString boundary conflicts. Other imports use normal appendLeft.
673
- let overwrite_target: PositionedImportDeclaration | null = null;
674
- if (to_add.has(PRECOMPILED_NAME) && full_removals.size > 0) {
675
- // Pick the first removable import to overwrite
676
- overwrite_target = full_removals.values().next().value ?? null;
677
- }
678
-
679
- // Generate the MdzPrecompiled import line separately if using overwrite
680
- if (overwrite_target) {
681
- const node_start = overwrite_target.start;
682
- const node_end = overwrite_target.end;
683
-
684
- // Find the start of the line (consume leading whitespace)
685
- let line_start = node_start;
686
- while (line_start > 0 && (source[line_start - 1] === '\t' || source[line_start - 1] === ' ')) {
687
- line_start--;
688
- }
689
-
690
- // Extract indentation from the original import line rather than hardcoding a tab.
691
- // The import node's start is after any leading whitespace, so the indentation is
692
- // the characters between the line start and the node start.
693
- const original_indent = source.slice(line_start, node_start);
694
- const precompiled_line = `${original_indent}import ${PRECOMPILED_NAME} from '${compiled_component_import}';`;
695
-
696
- s.overwrite(line_start, node_end, precompiled_line);
697
- to_add.delete(PRECOMPILED_NAME);
698
- }
699
-
700
- // When there are partial removals but no full-removal overwrite target,
701
- // the appendLeft at find_import_insert_position can conflict with the
702
- // partial removal's overwrite (they share the same node.end boundary).
703
- // Bundle the new imports into the first partial removal's overwrite instead.
704
- const insert_pos = find_import_insert_position(script);
705
- let partial_carrier: ImportRemovalAction | null = null;
706
- if (to_add.size > 0 && partial_removals.length > 0) {
707
- for (const action of partial_removals) {
708
- if (action.node.end === insert_pos) {
709
- partial_carrier = action;
710
- break;
711
- }
712
- }
713
- }
714
-
715
- // Add remaining imports — either bundled with a partial removal or via appendLeft
716
- let carrier_lines = '';
717
- if (partial_carrier && to_add.size > 0) {
718
- carrier_lines = '\n' + generate_import_lines(to_add);
719
- to_add.clear();
720
- }
721
- if (to_add.size > 0) {
722
- const lines = generate_import_lines(to_add);
723
- s.appendLeft(insert_pos, '\n' + lines);
724
- }
725
-
726
- // Remove remaining full Mdz imports (skip the overwrite target)
727
- for (const node of full_removals) {
728
- if (node === overwrite_target) continue;
729
- remove_import_declaration(s, node, source);
730
- }
731
-
732
- // Apply partial import removals
733
- for (const action of partial_removals) {
734
- remove_import_specifier(
735
- s,
736
- action.node,
737
- action.specifier_to_remove!,
738
- source,
739
- action === partial_carrier ? carrier_lines : '',
740
- );
741
- }
742
- };