Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
1befb90
feat: plugin hook filters
florian-lefebvre Dec 10, 2025
c383f54
wip
florian-lefebvre Dec 10, 2025
3feb29f
wip
florian-lefebvre Dec 10, 2025
5a5acbb
wip
florian-lefebvre Dec 10, 2025
c37813a
wip
florian-lefebvre Dec 10, 2025
3901a73
wip
florian-lefebvre Dec 10, 2025
fb6e01d
wip
florian-lefebvre Dec 10, 2025
e87df8d
wip
florian-lefebvre Dec 10, 2025
93c8cb2
wip
florian-lefebvre Dec 10, 2025
a3da79c
wip
florian-lefebvre Dec 10, 2025
e95e37e
wip
florian-lefebvre Dec 10, 2025
8f72ee4
wip
florian-lefebvre Dec 10, 2025
db456d0
wip
florian-lefebvre Dec 10, 2025
c76daa3
todos
florian-lefebvre Dec 10, 2025
e5a0b19
Merge branch 'next' into feat/plugin-hook-filters
florian-lefebvre Dec 10, 2025
bf3a9ed
fix
florian-lefebvre Dec 10, 2025
8750f02
fix: vite plugin config alias
florian-lefebvre Dec 10, 2025
8b23c66
feat: load
florian-lefebvre Dec 10, 2025
ecbb025
fix: misc
florian-lefebvre Dec 10, 2025
46e78fc
Merge branch 'next' into feat/plugin-hook-filters
florian-lefebvre Dec 10, 2025
e95d8e0
fix: regex
florian-lefebvre Dec 10, 2025
a358b76
fix: regex
florian-lefebvre Dec 10, 2025
da1cacd
fix: regex
florian-lefebvre Dec 10, 2025
6e5f017
fix: regex
florian-lefebvre Dec 11, 2025
4fa4903
Merge branch 'next' into feat/plugin-hook-filters
florian-lefebvre Dec 11, 2025
477d1ab
fix: regex
florian-lefebvre Dec 11, 2025
863606e
Merge branch 'next' into feat/plugin-hook-filters
florian-lefebvre Dec 11, 2025
5870997
fix: regex
florian-lefebvre Dec 11, 2025
3dc7098
improve regex
florian-lefebvre Dec 11, 2025
71a0b26
fix: unsufficient regex
florian-lefebvre Dec 11, 2025
fa7a8db
Merge branch 'next' into feat/plugin-hook-filters
florian-lefebvre Dec 11, 2025
e362a45
chore: format
florian-lefebvre Dec 12, 2025
d0cae56
Merge branch 'next' into feat/plugin-hook-filters
florian-lefebvre Dec 12, 2025
69bcb45
chore: format
florian-lefebvre Dec 12, 2025
dacba07
wip
florian-lefebvre Dec 12, 2025
547f756
Merge branch 'next' into feat/plugin-hook-filters
florian-lefebvre Dec 15, 2025
4c44b63
feat: plugin hook filters (part 2) (#15000)
florian-lefebvre Dec 15, 2025
4c5f2f3
Merge branch 'next' into feat/plugin-hook-filters
florian-lefebvre Dec 15, 2025
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
Prev Previous commit
Next Next commit
wip
  • Loading branch information
florian-lefebvre committed Dec 10, 2025
commit 3901a73697b30830a34f21f223babd6d3b0a4907
15 changes: 8 additions & 7 deletions packages/astro/src/vite-plugin-astro/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,13 +280,14 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl

const normalPlugin: vite.Plugin = {
name: 'astro:build:normal',
resolveId(id) {
// If Vite resolver can't resolve the Astro request, it's likely a virtual Astro file, fallback here instead
const parsedId = parseAstroRequest(id);
if (parsedId.query.astro) {
return id;
}
},
// TODO: check if this is useful. This just passes the id through AFAIK
// resolveId(id) {
// // If Vite resolver can't resolve the Astro request, it's likely a virtual Astro file, fallback here instead
// const parsedId = parseAstroRequest(id);
// if (parsedId.query.astro) {
// return id;
// }
// },
};

return [prePlugin, normalPlugin];
Expand Down
48 changes: 26 additions & 22 deletions packages/astro/src/vite-plugin-config-alias/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,26 +136,34 @@ export default function configAliasVitePlugin({
configResolved(config) {
patchCreateResolver(config, plugin);
},
async resolveId(id, importer, options) {
if (isVirtualId(id)) return;

// Handle aliases found from `compilerOptions.paths`. Unlike Vite aliases, tsconfig aliases
// are best effort only, so we have to manually replace them here, instead of using `vite.resolve.alias`
for (const alias of configAlias) {
if (alias.find.test(id)) {
const updatedId = id.replace(alias.find, alias.replacement);

// Vite may pass an id with "*" when resolving glob import paths
// Returning early allows Vite to handle the final resolution
// See https://github.com/withastro/astro/issues/9258#issuecomment-1838806157
if (updatedId.includes('*')) {
return updatedId;
resolveId: {
filter: {
// Everything but ids that start with virtual: or astro: OR contains \0
// TODO: check it works for null bytes
id: /^(?!virtual:|astro:)[^\0]*$/,
},
async handler(id, importer, options) {
// Handle aliases found from `compilerOptions.paths`. Unlike Vite aliases, tsconfig aliases
// are best effort only, so we have to manually replace them here, instead of using `vite.resolve.alias`
for (const alias of configAlias) {
if (alias.find.test(id)) {
const updatedId = id.replace(alias.find, alias.replacement);

// Vite may pass an id with "*" when resolving glob import paths
// Returning early allows Vite to handle the final resolution
// See https://github.com/withastro/astro/issues/9258#issuecomment-1838806157
if (updatedId.includes('*')) {
return updatedId;
}

const resolved = await this.resolve(updatedId, importer, {
skipSelf: true,
...options,
});
if (resolved) return resolved;
}

const resolved = await this.resolve(updatedId, importer, { skipSelf: true, ...options });
if (resolved) return resolved;
}
}
},
},
};

Expand Down Expand Up @@ -208,7 +216,3 @@ function patchCreateResolver(config: ResolvedConfig, postPlugin: VitePlugin) {
};
};
}

function isVirtualId(id: string) {
return id.includes('\0') || id.startsWith('virtual:') || id.startsWith('astro:');
}
223 changes: 123 additions & 100 deletions packages/astro/src/vite-plugin-css/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function* collectCSSWithOrder(
return;
}
// ?raw imports the underlying css but is handled as a string in the JS.
else if(id.endsWith('?raw')) {
else if (id.endsWith('?raw')) {
return;
}

Expand All @@ -83,110 +83,133 @@ export function astroDevCssPlugin({ routesList, command }: AstroVitePluginOption
// Cache CSS content by module ID to avoid re-reading
const cssContentCache = new Map<string, string>();

return [{
name: MODULE_DEV_CSS,

async configureServer(server) {
environment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr] as RunnableDevEnvironment;
},

resolveId(id) {
if (id === MODULE_DEV_CSS) {
return RESOLVED_MODULE_DEV_CSS;
}
if (id.startsWith(MODULE_DEV_CSS_PREFIX)) {
return RESOLVED_MODULE_DEV_CSS_PREFIX + id.slice(MODULE_DEV_CSS_PREFIX.length);
}
},

async load(id) {
if (id === RESOLVED_MODULE_DEV_CSS) {
return {
code: `export const css = new Set()`,
};
}
if (id.startsWith(RESOLVED_MODULE_DEV_CSS_PREFIX)) {
const componentPath = getComponentFromVirtualModuleCssName(
RESOLVED_MODULE_DEV_CSS_PREFIX,
id,
);

// Collect CSS by walking the dependency tree from the component
const cssWithOrder = new Map<string, ImportedDevStyle>();

// The virtual module name for this page, like virtual:astro:dev-css:index@_@astro
const componentPageId = getVirtualModulePageNameForComponent(componentPath);

// Ensure the page module is loaded. This will populate the graph and allow us to walk through.
await environment?.runner?.import(componentPageId);
const resolved = await environment?.pluginContainer.resolveId(componentPageId);

if(!resolved?.id) {
return {
code: 'export const css = new Set()'
};
}

// the vite.EnvironmentModuleNode has all of the info we need
const mod = environment?.moduleGraph.getModuleById(resolved.id);
return [
{
name: MODULE_DEV_CSS,

async configureServer(server) {
environment = server.environments[
ASTRO_VITE_ENVIRONMENT_NAMES.ssr
] as RunnableDevEnvironment;
},

resolveId: {
filter: {
id: new RegExp(`^(${MODULE_DEV_CSS}|${MODULE_DEV_CSS_PREFIX}.*)$`),
},
handler(id) {
if (id === MODULE_DEV_CSS) {
return RESOLVED_MODULE_DEV_CSS;
}
return RESOLVED_MODULE_DEV_CSS_PREFIX + id.slice(MODULE_DEV_CSS_PREFIX.length);
},
},

load: {
filter: {
id: new RegExp(`^(${RESOLVED_MODULE_DEV_CSS}|${RESOLVED_MODULE_DEV_CSS_PREFIX}.*)$`),
},
async handler(id) {
if (id === RESOLVED_MODULE_DEV_CSS) {
return {
code: `export const css = new Set()`,
};
}
if (id.startsWith(RESOLVED_MODULE_DEV_CSS_PREFIX)) {
const componentPath = getComponentFromVirtualModuleCssName(
RESOLVED_MODULE_DEV_CSS_PREFIX,
id,
);

// Collect CSS by walking the dependency tree from the component
const cssWithOrder = new Map<string, ImportedDevStyle>();

// The virtual module name for this page, like virtual:astro:dev-css:index@_@astro
const componentPageId = getVirtualModulePageNameForComponent(componentPath);

// Ensure the page module is loaded. This will populate the graph and allow us to walk through.
await environment?.runner?.import(componentPageId);
const resolved = await environment?.pluginContainer.resolveId(componentPageId);

if (!resolved?.id) {
return {
code: 'export const css = new Set()',
};
}

// the vite.EnvironmentModuleNode has all of the info we need
const mod = environment?.moduleGraph.getModuleById(resolved.id);

if (!mod) {
return {
code: 'export const css = new Set()',
};
}

// Walk through the graph depth-first
for (const collected of collectCSSWithOrder(componentPageId, mod!)) {
// Use the CSS file ID as the key to deduplicate while keeping best ordering
if (!cssWithOrder.has(collected.idKey)) {
// Look up actual content from cache if available
const content = cssContentCache.get(collected.id) || collected.content;
cssWithOrder.set(collected.idKey, { ...collected, content });
}
}

const cssArray = Array.from(cssWithOrder.values());
// Remove the temporary fields added during collection
const cleanedCss = cssArray.map(({ content, id: cssId, url }) => ({
content,
id: cssId,
url,
}));
return {
code: `export const css = new Set(${JSON.stringify(cleanedCss)})`,
};
}
},
},
Comment thread
florian-lefebvre marked this conversation as resolved.

if(!mod) {
return {
code: 'export const css = new Set()'
};
async transform(code, id) {
if (command === 'build') {
return;
}

// Walk through the graph depth-first
for (const collected of collectCSSWithOrder(componentPageId, mod!)) {
// Use the CSS file ID as the key to deduplicate while keeping best ordering
if (!cssWithOrder.has(collected.idKey)) {
// Look up actual content from cache if available
const content = cssContentCache.get(collected.id) || collected.content;
cssWithOrder.set(collected.idKey, { ...collected, content });
// Cache CSS content as we see it
if (isBuildableCSSRequest(id)) {
const mod = environment?.moduleGraph.getModuleById(id);
if (mod) {
cssContentCache.set(id, code);
}
}

const cssArray = Array.from(cssWithOrder.values());
// Remove the temporary fields added during collection
const cleanedCss = cssArray.map(({ content, id: cssId, url }) => ({ content, id: cssId, url }));
return {
code: `export const css = new Set(${JSON.stringify(cleanedCss)})`,
};
}
},

async transform(code, id) {
if (command === 'build') {
return;
}

// Cache CSS content as we see it
if (isBuildableCSSRequest(id)) {
const mod = environment?.moduleGraph.getModuleById(id);
if (mod) {
cssContentCache.set(id, code);
}
}
},
},
}, {
name: MODULE_DEV_CSS_ALL,
resolveId(id) {
if(id === MODULE_DEV_CSS_ALL) {
return RESOLVED_MODULE_DEV_CSS_ALL
}
{
name: MODULE_DEV_CSS_ALL,
resolveId: {
filter: {
id: new RegExp(`^${MODULE_DEV_CSS_ALL}$`),
},
handler() {
return RESOLVED_MODULE_DEV_CSS_ALL;
},
},
load: {
filter: {
id: new RegExp(`^${RESOLVED_MODULE_DEV_CSS_ALL}$`),
},
handler() {
// Creates a map of the component name to a function to import it
let code = `export const devCSSMap = new Map([`;
for (const route of routesList.routes) {
code += `\n\t[${JSON.stringify(route.component)}, () => import(${JSON.stringify(getDevCSSModuleName(route.component))})],`;
}
code += ']);';
return {
code,
};
},
},
},
load(id) {
if(id === RESOLVED_MODULE_DEV_CSS_ALL) {
// Creates a map of the component name to a function to import it
let code = `export const devCSSMap = new Map([`;
for(const route of routesList.routes) {
code += `\n\t[${JSON.stringify(route.component)}, () => import(${JSON.stringify(getDevCSSModuleName(route.component))})],`
}
code += ']);'
return {
code
};
}
}
}];
];
}
Loading