Skip to content

Commit 865191d

Browse files
author
Dido (Christoph Poelt)
committed
perf: add rolldown hook filters to resolveId, load, and transform
1 parent 3749cd1 commit 865191d

1 file changed

Lines changed: 103 additions & 80 deletions

File tree

‎src/index.ts‎

Lines changed: 103 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { crawlFrameworkPkgs } from 'vitefu';
1111
const require = createRequire(import.meta.url);
1212

1313
const runtimePublicPath = '/@solid-refresh';
14+
const runtimePublicPathFilter = /^\/@solid-refresh$/;
1415
const runtimeFilePath = require.resolve('solid-refresh/dist/solid-refresh.mjs');
1516
const runtimeCode = readFileSync(runtimeFilePath, 'utf-8');
1617

@@ -332,103 +333,125 @@ export default function solidPlugin(options: Partial<Options> = {}): Plugin {
332333
needHmr = config.command === 'serve' && config.mode !== 'production' && options.hot !== false;
333334
},
334335

335-
resolveId(id) {
336-
if (id === runtimePublicPath) return id;
336+
resolveId: {
337+
// Filter is used by Rolldown and newer Rollup/Vite versions (Vite >= 6.3.0) to skip
338+
// unnecessary Rust-to-JS calls. The inner check is kept for backward compatibility
339+
// with older versions that ignore hook filters and call the handler for every module.
340+
// @ts-ignore filter is not yet in Vite's type definitions but is supported at runtime
341+
filter: { id: runtimePublicPathFilter },
342+
handler(id) {
343+
if (id === runtimePublicPath) return id;
344+
},
337345
},
338346

339-
load(id) {
340-
if (id === runtimePublicPath) return runtimeCode;
347+
load: {
348+
// Same backward-compatibility strategy as resolveId above.
349+
// @ts-ignore filter is not yet in Vite's type definitions but is supported at runtime
350+
filter: { id: runtimePublicPathFilter },
351+
handler(id) {
352+
if (id === runtimePublicPath) return runtimeCode;
353+
},
341354
},
342355

343-
async transform(source, id, transformOptions) {
344-
const isSsr = this.environment
345-
? this.environment.config.consumer === 'server'
346-
: Boolean(transformOptions?.ssr);
347-
const currentFileExtension = getExtension(id);
348-
349-
const extensionsToWatch = options.extensions || [];
350-
const allExtensions = extensionsToWatch.map((extension) =>
351-
// An extension can be a string or a tuple [extension, options]
352-
typeof extension === 'string' ? extension : extension[0],
353-
);
354-
355-
if (!filter(id)) {
356-
return null;
357-
}
356+
transform: {
357+
// Limit invocations to files that could contain Solid JSX. Custom extensions
358+
// (options.extensions) cannot be represented as a static regex, so they are
359+
// still handled by the inner extension check below — this filter covers the
360+
// common case and is the source of the largest performance win.
361+
// The inner checks (filter(id) / extension regex) are kept as-is so that the
362+
// hook behaves correctly on older Vite/Rollup versions that ignore hook filters.
363+
// @ts-ignore filter is not yet in Vite's type definitions but is supported at runtime
364+
filter: { id: /\.[mc]?[tj]sx$/i },
365+
async handler(source, id, transformOptions) {
366+
const isSsr = this.environment
367+
? this.environment.config.consumer === 'server'
368+
: Boolean(transformOptions?.ssr);
369+
const currentFileExtension = getExtension(id);
370+
371+
const extensionsToWatch = options.extensions || [];
372+
const allExtensions = extensionsToWatch.map((extension) =>
373+
// An extension can be a string or a tuple [extension, options]
374+
typeof extension === 'string' ? extension : extension[0],
375+
);
376+
377+
if (!filter(id)) {
378+
return null;
379+
}
358380

359-
id = id.replace(/\?.*$/, '');
381+
id = id.replace(/\?.*$/, '');
360382

361-
if (!(/\.[mc]?[tj]sx$/i.test(id) || allExtensions.includes(currentFileExtension))) {
362-
return null;
363-
}
383+
if (!(/\.[mc]?[tj]sx$/i.test(id) || allExtensions.includes(currentFileExtension))) {
384+
return null;
385+
}
364386

365-
const inNodeModules = /node_modules/.test(id);
387+
const inNodeModules = /node_modules/.test(id);
366388

367-
let solidOptions: { generate: 'ssr' | 'dom'; hydratable: boolean };
389+
let solidOptions: { generate: 'ssr' | 'dom'; hydratable: boolean };
368390

369-
if (options.ssr) {
370-
if (isSsr) {
371-
solidOptions = { generate: 'ssr', hydratable: true };
391+
if (options.ssr) {
392+
if (isSsr) {
393+
solidOptions = { generate: 'ssr', hydratable: true };
394+
} else {
395+
solidOptions = { generate: 'dom', hydratable: true };
396+
}
372397
} else {
373-
solidOptions = { generate: 'dom', hydratable: true };
398+
solidOptions = { generate: 'dom', hydratable: false };
374399
}
375-
} else {
376-
solidOptions = { generate: 'dom', hydratable: false };
377-
}
378-
379-
// We need to know if the current file extension has a typescript options tied to it
380-
const shouldBeProcessedWithTypescript =
381-
/\.[mc]?tsx$/i.test(id) ||
382-
extensionsToWatch.some((extension) => {
383-
if (typeof extension === 'string') {
384-
return extension.includes('tsx');
385-
}
386-
387-
const [extensionName, extensionOptions] = extension;
388-
if (extensionName !== currentFileExtension) return false;
389400

390-
return extensionOptions.typescript;
391-
});
392-
const plugins: NonNullable<NonNullable<babel.TransformOptions['parserOpts']>['plugins']> = [
393-
'jsx',
394-
];
395-
396-
if (shouldBeProcessedWithTypescript) {
397-
plugins.push('typescript');
398-
}
399-
400-
const opts: babel.TransformOptions = {
401-
root: projectRoot,
402-
filename: id,
403-
sourceFileName: id,
404-
presets: [[solid, { ...solidOptions, ...(options.solid || {}) }]],
405-
plugins: needHmr && !isSsr && !inNodeModules ? [[solidRefresh, { bundler: 'vite' }]] : [],
406-
ast: false,
407-
sourceMaps: true,
408-
configFile: false,
409-
babelrc: false,
410-
parserOpts: {
411-
plugins,
412-
},
413-
};
414-
415-
// Default value for babel user options
416-
let babelUserOptions: babel.TransformOptions = {};
401+
// We need to know if the current file extension has a typescript options tied to it
402+
const shouldBeProcessedWithTypescript =
403+
/\.[mc]?tsx$/i.test(id) ||
404+
extensionsToWatch.some((extension) => {
405+
if (typeof extension === 'string') {
406+
return extension.includes('tsx');
407+
}
408+
409+
const [extensionName, extensionOptions] = extension;
410+
if (extensionName !== currentFileExtension) return false;
411+
412+
return extensionOptions.typescript;
413+
});
414+
const plugins: NonNullable<NonNullable<babel.TransformOptions['parserOpts']>['plugins']> = [
415+
'jsx',
416+
];
417+
418+
if (shouldBeProcessedWithTypescript) {
419+
plugins.push('typescript');
420+
}
417421

418-
if (options.babel) {
419-
if (typeof options.babel === 'function') {
420-
const babelOptions = options.babel(source, id, isSsr);
421-
babelUserOptions = babelOptions instanceof Promise ? await babelOptions : babelOptions;
422-
} else {
423-
babelUserOptions = options.babel;
422+
const opts: babel.TransformOptions = {
423+
root: projectRoot,
424+
filename: id,
425+
sourceFileName: id,
426+
presets: [[solid, { ...solidOptions, ...(options.solid || {}) }]],
427+
plugins: needHmr && !isSsr && !inNodeModules ? [[solidRefresh, { bundler: 'vite' }]] : [],
428+
ast: false,
429+
sourceMaps: true,
430+
configFile: false,
431+
babelrc: false,
432+
parserOpts: {
433+
plugins,
434+
},
435+
};
436+
437+
// Default value for babel user options
438+
let babelUserOptions: babel.TransformOptions = {};
439+
440+
if (options.babel) {
441+
if (typeof options.babel === 'function') {
442+
const babelOptions = options.babel(source, id, isSsr);
443+
babelUserOptions = babelOptions instanceof Promise ? await babelOptions : babelOptions;
444+
} else {
445+
babelUserOptions = options.babel;
446+
}
424447
}
425-
}
426448

427-
const babelOptions = mergeAndConcat(babelUserOptions, opts) as babel.TransformOptions;
449+
const babelOptions = mergeAndConcat(babelUserOptions, opts) as babel.TransformOptions;
428450

429-
const { code, map } = await babel.transformAsync(source, babelOptions);
451+
const { code, map } = await babel.transformAsync(source, babelOptions);
430452

431-
return { code, map };
453+
return { code, map };
454+
},
432455
},
433456
};
434457
}

0 commit comments

Comments
 (0)