Skip to content

Commit fe52a2d

Browse files
committed
feat: support compiling svelte.js/ts files in Svelte 5
1 parent ed67842 commit fe52a2d

File tree

5 files changed

+123
-51
lines changed

5 files changed

+123
-51
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# rollup-plugin-svelte changelog
22

3+
## 7.2.0
4+
5+
- Support compiling `svelte.js/ts` files in Svelte 5
6+
37
## 7.1.6
48

59
- Adjust inferred `css` option for Svelte 4

index.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const path = require('path');
22
const fs = require('fs');
33
const { resolve } = require('resolve.exports');
44
const { createFilter } = require('@rollup/pluginutils');
5-
const { compile, preprocess, VERSION } = require('svelte/compiler');
5+
const svelte = require('svelte/compiler');
66

77
const PREFIX = '[rollup-plugin-svelte]';
88

@@ -26,7 +26,7 @@ module.exports = function (options = {}) {
2626
const extensions = rest.extensions || ['.svelte'];
2727
const filter = createFilter(rest.include, rest.exclude);
2828

29-
if (VERSION[0] === '3') {
29+
if (svelte.VERSION[0] === '3') {
3030
compilerOptions.format = 'esm';
3131
}
3232

@@ -42,7 +42,7 @@ module.exports = function (options = {}) {
4242
const { onwarn, emitCss = true } = rest;
4343

4444
if (emitCss) {
45-
const [majorVer] = VERSION.split('.');
45+
const [majorVer] = svelte.VERSION.split('.');
4646
const cssOptionValue = majorVer > 3 ? 'external' : false;
4747
if (compilerOptions.css) {
4848
console.warn(
@@ -129,6 +129,24 @@ module.exports = function (options = {}) {
129129
async transform(code, id) {
130130
if (!filter(id)) return null;
131131

132+
if (
133+
Number(svelte.VERSION.split('.')[0]) >= 5 &&
134+
(id.endsWith('.svelte.js') || id.endsWith('.svelte.ts'))
135+
) {
136+
const compiled = svelte.compileModule(code, {
137+
filename: id,
138+
dev: compilerOptions.dev,
139+
generate: compilerOptions.generate,
140+
});
141+
142+
(compiled.warnings || []).forEach((warning) => {
143+
if (onwarn) onwarn(warning, this.warn);
144+
else this.warn(warning);
145+
});
146+
147+
return compiled.js;
148+
}
149+
132150
const extension = path.extname(id);
133151
if (!~extensions.indexOf(extension)) return null;
134152

@@ -137,13 +155,13 @@ module.exports = function (options = {}) {
137155
const svelte_options = { ...compilerOptions, filename };
138156

139157
if (rest.preprocess) {
140-
const processed = await preprocess(code, rest.preprocess, { filename });
158+
const processed = await svelte.preprocess(code, rest.preprocess, { filename });
141159
if (processed.dependencies) dependencies.push(...processed.dependencies);
142160
if (processed.map) svelte_options.sourcemap = processed.map;
143161
code = processed.code;
144162
}
145163

146-
const compiled = compile(code, svelte_options);
164+
const compiled = svelte.compile(code, svelte_options);
147165

148166
(compiled.warnings || []).forEach((warning) => {
149167
if (!emitCss && warning.code === 'css-unused-selector') return;

test/filename-test2/src/main.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { counter } from './runes.svelte.js';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const counter = $state({ value: 0 });

test/index.js

Lines changed: 94 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const sander = require('sander');
99
const plugin = require('..');
1010

1111
const context = {
12-
resolve: () => 'resolved'
12+
resolve: () => 'resolved',
1313
};
1414

1515
test('resolves using pkg.svelte', async () => {
@@ -22,15 +22,14 @@ test('resolves using pkg.svelte', async () => {
2222

2323
test('ignores built-in modules', async () => {
2424
const p = plugin();
25-
assert.is(
26-
await p.resolveId.call(context, 'path', path.resolve('test/foo/main.js')), undefined
27-
);
25+
assert.is(await p.resolveId.call(context, 'path', path.resolve('test/foo/main.js')), undefined);
2826
});
2927

3028
test('ignores esm modules that do not export package.json', async () => {
3129
const p = plugin();
3230
assert.is(
33-
await p.resolveId.call(context, 'esm-no-pkg-export', path.resolve('test/foo/main.js')), undefined
31+
await p.resolveId.call(context, 'esm-no-pkg-export', path.resolve('test/foo/main.js')),
32+
undefined
3433
);
3534
});
3635

@@ -45,7 +44,8 @@ test('resolves esm module that exports package.json', async () => {
4544
test('ignores virtual modules', async () => {
4645
const p = plugin();
4746
assert.is(
48-
await p.resolveId.call(context, 'path', path.resolve('\0some-plugin-generated-module')), undefined
47+
await p.resolveId.call(context, 'path', path.resolve('\0some-plugin-generated-module')),
48+
undefined
4949
);
5050
});
5151

@@ -72,8 +72,8 @@ test('respects `sourcemapExcludeSources` Rollup option', async () => {
7272

7373
const bundle = await rollup({
7474
input: 'test/sourcemap-test/src/main.js',
75-
plugins: [ plugin({ emitCss: false }) ],
76-
external: ['svelte/internal']
75+
plugins: [plugin({ emitCss: false })],
76+
external: ['svelte/internal'],
7777
});
7878

7979
const { output } = await bundle.generate({
@@ -98,43 +98,48 @@ test('respects `sourcemapExcludeSources` Rollup option', async () => {
9898

9999
test('squelches "unused CSS" warnings if `emitCss: false`', () => {
100100
const p = plugin({
101-
emitCss: false
101+
emitCss: false,
102102
});
103103

104-
p.transform.call({
105-
warn: warning => {
106-
throw new Error(warning.message);
107-
}
108-
}, `
104+
p.transform.call(
105+
{
106+
warn: (warning) => {
107+
throw new Error(warning.message);
108+
},
109+
},
110+
`
109111
<div></div>
110112
<style>
111113
.unused {
112114
color: red;
113115
}
114116
</style>
115-
`, 'test.svelte');
117+
`,
118+
'test.svelte'
119+
);
116120
});
117121

118122
test('preprocesses components', async () => {
119123
const p = plugin({
120124
preprocess: {
121125
markup: ({ content, filename }) => {
122126
return {
123-
code: content
124-
.replace('__REPLACEME__', 'replaced')
125-
.replace('__FILENAME__', filename),
127+
code: content.replace('__REPLACEME__', 'replaced').replace('__FILENAME__', filename),
126128
dependencies: ['foo'],
127129
};
128130
},
129131
style: () => null,
130-
}
132+
},
131133
});
132134

133-
const { code, dependencies } = await p.transform(`
135+
const { code, dependencies } = await p.transform(
136+
`
134137
<h1>Hello __REPLACEME__!</h1>
135138
<h2>file: __FILENAME__</h2>
136139
<style>h1 { color: red; }</style>
137-
`, 'test.svelte');
140+
`,
141+
'test.svelte'
142+
);
138143

139144
assert.is(code.indexOf('__REPLACEME__'), -1, 'content not modified');
140145
assert.is.not(code.indexOf('file: test.svelte'), -1, 'filename not replaced');
@@ -144,13 +149,16 @@ test('preprocesses components', async () => {
144149
test('emits a CSS file', async () => {
145150
const p = plugin();
146151

147-
const transformed = await p.transform(`<h1>Hello!</h1>
152+
const transformed = await p.transform(
153+
`<h1>Hello!</h1>
148154
149155
<style>
150156
h1 {
151157
color: red;
152158
}
153-
</style>`, `path/to/Input.svelte`);
159+
</style>`,
160+
`path/to/Input.svelte`
161+
);
154162

155163
assert.ok(transformed.code.indexOf(`import "path/to/Input.css";`) !== -1);
156164

@@ -160,7 +168,7 @@ test('emits a CSS file', async () => {
160168

161169
const loc = smc.originalPositionFor({
162170
line: 1,
163-
column: 0
171+
column: 0,
164172
});
165173

166174
assert.is(loc.source, 'Input.svelte');
@@ -171,13 +179,16 @@ test('emits a CSS file', async () => {
171179
test('properly escapes CSS paths', async () => {
172180
const p = plugin();
173181

174-
const transformed = await p.transform(`<h1>Hello!</h1>
182+
const transformed = await p.transform(
183+
`<h1>Hello!</h1>
175184
176185
<style>
177186
h1 {
178187
color: red;
179188
}
180-
</style>`, `path\\t'o\\Input.svelte`);
189+
</style>`,
190+
`path\\t'o\\Input.svelte`
191+
);
181192

182193
assert.ok(transformed.code.indexOf(`import "path\\\\t'o\\\\Input.css";`) !== -1);
183194

@@ -187,7 +198,7 @@ test('properly escapes CSS paths', async () => {
187198

188199
const loc = smc.originalPositionFor({
189200
line: 1,
190-
column: 0
201+
column: 0,
191202
});
192203

193204
assert.is(loc.source, 'Input.svelte');
@@ -206,20 +217,30 @@ test('intercepts warnings', async () => {
206217
if (warning.code === 'a11y-hidden') {
207218
handler(warning);
208219
}
209-
}
220+
},
210221
});
211222

212-
await p.transform.call({
213-
warn: warning => {
214-
handled.push(warning);
215-
}
216-
}, `
223+
await p.transform.call(
224+
{
225+
warn: (warning) => {
226+
handled.push(warning);
227+
},
228+
},
229+
`
217230
<h1 aria-hidden>Hello world!</h1>
218231
<marquee>wheee!!!</marquee>
219-
`, 'test.svelte');
232+
`,
233+
'test.svelte'
234+
);
220235

221-
assert.equal(warnings.map(w => w.code), ['a11y-hidden', 'a11y-distracting-elements']);
222-
assert.equal(handled.map(w => w.code), ['a11y-hidden']);
236+
assert.equal(
237+
warnings.map((w) => w.code),
238+
['a11y-hidden', 'a11y-distracting-elements']
239+
);
240+
assert.equal(
241+
handled.map((w) => w.code),
242+
['a11y-hidden']
243+
);
223244
});
224245

225246
test('handles filenames that happen to contain ".svelte"', async () => {
@@ -233,12 +254,12 @@ test('handles filenames that happen to contain ".svelte"', async () => {
233254
{
234255
async resolveId(id) {
235256
if (/A\.svelte/.test(id)) {
236-
await new Promise(f => setTimeout(f, 50));
257+
await new Promise((f) => setTimeout(f, 50));
237258
}
238-
}
259+
},
239260
},
240261
plugin({
241-
emitCss: true
262+
emitCss: true,
242263
}),
243264
{
244265
transform(code, id) {
@@ -250,10 +271,10 @@ test('handles filenames that happen to contain ".svelte"', async () => {
250271
});
251272
return '';
252273
}
253-
}
254-
}
274+
},
275+
},
255276
],
256-
external: ['svelte/internal']
277+
external: ['svelte/internal'],
257278
});
258279

259280
await bundle.write({
@@ -274,6 +295,33 @@ test('handles filenames that happen to contain ".svelte"', async () => {
274295
);
275296
});
276297

298+
// Needs Svelte 5
299+
test.skip('handles ".svelte.ts/js" files', async () => {
300+
sander.rimrafSync('test/filename-test2/dist');
301+
sander.mkdirSync('test/filename-test2/dist');
302+
303+
try {
304+
const bundle = await rollup({
305+
input: 'test/filename-test2/src/main.js',
306+
plugins: [plugin({})],
307+
external: ['svelte/internal'],
308+
});
309+
310+
await bundle.write({
311+
format: 'iife',
312+
file: 'test/filename-test2/dist/bundle.js',
313+
globals: { 'svelte/internal': 'svelte' },
314+
assetFileNames: '[name].[ext]',
315+
sourcemap: true,
316+
});
317+
} catch (err) {
318+
console.log(err);
319+
throw err;
320+
}
321+
322+
assert.not(fs.readFileSync('test/filename-test2/dist/bundle.js', 'utf8').includes('$state'));
323+
});
324+
277325
test('ignores ".html" extension by default', async () => {
278326
sander.rimrafSync('test/node_modules/widget/dist');
279327
sander.mkdirSync('test/node_modules/widget/dist');
@@ -282,7 +330,7 @@ test('ignores ".html" extension by default', async () => {
282330
const bundle = await rollup({
283331
input: 'test/node_modules/widget/index.js',
284332
external: ['svelte/internal'],
285-
plugins: [plugin()]
333+
plugins: [plugin()],
286334
});
287335

288336
await bundle.write({
@@ -311,9 +359,9 @@ test('allows ".html" extension if configured', async () => {
311359
external: ['svelte/internal'],
312360
plugins: [
313361
plugin({
314-
extensions: ['.html']
315-
})
316-
]
362+
extensions: ['.html'],
363+
}),
364+
],
317365
});
318366

319367
await bundle.write({

0 commit comments

Comments
 (0)