@rusveltejs/compiler 0.1.0 → 0.1.2

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/compiler.js CHANGED
@@ -4,6 +4,8 @@
4
4
  // built by Svelte's own code. Anything rusvelte doesn't handle falls back to the real compiler:
5
5
  // other Svelte versions, unsupported options, compile errors (so their messages and frames
6
6
  // are Svelte's own) and internal failures.
7
+ import { createRequire } from 'node:module';
8
+ import { fileURLToPath, pathToFileURL } from 'node:url';
7
9
  import * as native from './native.js';
8
10
 
9
11
  const debug = !!process.env.RUSVELTE_DEBUG;
@@ -132,17 +134,54 @@ class JsSourceMap {
132
134
  }
133
135
 
134
136
  /**
135
- * @param {{
136
- * real: typeof import('svelte/compiler'),
137
- * state: any,
138
- * CompileDiagnostic: any,
139
- * merge_with_preprocessor_map: Function,
140
- * get_source_name: Function,
141
- * MagicSourceMap: any
142
- * }} svelte
137
+ * The internals of the installed compiler that results are built with (its `state`,
138
+ * `CompileDiagnostic`, source map helpers and magic-string's `SourceMap`), loaded only once the
139
+ * versions match: another version may lay its files out differently
140
+ * @param {string} real_url the URL of the installed `svelte/compiler` entry
143
141
  */
144
- export function create(svelte) {
145
- const { real, state, CompileDiagnostic, merge_with_preprocessor_map, get_source_name, MagicSourceMap } = svelte;
142
+ async function load_internals(real_url) {
143
+ const at = (path) => new URL(path, real_url).href;
144
+ const [state, { CompileDiagnostic }, { merge_with_preprocessor_map, get_source_name }, { validate_component_options }] = await Promise.all([
145
+ import(at('./state.js')),
146
+ import(at('./utils/compile_diagnostic.js')),
147
+ import(at('./utils/mapped_code.js')),
148
+ import(at('./validate-options.js'))
149
+ ]);
150
+ const magic_string = createRequire(fileURLToPath(real_url)).resolve('magic-string');
151
+ // `require` resolves its CommonJS build, where `SourceMap` hangs off the default export
152
+ const magic = await import(pathToFileURL(magic_string).href);
153
+ const MagicSourceMap = magic.SourceMap ?? magic.default?.SourceMap;
154
+ for (const [name, value] of Object.entries({ CompileDiagnostic, merge_with_preprocessor_map, get_source_name, MagicSourceMap, validate_component_options })) {
155
+ if (typeof value !== 'function') throw new Error(`${name} is not available`);
156
+ }
157
+ if (typeof state.reset !== 'function' || typeof state.set_source !== 'function' || typeof state.adjust !== 'function') {
158
+ throw new Error('svelte/compiler state has an unexpected shape');
159
+ }
160
+ return { state, CompileDiagnostic, merge_with_preprocessor_map, get_source_name, MagicSourceMap, validate_component_options };
161
+ }
162
+
163
+ /**
164
+ * `compile` and `compileModule` for the installed compiler: rusvelte's where it can run, the
165
+ * installed compiler's otherwise
166
+ * @param {typeof import('svelte/compiler')} real
167
+ * @param {string} real_url
168
+ */
169
+ export async function create(real, real_url) {
170
+ const use_real = (reason) => {
171
+ console.warn(`[rusvelte] ${reason}; using svelte/compiler`);
172
+ return { compile: real.compile, compileModule: real.compileModule };
173
+ };
174
+ if (!native.native) return use_real(native.load_error);
175
+ if (real.VERSION !== native.svelteVersion && !process.env.RUSVELTE_ALLOW_VERSION_MISMATCH) {
176
+ return use_real(`svelte ${real.VERSION} is installed, but this rusvelte build reproduces svelte ${native.svelteVersion}`);
177
+ }
178
+ let internals;
179
+ try {
180
+ internals = await load_internals(real_url);
181
+ } catch (e) {
182
+ return use_real(`can't use the internals of the installed svelte/compiler (${e.message})`);
183
+ }
184
+ const { state, CompileDiagnostic, merge_with_preprocessor_map, get_source_name, MagicSourceMap, validate_component_options } = internals;
146
185
 
147
186
  /** results rusvelte produced (not the real compiler) */
148
187
  const native_results = new WeakSet();
@@ -151,18 +190,6 @@ export function create(svelte) {
151
190
  name = 'CompileWarning';
152
191
  }
153
192
 
154
- if (!native.native) {
155
- console.warn(`[rusvelte] ${native.load_error}; using svelte/compiler`);
156
- return { compile: real.compile, compileModule: real.compileModule };
157
- }
158
-
159
- const version_ok = real.VERSION === native.svelteVersion || !!process.env.RUSVELTE_ALLOW_VERSION_MISMATCH;
160
- if (!version_ok) {
161
- console.warn(
162
- `[rusvelte] svelte ${real.VERSION} is installed, but this rusvelte build reproduces svelte ${native.svelteVersion}; using svelte/compiler`
163
- );
164
- }
165
-
166
193
  /**
167
194
  * Runs the native compiler; null means "use the real one"
168
195
  * @param {boolean} module
@@ -170,10 +197,17 @@ export function create(svelte) {
170
197
  function run(source, options, module) {
171
198
  // `validate-options.js` defaults `rootDir` to the working directory
172
199
  if (options.rootDir === undefined && typeof process !== 'undefined') options = { ...options, rootDir: process.cwd() };
173
- if (!version_ok) return fell_back(`svelte ${real.VERSION}`), null;
200
+ // a lone surrogate doesn't survive the trip into Rust (its strings are valid UTF-8)
201
+ if (!source.isWellFormed()) return fell_back('the source has a lone surrogate'), null;
174
202
  const bad = unsupported_option(options, module ? MODULE_OPTIONS : COMPONENT_OPTIONS);
175
203
  if (bad) return fell_back(`option ${bad}`), null;
176
- const result = JSON.parse((module ? native.compileModule : native.compile)(source, native_options(options)));
204
+ let result;
205
+ try {
206
+ result = JSON.parse((module ? native.compileModule : native.compile)(source, native_options(options)));
207
+ } catch (e) {
208
+ // a broken or mismatched native build: the real compiler takes over
209
+ return fell_back(`native error: ${e.message}`), null;
210
+ }
177
211
  if (result.unsupported) return fell_back(result.unsupported), null;
178
212
  // let the real compiler throw, so the error is exactly Svelte's
179
213
  if (result.error) return fell_back(`compile error ${result.error.code}`), null;
@@ -181,19 +215,38 @@ export function create(svelte) {
181
215
  return result;
182
216
  }
183
217
 
184
- /** `state` as compile leaves it, so `CompileDiagnostic` fills in filename, start/end, frame */
185
- function set_state(source, options, runes) {
218
+ /**
219
+ * The result's warnings, made the way compile makes them: `state` is reset, the options are
220
+ * validated by the installed compiler (its deprecation warnings, once per instance), the
221
+ * warnings emitted before the analysis calls `state.adjust` are made with the filename as
222
+ * given, and the rest with the filename relative to `rootDir`. `CompileDiagnostic` fills in
223
+ * filename, start/end and frame from `state`. Returns null if the validation throws.
224
+ * @param {boolean} module
225
+ */
226
+ function warnings_of(source, options, result, module) {
186
227
  state.reset({ warning: options.warningFilter, filename: options.filename });
187
228
  state.set_source(source);
188
- state.adjust({ dev: !!options.dev, rootDir: options.rootDir ?? process.cwd(), runes });
189
- }
190
-
191
- function warnings_of(result, options) {
229
+ if (!module) {
230
+ try {
231
+ validate_component_options(options, '');
232
+ } catch {
233
+ return null;
234
+ }
235
+ }
236
+ const out = [...state.warnings];
192
237
  const filter = options.warningFilter ?? (() => true);
193
- const out = [];
194
- for (const w of result.warnings) {
238
+ const early = result.earlyWarnings ?? 0;
239
+ result.warnings.forEach((w, i) => {
240
+ if (i === early) {
241
+ state.adjust({ dev: !!options.dev, rootDir: options.rootDir ?? process.cwd(), runes: result.runes ?? true });
242
+ }
243
+ // the deprecation warnings come from the validation above
244
+ if (w.code.startsWith('options_deprecated_')) return;
195
245
  const warning = new CompileWarning(w.code, w.message, w.position ?? undefined);
196
246
  if (filter(warning)) out.push(warning);
247
+ });
248
+ if (result.warnings.length <= early) {
249
+ state.adjust({ dev: !!options.dev, rootDir: options.rootDir ?? process.cwd(), runes: result.runes ?? true });
197
250
  }
198
251
  return out;
199
252
  }
@@ -233,7 +286,8 @@ export function create(svelte) {
233
286
  source = remove_bom(source);
234
287
  const result = run(source, options, false);
235
288
  if (!result) return real.compile(source, options);
236
- set_state(source, options, result.runes);
289
+ const warnings = warnings_of(source, options, result, false);
290
+ if (!warnings) return real.compile(source, options);
237
291
  // `validate_component_options` defaults the filename
238
292
  options = { ...options, filename: options.filename ?? '(unknown)' };
239
293
 
@@ -256,7 +310,6 @@ export function create(svelte) {
256
310
  merge_with_preprocessor_map(css, options, css.map.sources[0]);
257
311
  }
258
312
 
259
- const warnings = warnings_of(result, options);
260
313
  let ast;
261
314
  const compiled = {
262
315
  js,
@@ -265,7 +318,8 @@ export function create(svelte) {
265
318
  metadata: { runes: result.runes },
266
319
  // parsed on demand: vite-plugin-svelte doesn't read it
267
320
  get ast() {
268
- return (ast ??= real.parse(source, { modern: !!options.modernAst, filename: options.filename }));
321
+ // compile's AST (TypeScript removed etc.); warnings are dropped so the filter isn't called twice
322
+ return (ast ??= real.compile(source, { ...options, generate: false, warningFilter: () => false }).ast);
269
323
  },
270
324
  set ast(value) {
271
325
  ast = value;
@@ -277,19 +331,28 @@ export function create(svelte) {
277
331
 
278
332
  /** @type {typeof real.compileModule} */
279
333
  function compileModule(source, options) {
334
+ const result = compile_module_native(source, options);
335
+ if (verify && native_results.has(result)) check('module', remove_bom(source), options, result);
336
+ return result;
337
+ }
338
+
339
+ /** @type {typeof real.compileModule} */
340
+ function compile_module_native(source, options) {
280
341
  source = remove_bom(source);
281
342
  const result = run(source, options, true);
282
343
  if (!result) return real.compileModule(source, options);
283
- set_state(source, options, true);
344
+ const warnings = warnings_of(source, options, result, true);
284
345
  options = { ...options, filename: options.filename ?? '(unknown)' };
285
346
  const js_source_name = get_source_name(options.filename, undefined, 'input.svelte.js');
286
- return {
347
+ const compiled = {
287
348
  js: { code: result.js.code, map: new JsSourceMap(result.js.mappings, js_source_name, source) },
288
349
  css: null,
289
- warnings: warnings_of(result, options),
350
+ warnings,
290
351
  metadata: { runes: true },
291
352
  ast: null
292
353
  };
354
+ native_results.add(compiled);
355
+ return compiled;
293
356
  }
294
357
 
295
358
  return { compile, compileModule };
package/native.js CHANGED
@@ -32,7 +32,13 @@ export let native = null;
32
32
  export let load_error = null;
33
33
  try {
34
34
  native = load();
35
+ // a build from another rusvelte version may not have this API
36
+ if (typeof native.compile !== 'function' || typeof native.compileModule !== 'function' || typeof native.svelteVersion !== 'function') {
37
+ throw new Error('it lacks compile, compileModule or svelteVersion');
38
+ }
39
+ native.version = native.svelteVersion();
35
40
  } catch (e) {
41
+ native = null;
36
42
  const where = process.env.RUSVELTE_NATIVE ?? platform_package();
37
43
  load_error = `no native module for ${process.platform}-${process.arch} (${where}): ${e.message.split('\n')[0]}`;
38
44
  }
@@ -40,4 +46,4 @@ try {
40
46
  export const compile = native?.compile;
41
47
  export const compileModule = native?.compileModule;
42
48
  /** The Svelte version whose output this build reproduces */
43
- export const svelteVersion = native?.svelteVersion() ?? null;
49
+ export const svelteVersion = native?.version ?? null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rusveltejs/compiler",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "The Svelte 5 compiler ported to Rust, as a drop-in for svelte/compiler in Vite builds",
5
5
  "keywords": [
6
6
  "svelte",
@@ -21,7 +21,8 @@
21
21
  "exports": {
22
22
  ".": "./native.js",
23
23
  "./register": "./register.js",
24
- "./vite": "./vite.js"
24
+ "./vite": "./vite.js",
25
+ "./package.json": "./package.json"
25
26
  },
26
27
  "files": [
27
28
  "native.js",
@@ -39,11 +40,11 @@
39
40
  "svelte": "^5.0.0"
40
41
  },
41
42
  "optionalDependencies": {
42
- "@rusveltejs/darwin-arm64": "0.1.0",
43
- "@rusveltejs/darwin-x64": "0.1.0",
44
- "@rusveltejs/linux-x64-gnu": "0.1.0",
45
- "@rusveltejs/linux-arm64-gnu": "0.1.0",
46
- "@rusveltejs/linux-x64-musl": "0.1.0",
47
- "@rusveltejs/win32-x64-msvc": "0.1.0"
43
+ "@rusveltejs/darwin-arm64": "0.1.2",
44
+ "@rusveltejs/darwin-x64": "0.1.2",
45
+ "@rusveltejs/linux-x64-gnu": "0.1.2",
46
+ "@rusveltejs/linux-arm64-gnu": "0.1.2",
47
+ "@rusveltejs/linux-x64-musl": "0.1.2",
48
+ "@rusveltejs/win32-x64-msvc": "0.1.2"
48
49
  }
49
50
  }
package/register.js CHANGED
@@ -7,24 +7,20 @@
7
7
  // or call `register()` before importing them dynamically (vite.js does).
8
8
  import { registerHooks } from 'node:module';
9
9
 
10
- const PACKAGE = new URL('./', import.meta.url).href;
11
10
  const SHIM = new URL('./svelte-compiler.js', import.meta.url).href;
12
11
  const COMPILER = new URL('./compiler.js', import.meta.url).href;
13
12
 
14
- let registered = false;
13
+ // one hook per process, even with several copies of this package installed: a second hook
14
+ // would wrap the first one's module instead of the real compiler
15
+ const REGISTERED = Symbol.for('@rusveltejs/compiler/register');
15
16
 
16
17
  export function register() {
17
- if (registered) return;
18
- registered = true;
18
+ if (globalThis[REGISTERED]) return;
19
+ globalThis[REGISTERED] = true;
19
20
  registerHooks({
20
21
  resolve(specifier, context, next) {
21
22
  const parent = context.parentURL ?? '';
22
- // the generated module's own imports resolve from the real compiler's location
23
- if (parent.startsWith(SHIM + '?')) {
24
- const real = new URL(parent).searchParams.get('real');
25
- return next(specifier, { ...context, parentURL: real });
26
- }
27
- if (specifier === 'svelte/compiler' && !parent.startsWith(PACKAGE) && context.conditions?.includes('import')) {
23
+ if (specifier === 'svelte/compiler' && !parent.startsWith(SHIM) && context.conditions?.includes('import')) {
28
24
  const real = next(specifier, context);
29
25
  return { url: `${SHIM}?real=${encodeURIComponent(real.url)}`, format: 'module', shortCircuit: true };
30
26
  }
@@ -32,17 +28,12 @@ export function register() {
32
28
  },
33
29
  load(url, context, next) {
34
30
  if (!url.startsWith(SHIM + '?')) return next(url, context);
35
- const real = new URL(url).searchParams.get('real');
36
- const at = (path) => JSON.stringify(new URL(path, real).href);
31
+ const real = JSON.stringify(new URL(url).searchParams.get('real'));
37
32
  const source = `
38
- export * from ${JSON.stringify(real)};
39
- import * as real from ${JSON.stringify(real)};
40
- import * as state from ${at('./state.js')};
41
- import { CompileDiagnostic } from ${at('./utils/compile_diagnostic.js')};
42
- import { merge_with_preprocessor_map, get_source_name } from ${at('./utils/mapped_code.js')};
43
- import { SourceMap as MagicSourceMap } from 'magic-string';
33
+ export * from ${real};
34
+ import * as real from ${real};
44
35
  import { create } from ${JSON.stringify(COMPILER)};
45
- export const { compile, compileModule } = create({ real, state, CompileDiagnostic, merge_with_preprocessor_map, get_source_name, MagicSourceMap });
36
+ export const { compile, compileModule } = await create(real, ${real});
46
37
  `;
47
38
  return { format: 'module', source, shortCircuit: true };
48
39
  }