@rsvelte/vite-plugin-svelte-native 0.2.7 → 0.3.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/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
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.7",
3
+ "version": "0.3.0",
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.7",
33
- "@rsvelte/vite-plugin-svelte-native-darwin-x64": "^0.2.7",
34
- "@rsvelte/vite-plugin-svelte-native-linux-arm64-gnu": "^0.2.7",
35
- "@rsvelte/vite-plugin-svelte-native-linux-x64-gnu": "^0.2.7",
36
- "@rsvelte/vite-plugin-svelte-native-win32-x64-msvc": "^0.2.7"
32
+ "@rsvelte/vite-plugin-svelte-native-darwin-arm64": "^0.3.0",
33
+ "@rsvelte/vite-plugin-svelte-native-darwin-x64": "^0.3.0",
34
+ "@rsvelte/vite-plugin-svelte-native-linux-arm64-gnu": "^0.3.0",
35
+ "@rsvelte/vite-plugin-svelte-native-linux-x64-gnu": "^0.3.0",
36
+ "@rsvelte/vite-plugin-svelte-native-win32-x64-msvc": "^0.3.0"
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.
@@ -155,6 +159,11 @@ const JS_NULL = 0xca;
155
159
  // 0xcb was the former whole-node JS_RAW_JSON escape (removed — TS type
156
160
  // annotations now ride a per-node trailer; see readOptTypeAnnotation).
157
161
  const JS_TS_PARAMETER_PROPERTY = 0xcc;
162
+ const JS_TS_AS_EXPRESSION = 0xcd;
163
+ const JS_TS_SATISFIES_EXPRESSION = 0xce;
164
+ const JS_TS_NON_NULL_EXPRESSION = 0xcf;
165
+ const JS_TS_TYPE_ASSERTION = 0xd0;
166
+ const JS_TS_INSTANTIATION_EXPRESSION = 0xd1;
158
167
 
159
168
  // LiteralValue inner tag (within a JS_LITERAL payload).
160
169
  const LV_NULL = 0;
@@ -581,6 +590,16 @@ function readNodeBody(ctx, tag, start, end) {
581
590
  return readJsBareExpr(ctx, 'TSParameterProperty', start, end);
582
591
  case JS_TS_MODULE_DECLARATION:
583
592
  return readJsTSModuleDeclaration(ctx, start, end);
593
+ case JS_TS_AS_EXPRESSION:
594
+ return readJsTSAssertion(ctx, 'TSAsExpression', start, end, true);
595
+ case JS_TS_SATISFIES_EXPRESSION:
596
+ return readJsTSAssertion(ctx, 'TSSatisfiesExpression', start, end, true);
597
+ case JS_TS_NON_NULL_EXPRESSION:
598
+ return readJsTSAssertion(ctx, 'TSNonNullExpression', start, end, false);
599
+ case JS_TS_TYPE_ASSERTION:
600
+ return readJsTSTypeAssertion(ctx, start, end);
601
+ case JS_TS_INSTANTIATION_EXPRESSION:
602
+ return readJsTSInstantiationExpression(ctx, start, end);
584
603
  case JS_COMMENT:
585
604
  return readJsComment_(ctx, start, end);
586
605
 
@@ -683,9 +702,11 @@ function readOptNode(ctx) {
683
702
  function readJsIdentifier(ctx, start, end) {
684
703
  const loc = readTypedLoc(ctx);
685
704
  const name = readStr(ctx);
705
+ const optional = readBool(ctx);
686
706
  const node = { type: 'Identifier', start, end };
687
707
  if (loc !== null) node.loc = loc;
688
708
  node.name = name;
709
+ if (optional) node.optional = true;
689
710
  const typeAnnotation = readOptTypeAnnotation(ctx);
690
711
  if (typeAnnotation !== null) node.typeAnnotation = typeAnnotation;
691
712
  return node;
@@ -794,19 +815,24 @@ function readJsNewExpression(ctx, start, end) {
794
815
  function readJsFunctionExpression(ctx, start, end) {
795
816
  const loc = readTypedLoc(ctx);
796
817
  const id = readOptNode(ctx);
818
+ const expression = readBool(ctx);
797
819
  const generator = readBool(ctx);
798
820
  const asyncFlag = readBool(ctx);
799
- const expression = readBool(ctx);
821
+ const typeParameters = readOptTypeAnnotation(ctx);
800
822
  const params = readChildArray(ctx);
801
823
  const body = readOptNode(ctx);
824
+ // Object-method inner functions carry `typeParameters` after `body` (acorn).
825
+ const typeParametersAfterBody = readBool(ctx);
802
826
  const node = { type: 'FunctionExpression', start, end };
803
827
  if (loc !== null) node.loc = loc;
804
828
  node.id = id;
829
+ node.expression = expression;
805
830
  node.generator = generator;
806
831
  node.async = asyncFlag;
807
- node.expression = expression;
832
+ if (typeParameters !== null && !typeParametersAfterBody) node.typeParameters = typeParameters;
808
833
  node.params = params;
809
834
  node.body = body;
835
+ if (typeParameters !== null && typeParametersAfterBody) node.typeParameters = typeParameters;
810
836
  return node;
811
837
  }
812
838
 
@@ -831,6 +857,7 @@ function readJsArrowFunctionExpression(ctx, start, end) {
831
857
  const asyncFlag = readBool(ctx);
832
858
  const params = readChildArray(ctx);
833
859
  const body = readNode(ctx);
860
+ const typeParameters = readOptTypeAnnotation(ctx);
834
861
  const node = { type: 'ArrowFunctionExpression', start, end };
835
862
  if (loc !== null) node.loc = loc;
836
863
  node.id = id;
@@ -839,6 +866,7 @@ function readJsArrowFunctionExpression(ctx, start, end) {
839
866
  node.async = asyncFlag;
840
867
  node.params = params;
841
868
  node.body = body;
869
+ if (typeParameters !== null) node.typeParameters = typeParameters;
842
870
  return node;
843
871
  }
844
872
 
@@ -975,6 +1003,45 @@ function readJsChainExpression(ctx, start, end) {
975
1003
  return node;
976
1004
  }
977
1005
 
1006
+ // TS assertion wrappers (`x as T` / `x satisfies T` / `x!`). As/Satisfies carry
1007
+ // a `typeAnnotation` trailer; NonNull does not (`hasTypeAnnotation` is false).
1008
+ function readJsTSAssertion(ctx, typeName, start, end, hasTypeAnnotation) {
1009
+ const loc = readTypedLoc(ctx);
1010
+ const expression = readNode(ctx);
1011
+ const node = { type: typeName, start, end };
1012
+ if (loc !== null) node.loc = loc;
1013
+ node.expression = expression;
1014
+ if (hasTypeAnnotation) {
1015
+ const typeAnnotation = readOptTypeAnnotation(ctx);
1016
+ if (typeAnnotation !== null) node.typeAnnotation = typeAnnotation;
1017
+ }
1018
+ return node;
1019
+ }
1020
+
1021
+ function readJsTSTypeAssertion(ctx, start, end) {
1022
+ // Wire order: loc, expression, typeAnnotation — but svelte/compiler emits
1023
+ // `typeAnnotation` before `expression`, so build the object in that order.
1024
+ const loc = readTypedLoc(ctx);
1025
+ const expression = readNode(ctx);
1026
+ const typeAnnotation = readOptTypeAnnotation(ctx);
1027
+ const node = { type: 'TSTypeAssertion', start, end };
1028
+ if (loc !== null) node.loc = loc;
1029
+ if (typeAnnotation !== null) node.typeAnnotation = typeAnnotation;
1030
+ node.expression = expression;
1031
+ return node;
1032
+ }
1033
+
1034
+ function readJsTSInstantiationExpression(ctx, start, end) {
1035
+ const loc = readTypedLoc(ctx);
1036
+ const expression = readNode(ctx);
1037
+ const typeArguments = readOptTypeAnnotation(ctx);
1038
+ const node = { type: 'TSInstantiationExpression', start, end };
1039
+ if (loc !== null) node.loc = loc;
1040
+ node.expression = expression;
1041
+ if (typeArguments !== null) node.typeArguments = typeArguments;
1042
+ return node;
1043
+ }
1044
+
978
1045
  function readJsMetaProperty(ctx, start, end) {
979
1046
  const loc = readTypedLoc(ctx);
980
1047
  const meta = readNode(ctx);
@@ -1116,15 +1183,19 @@ function readJsVariableDeclarator(ctx, start, end) {
1116
1183
  function readJsFunctionDeclaration(ctx, start, end) {
1117
1184
  const loc = readTypedLoc(ctx);
1118
1185
  const id = readOptNode(ctx);
1186
+ const expression = readBool(ctx);
1119
1187
  const generator = readBool(ctx);
1120
1188
  const asyncFlag = readBool(ctx);
1189
+ const typeParameters = readOptTypeAnnotation(ctx);
1121
1190
  const params = readChildArray(ctx);
1122
1191
  const body = readOptNode(ctx);
1123
1192
  const node = { type: 'FunctionDeclaration', start, end };
1124
1193
  if (loc !== null) node.loc = loc;
1125
1194
  node.id = id;
1195
+ node.expression = expression;
1126
1196
  node.generator = generator;
1127
1197
  node.async = asyncFlag;
1198
+ if (typeParameters !== null) node.typeParameters = typeParameters;
1128
1199
  node.params = params;
1129
1200
  node.body = body;
1130
1201
  return node;