@rusveltejs/compiler 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rusvelte contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,68 @@
1
- # Temporary Holding Version
1
+ # @rusveltejs/compiler
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The rusvelte compiler as a drop-in for `svelte/compiler` in Vite builds. vite-plugin-svelte and
4
+ SvelteKit keep working as they are; their calls to `compile` and `compileModule` go to the Rust
5
+ port instead, and get the same results the official compiler would return.
6
+
7
+ Experimental. It reproduces Svelte 5.57.2; with any other version installed it steps aside and
8
+ the official compiler does the work.
9
+
10
+ ## Use
11
+
12
+ ```sh
13
+ npm install -D @rusveltejs/compiler
14
+ ```
15
+
16
+ Prebuilt native modules cover macOS (arm64, x64), Linux (x64 and arm64 with glibc 2.17 or newer,
17
+ x64 with musl) and Windows (x64). On any other platform the package warns and uses the official
18
+ compiler. From a checkout of the repository instead:
19
+ `cargo build --release -p rusvelte-node && node packages/rusvelte/build.js`.
20
+
21
+ Then either load it for the whole process:
22
+
23
+ ```sh
24
+ NODE_OPTIONS="--import @rusveltejs/compiler/register" vite build
25
+ ```
26
+
27
+ or wrap the Svelte plugin in `vite.config`:
28
+
29
+ ```js
30
+ import { rusvelte } from '@rusveltejs/compiler/vite';
31
+
32
+ export default defineConfig({
33
+ plugins: [rusvelte(() => import('@sveltejs/kit/vite').then((m) => m.sveltekit()))]
34
+ });
35
+ ```
36
+
37
+ The import has to be dynamic, and nothing else in the config may import vite-plugin-svelte
38
+ statically, or it binds the official compiler before rusvelte can step in.
39
+
40
+ ## What it does
41
+
42
+ `register` adds a Node module hook: `svelte/compiler` resolves to a module that re-exports the
43
+ installed compiler with `compile` and `compileModule` replaced. Those call the native module,
44
+ then build the result with the installed compiler's own helpers (warnings with their frames,
45
+ source map objects, merging of preprocessor maps), so results have the same shape and content.
46
+
47
+ It falls back to the official compiler for:
48
+ - any Svelte version other than the one this build reproduces (5.57.2);
49
+ - options it doesn't implement, such as a `cssHash` function;
50
+ - components that fail to compile, so errors are exactly Svelte's;
51
+ - anything the native side can't handle.
52
+
53
+ ## Environment variables
54
+
55
+ - `RUSVELTE_VERIFY=1`: also compile everything with the official compiler and report any
56
+ difference (slower; for checking a project).
57
+ - `RUSVELTE_DEBUG=1`: report why a compilation fell back.
58
+ - `RUSVELTE_NATIVE=/path/to/rusvelte.node`: use another build of the native module.
59
+ - `RUSVELTE_ALLOW_VERSION_MISMATCH=1`: use rusvelte with other Svelte versions anyway. Its
60
+ output targets 5.57.2's runtime, so this can break your app.
61
+
62
+ ## Status
63
+
64
+ - Identical results to `svelte/compiler`, source maps included, on 20,410 compilations of
65
+ real projects through this package (`oracle/check_rusvelte_package.mjs`), in the option
66
+ sets Vite uses.
67
+ - Production `vite build` output identical to the official compiler's on the projects tried.
68
+ - Preprocessors (`vitePreprocess`, PostCSS, ...) still run in JavaScript, as before.
@@ -0,0 +1,87 @@
1
+ # Third-party notices
2
+
3
+ rusvelte ports code from the following MIT-licensed projects. Their notices follow.
4
+
5
+ ## [Svelte](https://github.com/sveltejs/svelte)
6
+
7
+ The parser, analysis and code generation are ports of `packages/svelte/src/compiler` (5.57.2); compiler messages and a11y data are generated from it.
8
+
9
+ ```
10
+ Copyright (c) 2016-2025 [Svelte Contributors](https://github.com/sveltejs/svelte/graphs/contributors)
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ ```
18
+
19
+ ## [Svelte language tools](https://github.com/sveltejs/language-tools)
20
+
21
+ svelte2tsx and svelte-check-rs are ports of `packages/svelte2tsx` and parts of `packages/svelte-check` and `packages/language-server`.
22
+
23
+ ```
24
+ Copyright (c) 2020-Present [these people](https://github.com/sveltejs/language-tools/graphs/contributors)
25
+
26
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
27
+
28
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
29
+
30
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
+ ```
32
+
33
+ ## [esrap](https://github.com/sveltejs/esrap)
34
+
35
+ `src/estree/print.rs` is a port of esrap 2.4.0.
36
+
37
+ ```
38
+ Copyright (c) 2023-2025 [these people](https://github.com/sveltejs/esrap/graphs/contributors)
39
+
40
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
41
+
42
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
43
+
44
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
45
+ ```
46
+
47
+ ## [magic-string](https://github.com/Rich-Harris/magic-string)
48
+
49
+ `src/magic_string.rs` is a port of magic-string.
50
+
51
+ ```
52
+ Copyright 2018 Rich Harris
53
+
54
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
55
+
56
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
57
+
58
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
59
+ ```
60
+
61
+ ## [vscode-css-languageservice](https://github.com/microsoft/vscode-css-languageservice)
62
+
63
+ `src/css_lint/` is a port of its CSS/SCSS/LESS parsers and lint rules; `tools/gen_css_data.mjs` generates property data from it.
64
+
65
+ ```
66
+ The MIT License (MIT)
67
+
68
+ Copyright (c) Microsoft
69
+
70
+ Permission is hereby granted, free of charge, to any person obtaining a copy
71
+ of this software and associated documentation files (the "Software"), to deal
72
+ in the Software without restriction, including without limitation the rights
73
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
74
+ copies of the Software, and to permit persons to whom the Software is
75
+ furnished to do so, subject to the following conditions:
76
+
77
+ The above copyright notice and this permission notice shall be included in all
78
+ copies or substantial portions of the Software.
79
+
80
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
81
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
82
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
83
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
84
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
85
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
86
+ SOFTWARE.
87
+ ```
package/compiler.js ADDED
@@ -0,0 +1,296 @@
1
+ // `compile` and `compileModule` backed by rusvelte, with the same results as `svelte/compiler`'s.
2
+ // The pieces of the real compiler they need (its `state`, `CompileDiagnostic` and source map
3
+ // helpers) are passed in by the generated module in register.js, so warnings and maps are
4
+ // built by Svelte's own code. Anything rusvelte doesn't handle falls back to the real compiler:
5
+ // other Svelte versions, unsupported options, compile errors (so their messages and frames
6
+ // are Svelte's own) and internal failures.
7
+ import * as native from './native.js';
8
+
9
+ const debug = !!process.env.RUSVELTE_DEBUG;
10
+ // `RUSVELTE_VERIFY=1` also runs the real compiler on every compilation and reports differences
11
+ const verify = !!process.env.RUSVELTE_VERIFY;
12
+
13
+ /** Counts of native compilations and fallbacks (by reason), for `RUSVELTE_DEBUG` and tests */
14
+ export const stats = { native: 0, fallback: {} };
15
+
16
+ /** @param {string} reason */
17
+ function fell_back(reason) {
18
+ stats.fallback[reason] = (stats.fallback[reason] ?? 0) + 1;
19
+ if (debug && stats.fallback[reason] === 1) console.warn(`[rusvelte] falling back to svelte/compiler: ${reason}`);
20
+ }
21
+
22
+ // Options rusvelte implements, with the values it accepts (`undefined` is always fine).
23
+ // Handled here rather than natively: `sourcemap`, `outputFilename`, `cssOutputFilename`,
24
+ // `warningFilter` and `modernAst`.
25
+ const COMPONENT_OPTIONS = {
26
+ filename: 'string',
27
+ generate: ['client', 'server'],
28
+ dev: 'boolean',
29
+ hmr: 'boolean',
30
+ css: ['external', 'injected'],
31
+ runes: 'boolean',
32
+ experimental: { async: 'boolean' },
33
+ accessors: 'boolean',
34
+ immutable: 'boolean',
35
+ customElement: 'boolean',
36
+ discloseVersion: 'boolean',
37
+ compatibility: { componentApi: [4, 5] },
38
+ namespace: ['html', 'svg', 'mathml'],
39
+ preserveComments: 'boolean',
40
+ preserveWhitespace: 'boolean',
41
+ fragments: ['html', 'tree'],
42
+ rootDir: 'string',
43
+ name: 'string',
44
+ sourcemap: 'any',
45
+ outputFilename: 'string',
46
+ cssOutputFilename: 'string',
47
+ warningFilter: 'function',
48
+ modernAst: 'boolean'
49
+ };
50
+
51
+ const MODULE_OPTIONS = {
52
+ filename: 'string',
53
+ generate: ['client', 'server'],
54
+ dev: 'boolean',
55
+ rootDir: 'string',
56
+ experimental: { async: 'boolean' },
57
+ warningFilter: 'function'
58
+ };
59
+
60
+ /**
61
+ * The first option rusvelte can't handle, or null
62
+ * @param {Record<string, any>} options
63
+ * @param {Record<string, any>} spec
64
+ * @param {string} [prefix]
65
+ * @returns {string | null}
66
+ */
67
+ function unsupported_option(options, spec, prefix = '') {
68
+ for (const [key, value] of Object.entries(options)) {
69
+ if (value === undefined) continue;
70
+ const expected = spec[key];
71
+ const name = prefix + key;
72
+ if (expected === undefined) return name;
73
+ if (expected === 'any') continue;
74
+ if (Array.isArray(expected)) {
75
+ if (!expected.includes(value)) return `${name}: ${JSON.stringify(value)}`;
76
+ } else if (typeof expected === 'object') {
77
+ if (value === null || typeof value !== 'object') return name;
78
+ const inner = unsupported_option(value, expected, name + '.');
79
+ if (inner) return inner;
80
+ } else if (typeof value !== expected) {
81
+ return name;
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+
87
+ /** Options as the native side reads them (functions and maps left out) */
88
+ function native_options(options) {
89
+ const { sourcemap, warningFilter, outputFilename, cssOutputFilename, modernAst, ...rest } = options;
90
+ return JSON.stringify(rest);
91
+ }
92
+
93
+ /** `remove_bom` */
94
+ function remove_bom(source) {
95
+ return source.charCodeAt(0) === 0xfeff ? source.slice(1) : source;
96
+ }
97
+
98
+ /** magic-string's `getRelativePath` */
99
+ function get_relative_path(from, to) {
100
+ const from_parts = from.split(/[/\\]/);
101
+ const to_parts = to.split(/[/\\]/);
102
+ from_parts.pop();
103
+ while (from_parts[0] === to_parts[0]) {
104
+ from_parts.shift();
105
+ to_parts.shift();
106
+ }
107
+ if (from_parts.length) {
108
+ let i = from_parts.length;
109
+ while (i--) from_parts[i] = '..';
110
+ }
111
+ return from_parts.concat(to_parts).join('/');
112
+ }
113
+
114
+ /** esrap's `SourceMap` (same fields, in the same order) */
115
+ class JsSourceMap {
116
+ version = 3;
117
+ names = [];
118
+
119
+ constructor(mappings, source_name, source) {
120
+ this.sources = [source_name || null];
121
+ this.sourcesContent = [source || null];
122
+ this.mappings = mappings;
123
+ }
124
+
125
+ toString() {
126
+ return JSON.stringify(this);
127
+ }
128
+
129
+ toUrl() {
130
+ return 'data:application/json;charset=utf-8;base64,' + Buffer.from(this.toString(), 'utf-8').toString('base64');
131
+ }
132
+ }
133
+
134
+ /**
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
143
+ */
144
+ export function create(svelte) {
145
+ const { real, state, CompileDiagnostic, merge_with_preprocessor_map, get_source_name, MagicSourceMap } = svelte;
146
+
147
+ /** results rusvelte produced (not the real compiler) */
148
+ const native_results = new WeakSet();
149
+
150
+ class CompileWarning extends CompileDiagnostic {
151
+ name = 'CompileWarning';
152
+ }
153
+
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
+ /**
167
+ * Runs the native compiler; null means "use the real one"
168
+ * @param {boolean} module
169
+ */
170
+ function run(source, options, module) {
171
+ // `validate-options.js` defaults `rootDir` to the working directory
172
+ if (options.rootDir === undefined && typeof process !== 'undefined') options = { ...options, rootDir: process.cwd() };
173
+ if (!version_ok) return fell_back(`svelte ${real.VERSION}`), null;
174
+ const bad = unsupported_option(options, module ? MODULE_OPTIONS : COMPONENT_OPTIONS);
175
+ if (bad) return fell_back(`option ${bad}`), null;
176
+ const result = JSON.parse((module ? native.compileModule : native.compile)(source, native_options(options)));
177
+ if (result.unsupported) return fell_back(result.unsupported), null;
178
+ // let the real compiler throw, so the error is exactly Svelte's
179
+ if (result.error) return fell_back(`compile error ${result.error.code}`), null;
180
+ stats.native++;
181
+ return result;
182
+ }
183
+
184
+ /** `state` as compile leaves it, so `CompileDiagnostic` fills in filename, start/end, frame */
185
+ function set_state(source, options, runes) {
186
+ state.reset({ warning: options.warningFilter, filename: options.filename });
187
+ state.set_source(source);
188
+ state.adjust({ dev: !!options.dev, rootDir: options.rootDir ?? process.cwd(), runes });
189
+ }
190
+
191
+ function warnings_of(result, options) {
192
+ const filter = options.warningFilter ?? (() => true);
193
+ const out = [];
194
+ for (const w of result.warnings) {
195
+ const warning = new CompileWarning(w.code, w.message, w.position ?? undefined);
196
+ if (filter(warning)) out.push(warning);
197
+ }
198
+ return out;
199
+ }
200
+
201
+ /** Reports the first difference between our result and the real compiler's */
202
+ function check(kind, source, options, ours) {
203
+ const theirs = (kind === 'module' ? real.compileModule : real.compile)(source, options);
204
+ const pairs = [
205
+ ['js', ours.js.code, theirs.js.code],
206
+ ['css', ours.css?.code, theirs.css?.code],
207
+ ['warnings', JSON.stringify(ours.warnings), JSON.stringify(theirs.warnings)]
208
+ ];
209
+ for (const [what, a, b] of pairs) {
210
+ if (a === b) continue;
211
+ const al = (a ?? '').split('\n');
212
+ const bl = (b ?? '').split('\n');
213
+ let i = 0;
214
+ while (i < al.length && al[i] === bl[i]) i++;
215
+ const shown = Object.fromEntries(Object.entries(options).filter(([, v]) => typeof v !== 'function'));
216
+ console.warn(
217
+ `[rusvelte] ${what} differs for ${options.filename} with ${JSON.stringify({ ...shown, sourcemap: undefined })}\n line ${i + 1}\n svelte: ${bl[i]}\n rusvelte: ${al[i]}`
218
+ );
219
+ stats.verify_failures = (stats.verify_failures ?? 0) + 1;
220
+ return;
221
+ }
222
+ }
223
+
224
+ /** @type {typeof real.compile} */
225
+ function compile(source, options) {
226
+ const result = compile_native(source, options);
227
+ if (verify && native_results.has(result)) check('component', remove_bom(source), options, result);
228
+ return result;
229
+ }
230
+
231
+ /** @type {typeof real.compile} */
232
+ function compile_native(source, options) {
233
+ source = remove_bom(source);
234
+ const result = run(source, options, false);
235
+ if (!result) return real.compile(source, options);
236
+ set_state(source, options, result.runes);
237
+ // `validate_component_options` defaults the filename
238
+ options = { ...options, filename: options.filename ?? '(unknown)' };
239
+
240
+ const js_source_name = get_source_name(options.filename, options.outputFilename, 'input.svelte');
241
+ const js = { code: result.js.code, map: new JsSourceMap(result.js.mappings, js_source_name, source) };
242
+ merge_with_preprocessor_map(js, options, js_source_name);
243
+
244
+ let css = null;
245
+ if (result.css) {
246
+ const file = options.cssOutputFilename || options.filename;
247
+ const map = new MagicSourceMap({
248
+ file: file ? file.split(/[/\\]/).pop() : undefined,
249
+ sources: [options.filename ? get_relative_path(file || '', options.filename) : file || ''],
250
+ sourcesContent: [source],
251
+ names: [],
252
+ mappings: []
253
+ });
254
+ map.mappings = result.css.mappings;
255
+ css = { code: result.css.code, map, hasGlobal: result.css.hasGlobal };
256
+ merge_with_preprocessor_map(css, options, css.map.sources[0]);
257
+ }
258
+
259
+ const warnings = warnings_of(result, options);
260
+ let ast;
261
+ const compiled = {
262
+ js,
263
+ css,
264
+ warnings,
265
+ metadata: { runes: result.runes },
266
+ // parsed on demand: vite-plugin-svelte doesn't read it
267
+ get ast() {
268
+ return (ast ??= real.parse(source, { modern: !!options.modernAst, filename: options.filename }));
269
+ },
270
+ set ast(value) {
271
+ ast = value;
272
+ }
273
+ };
274
+ native_results.add(compiled);
275
+ return compiled;
276
+ }
277
+
278
+ /** @type {typeof real.compileModule} */
279
+ function compileModule(source, options) {
280
+ source = remove_bom(source);
281
+ const result = run(source, options, true);
282
+ if (!result) return real.compileModule(source, options);
283
+ set_state(source, options, true);
284
+ options = { ...options, filename: options.filename ?? '(unknown)' };
285
+ const js_source_name = get_source_name(options.filename, undefined, 'input.svelte.js');
286
+ return {
287
+ js: { code: result.js.code, map: new JsSourceMap(result.js.mappings, js_source_name, source) },
288
+ css: null,
289
+ warnings: warnings_of(result, options),
290
+ metadata: { runes: true },
291
+ ast: null
292
+ };
293
+ }
294
+
295
+ return { compile, compileModule };
296
+ }
package/native.js ADDED
@@ -0,0 +1,43 @@
1
+ // The native module: `compile(source, optionsJson)` and `compileModule(source, optionsJson)`
2
+ // return JSON (see bindings/node/src/lib.rs). It comes from `RUSVELTE_NATIVE` if set, else a
3
+ // local build (`rusvelte.node`, made by `npm run build`), else the prebuilt package for this
4
+ // platform (`@rusveltejs/<platform>-<arch>[-<libc>]`, an optional dependency).
5
+ import { createRequire } from 'node:module';
6
+ import fs from 'node:fs';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const require = createRequire(import.meta.url);
10
+
11
+ /** The suffix of this platform's prebuilt package */
12
+ function platform_package() {
13
+ const { platform, arch } = process;
14
+ if (platform === 'linux') {
15
+ const glibc = process.report?.getReport?.().header?.glibcVersionRuntime;
16
+ return `@rusveltejs/linux-${arch}-${glibc ? 'gnu' : 'musl'}`;
17
+ }
18
+ if (platform === 'win32') return `@rusveltejs/win32-${arch}-msvc`;
19
+ return `@rusveltejs/${platform}-${arch}`;
20
+ }
21
+
22
+ function load() {
23
+ if (process.env.RUSVELTE_NATIVE) return require(process.env.RUSVELTE_NATIVE);
24
+ const local = fileURLToPath(new URL('./rusvelte.node', import.meta.url));
25
+ if (fs.existsSync(local)) return require(local);
26
+ return require(platform_package());
27
+ }
28
+
29
+ /** The native module, or null where there's none (compiler.js then uses svelte/compiler) */
30
+ export let native = null;
31
+ /** Why the native module couldn't be loaded */
32
+ export let load_error = null;
33
+ try {
34
+ native = load();
35
+ } catch (e) {
36
+ const where = process.env.RUSVELTE_NATIVE ?? platform_package();
37
+ load_error = `no native module for ${process.platform}-${process.arch} (${where}): ${e.message.split('\n')[0]}`;
38
+ }
39
+
40
+ export const compile = native?.compile;
41
+ export const compileModule = native?.compileModule;
42
+ /** The Svelte version whose output this build reproduces */
43
+ export const svelteVersion = native?.svelteVersion() ?? null;
package/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "@rusveltejs/compiler",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "The Svelte 5 compiler ported to Rust, as a drop-in for svelte/compiler in Vite builds",
5
+ "keywords": [
6
+ "svelte",
7
+ "compiler",
8
+ "rust",
9
+ "vite",
10
+ "sveltekit",
11
+ "oxc"
12
+ ],
13
+ "homepage": "https://nihilist-narwhal.github.io/rusvelte/",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/nihilist-narwhal/rusvelte.git",
17
+ "directory": "packages/rusvelte"
18
+ },
19
+ "license": "MIT",
20
+ "type": "module",
21
+ "exports": {
22
+ ".": "./native.js",
23
+ "./register": "./register.js",
24
+ "./vite": "./vite.js"
25
+ },
26
+ "files": [
27
+ "native.js",
28
+ "compiler.js",
29
+ "register.js",
30
+ "vite.js",
31
+ "README.md",
32
+ "LICENSE",
33
+ "THIRD_PARTY_NOTICES.md"
34
+ ],
35
+ "engines": {
36
+ "node": ">=22.15"
37
+ },
38
+ "peerDependencies": {
39
+ "svelte": "^5.0.0"
40
+ },
41
+ "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"
48
+ }
49
+ }
package/register.js ADDED
@@ -0,0 +1,52 @@
1
+ // Makes `import ... from 'svelte/compiler'` (in vite-plugin-svelte, SvelteKit, anything loaded
2
+ // after this) get a module that re-exports the installed svelte/compiler with `compile` and
3
+ // `compileModule` replaced by rusvelte's (see compiler.js). Load it before those packages:
4
+ //
5
+ // NODE_OPTIONS="--import @rusveltejs/compiler/register" vite build
6
+ //
7
+ // or call `register()` before importing them dynamically (vite.js does).
8
+ import { registerHooks } from 'node:module';
9
+
10
+ const PACKAGE = new URL('./', import.meta.url).href;
11
+ const SHIM = new URL('./svelte-compiler.js', import.meta.url).href;
12
+ const COMPILER = new URL('./compiler.js', import.meta.url).href;
13
+
14
+ let registered = false;
15
+
16
+ export function register() {
17
+ if (registered) return;
18
+ registered = true;
19
+ registerHooks({
20
+ resolve(specifier, context, next) {
21
+ 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')) {
28
+ const real = next(specifier, context);
29
+ return { url: `${SHIM}?real=${encodeURIComponent(real.url)}`, format: 'module', shortCircuit: true };
30
+ }
31
+ return next(specifier, context);
32
+ },
33
+ load(url, context, next) {
34
+ 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);
37
+ 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';
44
+ import { create } from ${JSON.stringify(COMPILER)};
45
+ export const { compile, compileModule } = create({ real, state, CompileDiagnostic, merge_with_preprocessor_map, get_source_name, MagicSourceMap });
46
+ `;
47
+ return { format: 'module', source, shortCircuit: true };
48
+ }
49
+ });
50
+ }
51
+
52
+ register();
package/vite.js ADDED
@@ -0,0 +1,23 @@
1
+ // Use rusvelte from vite.config without NODE_OPTIONS: wrap the import of the Svelte plugin so it
2
+ // happens after the `svelte/compiler` hook is registered.
3
+ //
4
+ // import { rusvelte } from '@rusveltejs/compiler/vite';
5
+ // export default defineConfig({
6
+ // plugins: [rusvelte(() => import('@sveltejs/kit/vite').then((m) => m.sveltekit()))]
7
+ // });
8
+ //
9
+ // (or `import('@sveltejs/vite-plugin-svelte').then((m) => m.svelte())`). The import must be
10
+ // dynamic: a static import of vite-plugin-svelte anywhere in the config (vitePreprocess, say)
11
+ // binds the real compiler before the hook exists. Vite accepts a promise of plugins.
12
+ import { register } from './register.js';
13
+ export { stats } from './compiler.js';
14
+
15
+ /**
16
+ * @template T
17
+ * @param {() => Promise<T>} load
18
+ * @returns {Promise<T>}
19
+ */
20
+ export function rusvelte(load) {
21
+ register();
22
+ return load();
23
+ }