@macroforge/vite-plugin 0.1.32 → 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 +55 -15
- package/dist/index.d.ts +174 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +314 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
# @macroforge/vite-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@macroforge/vite-plugin)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
##
|
|
37
|
+
## API
|
|
16
38
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,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
|
-
|
|
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.
|
|
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.
|
|
21
|
+
"macroforge": "^0.1.34"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"typescript": "^5.9.3",
|