@macroforge/vite-plugin 0.1.33 → 0.1.34

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,10 +1,32 @@
1
1
  # @macroforge/vite-plugin
2
2
 
3
- > **Warning:** This is a work in progress and probably won't work for you. Use at your own risk!
3
+ [![npm version](https://badge.fury.io/js/%40macroforge%2Fvite-plugin.svg)](https://www.npmjs.com/package/@macroforge/vite-plugin)
4
4
 
5
- Vite plugin for macroforge compile-time TypeScript macros.
5
+ ## Overview
6
6
 
7
- Part of the [macroforge](https://github.com/rymskip/macroforge-ts) project.
7
+ @macroforge/vite-plugin
8
+
9
+ Vite plugin for Macroforge compile-time TypeScript macro expansion.
10
+
11
+ This plugin integrates Macroforge's Rust-based macro expander into the Vite build pipeline,
12
+ enabling compile-time code generation through `@derive` decorators. It processes TypeScript
13
+ files during the build, expands macros, generates type definitions, and emits metadata.
14
+
15
+ @example
16
+ ```typescript
17
+ import { defineConfig } from 'vite';
18
+ import macroforgePlugin from '@macroforge/vite-plugin';
19
+
20
+ export default defineConfig({
21
+ plugins: [
22
+ macroforgePlugin({
23
+ generateTypes: true,
24
+ typesOutputDir: 'src/types/generated',
25
+ emitMetadata: true,
26
+ }),
27
+ ],
28
+ });
29
+ ```
8
30
 
9
31
  ## Installation
10
32
 
@@ -12,24 +34,42 @@ Part of the [macroforge](https://github.com/rymskip/macroforge-ts) project.
12
34
  npm install @macroforge/vite-plugin
13
35
  ```
14
36
 
15
- ## Usage
37
+ ## API
16
38
 
17
- ```typescript
18
- // vite.config.ts
19
- import macroforge from "@macroforge/vite-plugin";
39
+ ### Functions
40
+
41
+ - **`loadMacroConfig`** - Whether to preserve `@derive` decorators in the output code after macro expansion.
42
+ - **`napiMacrosPlugin`** - Creates a Vite plugin for Macroforge compile-time macro expansion.
43
+ - **`ensureDir`** - Ensures a directory exists, creating it recursively if necessary.
44
+ - **`writeTypeDefinitions`** - Writes generated TypeScript declaration files to the configured output directory.
45
+ - **`writeMetadata`** - Writes macro intermediate representation (IR) metadata to JSON files.
46
+ - **`formatTransformError`** - Formats transformation errors into user-friendly messages.
47
+
48
+ ### Types
49
+
50
+ - **`NapiMacrosPluginOptions`** - Configuration options for the Macroforge Vite plugin.
51
+ - **`MacroConfig`** - Glob patterns, regular expressions, or arrays of either to specify which files
20
52
 
53
+ ## Examples
54
+
55
+ ```typescript
56
+ import { defineConfig } from 'vite';
57
+ import macroforgePlugin from '@macroforge/vite-plugin';
21
58
  export default defineConfig({
22
- plugins: [
23
- macroforge({
24
- typesOutputDir: ".macroforge/types",
25
- metadataOutputDir: ".macroforge/meta",
26
- generateTypes: true,
27
- emitMetadata: true,
28
- }),
29
- ],
59
+ plugins: [
60
+ macroforgePlugin({
61
+ generateTypes: true,
62
+ typesOutputDir: 'src/types/generated',
63
+ emitMetadata: true,
64
+ }),
65
+ ],
30
66
  });
31
67
  ```
32
68
 
69
+ ## Documentation
70
+
71
+ See the [full documentation](https://macroforge.dev/docs/api/reference/typescript/vite-plugin) on the Macroforge website.
72
+
33
73
  ## License
34
74
 
35
75
  MIT
package/dist/index.d.ts CHANGED
@@ -1,12 +1,186 @@
1
+ /**
2
+ * @module @macroforge/vite-plugin
3
+ *
4
+ * Vite plugin for Macroforge compile-time TypeScript macro expansion.
5
+ *
6
+ * This plugin integrates Macroforge's Rust-based macro expander into the Vite build pipeline,
7
+ * enabling compile-time code generation through `@derive` decorators. It processes TypeScript
8
+ * files during the build, expands macros, generates type definitions, and emits metadata.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * // vite.config.ts
13
+ * import { defineConfig } from 'vite';
14
+ * import macroforgePlugin from '@macroforge/vite-plugin';
15
+ *
16
+ * export default defineConfig({
17
+ * plugins: [
18
+ * macroforgePlugin({
19
+ * generateTypes: true,
20
+ * typesOutputDir: 'src/types/generated',
21
+ * emitMetadata: true,
22
+ * }),
23
+ * ],
24
+ * });
25
+ * ```
26
+ *
27
+ * @packageDocumentation
28
+ */
1
29
  import { Plugin } from "vite";
30
+ /**
31
+ * Configuration options for the Macroforge Vite plugin.
32
+ *
33
+ * @public
34
+ * @example
35
+ * ```typescript
36
+ * const options: NapiMacrosPluginOptions = {
37
+ * include: ['src/**\/*.ts'],
38
+ * exclude: ['**\/*.test.ts'],
39
+ * generateTypes: true,
40
+ * typesOutputDir: 'src/types/generated',
41
+ * emitMetadata: true,
42
+ * metadataOutputDir: 'src/macros/metadata',
43
+ * };
44
+ * ```
45
+ */
2
46
  export interface NapiMacrosPluginOptions {
47
+ /**
48
+ * Glob patterns, regular expressions, or arrays of either to specify which files
49
+ * should be processed by the macro expander.
50
+ *
51
+ * @remarks
52
+ * If not specified, all `.ts` and `.tsx` files (excluding `node_modules`) are processed.
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * include: ['src/**\/*.ts', /components\/.*\.tsx$/]
57
+ * ```
58
+ */
3
59
  include?: string | RegExp | (string | RegExp)[];
60
+ /**
61
+ * Glob patterns, regular expressions, or arrays of either to specify which files
62
+ * should be excluded from macro processing.
63
+ *
64
+ * @remarks
65
+ * Files in `node_modules` are always excluded by default.
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * exclude: ['**\/*.test.ts', '**\/*.spec.ts']
70
+ * ```
71
+ */
4
72
  exclude?: string | RegExp | (string | RegExp)[];
73
+ /**
74
+ * Whether to generate TypeScript declaration files (`.d.ts`) for transformed code.
75
+ *
76
+ * @remarks
77
+ * When enabled, the plugin uses the TypeScript compiler to emit declaration files
78
+ * based on the macro-expanded code. This ensures type definitions accurately reflect
79
+ * the generated code.
80
+ *
81
+ * @default true
82
+ */
5
83
  generateTypes?: boolean;
84
+ /**
85
+ * Output directory for generated TypeScript declaration files.
86
+ *
87
+ * @remarks
88
+ * Path is relative to the project root. The directory structure of the source files
89
+ * is preserved within this output directory.
90
+ *
91
+ * @default "src/macros/generated"
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * // Source: src/models/User.ts
96
+ * // Output: src/types/generated/models/User.d.ts
97
+ * typesOutputDir: 'src/types/generated'
98
+ * ```
99
+ */
6
100
  typesOutputDir?: string;
101
+ /**
102
+ * Whether to emit macro intermediate representation (IR) metadata as JSON files.
103
+ *
104
+ * @remarks
105
+ * The metadata contains information about which macros were applied, their configurations,
106
+ * and the transformation results. This can be useful for debugging, tooling integration,
107
+ * or build analysis.
108
+ *
109
+ * @default true
110
+ */
7
111
  emitMetadata?: boolean;
112
+ /**
113
+ * Output directory for macro IR metadata JSON files.
114
+ *
115
+ * @remarks
116
+ * Path is relative to the project root. If not specified, defaults to the same
117
+ * directory as `typesOutputDir`. Metadata files are named with a `.macro-ir.json` suffix.
118
+ *
119
+ * @default Same as `typesOutputDir`
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * // Source: src/models/User.ts
124
+ * // Output: src/macros/metadata/models/User.macro-ir.json
125
+ * metadataOutputDir: 'src/macros/metadata'
126
+ * ```
127
+ */
8
128
  metadataOutputDir?: string;
9
129
  }
130
+ /**
131
+ * Creates a Vite plugin for Macroforge compile-time macro expansion.
132
+ *
133
+ * @remarks
134
+ * This is the main entry point for integrating Macroforge into a Vite build pipeline.
135
+ * The plugin:
136
+ *
137
+ * 1. **Runs early** (`enforce: "pre"`) to transform code before other plugins
138
+ * 2. **Processes TypeScript files** (`.ts` and `.tsx`) excluding `node_modules`
139
+ * 3. **Expands macros** using the Macroforge Rust binary via `expandSync()`
140
+ * 4. **Generates type definitions** for transformed code (optional, default: enabled)
141
+ * 5. **Emits metadata** about macro transformations (optional, default: enabled)
142
+ *
143
+ * **Plugin Lifecycle:**
144
+ * - `configResolved`: Initializes project root, loads config, and attempts to load Rust binary
145
+ * - `transform`: Processes each TypeScript file through the macro expander
146
+ *
147
+ * **Error Handling:**
148
+ * - If the Rust binary is not available, files pass through unchanged
149
+ * - Macro expansion errors are reported via Vite's `this.error()` mechanism
150
+ * - TypeScript emission errors are logged as warnings
151
+ *
152
+ * @param options - Plugin configuration options
153
+ *
154
+ * @returns A Vite plugin instance
155
+ *
156
+ * @public
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * // Basic usage
161
+ * import macroforgePlugin from '@macroforge/vite-plugin';
162
+ *
163
+ * export default defineConfig({
164
+ * plugins: [macroforgePlugin()],
165
+ * });
166
+ * ```
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * // With custom options
171
+ * import macroforgePlugin from '@macroforge/vite-plugin';
172
+ *
173
+ * export default defineConfig({
174
+ * plugins: [
175
+ * macroforgePlugin({
176
+ * generateTypes: true,
177
+ * typesOutputDir: 'src/types/generated',
178
+ * emitMetadata: false,
179
+ * }),
180
+ * ],
181
+ * });
182
+ * ```
183
+ */
10
184
  declare function napiMacrosPlugin(options?: NapiMacrosPluginOptions): Plugin;
11
185
  export default napiMacrosPlugin;
12
186
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAsC9B,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAChD,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAChD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AA6LD,iBAAS,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,MAAM,CA0LvE;AAED,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAuD9B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAEhD;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAEhD;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;;;;;;;;;;;;;OAeG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;;;;;;;;;;;;OAeG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAiTD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,iBAAS,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,MAAM,CAmTvE;AAED,eAAe,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,31 @@
1
+ /**
2
+ * @module @macroforge/vite-plugin
3
+ *
4
+ * Vite plugin for Macroforge compile-time TypeScript macro expansion.
5
+ *
6
+ * This plugin integrates Macroforge's Rust-based macro expander into the Vite build pipeline,
7
+ * enabling compile-time code generation through `@derive` decorators. It processes TypeScript
8
+ * files during the build, expands macros, generates type definitions, and emits metadata.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * // vite.config.ts
13
+ * import { defineConfig } from 'vite';
14
+ * import macroforgePlugin from '@macroforge/vite-plugin';
15
+ *
16
+ * export default defineConfig({
17
+ * plugins: [
18
+ * macroforgePlugin({
19
+ * generateTypes: true,
20
+ * typesOutputDir: 'src/types/generated',
21
+ * emitMetadata: true,
22
+ * }),
23
+ * ],
24
+ * });
25
+ * ```
26
+ *
27
+ * @packageDocumentation
28
+ */
1
29
  import { createRequire } from "module";
2
30
  import * as fs from "fs";
3
31
  import * as path from "path";
@@ -12,6 +40,23 @@ catch (error) {
12
40
  }
13
41
  const compilerOptionsCache = new Map();
14
42
  let cachedRequire;
43
+ /**
44
+ * Ensures that `require()` is available in the current execution context.
45
+ *
46
+ * @remarks
47
+ * This function handles the ESM/CommonJS interoperability problem. In pure ESM environments,
48
+ * `require` is not defined, but some native modules (like the Macroforge Rust binary) may
49
+ * depend on it being available. This function:
50
+ *
51
+ * 1. Returns the existing `require` if already available (CommonJS context)
52
+ * 2. Creates a synthetic `require` using Node's `createRequire` API (ESM context)
53
+ * 3. Exposes the created `require` on `globalThis` for native runtime loaders
54
+ * 4. Caches the result to avoid redundant creation
55
+ *
56
+ * @returns A Promise resolving to a Node.js `require` function
57
+ *
58
+ * @internal
59
+ */
15
60
  async function ensureRequire() {
16
61
  if (typeof require !== "undefined") {
17
62
  return require;
@@ -24,6 +69,31 @@ async function ensureRequire() {
24
69
  }
25
70
  return cachedRequire;
26
71
  }
72
+ /**
73
+ * Loads Macroforge configuration from `macroforge.json`.
74
+ *
75
+ * @remarks
76
+ * Searches for `macroforge.json` starting from `projectRoot` and traversing up the
77
+ * directory tree until found or the filesystem root is reached. This allows monorepo
78
+ * setups where the config may be at the workspace root rather than the package root.
79
+ *
80
+ * If the config file is found but cannot be parsed (invalid JSON), returns the
81
+ * default configuration rather than throwing an error.
82
+ *
83
+ * @param projectRoot - The directory to start searching from (usually Vite's resolved root)
84
+ *
85
+ * @returns The loaded configuration, or a default config if no file is found
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * // macroforge.json
90
+ * {
91
+ * "keepDecorators": true
92
+ * }
93
+ * ```
94
+ *
95
+ * @internal
96
+ */
27
97
  function loadMacroConfig(projectRoot) {
28
98
  let current = projectRoot;
29
99
  const fallback = { keepDecorators: false };
@@ -46,6 +116,40 @@ function loadMacroConfig(projectRoot) {
46
116
  }
47
117
  return fallback;
48
118
  }
119
+ /**
120
+ * Retrieves and normalizes TypeScript compiler options for declaration emission.
121
+ *
122
+ * @remarks
123
+ * This function reads the project's `tsconfig.json` and adjusts the compiler options
124
+ * specifically for generating declaration files from macro-expanded code. The function:
125
+ *
126
+ * 1. Locates `tsconfig.json` using TypeScript's built-in config file discovery
127
+ * 2. Parses and validates the configuration
128
+ * 3. Normalizes options for declaration-only emission
129
+ * 4. Caches results per project root for performance
130
+ *
131
+ * **Forced Options:**
132
+ * - `declaration: true` - Enable declaration file output
133
+ * - `emitDeclarationOnly: true` - Only emit `.d.ts` files, not JavaScript
134
+ * - `noEmitOnError: false` - Continue emission even with type errors
135
+ * - `incremental: false` - Disable incremental compilation
136
+ *
137
+ * **Default Options** (applied if not specified in tsconfig):
138
+ * - `moduleResolution: Bundler` - Modern bundler-style resolution
139
+ * - `module: ESNext` - ES module output
140
+ * - `target: ESNext` - Latest ECMAScript target
141
+ * - `strict: true` - Enable strict type checking
142
+ * - `skipLibCheck: true` - Skip type checking of declaration files
143
+ *
144
+ * **Removed Options:**
145
+ * - `outDir` and `outFile` - Removed to allow programmatic output control
146
+ *
147
+ * @param projectRoot - The project root directory to search for tsconfig.json
148
+ *
149
+ * @returns Normalized compiler options, or `undefined` if TypeScript is not available
150
+ *
151
+ * @internal
152
+ */
49
153
  function getCompilerOptions(projectRoot) {
50
154
  if (!tsModule) {
51
155
  return undefined;
@@ -81,6 +185,7 @@ function getCompilerOptions(projectRoot) {
81
185
  else {
82
186
  options = {};
83
187
  }
188
+ // Normalize options for declaration-only emission
84
189
  const normalized = {
85
190
  ...options,
86
191
  declaration: true,
@@ -88,8 +193,10 @@ function getCompilerOptions(projectRoot) {
88
193
  noEmitOnError: false,
89
194
  incremental: false,
90
195
  };
196
+ // Remove output path options to allow programmatic control
91
197
  delete normalized.outDir;
92
198
  delete normalized.outFile;
199
+ // Apply sensible defaults for modern TypeScript projects
93
200
  normalized.moduleResolution ??= tsModule.ModuleResolutionKind.Bundler;
94
201
  normalized.module ??= tsModule.ModuleKind.ESNext;
95
202
  normalized.target ??= tsModule.ScriptTarget.ESNext;
@@ -98,6 +205,36 @@ function getCompilerOptions(projectRoot) {
98
205
  compilerOptionsCache.set(projectRoot, normalized);
99
206
  return normalized;
100
207
  }
208
+ /**
209
+ * Generates TypeScript declaration files (`.d.ts`) from in-memory source code.
210
+ *
211
+ * @remarks
212
+ * This function creates a virtual TypeScript compilation environment to emit declaration
213
+ * files from macro-expanded code. It sets up a custom compiler host that serves the
214
+ * transformed source from memory while delegating other file operations to the filesystem.
215
+ *
216
+ * **Virtual Compiler Host:**
217
+ * The custom compiler host intercepts file operations for the target file:
218
+ * - `getSourceFile`: Returns the in-memory code for the target file, filesystem for others
219
+ * - `readFile`: Returns the in-memory code for the target file, filesystem for others
220
+ * - `fileExists`: Reports the target file as existing even though it's virtual
221
+ *
222
+ * This approach allows generating declarations for transformed code without writing
223
+ * intermediate files to disk.
224
+ *
225
+ * **Error Handling:**
226
+ * If declaration emission fails (e.g., due to type errors in the transformed code),
227
+ * diagnostics are formatted and logged as warnings, and `undefined` is returned.
228
+ *
229
+ * @param code - The macro-expanded TypeScript source code
230
+ * @param fileName - The original file path (used for module resolution and output naming)
231
+ * @param projectRoot - The project root directory (used for diagnostic formatting)
232
+ *
233
+ * @returns The generated declaration file content, or `undefined` if emission failed
234
+ * or TypeScript is not available
235
+ *
236
+ * @internal
237
+ */
101
238
  function emitDeclarationsFromCode(code, fileName, projectRoot) {
102
239
  if (!tsModule) {
103
240
  return undefined;
@@ -109,6 +246,7 @@ function emitDeclarationsFromCode(code, fileName, projectRoot) {
109
246
  const normalizedFileName = path.resolve(fileName);
110
247
  const sourceText = code;
111
248
  const compilerHost = tsModule.createCompilerHost(compilerOptions, true);
249
+ // Override getSourceFile to serve in-memory code for the target file
112
250
  compilerHost.getSourceFile = (requestedFileName, languageVersion) => {
113
251
  if (path.resolve(requestedFileName) === normalizedFileName) {
114
252
  return tsModule.createSourceFile(requestedFileName, sourceText, languageVersion, true);
@@ -118,15 +256,18 @@ function emitDeclarationsFromCode(code, fileName, projectRoot) {
118
256
  ? tsModule.createSourceFile(requestedFileName, text, languageVersion, true)
119
257
  : undefined;
120
258
  };
259
+ // Override readFile to serve in-memory code for the target file
121
260
  compilerHost.readFile = (requestedFileName) => {
122
261
  return path.resolve(requestedFileName) === normalizedFileName
123
262
  ? sourceText
124
263
  : tsModule.sys.readFile(requestedFileName);
125
264
  };
265
+ // Override fileExists to report the virtual file as existing
126
266
  compilerHost.fileExists = (requestedFileName) => {
127
267
  return (path.resolve(requestedFileName) === normalizedFileName ||
128
268
  tsModule.sys.fileExists(requestedFileName));
129
269
  };
270
+ // Capture emitted declaration content
130
271
  let output;
131
272
  const writeFile = (outputName, text) => {
132
273
  if (outputName.endsWith(".d.ts")) {
@@ -135,6 +276,7 @@ function emitDeclarationsFromCode(code, fileName, projectRoot) {
135
276
  };
136
277
  const program = tsModule.createProgram([normalizedFileName], compilerOptions, compilerHost);
137
278
  const emitResult = program.emit(undefined, writeFile, undefined, true);
279
+ // Log diagnostics if emission was skipped due to errors
138
280
  if (emitResult.emitSkipped && emitResult.diagnostics.length > 0) {
139
281
  const formatted = tsModule.formatDiagnosticsWithColorAndContext(emitResult.diagnostics, {
140
282
  getCurrentDirectory: () => projectRoot,
@@ -146,20 +288,100 @@ function emitDeclarationsFromCode(code, fileName, projectRoot) {
146
288
  }
147
289
  return output;
148
290
  }
291
+ /**
292
+ * Creates a Vite plugin for Macroforge compile-time macro expansion.
293
+ *
294
+ * @remarks
295
+ * This is the main entry point for integrating Macroforge into a Vite build pipeline.
296
+ * The plugin:
297
+ *
298
+ * 1. **Runs early** (`enforce: "pre"`) to transform code before other plugins
299
+ * 2. **Processes TypeScript files** (`.ts` and `.tsx`) excluding `node_modules`
300
+ * 3. **Expands macros** using the Macroforge Rust binary via `expandSync()`
301
+ * 4. **Generates type definitions** for transformed code (optional, default: enabled)
302
+ * 5. **Emits metadata** about macro transformations (optional, default: enabled)
303
+ *
304
+ * **Plugin Lifecycle:**
305
+ * - `configResolved`: Initializes project root, loads config, and attempts to load Rust binary
306
+ * - `transform`: Processes each TypeScript file through the macro expander
307
+ *
308
+ * **Error Handling:**
309
+ * - If the Rust binary is not available, files pass through unchanged
310
+ * - Macro expansion errors are reported via Vite's `this.error()` mechanism
311
+ * - TypeScript emission errors are logged as warnings
312
+ *
313
+ * @param options - Plugin configuration options
314
+ *
315
+ * @returns A Vite plugin instance
316
+ *
317
+ * @public
318
+ *
319
+ * @example
320
+ * ```typescript
321
+ * // Basic usage
322
+ * import macroforgePlugin from '@macroforge/vite-plugin';
323
+ *
324
+ * export default defineConfig({
325
+ * plugins: [macroforgePlugin()],
326
+ * });
327
+ * ```
328
+ *
329
+ * @example
330
+ * ```typescript
331
+ * // With custom options
332
+ * import macroforgePlugin from '@macroforge/vite-plugin';
333
+ *
334
+ * export default defineConfig({
335
+ * plugins: [
336
+ * macroforgePlugin({
337
+ * generateTypes: true,
338
+ * typesOutputDir: 'src/types/generated',
339
+ * emitMetadata: false,
340
+ * }),
341
+ * ],
342
+ * });
343
+ * ```
344
+ */
149
345
  function napiMacrosPlugin(options = {}) {
346
+ /**
347
+ * Reference to the loaded Macroforge Rust binary module.
348
+ * Contains the `expandSync` function for synchronous macro expansion.
349
+ * Will be `undefined` if the binary failed to load.
350
+ */
150
351
  let rustTransformer;
352
+ /** The resolved Vite project root directory */
151
353
  let projectRoot;
354
+ /** Loaded configuration from macroforge.json */
152
355
  let macroConfig = { keepDecorators: false };
356
+ // Resolve options with defaults
153
357
  const generateTypes = options.generateTypes !== false; // Default to true
154
358
  const typesOutputDir = options.typesOutputDir || "src/macros/generated";
155
359
  const emitMetadata = options.emitMetadata !== false;
156
360
  const metadataOutputDir = options.metadataOutputDir || typesOutputDir;
157
- // Ensure directory exists
361
+ /**
362
+ * Ensures a directory exists, creating it recursively if necessary.
363
+ *
364
+ * @param dir - The directory path to ensure exists
365
+ */
158
366
  function ensureDir(dir) {
159
367
  if (!fs.existsSync(dir)) {
160
368
  fs.mkdirSync(dir, { recursive: true });
161
369
  }
162
370
  }
371
+ /**
372
+ * Writes generated TypeScript declaration files to the configured output directory.
373
+ *
374
+ * @remarks
375
+ * Preserves the source file's directory structure within the output directory.
376
+ * Implements change detection to avoid unnecessary file writes - only writes
377
+ * if the content differs from the existing file (or the file doesn't exist).
378
+ *
379
+ * Output path formula:
380
+ * `{projectRoot}/{typesOutputDir}/{relative/path/to/source}/{filename}.d.ts`
381
+ *
382
+ * @param id - The absolute path of the source file
383
+ * @param types - The generated declaration file content
384
+ */
163
385
  function writeTypeDefinitions(id, types) {
164
386
  const relativePath = path.relative(projectRoot, id);
165
387
  const parsed = path.parse(relativePath);
@@ -179,6 +401,23 @@ function napiMacrosPlugin(options = {}) {
179
401
  console.error(`[@macroforge/vite-plugin] Failed to write type definitions for ${id}:`, error);
180
402
  }
181
403
  }
404
+ /**
405
+ * Writes macro intermediate representation (IR) metadata to JSON files.
406
+ *
407
+ * @remarks
408
+ * Preserves the source file's directory structure within the output directory.
409
+ * Implements change detection to avoid unnecessary file writes - only writes
410
+ * if the content differs from the existing file (or the file doesn't exist).
411
+ *
412
+ * Output path formula:
413
+ * `{projectRoot}/{metadataOutputDir}/{relative/path/to/source}/{filename}.macro-ir.json`
414
+ *
415
+ * The metadata contains information about which macros were applied and their
416
+ * transformation results, useful for debugging and tooling integration.
417
+ *
418
+ * @param id - The absolute path of the source file
419
+ * @param metadata - The macro IR metadata as a JSON string
420
+ */
182
421
  function writeMetadata(id, metadata) {
183
422
  const relativePath = path.relative(projectRoot, id);
184
423
  const parsed = path.parse(relativePath);
@@ -198,6 +437,19 @@ function napiMacrosPlugin(options = {}) {
198
437
  console.error(`[@macroforge/vite-plugin] Failed to write metadata for ${id}:`, error);
199
438
  }
200
439
  }
440
+ /**
441
+ * Formats transformation errors into user-friendly messages.
442
+ *
443
+ * @remarks
444
+ * Handles both Error instances and unknown error types. For Error instances,
445
+ * includes the full stack trace if available. Paths are made relative to the
446
+ * project root for readability.
447
+ *
448
+ * @param error - The caught error (can be any type)
449
+ * @param id - The absolute path of the file that failed to transform
450
+ *
451
+ * @returns A formatted error message string with plugin prefix
452
+ */
201
453
  function formatTransformError(error, id) {
202
454
  const relative = projectRoot ? path.relative(projectRoot, id) || id : id;
203
455
  if (error instanceof Error) {
@@ -209,8 +461,33 @@ function napiMacrosPlugin(options = {}) {
209
461
  return `[@macroforge/vite-plugin] Failed to transform ${relative}: ${String(error)}`;
210
462
  }
211
463
  return {
464
+ /**
465
+ * The unique identifier for this plugin.
466
+ * Used by Vite for plugin ordering and error reporting.
467
+ */
212
468
  name: "@macroforge/vite-plugin",
469
+ /**
470
+ * Run this plugin before other plugins.
471
+ *
472
+ * @remarks
473
+ * Macro expansion must happen early in the build pipeline so that
474
+ * subsequent plugins (like TypeScript compilation) see the expanded code.
475
+ */
213
476
  enforce: "pre",
477
+ /**
478
+ * Hook called when Vite config has been resolved.
479
+ *
480
+ * @remarks
481
+ * Performs plugin initialization:
482
+ * 1. Stores the resolved project root directory
483
+ * 2. Loads the Macroforge configuration from `macroforge.json`
484
+ * 3. Attempts to load the Rust macro expander binary
485
+ *
486
+ * If the Rust binary fails to load, a warning is logged but the plugin
487
+ * continues to function (files will pass through unchanged).
488
+ *
489
+ * @param config - The resolved Vite configuration
490
+ */
214
491
  configResolved(config) {
215
492
  projectRoot = config.root;
216
493
  macroConfig = loadMacroConfig(projectRoot);
@@ -223,7 +500,35 @@ function napiMacrosPlugin(options = {}) {
223
500
  console.warn(error);
224
501
  }
225
502
  },
503
+ /**
504
+ * Transform hook for processing TypeScript files through the macro expander.
505
+ *
506
+ * @remarks
507
+ * This is the core of the plugin. For each TypeScript file (`.ts` or `.tsx`):
508
+ *
509
+ * 1. **Filtering**: Skips non-TypeScript files and `node_modules`
510
+ * 2. **Expansion**: Calls the Rust binary's `expandSync()` function
511
+ * 3. **Diagnostics**: Reports errors via `this.error()`, logs warnings
512
+ * 4. **Post-processing**: Removes macro-only imports to prevent SSR issues
513
+ * 5. **Type Generation**: Optionally generates `.d.ts` files
514
+ * 6. **Metadata Emission**: Optionally writes macro IR JSON files
515
+ *
516
+ * **Return Value:**
517
+ * - Returns `null` if the file should not be transformed (not TS, in node_modules, etc.)
518
+ * - Returns `{ code, map }` with the transformed code (source maps not yet supported)
519
+ *
520
+ * **Error Handling:**
521
+ * - Macro expansion errors are reported via Vite's error mechanism
522
+ * - Vite plugin errors are re-thrown to preserve plugin attribution
523
+ * - Other errors are formatted and reported
524
+ *
525
+ * @param code - The source code to transform
526
+ * @param id - The absolute file path
527
+ *
528
+ * @returns Transformed code and source map, or null if no transformation needed
529
+ */
226
530
  async transform(code, id) {
531
+ // Ensure require() is available for native module loading
227
532
  await ensureRequire();
228
533
  // Only transform TypeScript files
229
534
  if (!id.endsWith(".ts") && !id.endsWith(".tsx")) {
@@ -239,10 +544,11 @@ function napiMacrosPlugin(options = {}) {
239
544
  return null;
240
545
  }
241
546
  try {
547
+ // Perform macro expansion via the Rust binary
242
548
  const result = rustTransformer.expandSync(code, id, {
243
549
  keepDecorators: macroConfig.keepDecorators,
244
550
  });
245
- // Report diagnostics
551
+ // Report diagnostics from macro expansion
246
552
  for (const diag of result.diagnostics) {
247
553
  if (diag.level === "error") {
248
554
  const message = `Macro error at ${id}:${diag.start ?? "?"}-${diag.end ?? "?"}: ${diag.message}`;
@@ -255,19 +561,23 @@ function napiMacrosPlugin(options = {}) {
255
561
  }
256
562
  if (result && result.code) {
257
563
  // TODO: Needs complete overhaul and dynamic attribute removal NO HARDCODING
564
+ // Decorator removal is currently handled by the Rust binary based on keepDecorators config
258
565
  // if (!macroConfig.keepDecorators) {
259
566
  // result.code = result.code
260
567
  // .replace(/\/\*\*\s*@derive[\s\S]*?\*\/\s*/gi, "")
261
568
  // .replace(/\/\*\*\s*@debug[\s\S]*?\*\/\s*/gi, "");
262
569
  // }
263
570
  // Remove macro-only imports so SSR output doesn't load native bindings
571
+ // These imports are only needed at compile-time for type checking
264
572
  result.code = result.code.replace(/\/\*\*\s*import\s+macro[\s\S]*?\*\/\s*/gi, "");
573
+ // Generate type definitions if enabled
265
574
  if (generateTypes) {
266
575
  const emitted = emitDeclarationsFromCode(result.code, id, projectRoot);
267
576
  if (emitted) {
268
577
  writeTypeDefinitions(id, emitted);
269
578
  }
270
579
  }
580
+ // Write macro IR metadata if enabled
271
581
  if (emitMetadata && result.metadata) {
272
582
  writeMetadata(id, result.metadata);
273
583
  }
@@ -278,9 +588,11 @@ function napiMacrosPlugin(options = {}) {
278
588
  }
279
589
  }
280
590
  catch (error) {
591
+ // Re-throw Vite plugin errors to preserve plugin attribution
281
592
  if (error && typeof error === "object" && "plugin" in error) {
282
593
  throw error;
283
594
  }
595
+ // Format and report other errors
284
596
  const message = formatTransformError(error, id);
285
597
  this.error(message);
286
598
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@macroforge/vite-plugin",
3
- "version": "0.1.33",
3
+ "version": "0.1.34",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,7 +18,7 @@
18
18
  "test": "npm run build && node --test tests/**/*.test.js"
19
19
  },
20
20
  "dependencies": {
21
- "macroforge": "^0.1.33"
21
+ "macroforge": "^0.1.34"
22
22
  },
23
23
  "devDependencies": {
24
24
  "typescript": "^5.9.3",