@rsvelte/vite-plugin-svelte-native 0.2.8 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs CHANGED
@@ -51,6 +51,72 @@ try {
51
51
  );
52
52
  }
53
53
 
54
+ // The NAPI boundary can't accept function values, so resolve them here: evaluate
55
+ // `customElement`/`css`/`runes` and hand back `warningFilter` for the caller to
56
+ // post-filter warnings (which never affect codegen).
57
+ function prepareCompileOptions(options) {
58
+ if (options == null) return { options, warningFilter: undefined };
59
+ const { customElement, css, runes, warningFilter } = options;
60
+ const hasParametric =
61
+ typeof customElement === 'function' ||
62
+ typeof css === 'function' ||
63
+ typeof runes === 'function';
64
+ const hasWarningFilter = typeof warningFilter === 'function';
65
+ if (!hasParametric && !hasWarningFilter) {
66
+ return { options, warningFilter: undefined };
67
+ }
68
+ // Svelte defaults `filename` to '(unknown)' before invoking these functions.
69
+ const meta = { filename: options.filename ?? '(unknown)' };
70
+ const resolved = { ...options };
71
+ if (typeof customElement === 'function') resolved.customElement = customElement(meta);
72
+ if (typeof css === 'function') resolved.css = css(meta);
73
+ if (typeof runes === 'function') resolved.runes = runes(meta);
74
+ if (hasWarningFilter) delete resolved.warningFilter;
75
+ return { options: resolved, warningFilter: hasWarningFilter ? warningFilter : undefined };
76
+ }
77
+
78
+ // Port of Svelte's `hash()` (submodules/svelte/packages/svelte/src/utils.js),
79
+ // handed to a user `cssHash` callback so custom scope classes match upstream's digest.
80
+ const regexReturnCharacters = /\r/g;
81
+ function hash(str) {
82
+ str = str.replace(regexReturnCharacters, '');
83
+ let h = 5381;
84
+ let i = str.length;
85
+ while (i--) h = ((h << 5) - h) ^ str.charCodeAt(i);
86
+ return (h >>> 0).toString(36);
87
+ }
88
+
89
+ // Adapt a user `cssHash` to the bridge shape. It must never reject: a rejected
90
+ // Promise crossing the NAPI boundary can crash V8 during threadsafe-function
91
+ // teardown — so a throw becomes `{ error }` (Rust turns it into a compile failure)
92
+ // and a non-string return becomes `{ value: null }` (Rust falls back to the default hash).
93
+ function makeCssHashCallback(userCssHash) {
94
+ return async (name, filename, css) => {
95
+ try {
96
+ const result = await userCssHash({ hash, css, name, filename });
97
+ return { value: typeof result === 'string' ? result : null };
98
+ } catch (err) {
99
+ return { error: err instanceof Error ? err.message : String(err) };
100
+ }
101
+ };
102
+ }
103
+
104
+ function applyWarningFilter(result, warningFilter) {
105
+ if (!warningFilter || result == null) return result;
106
+ const warnings = result.warnings;
107
+ if (Array.isArray(warnings) && warnings.length) {
108
+ // `warnings` is a lazy getter on the envelope-decoded result; redefine it
109
+ // as a plain data property so the filtered array replaces the accessor.
110
+ Object.defineProperty(result, 'warnings', {
111
+ value: warnings.filter((warning) => warningFilter(warning)),
112
+ writable: true,
113
+ enumerable: true,
114
+ configurable: true,
115
+ });
116
+ }
117
+ return result;
118
+ }
119
+
54
120
  // `compile` / `compileModule` are wrapped to route through the
55
121
  // raw-transfer envelope (`compileEnvelope`): the Rust side hands us
56
122
  // one `Buffer`, the JS side lazy-decodes only the fields the caller
@@ -62,11 +128,23 @@ try {
62
128
  // directly. The legacy JSON path is preserved as `compileLegacy` for
63
129
  // parity testing and as an escape hatch.
64
130
  function compile(source, options) {
65
- return decodeEnvelope(binding.compileEnvelope(source, options));
131
+ if (typeof options?.cssHash === 'function') {
132
+ // A dynamic cssHash needs the Rust→JS callback bridge, which would deadlock
133
+ // the JS event loop on the sync path; direct the caller to `compileAsync`.
134
+ throw new Error(
135
+ '[@rsvelte/vite-plugin-svelte-native] A dynamic `cssHash` function requires the async compile path; call `compileAsync(source, options)` instead. (A constant hash can use `cssHashOverride`.)',
136
+ );
137
+ }
138
+ const { options: resolved, warningFilter } = prepareCompileOptions(options);
139
+ return applyWarningFilter(decodeEnvelope(binding.compileEnvelope(source, resolved)), warningFilter);
66
140
  }
67
141
 
68
142
  function compileModule(source, options) {
69
- return decodeEnvelope(binding.compileModuleEnvelope(source, options));
143
+ const { options: resolved, warningFilter } = prepareCompileOptions(options);
144
+ return applyWarningFilter(
145
+ decodeEnvelope(binding.compileModuleEnvelope(source, resolved)),
146
+ warningFilter,
147
+ );
70
148
  }
71
149
 
72
150
  // `compileBatch([{source, options}, …])` compiles N files in
@@ -74,8 +152,29 @@ function compileModule(source, options) {
74
152
  // exactly once. The returned array is the same length as the input;
75
153
  // each slot is either a `CompileResult` or an `Error` (parse
76
154
  // failures don't abort the whole batch).
155
+ // Batch compilation runs on rayon workers with no JS event loop to service the
156
+ // callback, so a dynamic cssHash can't be bridged here — reject it rather than drop it.
157
+ function assertNoDynamicCssHash(options) {
158
+ if (typeof options?.cssHash === 'function') {
159
+ throw new Error(
160
+ '[@rsvelte/vite-plugin-svelte-native] A dynamic `cssHash` function is not supported in compileBatch; compile such files individually with `compileAsync`.',
161
+ );
162
+ }
163
+ }
164
+
77
165
  function compileBatch(inputs) {
78
- return decodeBatch(binding.compileBatch(inputs));
166
+ const filters = [];
167
+ const prepared = inputs.map((input, i) => {
168
+ assertNoDynamicCssHash(input.options);
169
+ const { options, warningFilter } = prepareCompileOptions(input.options);
170
+ if (warningFilter) filters[i] = warningFilter;
171
+ return options === input.options ? input : { source: input.source, options };
172
+ });
173
+ const results = decodeBatch(binding.compileBatch(prepared));
174
+ if (filters.length) {
175
+ results.forEach((result, i) => applyWarningFilter(result, filters[i]));
176
+ }
177
+ return results;
79
178
  }
80
179
 
81
180
  // `compileAsync` / `compileBatchAsync` release the JS event loop
@@ -84,11 +183,35 @@ function compileBatch(inputs) {
84
183
  // (Vite middleware, SSR pre-render) — the await yields control
85
184
  // instead of blocking V8.
86
185
  async function compileAsync(source, options) {
87
- return decodeEnvelope(await binding.compileEnvelopeAsync(source, options));
186
+ const { options: resolved, warningFilter } = prepareCompileOptions(options);
187
+ if (typeof options?.cssHash === 'function') {
188
+ // The bridge entry returns a plain (JSON) CompileResult, not an envelope.
189
+ const result = await binding.compileWithCssHash(
190
+ source,
191
+ resolved,
192
+ makeCssHashCallback(options.cssHash),
193
+ );
194
+ return applyWarningFilter(result, warningFilter);
195
+ }
196
+ return applyWarningFilter(
197
+ decodeEnvelope(await binding.compileEnvelopeAsync(source, resolved)),
198
+ warningFilter,
199
+ );
88
200
  }
89
201
 
90
202
  async function compileBatchAsync(inputs) {
91
- return decodeBatch(await binding.compileBatchAsync(inputs));
203
+ const filters = [];
204
+ const prepared = inputs.map((input, i) => {
205
+ assertNoDynamicCssHash(input.options);
206
+ const { options, warningFilter } = prepareCompileOptions(input.options);
207
+ if (warningFilter) filters[i] = warningFilter;
208
+ return options === input.options ? input : { source: input.source, options };
209
+ });
210
+ const results = decodeBatch(await binding.compileBatchAsync(prepared));
211
+ if (filters.length) {
212
+ results.forEach((result, i) => applyWarningFilter(result, filters[i]));
213
+ }
214
+ return results;
92
215
  }
93
216
 
94
217
  // Re-export every NAPI function as its own named binding so node's
@@ -141,4 +264,4 @@ module.exports.decodeParseEnvelope = decodeParseEnvelope;
141
264
  // with `submodules/svelte/packages/svelte/package.json` by hand; run
142
265
  // `node scripts/dev/check-vps-version.mjs` (also wired into CI) to
143
266
  // catch drift.
144
- module.exports.VERSION = '5.56.4';
267
+ module.exports.VERSION = '5.56.8';
package/index.d.ts CHANGED
@@ -22,29 +22,56 @@ export interface CompileOptions {
22
22
  rootDir?: string;
23
23
  /** Component identifier hint. */
24
24
  name?: string;
25
- /** Compile as a custom element. */
26
- customElement?: boolean;
25
+ /**
26
+ * Compile as a custom element. A function form `({ filename }) => boolean`
27
+ * is evaluated once at the binding boundary.
28
+ */
29
+ customElement?: boolean | ((meta: { filename: string }) => boolean);
27
30
  /** Generate `accessors`. */
28
31
  accessors?: boolean;
29
32
  /** HTML namespace. */
30
33
  namespace?: 'html' | 'svg' | 'mathml';
31
34
  /** Hint that bindings are immutable. */
32
35
  immutable?: boolean;
33
- /** Output CSS injected into the bundle or as an external asset. */
34
- css?: 'injected' | 'external';
35
- /** Custom hash function for CSS scoping — currently honored as a string-mapper. */
36
+ /**
37
+ * Output CSS injected into the bundle or as an external asset. A function
38
+ * form `({ filename }) => 'injected' | 'external'` is evaluated once at the
39
+ * binding boundary.
40
+ */
41
+ css?:
42
+ | 'injected'
43
+ | 'external'
44
+ | ((meta: { filename: string }) => 'injected' | 'external');
45
+ /**
46
+ * Custom hash function for CSS scoping. Called once per component with the
47
+ * component's CSS; the returned string becomes the scope class. Because it
48
+ * depends on the CSS, it is bridged into the compiler through a threadsafe
49
+ * callback and is only supported on the async path — use `compileAsync`.
50
+ * The synchronous `compile` throws if given a `cssHash` function; pass a
51
+ * pre-computed constant via `cssHashOverride` instead.
52
+ */
36
53
  cssHash?: (args: {
37
54
  hash: (input: string) => string;
38
55
  css: string;
39
56
  name: string;
40
- filename: string | undefined;
57
+ filename: string;
41
58
  }) => string;
59
+ /**
60
+ * Pre-computed constant CSS scope hash. Escape hatch for callers (e.g.
61
+ * `@rsvelte/vite-plugin-svelte`'s HMR path) that already know the hash and
62
+ * want to skip the `cssHash` callback bridge entirely.
63
+ */
64
+ cssHashOverride?: string;
42
65
  /** Preserve HTML comments in output. */
43
66
  preserveComments?: boolean;
44
67
  /** Preserve whitespace in the template. */
45
68
  preserveWhitespace?: boolean;
46
- /** Force runes mode (`true`), legacy (`false`), or auto-detect (`undefined`). */
47
- runes?: boolean;
69
+ /**
70
+ * Force runes mode (`true`), legacy (`false`), or auto-detect (`undefined`).
71
+ * A function form `({ filename }) => boolean | undefined` is evaluated once
72
+ * at the binding boundary.
73
+ */
74
+ runes?: boolean | ((meta: { filename: string }) => boolean | undefined);
48
75
  /** Disclose the compiler version in the output banner. */
49
76
  discloseVersion?: boolean;
50
77
  /** Source-map options (forwarded through magic-string). */
@@ -70,6 +97,7 @@ export interface ModuleCompileOptions {
70
97
  generate?: 'client' | 'server' | false;
71
98
  filename?: string;
72
99
  rootDir?: string;
100
+ /** Filter compiler warnings (applied to the returned `warnings` array). */
73
101
  warningFilter?: (warning: Warning) => boolean;
74
102
  }
75
103
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsvelte/vite-plugin-svelte-native",
3
- "version": "0.2.8",
3
+ "version": "0.3.1",
4
4
  "description": "NAPI bindings to the rsvelte compiler — used by the @rsvelte vite-plugin-svelte shim",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,11 +29,11 @@
29
29
  "index.d.ts"
30
30
  ],
31
31
  "optionalDependencies": {
32
- "@rsvelte/vite-plugin-svelte-native-darwin-arm64": "^0.2.8",
33
- "@rsvelte/vite-plugin-svelte-native-darwin-x64": "^0.2.8",
34
- "@rsvelte/vite-plugin-svelte-native-linux-arm64-gnu": "^0.2.8",
35
- "@rsvelte/vite-plugin-svelte-native-linux-x64-gnu": "^0.2.8",
36
- "@rsvelte/vite-plugin-svelte-native-win32-x64-msvc": "^0.2.8"
32
+ "@rsvelte/vite-plugin-svelte-native-darwin-arm64": "^0.3.1",
33
+ "@rsvelte/vite-plugin-svelte-native-linux-arm64-gnu": "^0.3.1",
34
+ "@rsvelte/vite-plugin-svelte-native-darwin-x64": "^0.3.1",
35
+ "@rsvelte/vite-plugin-svelte-native-linux-x64-gnu": "^0.3.1",
36
+ "@rsvelte/vite-plugin-svelte-native-win32-x64-msvc": "^0.3.1"
37
37
  },
38
38
  "publishConfig": {
39
39
  "access": "public"
package/parse-envelope.js CHANGED
@@ -15,7 +15,11 @@
15
15
  const { isWindowOutOfBounds, windowOutOfBoundsMessage } = require('./lib/bounds-check.js');
16
16
 
17
17
  const MAGIC = 0x3156_5052; // "RPV1" little-endian
18
- const VERSION = 1;
18
+ // Bumped for the function-node AST-fidelity changes (FunctionExpression field
19
+ // reorder, `typeParameters` on function-like nodes, Identifier `optional`);
20
+ // v4 adds the object-method `typeParameters`-after-`body` flag byte.
21
+ // Keep in lockstep with `napi_raw_parse.rs`'s `VERSION`.
22
+ const VERSION = 4;
19
23
  const HEADER_LEN = 24;
20
24
 
21
25
  // Tags — must mirror napi_raw_parse.rs.
@@ -698,9 +702,11 @@ function readOptNode(ctx) {
698
702
  function readJsIdentifier(ctx, start, end) {
699
703
  const loc = readTypedLoc(ctx);
700
704
  const name = readStr(ctx);
705
+ const optional = readBool(ctx);
701
706
  const node = { type: 'Identifier', start, end };
702
707
  if (loc !== null) node.loc = loc;
703
708
  node.name = name;
709
+ if (optional) node.optional = true;
704
710
  const typeAnnotation = readOptTypeAnnotation(ctx);
705
711
  if (typeAnnotation !== null) node.typeAnnotation = typeAnnotation;
706
712
  return node;
@@ -809,19 +815,24 @@ function readJsNewExpression(ctx, start, end) {
809
815
  function readJsFunctionExpression(ctx, start, end) {
810
816
  const loc = readTypedLoc(ctx);
811
817
  const id = readOptNode(ctx);
818
+ const expression = readBool(ctx);
812
819
  const generator = readBool(ctx);
813
820
  const asyncFlag = readBool(ctx);
814
- const expression = readBool(ctx);
821
+ const typeParameters = readOptTypeAnnotation(ctx);
815
822
  const params = readChildArray(ctx);
816
823
  const body = readOptNode(ctx);
824
+ // Object-method inner functions carry `typeParameters` after `body` (acorn).
825
+ const typeParametersAfterBody = readBool(ctx);
817
826
  const node = { type: 'FunctionExpression', start, end };
818
827
  if (loc !== null) node.loc = loc;
819
828
  node.id = id;
829
+ node.expression = expression;
820
830
  node.generator = generator;
821
831
  node.async = asyncFlag;
822
- node.expression = expression;
832
+ if (typeParameters !== null && !typeParametersAfterBody) node.typeParameters = typeParameters;
823
833
  node.params = params;
824
834
  node.body = body;
835
+ if (typeParameters !== null && typeParametersAfterBody) node.typeParameters = typeParameters;
825
836
  return node;
826
837
  }
827
838
 
@@ -846,6 +857,7 @@ function readJsArrowFunctionExpression(ctx, start, end) {
846
857
  const asyncFlag = readBool(ctx);
847
858
  const params = readChildArray(ctx);
848
859
  const body = readNode(ctx);
860
+ const typeParameters = readOptTypeAnnotation(ctx);
849
861
  const node = { type: 'ArrowFunctionExpression', start, end };
850
862
  if (loc !== null) node.loc = loc;
851
863
  node.id = id;
@@ -854,6 +866,7 @@ function readJsArrowFunctionExpression(ctx, start, end) {
854
866
  node.async = asyncFlag;
855
867
  node.params = params;
856
868
  node.body = body;
869
+ if (typeParameters !== null) node.typeParameters = typeParameters;
857
870
  return node;
858
871
  }
859
872
 
@@ -1170,15 +1183,19 @@ function readJsVariableDeclarator(ctx, start, end) {
1170
1183
  function readJsFunctionDeclaration(ctx, start, end) {
1171
1184
  const loc = readTypedLoc(ctx);
1172
1185
  const id = readOptNode(ctx);
1186
+ const expression = readBool(ctx);
1173
1187
  const generator = readBool(ctx);
1174
1188
  const asyncFlag = readBool(ctx);
1189
+ const typeParameters = readOptTypeAnnotation(ctx);
1175
1190
  const params = readChildArray(ctx);
1176
1191
  const body = readOptNode(ctx);
1177
1192
  const node = { type: 'FunctionDeclaration', start, end };
1178
1193
  if (loc !== null) node.loc = loc;
1179
1194
  node.id = id;
1195
+ node.expression = expression;
1180
1196
  node.generator = generator;
1181
1197
  node.async = asyncFlag;
1198
+ if (typeParameters !== null) node.typeParameters = typeParameters;
1182
1199
  node.params = params;
1183
1200
  node.body = body;
1184
1201
  return node;