@rsvelte/vite-plugin-svelte 0.4.2 → 0.5.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/README.md +80 -10
- package/package.json +5 -2
- package/src/plugins/setup-optimizer.js +7 -2
- package/src/utils/compile.js +20 -9
- package/src/utils/map-edits.js +76 -0
package/README.md
CHANGED
|
@@ -5,13 +5,27 @@ by the Rust [rsvelte](https://github.com/baseballyama/rsvelte) compiler. It is a
|
|
|
5
5
|
fork of the official [`@sveltejs/vite-plugin-svelte`](https://github.com/sveltejs/vite-plugin-svelte)
|
|
6
6
|
whose compile / preprocess / HMR calls route through the rsvelte compiler (via
|
|
7
7
|
[`@rsvelte/vite-plugin-svelte-native`](https://www.npmjs.com/package/@rsvelte/vite-plugin-svelte-native))
|
|
8
|
-
instead of `svelte/compiler
|
|
9
|
-
Rust toolchain with the same plugin surface.
|
|
8
|
+
instead of `svelte/compiler` — a drop-in replacement with the same plugin surface.
|
|
10
9
|
|
|
11
|
-
> **⚠️ Early stage.**
|
|
12
|
-
> but treat it as experimental. Supports Vite 6, 7 and 8.
|
|
10
|
+
> **⚠️ Early stage.** Treat it as experimental.
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
The Rust compiler ships as a native binding via `optionalDependencies` and is
|
|
13
|
+
resolved automatically for your platform.
|
|
14
|
+
|
|
15
|
+
**Compatibility:** Svelte `^5.0.0`, Vite `6.3+`, `7`, or `8`.
|
|
16
|
+
|
|
17
|
+
## Which setup are you using?
|
|
18
|
+
|
|
19
|
+
| Setup | How to wire it up |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| **Plain Vite + Svelte** — you call `svelte()` yourself in `vite.config` | [(A) Import the plugin directly](#a-plain-vite--svelte) |
|
|
22
|
+
| **SvelteKit** — your `vite.config` only has `sveltekit()` | [(B) Alias it with a package-manager override](#b-sveltekit) |
|
|
23
|
+
|
|
24
|
+
If you are on SvelteKit, do **not** add `svelte()` to your plugins list — see (B).
|
|
25
|
+
|
|
26
|
+
## (A) Plain Vite + Svelte
|
|
27
|
+
|
|
28
|
+
Install the plugin:
|
|
15
29
|
|
|
16
30
|
```bash
|
|
17
31
|
npm install -D @rsvelte/vite-plugin-svelte
|
|
@@ -19,10 +33,7 @@ npm install -D @rsvelte/vite-plugin-svelte
|
|
|
19
33
|
# yarn add -D @rsvelte/vite-plugin-svelte
|
|
20
34
|
```
|
|
21
35
|
|
|
22
|
-
|
|
23
|
-
resolved automatically for your platform.
|
|
24
|
-
|
|
25
|
-
## Usage
|
|
36
|
+
Add it to your Vite config:
|
|
26
37
|
|
|
27
38
|
```js
|
|
28
39
|
// vite.config.js
|
|
@@ -38,7 +49,7 @@ export default defineConfig({
|
|
|
38
49
|
});
|
|
39
50
|
```
|
|
40
51
|
|
|
41
|
-
The
|
|
52
|
+
The package exposes the same API as the official plugin, including
|
|
42
53
|
`vitePreprocess` and `loadSvelteConfig`:
|
|
43
54
|
|
|
44
55
|
```js
|
|
@@ -53,6 +64,65 @@ export default {
|
|
|
53
64
|
};
|
|
54
65
|
```
|
|
55
66
|
|
|
67
|
+
## (B) SvelteKit
|
|
68
|
+
|
|
69
|
+
In a SvelteKit app your `vite.config` only lists `sveltekit()`, and the Svelte
|
|
70
|
+
plugin is loaded **from inside `@sveltejs/kit`** under the package name
|
|
71
|
+
`@sveltejs/vite-plugin-svelte` — it never appears in your config. So the way to
|
|
72
|
+
swap in rsvelte is to redirect that package's module resolution with a
|
|
73
|
+
package-manager override (alias), not to touch `vite.config` at all.
|
|
74
|
+
|
|
75
|
+
Add the override to your `package.json`, then reinstall:
|
|
76
|
+
|
|
77
|
+
```jsonc
|
|
78
|
+
// pnpm
|
|
79
|
+
{
|
|
80
|
+
"pnpm": {
|
|
81
|
+
"overrides": {
|
|
82
|
+
"@sveltejs/vite-plugin-svelte": "npm:@rsvelte/vite-plugin-svelte@^0.4.1"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```jsonc
|
|
89
|
+
// npm
|
|
90
|
+
{
|
|
91
|
+
"overrides": {
|
|
92
|
+
"@sveltejs/vite-plugin-svelte": "npm:@rsvelte/vite-plugin-svelte@^0.4.1"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```jsonc
|
|
98
|
+
// yarn (v1 and Berry)
|
|
99
|
+
{
|
|
100
|
+
"resolutions": {
|
|
101
|
+
"@sveltejs/vite-plugin-svelte": "npm:@rsvelte/vite-plugin-svelte@^0.4.1"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Because the override targets the package name, it also redirects the
|
|
107
|
+
`import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'` in your
|
|
108
|
+
`svelte.config.js` to the rsvelte version — no change needed there either.
|
|
109
|
+
|
|
110
|
+
### Don't do this
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
// vite.config.js — ❌ WRONG
|
|
114
|
+
import { sveltekit } from '@sveltejs/kit/vite';
|
|
115
|
+
import { svelte } from '@rsvelte/vite-plugin-svelte';
|
|
116
|
+
|
|
117
|
+
export default {
|
|
118
|
+
plugins: [sveltekit(), svelte()] // two Svelte plugins → double compilation
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`sveltekit()` already loads the Svelte plugin internally. Adding `svelte()`
|
|
123
|
+
alongside it registers a second one, so every component compiles twice. Use the
|
|
124
|
+
override in (B) instead.
|
|
125
|
+
|
|
56
126
|
## Documentation
|
|
57
127
|
|
|
58
128
|
Because this is a fork of `@sveltejs/vite-plugin-svelte`, the upstream
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rsvelte/vite-plugin-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "dominikg",
|
|
6
6
|
"files": [
|
|
@@ -34,12 +34,15 @@
|
|
|
34
34
|
},
|
|
35
35
|
"homepage": "https://github.com/baseballyama/rsvelte/tree/main/apps/npm/vite-plugin-svelte#readme",
|
|
36
36
|
"dependencies": {
|
|
37
|
+
"@jridgewell/remapping": "^2.3.5",
|
|
38
|
+
"@jridgewell/trace-mapping": "^0.3.31",
|
|
37
39
|
"@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
|
|
40
|
+
"acorn": "^8.16.0",
|
|
38
41
|
"deepmerge": "^4.3.1",
|
|
39
42
|
"magic-string": "^1.0.0",
|
|
40
43
|
"obug": "^2.1.0",
|
|
41
44
|
"vitefu": "^1.1.1",
|
|
42
|
-
"@rsvelte/vite-plugin-svelte-native": "^0.
|
|
45
|
+
"@rsvelte/vite-plugin-svelte-native": "^0.3.7"
|
|
43
46
|
},
|
|
44
47
|
"peerDependencies": {
|
|
45
48
|
"svelte": "^5.0.0",
|
|
@@ -201,7 +201,8 @@ async function compileSvelte(options, { filename, code }, statsCollection) {
|
|
|
201
201
|
|
|
202
202
|
if (compileOptions.hmr && options.emitCss) {
|
|
203
203
|
const hash = `s-${safeBase64Hash(normalize(filename, options.root))}`;
|
|
204
|
-
compileOptions.
|
|
204
|
+
compileOptions.cssHashOverride = hash;
|
|
205
|
+
delete compileOptions.cssHash;
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
let preprocessed;
|
|
@@ -239,7 +240,11 @@ async function compileSvelte(options, { filename, code }, statsCollection) {
|
|
|
239
240
|
}
|
|
240
241
|
: compileOptions;
|
|
241
242
|
const endStat = statsCollection?.start(filename);
|
|
242
|
-
|
|
243
|
+
// A dynamic cssHash function only works through the async callback bridge.
|
|
244
|
+
const compiled =
|
|
245
|
+
typeof finalCompileOptions.cssHash === 'function'
|
|
246
|
+
? await svelte.compileAsync(finalCode, finalCompileOptions)
|
|
247
|
+
: svelte.compile(finalCode, finalCompileOptions);
|
|
243
248
|
if (endStat) {
|
|
244
249
|
endStat();
|
|
245
250
|
}
|
package/src/utils/compile.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as svelte from '@rsvelte/vite-plugin-svelte-native';
|
|
2
2
|
import { safeBase64Hash } from './hash.js';
|
|
3
3
|
import { log } from './log.js';
|
|
4
|
+
import { addPartialAcceptExports, applyMapEdits } from './map-edits.js';
|
|
4
5
|
|
|
5
6
|
import { mapToRelative } from './sourcemaps.js';
|
|
6
7
|
import { enhanceCompileError } from './error.js';
|
|
@@ -59,7 +60,12 @@ export function createCompileSvelte() {
|
|
|
59
60
|
let finalCode = code;
|
|
60
61
|
if (compileOptions.hmr && options.emitCss) {
|
|
61
62
|
const hash = `s-${safeBase64Hash(normalizedFilename)}`;
|
|
62
|
-
|
|
63
|
+
// Constant hash keeps the css scope class stable across HMR updates; the
|
|
64
|
+
// override path skips the cssHash callback bridge entirely. Drop any user
|
|
65
|
+
// cssHash so the override is the sole hash source (matches upstream, which
|
|
66
|
+
// overwrites cssHash here).
|
|
67
|
+
compileOptions.cssHashOverride = hash;
|
|
68
|
+
delete compileOptions.cssHash;
|
|
63
69
|
const closeStylePos = code.lastIndexOf('</style>');
|
|
64
70
|
if (closeStylePos > -1) {
|
|
65
71
|
// inject rule that forces compile to attach scope class to every node in the template
|
|
@@ -93,18 +99,22 @@ export function createCompileSvelte() {
|
|
|
93
99
|
/** @type {import('@rsvelte/vite-plugin-svelte-native').CompileResult} */
|
|
94
100
|
let compiled;
|
|
95
101
|
try {
|
|
96
|
-
|
|
102
|
+
// A dynamic cssHash function only works through the async callback
|
|
103
|
+
// bridge; everything else stays on the faster synchronous path.
|
|
104
|
+
compiled =
|
|
105
|
+
typeof finalCompileOptions.cssHash === 'function'
|
|
106
|
+
? await svelte.compileAsync(finalCode, { ...finalCompileOptions, filename })
|
|
107
|
+
: svelte.compile(finalCode, { ...finalCompileOptions, filename });
|
|
97
108
|
|
|
98
109
|
// patch output with partial accept until svelte does it
|
|
99
110
|
// TODO remove later
|
|
100
111
|
if (
|
|
101
112
|
options.server?.config.experimental.hmrPartialAccept &&
|
|
102
|
-
compiled.js.code.includes('import.meta.hot
|
|
113
|
+
compiled.js.code.includes('import.meta.hot')
|
|
103
114
|
) {
|
|
104
|
-
compiled
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
);
|
|
115
|
+
applyMapEdits(compiled, filename, (editor, generated) => {
|
|
116
|
+
addPartialAcceptExports(editor, generated);
|
|
117
|
+
});
|
|
108
118
|
}
|
|
109
119
|
} catch (e) {
|
|
110
120
|
enhanceCompileError(e, code, options.preprocess);
|
|
@@ -127,8 +137,9 @@ export function createCompileSvelte() {
|
|
|
127
137
|
const hasCss = compiled.css?.code?.trim()?.length ?? 0 > 0;
|
|
128
138
|
// compiler might not emit css with mode none or it may be empty
|
|
129
139
|
if (emitCss && hasCss) {
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
applyMapEdits(compiled, filename, (editor) => {
|
|
141
|
+
editor.append(`\nimport ${JSON.stringify(cssId)};\n`);
|
|
142
|
+
});
|
|
132
143
|
}
|
|
133
144
|
}
|
|
134
145
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import remapping from '@jridgewell/remapping';
|
|
2
|
+
import { parse } from 'acorn';
|
|
3
|
+
import MagicString from 'magic-string';
|
|
4
|
+
|
|
5
|
+
/** @param {any} callee */
|
|
6
|
+
function isHotAcceptCallee(callee) {
|
|
7
|
+
return (
|
|
8
|
+
callee.type === 'MemberExpression' &&
|
|
9
|
+
!callee.computed &&
|
|
10
|
+
callee.property.type === 'Identifier' &&
|
|
11
|
+
callee.property.name === 'accept' &&
|
|
12
|
+
callee.object.type === 'MemberExpression' &&
|
|
13
|
+
!callee.object.computed &&
|
|
14
|
+
callee.object.property.type === 'Identifier' &&
|
|
15
|
+
callee.object.property.name === 'hot' &&
|
|
16
|
+
callee.object.object.type === 'MetaProperty' &&
|
|
17
|
+
callee.object.object.meta.name === 'import' &&
|
|
18
|
+
callee.object.object.property.name === 'meta'
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {any} node
|
|
24
|
+
* @param {any[]} calls
|
|
25
|
+
*/
|
|
26
|
+
function collectHotAcceptCalls(node, calls) {
|
|
27
|
+
if (!node || typeof node !== 'object') return;
|
|
28
|
+
if (node.type === 'CallExpression' && isHotAcceptCallee(node.callee)) {
|
|
29
|
+
calls.push(node);
|
|
30
|
+
}
|
|
31
|
+
for (const value of Object.values(node)) {
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
for (const child of value) collectHotAcceptCalls(child, calls);
|
|
34
|
+
} else if (value && typeof value === 'object' && typeof value.type === 'string') {
|
|
35
|
+
collectHotAcceptCalls(value, calls);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {{ js: { code: string, map: any } }} compiled
|
|
42
|
+
* @param {string} filename
|
|
43
|
+
* @param {(editor: MagicString, code: string) => void} edit
|
|
44
|
+
*/
|
|
45
|
+
export function applyMapEdits(compiled, filename, edit) {
|
|
46
|
+
const editor = new MagicString(compiled.js.code);
|
|
47
|
+
edit(editor, compiled.js.code);
|
|
48
|
+
const previousMap = compiled.js.map;
|
|
49
|
+
compiled.js.code = editor.toString();
|
|
50
|
+
if (!previousMap) return;
|
|
51
|
+
const editMap = editor.generateMap({ hires: true, source: filename });
|
|
52
|
+
let loadedPreviousMap = false;
|
|
53
|
+
compiled.js.map = remapping(/** @type {any} */ (editMap), (source) => {
|
|
54
|
+
if (source !== filename || loadedPreviousMap) return null;
|
|
55
|
+
loadedPreviousMap = true;
|
|
56
|
+
return /** @type {any} */ (previousMap);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {MagicString} editor
|
|
62
|
+
* @param {string} code
|
|
63
|
+
*/
|
|
64
|
+
export function addPartialAcceptExports(editor, code) {
|
|
65
|
+
const calls = [];
|
|
66
|
+
collectHotAcceptCalls(parse(code, { ecmaVersion: 'latest', sourceType: 'module' }), calls);
|
|
67
|
+
for (const call of calls) {
|
|
68
|
+
const firstArgument = call.arguments[0];
|
|
69
|
+
if (!firstArgument || firstArgument.type === 'SpreadElement') continue;
|
|
70
|
+
editor.overwrite(
|
|
71
|
+
call.callee.start,
|
|
72
|
+
firstArgument.start,
|
|
73
|
+
`${code.slice(call.callee.start, call.callee.end)}Exports([\"default\"],`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|