Skip to content

Commit dfd4cc6

Browse files
authored
refactor: allow infix notation for svelte modules (#901)
* refactor: allow infix notation for svelte modules and prevent custom extension prebundling * docs: regenerate types * refactor: use 2 esbuild plugins to prebundle .svelte and .svelte.js (simplifies regex and separates logic at cost of some code duplication) * fix: regex escape . * fix: prebundle with dev: true by default
1 parent 85acc9f commit dfd4cc6

File tree

15 files changed

+161
-30
lines changed

15 files changed

+161
-30
lines changed

.changeset/angry-pumpkins-deny.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/vite-plugin-svelte': major
3+
---
4+
5+
only prebundle files with default filenames (.svelte for components, .svelte.(js|ts) for modules)

.changeset/friendly-wombats-listen.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/vite-plugin-svelte': patch
3+
---
4+
5+
prebundle with dev: true by default

.changeset/lazy-bats-warn.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@sveltejs/vite-plugin-svelte': minor
3+
---
4+
5+
allow infix notation for svelte modules
6+
7+
Previously, only suffix notation `.svelte.js` was allowed, now you can also use `.svelte.test.js` or `.svelte.stories.js`.
8+
This helps when writing testcases or other auxillary code where you may want to use runes too.

packages/e2e-tests/prebundle-svelte-deps/__tests__/prebundle-svelte-deps.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ async function expectPageToWork() {
1717
expect(await getText('#api-only')).toBe('api loaded: true');
1818
expect(await getText('#simple .label')).toBe('dependency-import');
1919
expect(await getText('#exports-simple .label')).toBe('dependency-import');
20+
expect(await getText('#module button:first-child')).toBe('count is 0');
2021
}
2122

2223
if (!isBuild) {
@@ -31,6 +32,7 @@ if (!isBuild) {
3132
expect(optimizedPaths).toContain('e2e-test-dep-svelte-exports-simple');
3233
expect(optimizedPaths).toContain('e2e-test-dep-svelte-api-only');
3334
expect(optimizedPaths).toContain('e2e-test-dep-svelte-nested');
35+
expect(optimizedPaths).toContain('e2e-test-dep-svelte-module');
3436
});
3537

3638
test('should not optimize excluded svelte dependencies', () => {

packages/e2e-tests/prebundle-svelte-deps/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"e2e-test-dep-svelte-exports-simple": "file:../_test_dependencies/svelte-exports-simple",
1414
"e2e-test-dep-svelte-hybrid": "file:../_test_dependencies/svelte-hybrid",
1515
"e2e-test-dep-svelte-nested": "file:../_test_dependencies/svelte-nested",
16-
"e2e-test-dep-svelte-simple": "file:../_test_dependencies/svelte-simple"
16+
"e2e-test-dep-svelte-simple": "file:../_test_dependencies/svelte-simple",
17+
"e2e-test-dep-svelte-module": "file:../_test_dependencies/svelte-module"
1718
},
1819
"devDependencies": {
1920
"@sveltejs/vite-plugin-svelte": "workspace:^",

packages/e2e-tests/prebundle-svelte-deps/src/App.svelte

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import { Message as Nested } from 'e2e-test-dep-svelte-nested';
66
import { setSomeContext } from 'e2e-test-dep-svelte-api-only';
77
import { getContext } from 'svelte';
8+
import { Counter } from 'e2e-test-dep-svelte-module';
89
setSomeContext();
910
const apiOnlyLoaded = !!getContext('svelte-api-only');
1011
</script>
@@ -23,4 +24,7 @@
2324
<div id="exports-simple">
2425
<Dependency />
2526
</div>
27+
<div id="module">
28+
<Counter />
29+
</div>
2630
</main>

packages/vite-plugin-svelte/src/public.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,15 @@ interface ExperimentalOptions {
175175
}
176176

177177
interface CompileModuleOptions {
178+
/**
179+
* infix that must be present in filename
180+
* @default ['.svelte.']
181+
*/
182+
infixes?: string[];
183+
/**
184+
* module extensions
185+
* @default ['.ts','.js']
186+
*/
178187
extensions?: string[];
179188
include?: Arrayable<string>;
180189
exclude?: Arrayable<string>;

packages/vite-plugin-svelte/src/utils/compile.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,11 @@ export function createCompileSvelte() {
128128
...dynamicCompileOptions
129129
}
130130
: compileOptions;
131-
132131
const endStat = stats?.start(filename);
133132
/** @type {import('svelte/compiler').CompileResult} */
134133
let compiled;
135134
try {
136-
compiled = svelte.compile(finalCode, finalCompileOptions);
135+
compiled = svelte.compile(finalCode, { ...finalCompileOptions, filename: filename });
137136
// patch output with partial accept until svelte does it
138137
// TODO remove later
139138
if (

packages/vite-plugin-svelte/src/utils/constants.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,7 @@ export const SVELTE_EXPORT_CONDITIONS = ['svelte'];
2020

2121
export const FAQ_LINK_MISSING_EXPORTS_CONDITION =
2222
'https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#missing-exports-condition';
23+
24+
export const DEFAULT_SVELTE_EXT = ['.svelte'];
25+
export const DEFAULT_SVELTE_MODULE_INFIX = ['.svelte.'];
26+
export const DEFAULT_SVELTE_MODULE_EXT = ['.js', '.ts'];

packages/vite-plugin-svelte/src/utils/esbuild.js

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import { toESBuildError } from './error.js';
99
*/
1010

1111
export const facadeEsbuildSveltePluginName = 'vite-plugin-svelte:facade';
12-
13-
const svelteModuleExtension = '.svelte.js';
12+
export const facadeEsbuildSvelteModulePluginName = 'vite-plugin-svelte-module:facade';
1413

1514
/**
1615
* @param {import('../types/options.d.ts').ResolvedOptions} options
@@ -24,18 +23,15 @@ export function esbuildSveltePlugin(options) {
2423
// Otherwise this would heavily slow down the scanning phase.
2524
if (build.initialOptions.plugins?.some((v) => v.name === 'vite:dep-scan')) return;
2625

27-
const svelteExtensions = (options.extensions ?? ['.svelte']).map((ext) => ext.slice(1));
28-
svelteExtensions.push(svelteModuleExtension.slice(1));
29-
30-
const svelteFilter = new RegExp('\\.(' + svelteExtensions.join('|') + ')(\\?.*)?$');
26+
const filter = /\.svelte(?:\?.*)?$/;
3127
/** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
3228
let statsCollection;
3329
build.onStart(() => {
34-
statsCollection = options.stats?.startCollection('prebundle libraries', {
30+
statsCollection = options.stats?.startCollection('prebundle library components', {
3531
logResult: (c) => c.stats.length > 1
3632
});
3733
});
38-
build.onLoad({ filter: svelteFilter }, async ({ path: filename }) => {
34+
build.onLoad({ filter }, async ({ path: filename }) => {
3935
const code = readFileSync(filename, 'utf8');
4036
try {
4137
const contents = await compileSvelte(options, { filename, code }, statsCollection);
@@ -58,26 +54,14 @@ export function esbuildSveltePlugin(options) {
5854
* @returns {Promise<string>}
5955
*/
6056
async function compileSvelte(options, { filename, code }, statsCollection) {
61-
if (filename.endsWith(svelteModuleExtension)) {
62-
const endStat = statsCollection?.start(filename);
63-
const compiled = svelte.compileModule(code, {
64-
filename,
65-
generate: 'client'
66-
});
67-
if (endStat) {
68-
endStat();
69-
}
70-
return compiled.js.map
71-
? compiled.js.code + '//# sourceMappingURL=' + compiled.js.map.toUrl()
72-
: compiled.js.code;
73-
}
7457
let css = options.compilerOptions.css;
7558
if (css !== 'injected') {
7659
// TODO ideally we'd be able to externalize prebundled styles too, but for now always put them in the js
7760
css = 'injected';
7861
}
7962
/** @type {import('svelte/compiler').CompileOptions} */
8063
const compileOptions = {
64+
dev: true, // default to dev: true because prebundling is only used in dev
8165
...options.compilerOptions,
8266
css,
8367
filename,
@@ -127,3 +111,60 @@ async function compileSvelte(options, { filename, code }, statsCollection) {
127111
? compiled.js.code + '//# sourceMappingURL=' + compiled.js.map.toUrl()
128112
: compiled.js.code;
129113
}
114+
115+
/**
116+
* @param {import('../types/options.d.ts').ResolvedOptions} options
117+
* @returns {EsbuildPlugin}
118+
*/
119+
export function esbuildSvelteModulePlugin(options) {
120+
return {
121+
name: 'vite-plugin-svelte-module:optimize-svelte',
122+
setup(build) {
123+
// Skip in scanning phase as Vite already handles scanning Svelte files.
124+
// Otherwise this would heavily slow down the scanning phase.
125+
if (build.initialOptions.plugins?.some((v) => v.name === 'vite:dep-scan')) return;
126+
127+
const filter = /\.svelte\.[jt]s(?:\?.*)?$/;
128+
/** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
129+
let statsCollection;
130+
build.onStart(() => {
131+
statsCollection = options.stats?.startCollection('prebundle library modules', {
132+
logResult: (c) => c.stats.length > 1
133+
});
134+
});
135+
build.onLoad({ filter }, async ({ path: filename }) => {
136+
const code = readFileSync(filename, 'utf8');
137+
try {
138+
const contents = await compileSvelteModule(options, { filename, code }, statsCollection);
139+
return { contents };
140+
} catch (e) {
141+
return { errors: [toESBuildError(e, options)] };
142+
}
143+
});
144+
build.onEnd(() => {
145+
statsCollection?.finish();
146+
});
147+
}
148+
};
149+
}
150+
151+
/**
152+
* @param {import('../types/options.d.ts').ResolvedOptions} options
153+
* @param {{ filename: string; code: string }} input
154+
* @param {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection} [statsCollection]
155+
* @returns {Promise<string>}
156+
*/
157+
async function compileSvelteModule(options, { filename, code }, statsCollection) {
158+
const endStat = statsCollection?.start(filename);
159+
const compiled = svelte.compileModule(code, {
160+
dev: options.compilerOptions?.dev ?? true, // default to dev: true because prebundling is only used in dev
161+
filename,
162+
generate: 'client'
163+
});
164+
if (endStat) {
165+
endStat();
166+
}
167+
return compiled.js.map
168+
? compiled.js.code + '//# sourceMappingURL=' + compiled.js.map.toUrl()
169+
: compiled.js.code;
170+
}

0 commit comments

Comments
 (0)