Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/angry-pumpkins-deny.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/vite-plugin-svelte': major
---

only prebundle files with default filenames (.svelte for components, .svelte.(js|ts) for modules)
8 changes: 8 additions & 0 deletions .changeset/lazy-bats-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@sveltejs/vite-plugin-svelte': minor
---

allow infix notation for svelte modules

Previously, only suffix notation `.svelte.js` was allowed, now you can also use `.svelte.test.js` or `.svelte.stories.js`.
This helps when writing testcases or other auxillary code where you may want to use runes too.
9 changes: 9 additions & 0 deletions packages/vite-plugin-svelte/src/public.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ interface ExperimentalOptions {
}

interface CompileModuleOptions {
/**
* infix that must be present in filename
* @default ['.svelte.']
*/
infixes?: string[];
/**
* module extensions
* @default ['.ts','.js']
*/
extensions?: string[];
include?: Arrayable<string>;
exclude?: Arrayable<string>;
Expand Down
3 changes: 1 addition & 2 deletions packages/vite-plugin-svelte/src/utils/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,11 @@ export function createCompileSvelte() {
...dynamicCompileOptions
}
: compileOptions;

const endStat = stats?.start(filename);
/** @type {import('svelte/compiler').CompileResult} */
let compiled;
try {
compiled = svelte.compile(finalCode, finalCompileOptions);
compiled = svelte.compile(finalCode, { ...finalCompileOptions, filename: filename });
// patch output with partial accept until svelte does it
// TODO remove later
if (
Expand Down
4 changes: 4 additions & 0 deletions packages/vite-plugin-svelte/src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ export const SVELTE_EXPORT_CONDITIONS = ['svelte'];

export const FAQ_LINK_MISSING_EXPORTS_CONDITION =
'https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#missing-exports-condition';

export const DEFAULT_SVELTE_EXT = ['.svelte'];
export const DEFAULT_SVELTE_MODULE_INFIX = ['.svelte.'];
export const DEFAULT_SVELTE_MODULE_EXT = ['.js', '.ts'];
32 changes: 25 additions & 7 deletions packages/vite-plugin-svelte/src/utils/esbuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { readFileSync } from 'node:fs';
import * as svelte from 'svelte/compiler';
import { log } from './log.js';
import { toESBuildError } from './error.js';
import {
DEFAULT_SVELTE_EXT,
DEFAULT_SVELTE_MODULE_EXT,
DEFAULT_SVELTE_MODULE_INFIX
} from './constants.js';

/**
* @typedef {NonNullable<import('vite').DepOptimizationOptions['esbuildOptions']>} EsbuildOptions
Expand All @@ -10,7 +15,14 @@ import { toESBuildError } from './error.js';

export const facadeEsbuildSveltePluginName = 'vite-plugin-svelte:facade';

const svelteModuleExtension = '.svelte.js';
/**
* escape regex special chars
* @param {string} s
* @returns {string}
*/
function rescape(s) {
return s.replace(/([/.+^${}()|[\]\\])/g, '\\$1');
}

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

const svelteExtensions = (options.extensions ?? ['.svelte']).map((ext) => ext.slice(1));
svelteExtensions.push(svelteModuleExtension.slice(1));

const svelteFilter = new RegExp('\\.(' + svelteExtensions.join('|') + ')(\\?.*)?$');
const svelteExtensions = DEFAULT_SVELTE_EXT;
const svelteModuleInfixes = DEFAULT_SVELTE_MODULE_INFIX;
const svelteModuleExt = DEFAULT_SVELTE_MODULE_EXT;
const svelteExtRE = `(?:${svelteExtensions.map(rescape).join('|')})`;
const svelteModuleRE = `(?:${svelteModuleInfixes.map((infix) => rescape(infix.slice(0, -1))).join('|')})(?:\\.[^.]+)*(?:${svelteModuleExt.map(rescape).join('|')})`;
const optionalQueryStringRE = '(?:\\?.*)?';
// one regex that matches either .svelte OR .svelte(.something)?.(js|ts) optionally followed by a querystring ?foo=bar
// onload decides which one to call
// TODO: better split this in two plugins? but then we need 2 facades too
const filter = new RegExp(`${svelteModuleRE}|${svelteExtRE}${optionalQueryStringRE}$`);
/** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
let statsCollection;
build.onStart(() => {
statsCollection = options.stats?.startCollection('prebundle libraries', {
logResult: (c) => c.stats.length > 1
});
});
build.onLoad({ filter: svelteFilter }, async ({ path: filename }) => {
build.onLoad({ filter }, async ({ path: filename }) => {
const code = readFileSync(filename, 'utf8');
try {
const contents = await compileSvelte(options, { filename, code }, statsCollection);
Expand All @@ -58,7 +76,7 @@ export function esbuildSveltePlugin(options) {
* @returns {Promise<string>}
*/
async function compileSvelte(options, { filename, code }, statsCollection) {
if (filename.endsWith(svelteModuleExtension)) {
if (DEFAULT_SVELTE_MODULE_EXT.some((ext) => filename.endsWith(ext))) {
const endStat = statsCollection?.start(filename);
const compiled = svelte.compileModule(code, {
filename,
Expand Down
25 changes: 23 additions & 2 deletions packages/vite-plugin-svelte/src/utils/id.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createFilter, normalizePath } from 'vite';
import * as fs from 'node:fs';
import { log } from './log.js';
import { DEFAULT_SVELTE_MODULE_EXT, DEFAULT_SVELTE_MODULE_INFIX } from './constants.js';

const VITE_FS_PREFIX = '/@fs/';
const IS_WINDOWS = process.platform === 'win32';
Expand Down Expand Up @@ -169,6 +170,21 @@ function buildFilter(include, exclude, extensions) {
return (filename) => rollupFilter(filename) && extensions.some((ext) => filename.endsWith(ext));
}

/**
* @param {import('../public.d.ts').Options['include'] | undefined} include
* @param {import('../public.d.ts').Options['exclude'] | undefined} exclude
* @param {string[]} infixes
* @param {string[]} extensions
* @returns {(filename: string) => boolean}
*/
function buildModuleFilter(include, exclude, infixes, extensions) {
const rollupFilter = createFilter(include, exclude);
return (filename) =>
rollupFilter(filename) &&
infixes.some((infix) => filename.includes(infix)) &&
extensions.some((ext) => filename.endsWith(ext));
}

/**
* @param {import('../types/options.d.ts').ResolvedOptions} options
* @returns {import('../types/id.d.ts').IdParser}
Expand All @@ -190,10 +206,15 @@ export function buildIdParser(options) {
* @returns {import('../types/id.d.ts').ModuleIdParser}
*/
export function buildModuleIdParser(options) {
const { include, exclude, extensions } = options?.experimental?.compileModule ?? {};
const {
include,
exclude,
infixes = DEFAULT_SVELTE_MODULE_INFIX,
extensions = DEFAULT_SVELTE_MODULE_EXT
} = options?.experimental?.compileModule ?? {};
const root = options.root;
const normalizedRoot = normalizePath(root);
const filter = buildFilter(include, exclude, extensions ?? ['.svelte.js', '.svelte.ts']);
const filter = buildModuleFilter(include, exclude, infixes, extensions);
return (id, ssr, timestamp = Date.now()) => {
const { filename, rawQuery } = splitId(id);
if (filter(filename)) {
Expand Down
3 changes: 2 additions & 1 deletion packages/vite-plugin-svelte/src/utils/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { normalizePath } from 'vite';
import { isDebugNamespaceEnabled, log } from './log.js';
import { loadSvelteConfig } from './load-svelte-config.js';
import {
DEFAULT_SVELTE_EXT,
FAQ_LINK_MISSING_EXPORTS_CONDITION,
SVELTE_EXPORT_CONDITIONS,
SVELTE_IMPORTS,
Expand Down Expand Up @@ -137,7 +138,7 @@ export async function preResolveOptions(inlineOptions, viteUserConfig, viteEnv)
const isBuild = viteEnv.command === 'build';
/** @type {Partial<import('../types/options.d.ts').PreResolvedOptions>} */
const defaultOptions = {
extensions: ['.svelte'],
extensions: DEFAULT_SVELTE_EXT,
emitCss: true,
prebundleSvelteLibraries: !isBuild
};
Expand Down
9 changes: 9 additions & 0 deletions packages/vite-plugin-svelte/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,15 @@ declare module '@sveltejs/vite-plugin-svelte' {
}

interface CompileModuleOptions {
/**
* infix that must be present in filename
* @default ['.svelte.']
*/
infixes?: string[];
/**
* module extensions
* @default ['.ts','.js']
*/
extensions?: string[];
include?: Arrayable<string>;
exclude?: Arrayable<string>;
Expand Down
2 changes: 1 addition & 1 deletion packages/vite-plugin-svelte/types/index.d.ts.map
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@
null,
null
],
"mappings": ";;;;aAMYA,OAAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA6GFC,YAAYA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAqEZC,qBAAqBA;;;;;;;;;;;;;iBChKtBC,MAAMA;iBCTNC,cAAcA;iBCgBRC,gBAAgBA"
"mappings": ";;;;aAMYA,OAAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA6GFC,YAAYA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA8EZC,qBAAqBA;;;;;;;;;;;;;iBCzKtBC,MAAMA;iBCTNC,cAAcA;iBCgBRC,gBAAgBA"
}