@macroforge/svelte-preprocessor 0.1.79 → 0.1.83

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 CHANGED
@@ -1,37 +1,10 @@
1
1
  # @macroforge/svelte-preprocessor
2
2
 
3
- Svelte preprocessor for expanding Macroforge macros in component script blocks
4
-
5
3
  [![npm version](https://badge.fury.io/js/%40macroforge%2Fsvelte-preprocessor.svg)](https://www.npmjs.com/package/@macroforge/svelte-preprocessor)
6
4
 
7
5
  ## Overview
8
6
 
9
- @macroforge/svelte-preprocessor
10
-
11
- Svelte preprocessor for expanding Macroforge macros in component script blocks.
12
-
13
- This module provides integration between Macroforge's macro expansion system and
14
- Svelte's preprocessing pipeline. It intercepts `<script>` blocks in `.svelte`
15
- files, detects `@derive` decorators, and expands them into generated code before
16
- TypeScript compilation occurs.
17
-
18
- ## How It Works
19
-
20
- 1. The preprocessor is registered in `svelte.config.js` as part of the
21
- preprocess array
22
- 2. When Svelte compiles a component, it passes each `<script>` block to this
23
- preprocessor
24
- 3. The preprocessor checks if the script contains `@derive` decorators
25
- 4. If found, it calls the native `macroforge` binding to expand the macros
26
- 5. The expanded code replaces the original script content
27
-
28
- ## Important Notes
29
-
30
- - Must be placed BEFORE other preprocessors (like `vitePreprocess()`) in the
31
- chain
32
- - Uses lazy-loading for native bindings to avoid initialization overhead
33
- - Gracefully degrades if native bindings are unavailable
34
- - Only processes TypeScript blocks by default (configurable via options)
7
+ Svelte preprocessor for expanding Macroforge macros in component script blocks
35
8
 
36
9
  ## Installation
37
10
 
@@ -43,29 +16,23 @@ npm install @macroforge/svelte-preprocessor
43
16
 
44
17
  ### Functions
45
18
 
46
- - **`macroforgePreprocess`** - Whether to preserve `@derive` decorators in the
47
- expanded output.
19
+ - **`macroforgePreprocess`** - Creates a Svelte preprocessor that expands Macroforge macros in
20
+ `<script>` blocks.
48
21
 
49
- ### Types
22
+ ### Interfaces
50
23
 
51
- - **`ExpandResult`** - Whether to preserve `@derive` decorators in the expanded
52
- output.
53
- - **`MacroforgePreprocessorOptions`** - Configuration options for the Macroforge
54
- Svelte preprocessor.
24
+ - **`MacroforgePreprocessorOptions`** - Configuration options for the Macroforge Svelte
25
+ preprocessor.
55
26
 
56
- ## Examples
27
+ ### Constants
57
28
 
58
- ```typescript
59
- macroforgePreprocess();
60
- macroforgePreprocess({ keepDecorators: true });
61
- macroforgePreprocess({ processJavaScript: true });
62
- ```
29
+ - **`default`** - Default export for convenient importing.
63
30
 
64
31
  ## Documentation
65
32
 
66
33
  See the
67
- [full documentation](https://macroforge.dev/docs/api/reference/typescript/svelte-preprocessor)
68
- on the Macroforge website.
34
+ [full documentation](https://macroforge.dev/docs/api/reference/typescript/svelte-preprocessor) on
35
+ the Macroforge website.
69
36
 
70
37
  ## License
71
38
 
package/dist/index.d.ts CHANGED
@@ -25,7 +25,7 @@
25
25
  *
26
26
  * @packageDocumentation
27
27
  */
28
- import type { PreprocessorGroup } from "svelte/compiler";
28
+ import type { PreprocessorGroup } from 'svelte/compiler';
29
29
  /**
30
30
  * Configuration options for the Macroforge Svelte preprocessor.
31
31
  *
@@ -92,15 +92,18 @@ export interface MacroforgePreprocessorOptions {
92
92
  * The preprocessor performs these steps for each script block:
93
93
  *
94
94
  * 1. **Language Check** - Verifies the script is TypeScript (or JavaScript if enabled)
95
- * 2. **Quick Scan** - Skips blocks without `@derive` (performance optimization)
96
- * 3. **Expansion** - Calls the native engine to expand macros
97
- * 4. **Diagnostics** - Logs any errors or warnings from expansion
98
- * 5. **Return** - Returns transformed code or `undefined` if unchanged
95
+ * 2. **Expansion** - Calls the native engine to expand macros
96
+ * 3. **Diagnostics** - Logs any errors or warnings from expansion
97
+ * 4. **Return** - Returns transformed code or `undefined` if unchanged
98
+ *
99
+ * Every matching script block is passed to the engine; there is no
100
+ * pre-scan for `@derive`, so unchanged blocks simply come back identical.
99
101
  *
100
102
  * ## Error Handling
101
103
  *
102
104
  * The preprocessor is designed to be resilient:
103
- * - If native bindings fail to load, it silently skips processing
105
+ * - If native bindings fail to load, it logs a warning, skips the block, and
106
+ * retries the import on the next block (so a warning is emitted per attempt)
104
107
  * - If macro expansion throws, it logs a warning and continues
105
108
  * - Svelte compilation proceeds even if preprocessing fails
106
109
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAgB,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AA6OvE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,6BAA6B;IAC5C;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;;;;;;;;;;OAaG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,6BAAkC,GAC1C,iBAAiB,CA6JnB;AAED;;;;;;;GAOG;AACH,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAgB,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AA+OvE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,6BAA6B;IAC1C;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;;;;;;;;;;OAaG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,wBAAgB,oBAAoB,CAChC,OAAO,GAAE,6BAAkC,GAC5C,iBAAiB,CA+InB;AAED;;;;;;;GAOG;AACH,eAAe,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -25,14 +25,13 @@
25
25
  *
26
26
  * @packageDocumentation
27
27
  */
28
- import { hasMacroAnnotations } from "@macroforge/shared";
29
28
  // ============================================================================
30
29
  // Source Map Generation
31
30
  // ============================================================================
32
- const VLQ_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
31
+ const VLQ_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
33
32
  function vlqEncode(value) {
34
33
  let vlq = value < 0 ? (-value << 1) | 1 : value << 1;
35
- let encoded = "";
34
+ let encoded = '';
36
35
  do {
37
36
  let digit = vlq & 0x1f;
38
37
  vlq >>>= 5;
@@ -46,7 +45,7 @@ function vlqEncode(value) {
46
45
  function buildLineOffsets(text) {
47
46
  const offsets = [0];
48
47
  for (let i = 0; i < text.length; i++) {
49
- if (text[i] === "\n")
48
+ if (text[i] === '\n')
50
49
  offsets.push(i + 1);
51
50
  }
52
51
  return offsets;
@@ -80,7 +79,7 @@ function buildSourceMap(original, expanded, mapping, filename) {
80
79
  lineSegments.get(exp.line).push({
81
80
  expCol: exp.column,
82
81
  origLine: orig.line,
83
- origCol: orig.column,
82
+ origCol: orig.column
84
83
  });
85
84
  }
86
85
  // Encode VLQ mappings
@@ -90,7 +89,7 @@ function buildSourceMap(original, expanded, mapping, filename) {
90
89
  for (let line = 0; line < totalLines; line++) {
91
90
  const segs = lineSegments.get(line);
92
91
  if (!segs || segs.length === 0) {
93
- mappingsArr.push("");
92
+ mappingsArr.push('');
94
93
  continue;
95
94
  }
96
95
  segs.sort((a, b) => a.expCol - b.expCol);
@@ -105,14 +104,14 @@ function buildSourceMap(original, expanded, mapping, filename) {
105
104
  prevOrigLine = seg.origLine;
106
105
  prevOrigCol = seg.origCol;
107
106
  }
108
- mappingsArr.push(parts.join(","));
107
+ mappingsArr.push(parts.join(','));
109
108
  }
110
109
  return {
111
110
  version: 3,
112
111
  sources: [filename],
113
112
  sourcesContent: [original],
114
113
  names: [],
115
- mappings: mappingsArr.join(";"),
114
+ mappings: mappingsArr.join(';')
116
115
  };
117
116
  }
118
117
  /**
@@ -121,7 +120,7 @@ function buildSourceMap(original, expanded, mapping, filename) {
121
120
  * This variable implements a lazy-loading pattern:
122
121
  * - Initially `null`, indicating the binding hasn't been loaded yet
123
122
  * - Set to the actual function after first successful load
124
- * - Remains `null` if loading fails (graceful degradation)
123
+ * - Remains `null` if loading fails, so the next call retries the import
125
124
  *
126
125
  * The lazy-loading approach avoids loading native bindings at module import time,
127
126
  * which improves startup performance when the preprocessor is registered but no
@@ -137,17 +136,20 @@ let expandSync = null;
137
136
  *
138
137
  * 1. On first call, dynamically imports the `macroforge` package
139
138
  * 2. Extracts and caches the `expandSync` function
140
- * 3. On subsequent calls, returns the cached function immediately
139
+ * 3. On subsequent calls after a successful load, returns the cached function
140
+ * immediately
141
141
  *
142
142
  * The function is async because dynamic imports return promises, even though
143
- * the underlying `expandSync` function is synchronous. This async wrapper
144
- * only runs once; after initialization, the cached sync function is used directly.
143
+ * the underlying `expandSync` function is synchronous. After a successful
144
+ * initialization, the cached sync function is used directly.
145
145
  *
146
146
  * ## Error Handling
147
147
  *
148
148
  * If the native bindings fail to load (e.g., missing native module, architecture
149
- * mismatch), the function logs a warning and returns `null`. This allows the
150
- * preprocessor to gracefully skip macro expansion rather than crashing the build.
149
+ * mismatch), the function logs a warning and returns `null`. Because failure
150
+ * leaves the cache `null`, the import (and the warning) is retried on every
151
+ * subsequent call. This allows the preprocessor to skip macro expansion rather
152
+ * than crashing the build.
151
153
  *
152
154
  * @returns The cached `expandSync` function, or `null` if loading failed
153
155
  * @internal
@@ -156,12 +158,12 @@ async function ensureExpandSync() {
156
158
  if (expandSync === null) {
157
159
  try {
158
160
  // Dynamic import defers loading until first use
159
- const macroforge = await import("macroforge");
161
+ const macroforge = await import('macroforge');
160
162
  expandSync = macroforge.expandSync;
161
163
  }
162
164
  catch (error) {
163
165
  // Log warning but don't throw - allows graceful degradation
164
- console.warn("[@macroforge/svelte-preprocessor] Failed to load macroforge native bindings:", error);
166
+ console.warn('[@macroforge/svelte-preprocessor] Failed to load macroforge native bindings:', error);
165
167
  expandSync = null;
166
168
  }
167
169
  }
@@ -186,15 +188,18 @@ async function ensureExpandSync() {
186
188
  * The preprocessor performs these steps for each script block:
187
189
  *
188
190
  * 1. **Language Check** - Verifies the script is TypeScript (or JavaScript if enabled)
189
- * 2. **Quick Scan** - Skips blocks without `@derive` (performance optimization)
190
- * 3. **Expansion** - Calls the native engine to expand macros
191
- * 4. **Diagnostics** - Logs any errors or warnings from expansion
192
- * 5. **Return** - Returns transformed code or `undefined` if unchanged
191
+ * 2. **Expansion** - Calls the native engine to expand macros
192
+ * 3. **Diagnostics** - Logs any errors or warnings from expansion
193
+ * 4. **Return** - Returns transformed code or `undefined` if unchanged
194
+ *
195
+ * Every matching script block is passed to the engine; there is no
196
+ * pre-scan for `@derive`, so unchanged blocks simply come back identical.
193
197
  *
194
198
  * ## Error Handling
195
199
  *
196
200
  * The preprocessor is designed to be resilient:
197
- * - If native bindings fail to load, it silently skips processing
201
+ * - If native bindings fail to load, it logs a warning, skips the block, and
202
+ * retries the import on the next block (so a warning is emitted per attempt)
198
203
  * - If macro expansion throws, it logs a warning and continues
199
204
  * - Svelte compilation proceeds even if preprocessing fails
200
205
  *
@@ -243,9 +248,9 @@ export function macroforgePreprocess(options = {}) {
243
248
  *
244
249
  * @returns An object with `code` property if transformed, or `undefined` if no changes
245
250
  */
246
- const scriptPreprocessor = async ({ content, filename, attributes, }) => {
247
- if (filename?.includes("macroforge.svelte")) {
248
- console.log(`[PREPROC DEBUG] file=${filename}, lang=${attributes.lang}, type=${attributes.type}, has @derive=${content.includes("@derive")}, has serviceDeserialize=${content.includes("function serviceDeserialize")}`);
251
+ const scriptPreprocessor = async ({ content, filename, attributes }) => {
252
+ if (filename?.includes('macroforge.svelte')) {
253
+ console.log(`[PREPROC DEBUG] file=${filename}, lang=${attributes.lang}, type=${attributes.type}, has @derive=${content.includes('@derive')}, has serviceDeserialize=${content.includes('function serviceDeserialize')}`);
249
254
  }
250
255
  /*
251
256
  * STEP 1: Language Detection
@@ -261,25 +266,15 @@ export function macroforgePreprocess(options = {}) {
261
266
  * - <script type="module"> → isJavaScript = true
262
267
  */
263
268
  const lang = attributes.lang || attributes.type;
264
- const isTypeScript = lang === "ts" || lang === "typescript";
265
- const isJavaScript = !lang || lang === "js" || lang === "javascript" ||
266
- lang === "module";
269
+ const isTypeScript = lang === 'ts' || lang === 'typescript';
270
+ const isJavaScript = !lang || lang === 'js' || lang === 'javascript' ||
271
+ lang === 'module';
267
272
  // Skip non-TypeScript blocks unless processJavaScript is enabled
268
273
  if (!isTypeScript && !(processJavaScript && isJavaScript)) {
269
274
  return; // Return undefined = no changes, Svelte keeps original content
270
275
  }
271
276
  /*
272
- * STEP 2: Quick Scan Optimization
273
- *
274
- * Before loading native bindings, do a cheap string check for "@derive".
275
- * Most components won't have macros, so this saves the cost of loading
276
- * and calling the native expansion engine in the common case.
277
- */
278
- if (!hasMacroAnnotations(content)) {
279
- return;
280
- }
281
- /*
282
- * STEP 3: Load Native Bindings
277
+ * STEP 2: Load Native Bindings
283
278
  *
284
279
  * The expansion engine is a native module (Rust compiled to Node addon).
285
280
  * We lazy-load it on first use to avoid startup overhead.
@@ -287,36 +282,37 @@ export function macroforgePreprocess(options = {}) {
287
282
  const expand = await ensureExpandSync();
288
283
  if (!expand) {
289
284
  // Native bindings unavailable (missing module, architecture mismatch, etc.)
290
- // Silently skip - the component will fail later if macros are actually needed
285
+ // ensureExpandSync already logged a warning; skip this block. The import
286
+ // is retried on the next block, so the warning repeats per attempt.
291
287
  return;
292
288
  }
293
289
  try {
294
290
  /*
295
- * STEP 4: Macro Expansion
291
+ * STEP 3: Macro Expansion
296
292
  *
297
293
  * Call the native engine to parse the TypeScript, find @derive decorators,
298
294
  * and generate the expanded code with all macro-derived methods/properties.
299
295
  */
300
- const result = expand(content, filename || "component.svelte", {
301
- keepDecorators,
296
+ const result = expand(content, filename || 'component.svelte', {
297
+ keepDecorators
302
298
  });
303
299
  /*
304
- * STEP 5: Diagnostic Reporting
300
+ * STEP 4: Diagnostic Reporting
305
301
  *
306
302
  * The expansion engine may report errors (invalid macro syntax, unknown macros)
307
303
  * or warnings (deprecated patterns, suggestions). Log these to help developers
308
304
  * debug issues without failing the build.
309
305
  */
310
306
  for (const diag of result.diagnostics) {
311
- if (diag.level === "error") {
307
+ if (diag.level === 'error') {
312
308
  console.error(`[@macroforge/svelte-preprocessor] Error in ${filename}: ${diag.message}`);
313
309
  }
314
- else if (diag.level === "warning") {
310
+ else if (diag.level === 'warning') {
315
311
  console.warn(`[@macroforge/svelte-preprocessor] Warning in ${filename}: ${diag.message}`);
316
312
  }
317
313
  }
318
314
  /*
319
- * STEP 6: Return Transformed Code
315
+ * STEP 5: Return Transformed Code
320
316
  *
321
317
  * Only return a result if the code was actually modified. Returning undefined
322
318
  * tells Svelte to keep the original content, which is more efficient than
@@ -324,7 +320,7 @@ export function macroforgePreprocess(options = {}) {
324
320
  *
325
321
  * The return object can include:
326
322
  * - code: The transformed source code (required)
327
- * - map: Source map for debugging (optional, not yet implemented)
323
+ * - map: v3 source map built from the engine's segment mapping (when available)
328
324
  */
329
325
  if (result.code && result.code !== content) {
330
326
  const mapping = result.sourceMapping;
@@ -358,8 +354,8 @@ export function macroforgePreprocess(options = {}) {
358
354
  * Note: We only handle script blocks, not markup or style blocks.
359
355
  */
360
356
  return {
361
- name: "macroforge",
362
- script: scriptPreprocessor,
357
+ name: 'macroforge',
358
+ script: scriptPreprocessor
363
359
  };
364
360
  }
365
361
  /**
package/package.json CHANGED
@@ -40,9 +40,9 @@
40
40
  "build": "deno install --node-modules-dir --quiet && deno run -A npm:typescript@5/tsc -p tsconfig.json",
41
41
  "clean": "rm -rf dist",
42
42
  "cleanbuild": "deno task clean && deno task build",
43
- "test": "deno task build && node --test tests/**/*.test.js"
43
+ "test": "deno test --allow-read --allow-env --allow-write --unstable-detect-cjs tests/"
44
44
  },
45
45
  "type": "module",
46
46
  "types": "dist/index.d.ts",
47
- "version": "0.1.79"
47
+ "version": "0.1.83"
48
48
  }