@rusveltejs/compiler 0.1.0 → 0.1.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/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;
@@ -192,6 +226,7 @@ export function create(svelte) {
192
226
  const filter = options.warningFilter ?? (() => true);
193
227
  const out = [];
194
228
  for (const w of result.warnings) {
229
+ if (w.code.startsWith('options_deprecated_')) continue;
195
230
  const warning = new CompileWarning(w.code, w.message, w.position ?? undefined);
196
231
  if (filter(warning)) out.push(warning);
197
232
  }
@@ -234,6 +269,14 @@ export function create(svelte) {
234
269
  const result = run(source, options, false);
235
270
  if (!result) return real.compile(source, options);
236
271
  set_state(source, options, result.runes);
272
+ // the option warnings (deprecated options) come from the installed compiler's own
273
+ // validation, so its once-per-instance memory and the warning filter apply as in compile
274
+ try {
275
+ validate_component_options(options, '');
276
+ } catch {
277
+ return real.compile(source, options);
278
+ }
279
+ const option_warnings = [...state.warnings];
237
280
  // `validate_component_options` defaults the filename
238
281
  options = { ...options, filename: options.filename ?? '(unknown)' };
239
282
 
@@ -256,7 +299,7 @@ export function create(svelte) {
256
299
  merge_with_preprocessor_map(css, options, css.map.sources[0]);
257
300
  }
258
301
 
259
- const warnings = warnings_of(result, options);
302
+ const warnings = [...option_warnings, ...warnings_of(result, options)];
260
303
  let ast;
261
304
  const compiled = {
262
305
  js,
@@ -265,7 +308,8 @@ export function create(svelte) {
265
308
  metadata: { runes: result.runes },
266
309
  // parsed on demand: vite-plugin-svelte doesn't read it
267
310
  get ast() {
268
- return (ast ??= real.parse(source, { modern: !!options.modernAst, filename: options.filename }));
311
+ // compile's AST (TypeScript removed etc.); warnings are dropped so the filter isn't called twice
312
+ return (ast ??= real.compile(source, { ...options, generate: false, warningFilter: () => false }).ast);
269
313
  },
270
314
  set ast(value) {
271
315
  ast = value;
@@ -277,19 +321,28 @@ export function create(svelte) {
277
321
 
278
322
  /** @type {typeof real.compileModule} */
279
323
  function compileModule(source, options) {
324
+ const result = compile_module_native(source, options);
325
+ if (verify && native_results.has(result)) check('module', remove_bom(source), options, result);
326
+ return result;
327
+ }
328
+
329
+ /** @type {typeof real.compileModule} */
330
+ function compile_module_native(source, options) {
280
331
  source = remove_bom(source);
281
332
  const result = run(source, options, true);
282
333
  if (!result) return real.compileModule(source, options);
283
334
  set_state(source, options, true);
284
335
  options = { ...options, filename: options.filename ?? '(unknown)' };
285
336
  const js_source_name = get_source_name(options.filename, undefined, 'input.svelte.js');
286
- return {
337
+ const compiled = {
287
338
  js: { code: result.js.code, map: new JsSourceMap(result.js.mappings, js_source_name, source) },
288
339
  css: null,
289
340
  warnings: warnings_of(result, options),
290
341
  metadata: { runes: true },
291
342
  ast: null
292
343
  };
344
+ native_results.add(compiled);
345
+ return compiled;
293
346
  }
294
347
 
295
348
  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.1",
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.1",
44
+ "@rusveltejs/darwin-x64": "0.1.1",
45
+ "@rusveltejs/linux-x64-gnu": "0.1.1",
46
+ "@rusveltejs/linux-arm64-gnu": "0.1.1",
47
+ "@rusveltejs/linux-x64-musl": "0.1.1",
48
+ "@rusveltejs/win32-x64-msvc": "0.1.1"
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
  }