@acusti/vite-plugin-react-compiler 0.7.0 → 0.9.0

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
@@ -97,7 +97,8 @@ The plugin takes an optional options object with four properties:
97
97
  `enableTreatRefLikeIdentifiersAsRefs`, and so on. Callback-valued options
98
98
  (e.g. `logger`, function-valued `sources`) don’t cross the native
99
99
  boundary and aren’t supported; `sources` accepts an array of filename
100
- substrings instead.
100
+ substrings instead, and `reportDiagnostics` (below) covers the common use
101
+ of `logger`, seeing what the compiler skipped.
101
102
  - **`memoize`**: cache transform results per module id, keyed by a content
102
103
  hash (bounded to one entry per file). Multi-environment builds (e.g.
103
104
  React Router apps building client + SSR environments) run every transform
@@ -113,11 +114,28 @@ reactCompiler({
113
114
  });
114
115
  ```
115
116
 
116
- If the compiler reports a fatal error (a parse failure or rejected
117
- options), the plugin fails the build via `this.error(...)`, just like a
118
- Babel syntax error would. Nonfatal compiler bail-outs behave as they do
119
- with the Babel plugin under `panicThreshold: 'none'`: the affected function
120
- is left uncompiled and the build continues.
117
+ If the compiler reports a fatal error (a parse failure, rejected options,
118
+ or a diagnostic escalated by `panicThreshold`), the plugin fails the build
119
+ via `this.error(...)`, just like a Babel syntax error would. Nonfatal
120
+ compiler bail-outs behave as they do with the Babel plugin under
121
+ `panicThreshold: 'none'`: the affected function is left uncompiled and the
122
+ build continues.
123
+
124
+ Those bail-outs are silent by default. To see what the compiler skipped and
125
+ why, set `compiler: { reportDiagnostics: true }`: each recoverable
126
+ diagnostic (a bail-out, or a react-hooks lint suppression that opts a
127
+ function out) is printed as a warning in your build or dev server output,
128
+ with the compiler’s code frame and its position in the file, and the build
129
+ continues.
130
+
131
+ ```ts
132
+ reactCompiler({ compiler: { reportDiagnostics: true } });
133
+ ```
134
+
135
+ With `memoize` on, a file’s diagnostics are reported once per distinct
136
+ content, not once per environment. With `panicThreshold` set to
137
+ `'critical_errors'` or `'all_errors'`, the diagnostic that escalates fails
138
+ the build through `this.error(...)` as described above instead of warning.
121
139
 
122
140
  [compiler options]:
123
141
  https://react.dev/reference/react-compiler/configuration
package/dist/index.d.ts CHANGED
@@ -23,6 +23,8 @@ export type Options = {
23
23
  * Options passed verbatim to React Compiler, using the same names as
24
24
  * babel-plugin-react-compiler (compilationMode, panicThreshold
25
25
  * (defaults to 'none'), target (defaults to '19'), environment, etc.).
26
+ * Set reportDiagnostics to true to have the compiler’s recoverable
27
+ * diagnostics (e.g. bail-outs) reported as build warnings.
26
28
  */
27
29
  compiler?: ReactCompilerOptions;
28
30
  };
package/dist/index.js CHANGED
@@ -21,9 +21,10 @@ function vitePluginReactCompiler(options = {}) {
21
21
  reactCompiler: options.compiler ?? {},
22
22
  sourcemap: true
23
23
  });
24
- if (transformed.fatal) {
25
- const byteStart = transformed.errors[0]?.labels[0]?.start;
26
- this.error(transformed.errors.map((error) => error.codeframe ? `${error.message}\n${error.codeframe}` : error.message).join("\n"), byteStart == null ? void 0 : Buffer.from(code, "utf8").subarray(0, byteStart).toString("utf8").length);
24
+ if (transformed.fatal) this.error(transformed.errors.map(formatDiagnostic).join("\n"), getPosition(Buffer.from(code, "utf8"), transformed.errors[0]));
25
+ if (transformed.errors.length > 0) {
26
+ const utf8Code = Buffer.from(code, "utf8");
27
+ for (const error of transformed.errors) this.warn(formatDiagnostic(error), getPosition(utf8Code, error));
27
28
  }
28
29
  return {
29
30
  code: transformed.code,
@@ -46,6 +47,13 @@ function vitePluginReactCompiler(options = {}) {
46
47
  }
47
48
  };
48
49
  }
50
+ function formatDiagnostic(error) {
51
+ return error.codeframe ? `${error.message}\n${error.codeframe}` : error.message;
52
+ }
53
+ function getPosition(utf8Code, error) {
54
+ const byteStart = error?.labels[0]?.start;
55
+ return byteStart == null ? void 0 : utf8Code.subarray(0, byteStart).toString("utf8").length;
56
+ }
49
57
  //#endregion
50
58
  export { vitePluginReactCompiler as default };
51
59
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { Buffer } from 'node:buffer';\nimport { createHash } from 'node:crypto';\n\nimport {\n type ReactCompilerOptions,\n type SourceMap,\n transform,\n} from 'oxc-transform-react';\nimport { createFilter, type FilterPattern, type Plugin } from 'vite';\n\nexport type Options = {\n /**\n * Modules to exclude from the compiler pass (takes precedence over\n * include).\n * @default /[/\\\\]node_modules[/\\\\]/\n */\n exclude?: FilterPattern;\n /**\n * Modules to run the compiler pass on.\n * @default /\\.[jt]sx?$/\n */\n include?: FilterPattern;\n /**\n * Cache transform results by module id + content hash so repeated\n * transforms of identical content (e.g. one per environment in\n * multi-environment builds) only run the compiler once per file.\n * @default true\n */\n memoize?: boolean;\n /**\n * Options passed verbatim to React Compiler, using the same names as\n * babel-plugin-react-compiler (compilationMode, panicThreshold\n * (defaults to 'none'), target (defaults to '19'), environment, etc.).\n */\n compiler?: ReactCompilerOptions;\n};\n\ntype CachedTransform = {\n hash: string;\n result: Promise<TransformOutput>;\n};\n\ntype TransformOutput = { code: string; map?: SourceMap };\n\nconst defaultExclude = /[/\\\\]node_modules[/\\\\]/;\nconst defaultInclude = /\\.[jt]sx?$/;\n\nexport default function vitePluginReactCompiler(options: Options = {}): Plugin {\n const filter = createFilter(\n options.include ?? defaultInclude,\n options.exclude ?? defaultExclude,\n );\n const isMemoizing = options.memoize !== false;\n // one entry per module id, replaced whenever the content hash changes;\n // holds the promise so overlapping transforms of identical content\n // (e.g. concurrent client + SSR environments) share a single run\n const cache = new Map<string, CachedTransform>();\n\n return {\n enforce: 'pre',\n name: 'vite-plugin-react-compiler',\n transform(code, id) {\n // vite module ids can carry a query suffix (e.g. sub-request\n // queries from other plugins); filter, cache, and compile by\n // the underlying file path — like @vitejs/plugin-react — so\n // the cache stays one-entry-per-file and sourcemap sources\n // don’t include vite queries\n const [filepath] = id.split('?');\n if (!filter(filepath)) return null;\n\n const run = async (): Promise<TransformOutput> => {\n const transformed = await transform(filepath, code, {\n // leave JSX for vite’s own pipeline (refresh, dev runtime)\n jsx: 'preserve',\n reactCompiler: options.compiler ?? {},\n sourcemap: true,\n });\n\n // fatal means a parse failure or rejected options: no code\n // was emitted, so break the build like a Babel syntax error\n // would, forwarding the first error’s position so vite can\n // report the location alongside oxc’s own code frames\n if (transformed.fatal) {\n // oxc label offsets are utf-8 byte offsets, but rollup’s\n // pos argument indexes the (utf-16) code string\n const byteStart = transformed.errors[0]?.labels[0]?.start;\n this.error(\n transformed.errors\n .map((error) =>\n error.codeframe\n ? `${error.message}\\n${error.codeframe}`\n : error.message,\n )\n .join('\\n'),\n byteStart == null\n ? undefined\n : Buffer.from(code, 'utf8')\n .subarray(0, byteStart)\n .toString('utf8').length,\n );\n }\n\n return { code: transformed.code, map: transformed.map };\n };\n\n if (!isMemoizing) return run();\n\n const hash = createHash('sha1').update(code).digest('hex');\n const cached = cache.get(filepath);\n if (cached?.hash === hash) return cached.result;\n\n const result = run();\n cache.set(filepath, { hash, result });\n // drop failed transforms so the next request retries them\n result.catch(() => {\n if (cache.get(filepath)?.result === result) cache.delete(filepath);\n });\n return result;\n },\n };\n}\n"],"mappings":";;;;;AA4CA,IAAM,iBAAiB;AACvB,IAAM,iBAAiB;AAEvB,SAAwB,wBAAwB,UAAmB,CAAC,GAAW;CAC3E,MAAM,SAAS,aACX,QAAQ,WAAW,gBACnB,QAAQ,WAAW,cACvB;CACA,MAAM,cAAc,QAAQ,YAAY;CAIxC,MAAM,wBAAQ,IAAI,IAA6B;CAE/C,OAAO;EACH,SAAS;EACT,MAAM;EACN,UAAU,MAAM,IAAI;GAMhB,MAAM,CAAC,YAAY,GAAG,MAAM,GAAG;GAC/B,IAAI,CAAC,OAAO,QAAQ,GAAG,OAAO;GAE9B,MAAM,MAAM,YAAsC;IAC9C,MAAM,cAAc,MAAM,UAAU,UAAU,MAAM;KAEhD,KAAK;KACL,eAAe,QAAQ,YAAY,CAAC;KACpC,WAAW;IACf,CAAC;IAMD,IAAI,YAAY,OAAO;KAGnB,MAAM,YAAY,YAAY,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE;KACpD,KAAK,MACD,YAAY,OACP,KAAK,UACF,MAAM,YACA,GAAG,MAAM,QAAQ,IAAI,MAAM,cAC3B,MAAM,OAChB,CAAC,CACA,KAAK,IAAI,GACd,aAAa,OACP,KAAA,IACA,OAAO,KAAK,MAAM,MAAM,CAAC,CACpB,SAAS,GAAG,SAAS,CAAC,CACtB,SAAS,MAAM,CAAC,CAAC,MAChC;IACJ;IAEA,OAAO;KAAE,MAAM,YAAY;KAAM,KAAK,YAAY;IAAI;GAC1D;GAEA,IAAI,CAAC,aAAa,OAAO,IAAI;GAE7B,MAAM,OAAO,WAAW,MAAM,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,KAAK;GACzD,MAAM,SAAS,MAAM,IAAI,QAAQ;GACjC,IAAI,QAAQ,SAAS,MAAM,OAAO,OAAO;GAEzC,MAAM,SAAS,IAAI;GACnB,MAAM,IAAI,UAAU;IAAE;IAAM;GAAO,CAAC;GAEpC,OAAO,YAAY;IACf,IAAI,MAAM,IAAI,QAAQ,CAAC,EAAE,WAAW,QAAQ,MAAM,OAAO,QAAQ;GACrE,CAAC;GACD,OAAO;EACX;CACJ;AACJ"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { Buffer } from 'node:buffer';\nimport { createHash } from 'node:crypto';\n\nimport {\n type OxcError,\n type ReactCompilerOptions,\n type SourceMap,\n transform,\n} from 'oxc-transform-react';\nimport { createFilter, type FilterPattern, type Plugin } from 'vite';\n\nexport type Options = {\n /**\n * Modules to exclude from the compiler pass (takes precedence over\n * include).\n * @default /[/\\\\]node_modules[/\\\\]/\n */\n exclude?: FilterPattern;\n /**\n * Modules to run the compiler pass on.\n * @default /\\.[jt]sx?$/\n */\n include?: FilterPattern;\n /**\n * Cache transform results by module id + content hash so repeated\n * transforms of identical content (e.g. one per environment in\n * multi-environment builds) only run the compiler once per file.\n * @default true\n */\n memoize?: boolean;\n /**\n * Options passed verbatim to React Compiler, using the same names as\n * babel-plugin-react-compiler (compilationMode, panicThreshold\n * (defaults to 'none'), target (defaults to '19'), environment, etc.).\n * Set reportDiagnostics to true to have the compiler’s recoverable\n * diagnostics (e.g. bail-outs) reported as build warnings.\n */\n compiler?: ReactCompilerOptions;\n};\n\ntype CachedTransform = {\n hash: string;\n result: Promise<TransformOutput>;\n};\n\ntype TransformOutput = { code: string; map?: SourceMap };\n\nconst defaultExclude = /[/\\\\]node_modules[/\\\\]/;\nconst defaultInclude = /\\.[jt]sx?$/;\n\nexport default function vitePluginReactCompiler(options: Options = {}): Plugin {\n const filter = createFilter(\n options.include ?? defaultInclude,\n options.exclude ?? defaultExclude,\n );\n const isMemoizing = options.memoize !== false;\n // one entry per module id, replaced whenever the content hash changes;\n // holds the promise so overlapping transforms of identical content\n // (e.g. concurrent client + SSR environments) share a single run\n const cache = new Map<string, CachedTransform>();\n\n return {\n enforce: 'pre',\n name: 'vite-plugin-react-compiler',\n transform(code, id) {\n // vite module ids can carry a query suffix (e.g. sub-request\n // queries from other plugins); filter, cache, and compile by\n // the underlying file path — like @vitejs/plugin-react — so\n // the cache stays one-entry-per-file and sourcemap sources\n // don’t include vite queries\n const [filepath] = id.split('?');\n if (!filter(filepath)) return null;\n\n const run = async (): Promise<TransformOutput> => {\n const transformed = await transform(filepath, code, {\n // leave JSX for vite’s own pipeline (refresh, dev runtime)\n jsx: 'preserve',\n reactCompiler: options.compiler ?? {},\n sourcemap: true,\n });\n\n // fatal means a parse failure, rejected options, or a\n // diagnostic escalated by panicThreshold: no code was\n // emitted, so break the build like a Babel syntax error\n // would, forwarding the first error’s position so vite can\n // report the location alongside oxc’s own code frames\n if (transformed.fatal) {\n this.error(\n transformed.errors.map(formatDiagnostic).join('\\n'),\n getPosition(Buffer.from(code, 'utf8'), transformed.errors[0]),\n );\n }\n\n // anything left in errors is recoverable (the code was\n // emitted): the compiler’s bail-outs and rule suppressions,\n // present only once reportDiagnostics is on, and oxc’s own\n // non-fatal diagnostics (e.g. a partially supported\n // typescript namespace), which vite’s oxc pass never sees\n // because the typescript is already stripped; oxc labels\n // the compiler’s diagnostics severity 'Error', so warn\n // about each one at its own position rather than failing\n // the build\n if (transformed.errors.length > 0) {\n const utf8Code = Buffer.from(code, 'utf8');\n for (const error of transformed.errors) {\n this.warn(formatDiagnostic(error), getPosition(utf8Code, error));\n }\n }\n\n return { code: transformed.code, map: transformed.map };\n };\n\n if (!isMemoizing) return run();\n\n const hash = createHash('sha1').update(code).digest('hex');\n const cached = cache.get(filepath);\n if (cached?.hash === hash) return cached.result;\n\n const result = run();\n cache.set(filepath, { hash, result });\n // drop failed transforms so the next request retries them\n result.catch(() => {\n if (cache.get(filepath)?.result === result) cache.delete(filepath);\n });\n return result;\n },\n };\n}\n\n// oxc’s message followed by its code frame (when it has one)\nfunction formatDiagnostic(error: OxcError): string {\n return error.codeframe ? `${error.message}\\n${error.codeframe}` : error.message;\n}\n\n// oxc label offsets are utf-8 byte offsets, but rollup’s pos argument\n// indexes the (utf-16) code string; takes the code already encoded as utf-8\n// so callers converting several positions encode it once\nfunction getPosition(utf8Code: Buffer, error: OxcError | undefined): number | undefined {\n const byteStart = error?.labels[0]?.start;\n return byteStart == null\n ? undefined\n : utf8Code.subarray(0, byteStart).toString('utf8').length;\n}\n"],"mappings":";;;;;AA+CA,IAAM,iBAAiB;AACvB,IAAM,iBAAiB;AAEvB,SAAwB,wBAAwB,UAAmB,CAAC,GAAW;CAC3E,MAAM,SAAS,aACX,QAAQ,WAAW,gBACnB,QAAQ,WAAW,cACvB;CACA,MAAM,cAAc,QAAQ,YAAY;CAIxC,MAAM,wBAAQ,IAAI,IAA6B;CAE/C,OAAO;EACH,SAAS;EACT,MAAM;EACN,UAAU,MAAM,IAAI;GAMhB,MAAM,CAAC,YAAY,GAAG,MAAM,GAAG;GAC/B,IAAI,CAAC,OAAO,QAAQ,GAAG,OAAO;GAE9B,MAAM,MAAM,YAAsC;IAC9C,MAAM,cAAc,MAAM,UAAU,UAAU,MAAM;KAEhD,KAAK;KACL,eAAe,QAAQ,YAAY,CAAC;KACpC,WAAW;IACf,CAAC;IAOD,IAAI,YAAY,OACZ,KAAK,MACD,YAAY,OAAO,IAAI,gBAAgB,CAAC,CAAC,KAAK,IAAI,GAClD,YAAY,OAAO,KAAK,MAAM,MAAM,GAAG,YAAY,OAAO,EAAE,CAChE;IAYJ,IAAI,YAAY,OAAO,SAAS,GAAG;KAC/B,MAAM,WAAW,OAAO,KAAK,MAAM,MAAM;KACzC,KAAK,MAAM,SAAS,YAAY,QAC5B,KAAK,KAAK,iBAAiB,KAAK,GAAG,YAAY,UAAU,KAAK,CAAC;IAEvE;IAEA,OAAO;KAAE,MAAM,YAAY;KAAM,KAAK,YAAY;IAAI;GAC1D;GAEA,IAAI,CAAC,aAAa,OAAO,IAAI;GAE7B,MAAM,OAAO,WAAW,MAAM,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,KAAK;GACzD,MAAM,SAAS,MAAM,IAAI,QAAQ;GACjC,IAAI,QAAQ,SAAS,MAAM,OAAO,OAAO;GAEzC,MAAM,SAAS,IAAI;GACnB,MAAM,IAAI,UAAU;IAAE;IAAM;GAAO,CAAC;GAEpC,OAAO,YAAY;IACf,IAAI,MAAM,IAAI,QAAQ,CAAC,EAAE,WAAW,QAAQ,MAAM,OAAO,QAAQ;GACrE,CAAC;GACD,OAAO;EACX;CACJ;AACJ;AAGA,SAAS,iBAAiB,OAAyB;CAC/C,OAAO,MAAM,YAAY,GAAG,MAAM,QAAQ,IAAI,MAAM,cAAc,MAAM;AAC5E;AAKA,SAAS,YAAY,UAAkB,OAAiD;CACpF,MAAM,YAAY,OAAO,OAAO,EAAE,EAAE;CACpC,OAAO,aAAa,OACd,KAAA,IACA,SAAS,SAAS,GAAG,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC;AAC3D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acusti/vite-plugin-react-compiler",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Vite plugin that runs React Compiler via oxc-transform-react, the native Rust port’s Node bindings, instead of Babel",
5
5
  "keywords": [
6
6
  "memoization",
@@ -41,7 +41,7 @@
41
41
  "tsc": "tsc --noEmit"
42
42
  },
43
43
  "dependencies": {
44
- "oxc-transform-react": "0.153.0"
44
+ "oxc-transform-react": "0.154.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^26.1.0",