@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 +9 -0
- package/README.md +67 -2
- package/THIRD_PARTY_NOTICES.md +87 -0
- package/compiler.js +296 -0
- package/native.js +43 -0
- package/package.json +47 -4
- package/register.js +52 -0
- package/vite.js +23 -0
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
|
-
#
|
|
1
|
+
# @rusveltejs/compiler
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
+
}
|