0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-04-14 23:51:49 -05:00

feat: improve rolldown compatibility (#13519)

* feat: improve rolldown compatibility

* chore: remove moduleType

* feat: use object shape

* chore: changeset
This commit is contained in:
Florian Lefebvre 2025-04-03 18:15:24 +02:00 committed by GitHub
parent 90b0ac1fe1
commit 3323f5c554
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 175 additions and 140 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Refactors some internals to improve Rolldown compatibility

View file

@ -126,7 +126,7 @@ export function vitePluginActions({
shouldAppendForwardSlash(settings.config.trailingSlash, settings.config.build.format),
),
);
return code;
return { code };
},
};
}

View file

@ -117,32 +117,34 @@ export default function assets({ settings }: { settings: AstroSettings }): vite.
},
load(id) {
if (id === resolvedVirtualModuleId) {
return /* ts */ `
export { getConfiguredImageService, isLocalService } from "astro/assets";
import { getImage as getImageInternal } from "astro/assets";
export { default as Image } from "astro/components/${imageComponentPrefix}Image.astro";
export { default as Picture } from "astro/components/${imageComponentPrefix}Picture.astro";
export { inferRemoteSize } from "astro/assets/utils/inferRemoteSize.js";
return {
code: `
export { getConfiguredImageService, isLocalService } from "astro/assets";
import { getImage as getImageInternal } from "astro/assets";
export { default as Image } from "astro/components/${imageComponentPrefix}Image.astro";
export { default as Picture } from "astro/components/${imageComponentPrefix}Picture.astro";
export { inferRemoteSize } from "astro/assets/utils/inferRemoteSize.js";
export const imageConfig = ${JSON.stringify({ ...settings.config.image, experimentalResponsiveImages: settings.config.experimental.responsiveImages })};
// This is used by the @astrojs/node integration to locate images.
// It's unused on other platforms, but on some platforms like Netlify (and presumably also Vercel)
// new URL("dist/...") is interpreted by the bundler as a signal to include that directory
// in the Lambda bundle, which would bloat the bundle with images.
// To prevent this, we mark the URL construction as pure,
// so that it's tree-shaken away for all platforms that don't need it.
export const outDir = /* #__PURE__ */ new URL(${JSON.stringify(
new URL(
settings.buildOutput === 'server'
? settings.config.build.client
: settings.config.outDir,
),
)});
export const assetsDir = /* #__PURE__ */ new URL(${JSON.stringify(
settings.config.build.assets,
)}, outDir);
export const getImage = async (options) => await getImageInternal(options, imageConfig);
`;
export const imageConfig = ${JSON.stringify({ ...settings.config.image, experimentalResponsiveImages: settings.config.experimental.responsiveImages })};
// This is used by the @astrojs/node integration to locate images.
// It's unused on other platforms, but on some platforms like Netlify (and presumably also Vercel)
// new URL("dist/...") is interpreted by the bundler as a signal to include that directory
// in the Lambda bundle, which would bloat the bundle with images.
// To prevent this, we mark the URL construction as pure,
// so that it's tree-shaken away for all platforms that don't need it.
export const outDir = /* #__PURE__ */ new URL(${JSON.stringify(
new URL(
settings.buildOutput === 'server'
? settings.config.build.client
: settings.config.outDir,
),
)});
export const assetsDir = /* #__PURE__ */ new URL(${JSON.stringify(
settings.config.build.assets,
)}, outDir);
export const getImage = async (options) => await getImageInternal(options, imageConfig);
`,
};
}
},
buildStart() {
@ -151,7 +153,7 @@ export default function assets({ settings }: { settings: AstroSettings }): vite.
},
// In build, rewrite paths to ESM imported images in code to their final location
async renderChunk(code) {
const assetUrlRE = /__ASTRO_ASSET_IMAGE__([\w$]{8})__(?:_(.*?)__)?/g;
const assetUrlRE = /__ASTRO_ASSET_IMAGE__([\w$]+)__(?:_(.*?)__)?/g;
let match;
let s;
@ -205,7 +207,7 @@ export default function assets({ settings }: { settings: AstroSettings }): vite.
return;
}
const emitFile = shouldEmitFile ? this.emitFile : undefined;
const emitFile = shouldEmitFile ? this.emitFile.bind(this) : undefined;
const imageMetadata = await emitESMImage(
id,
this.meta.watchMode,
@ -223,20 +225,24 @@ export default function assets({ settings }: { settings: AstroSettings }): vite.
if (settings.config.experimental.svg && /\.svg$/.test(id)) {
const { contents, ...metadata } = imageMetadata;
// We know that the contents are present, as we only emit this property for SVG files
return makeSvgComponent(metadata, contents!);
return { code: makeSvgComponent(metadata, contents!) };
}
// We can only reliably determine if an image is used on the server, as we need to track its usage throughout the entire build.
// Since you cannot use image optimization on the client anyway, it's safe to assume that if the user imported
// an image on the client, it should be present in the final build.
if (options?.ssr) {
return `export default ${getProxyCode(
imageMetadata,
settings.buildOutput === 'server',
)}`;
return {
code: `export default ${getProxyCode(
imageMetadata,
settings.buildOutput === 'server',
)}`,
};
} else {
globalThis.astroAsset.referencedImages.add(imageMetadata.fsPath);
return `export default ${JSON.stringify(imageMetadata)}`;
return {
code: `export default ${JSON.stringify(imageMetadata)}`,
};
}
}
},

View file

@ -141,7 +141,7 @@ export function astroContentVirtualModPlugin({
}
if (id === RESOLVED_DATA_STORE_VIRTUAL_ID) {
if (!fs.existsSync(dataStoreFile)) {
return 'export default new Map()';
return { code: 'export default new Map()' };
}
const jsonData = await fs.promises.readFile(dataStoreFile, 'utf-8');
@ -161,18 +161,20 @@ export function astroContentVirtualModPlugin({
if (id === ASSET_IMPORTS_RESOLVED_STUB_ID) {
const assetImportsFile = new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir);
if (!fs.existsSync(assetImportsFile)) {
return 'export default new Map()';
}
return fs.readFileSync(assetImportsFile, 'utf-8');
return {
code: fs.existsSync(assetImportsFile)
? fs.readFileSync(assetImportsFile, 'utf-8')
: 'export default new Map()',
};
}
if (id === MODULES_MJS_VIRTUAL_ID) {
const modules = new URL(MODULES_IMPORTS_FILE, settings.dotAstroDir);
if (!fs.existsSync(modules)) {
return 'export default new Map()';
}
return fs.readFileSync(modules, 'utf-8');
return {
code: fs.existsSync(modules)
? fs.readFileSync(modules, 'utf-8')
: 'export default new Map()',
};
}
},

View file

@ -64,7 +64,9 @@ export function vitePluginComponentEntry(internals: BuildInternals): VitePlugin
const componentId = id.slice(astroEntryPrefix.length);
const exportNames = componentToExportNames.get(componentId);
if (exportNames) {
return `export { ${exportNames.join(', ')} } from ${JSON.stringify(componentId)}`;
return {
code: `export { ${exportNames.join(', ')} } from ${JSON.stringify(componentId)}`,
};
}
}
},

View file

@ -63,7 +63,7 @@ function vitePluginManifest(options: StaticBuildOptions, internals: BuildInterna
];
const exports = [`export { manifest }`];
return [...imports, ...contents, ...exports].join('\n');
return { code: [...imports, ...contents, ...exports].join('\n') };
}
},

View file

@ -50,7 +50,7 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V
imports.push(`import { renderers } from "${RENDERERS_MODULE_ID}";`);
exports.push(`export { renderers };`);
return `${imports.join('\n')}${exports.join('\n')}`;
return { code: `${imports.join('\n')}${exports.join('\n')}` };
}
}
}

View file

@ -57,6 +57,9 @@ function getNonPrerenderOnlyChunks(bundle: Rollup.OutputBundle, internals: Build
nonPrerenderOnlyEntryChunks.add(chunk);
}
if (chunk.type === 'chunk' && chunk.isDynamicEntry) {
nonPrerenderOnlyEntryChunks.add(chunk);
}
}
// From the `nonPrerenderedEntryChunks`, we crawl all the imports/dynamicImports to find all

View file

@ -37,9 +37,9 @@ export function vitePluginRenderers(opts: StaticBuildOptions): VitePlugin {
exports.push(`export const renderers = [${rendererItems}];`);
return `${imports.join('\n')}\n${exports.join('\n')}`;
return { code: `${imports.join('\n')}\n${exports.join('\n')}` };
} else {
return `export const renderers = [];`;
return { code: `export const renderers = [];` };
}
}
},

View file

@ -30,7 +30,7 @@ function vitePluginAdapter(adapter: AstroAdapter): VitePlugin {
},
async load(id) {
if (id === RESOLVED_ADAPTER_VIRTUAL_MODULE_ID) {
return `export * from ${JSON.stringify(adapter.serverEntrypoint)};`;
return { code: `export * from ${JSON.stringify(adapter.serverEntrypoint)};` };
}
},
};
@ -103,7 +103,7 @@ function vitePluginSSR(
const ssrCode = generateSSRCode(adapter, middleware!.id);
imports.push(...ssrCode.imports);
contents.push(...ssrCode.contents);
return [...imports, ...contents, ...exports].join('\n');
return { code: [...imports, ...contents, ...exports].join('\n') };
}
},
async generateBundle(_opts, bundle) {

View file

@ -44,7 +44,7 @@ export function vitePluginMiddleware({ settings }: { settings: AstroSettings }):
if (!userMiddlewareIsPresent && settings.config.i18n?.routing === 'manual') {
throw new AstroError(MissingMiddlewareForInternationalization);
}
return 'export const onRequest = (_, next) => next()';
return { code: 'export const onRequest = (_, next) => next()' };
} else if (id === MIDDLEWARE_MODULE_ID) {
if (!userMiddlewareIsPresent && settings.config.i18n?.routing === 'manual') {
throw new AstroError(MissingMiddlewareForInternationalization);
@ -53,7 +53,7 @@ export function vitePluginMiddleware({ settings }: { settings: AstroSettings }):
const preMiddleware = createMiddlewareImports(settings.middlewares.pre, 'pre');
const postMiddleware = createMiddlewareImports(settings.middlewares.post, 'post');
const source = `
const code = `
${
userMiddlewareIsPresent
? `import { onRequest as userOnRequest } from '${resolvedMiddlewareId}';`
@ -69,7 +69,7 @@ export const onRequest = sequence(
);
`.trim();
return source;
return { code };
}
},
};

View file

@ -27,7 +27,7 @@ export function vitePluginServerIslands({ settings, logger }: AstroPluginOptions
},
load(id) {
if (id === RESOLVED_VIRTUAL_ISLAND_MAP_ID) {
return `export const serverIslandMap = ${serverIslandPlaceholder};`;
return { code: `export const serverIslandMap = ${serverIslandPlaceholder};` };
}
},
transform(_code, id) {

View file

@ -72,12 +72,12 @@ export function astroEnv({ settings, sync, envLoader }: AstroEnvPluginParams): P
load(id, options) {
if (id === resolveVirtualModuleId(VIRTUAL_MODULES_IDS.client)) {
ensureTemplateAreLoaded();
return templates!.client;
return { code: templates!.client };
}
if (id === resolveVirtualModuleId(VIRTUAL_MODULES_IDS.server)) {
if (options?.ssr) {
ensureTemplateAreLoaded();
return templates!.server;
return { code: templates!.server };
}
throw new AstroError({
...AstroErrorData.ServerOnlyModule,
@ -86,7 +86,7 @@ export function astroEnv({ settings, sync, envLoader }: AstroEnvPluginParams): P
}
if (id === resolveVirtualModuleId(VIRTUAL_MODULES_IDS.internal)) {
ensureTemplateAreLoaded();
return templates!.internal;
return { code: templates!.internal };
}
},
};

View file

@ -42,7 +42,7 @@ export default function virtualModulePlugin({
});
}
// There's nothing wrong about using `/client` on the server
return `${serializeClientConfig(manifest)};`;
return { code: serializeClientConfig(manifest) };
}
// server
else if (id == RESOLVED_VIRTUAL_SERVER_ID) {
@ -58,7 +58,7 @@ export default function virtualModulePlugin({
message: AstroErrorData.ServerOnlyModule.message(VIRTUAL_SERVER_ID),
});
}
return `${serializeServerConfig(manifest)};`;
return { code: serializeServerConfig(manifest) };
}
},
};

View file

@ -40,7 +40,7 @@ export default function astroPrefetch({ settings }: { settings: AstroSettings })
load(id) {
if (id === resolvedVirtualModuleId) {
if (!prefetch) throwPrefetchNotEnabledError();
return `export { prefetch } from "astro/virtual-modules/prefetch.js";`;
return { code: `export { prefetch } from "astro/virtual-modules/prefetch.js";` };
}
},
transform(code, id) {

View file

@ -58,61 +58,63 @@ export default function astroDevToolbar({ settings, logger }: AstroPluginOptions
},
async load(id) {
if (id === resolvedPrivateVirtualModuleId) {
return `
export const loadDevToolbarApps = async () => {
return (await Promise.all([${settings.devToolbarApps
.map(
(plugin) =>
`safeLoadPlugin(${JSON.stringify(
plugin,
)}, async () => (await import(${JSON.stringify(
typeof plugin === 'string' ? plugin : plugin.entrypoint.toString(),
)})).default, ${JSON.stringify(
typeof plugin === 'string' ? plugin : plugin.entrypoint.toString(),
)})`,
)
.join(',')}]));
};
return {
code: `
export const loadDevToolbarApps = async () => {
return (await Promise.all([${settings.devToolbarApps
.map(
(plugin) =>
`safeLoadPlugin(${JSON.stringify(
plugin,
)}, async () => (await import(${JSON.stringify(
typeof plugin === 'string' ? plugin : plugin.entrypoint.toString(),
)})).default, ${JSON.stringify(
typeof plugin === 'string' ? plugin : plugin.entrypoint.toString(),
)})`,
)
.join(',')}]));
};
async function safeLoadPlugin(appDefinition, importEntrypoint, entrypoint) {
try {
let app;
if (typeof appDefinition === 'string') {
app = await importEntrypoint();
async function safeLoadPlugin(appDefinition, importEntrypoint, entrypoint) {
try {
let app;
if (typeof appDefinition === 'string') {
app = await importEntrypoint();
if (typeof app !== 'object' || !app.id || !app.name) {
throw new Error("Apps must default export an object with an id, and a name.");
}
} else {
app = appDefinition;
if (typeof app !== 'object' || !app.id || !app.name) {
throw new Error("Apps must default export an object with an id, and a name.");
}
} else {
app = appDefinition;
if (typeof app !== 'object' || !app.id || !app.name || !app.entrypoint) {
throw new Error("Apps must be an object with an id, a name and an entrypoint.");
if (typeof app !== 'object' || !app.id || !app.name || !app.entrypoint) {
throw new Error("Apps must be an object with an id, a name and an entrypoint.");
}
const loadedApp = await importEntrypoint();
if (typeof loadedApp !== 'object') {
throw new Error("App entrypoint must default export an object.");
}
app = { ...app, ...loadedApp };
}
const loadedApp = await importEntrypoint();
return app;
} catch (err) {
console.error(\`Failed to load dev toolbar app from \${entrypoint}: \${err.message}\`);
if (typeof loadedApp !== 'object') {
throw new Error("App entrypoint must default export an object.");
if (import.meta.hot) {
import.meta.hot.send('astro:devtoolbar:error:load', { entrypoint: entrypoint, error: err.message })
}
app = { ...app, ...loadedApp };
}
return app;
} catch (err) {
console.error(\`Failed to load dev toolbar app from \${entrypoint}: \${err.message}\`);
if (import.meta.hot) {
import.meta.hot.send('astro:devtoolbar:error:load', { entrypoint: entrypoint, error: err.message })
return undefined;
}
return undefined;
}
return undefined;
}
`;
`,
};
}
},
};

View file

@ -27,26 +27,30 @@ export default function astroTransitions({ settings }: { settings: AstroSettings
},
load(id) {
if (id === resolvedVirtualModuleId) {
return `
export * from "astro/virtual-modules/transitions.js";
export {
default as ViewTransitions,
default as ClientRouter
} from "astro/components/ClientRouter.astro";
`;
return {
code: `
export * from "astro/virtual-modules/transitions.js";
export {
default as ViewTransitions,
default as ClientRouter
} from "astro/components/ClientRouter.astro";
`,
};
}
if (id === resolvedVirtualClientModuleId) {
return `
export { navigate, supportsViewTransitions, transitionEnabledOnThisPage } from "astro/virtual-modules/transitions-router.js";
export * from "astro/virtual-modules/transitions-types.js";
export {
TRANSITION_BEFORE_PREPARATION, isTransitionBeforePreparationEvent, TransitionBeforePreparationEvent,
TRANSITION_AFTER_PREPARATION,
TRANSITION_BEFORE_SWAP, isTransitionBeforeSwapEvent, TransitionBeforeSwapEvent,
TRANSITION_AFTER_SWAP, TRANSITION_PAGE_LOAD
} from "astro/virtual-modules/transitions-events.js";
export { swapFunctions } from "astro/virtual-modules/transitions-swap-functions.js";
`;
return {
code: `
export { navigate, supportsViewTransitions, transitionEnabledOnThisPage } from "astro/virtual-modules/transitions-router.js";
export * from "astro/virtual-modules/transitions-types.js";
export {
TRANSITION_BEFORE_PREPARATION, isTransitionBeforePreparationEvent, TransitionBeforePreparationEvent,
TRANSITION_AFTER_PREPARATION,
TRANSITION_BEFORE_SWAP, isTransitionBeforeSwapEvent, TransitionBeforeSwapEvent,
TRANSITION_AFTER_SWAP, TRANSITION_PAGE_LOAD
} from "astro/virtual-modules/transitions-events.js";
export { swapFunctions } from "astro/virtual-modules/transitions-swap-functions.js";
`,
};
}
},
transform(code, id) {

View file

@ -59,8 +59,10 @@ export default function loadFallbackPlugin({
}
},
async load(id) {
const source = await tryLoadModule(id);
return source;
const code = await tryLoadModule(id);
if (code) {
return { code };
}
},
},
{

View file

@ -30,22 +30,28 @@ export default function astroScriptsPlugin({ settings }: { settings: AstroSettin
async load(id) {
if (id === BEFORE_HYDRATION_SCRIPT_ID) {
return settings.scripts
.filter((s) => s.stage === 'before-hydration')
.map((s) => s.content)
.join('\n');
return {
code: settings.scripts
.filter((s) => s.stage === 'before-hydration')
.map((s) => s.content)
.join('\n'),
};
}
if (id === PAGE_SCRIPT_ID) {
return settings.scripts
.filter((s) => s.stage === 'page')
.map((s) => s.content)
.join('\n');
return {
code: settings.scripts
.filter((s) => s.stage === 'page')
.map((s) => s.content)
.join('\n'),
};
}
if (id === PAGE_SSR_SCRIPT_ID) {
return settings.scripts
.filter((s) => s.stage === 'page-ssr')
.map((s) => s.content)
.join('\n');
return {
code: settings.scripts
.filter((s) => s.stage === 'page-ssr')
.map((s) => s.content)
.join('\n'),
};
}
return null;
},

View file

@ -14,12 +14,13 @@ export function vitePluginSSRManifest(): VitePlugin {
},
load(id) {
if (id === resolvedManifestVirtualModuleId) {
return `export let manifest = {};
return {
code: `export let manifest = {};
export function _privateSetManifestDontUseThis(ssrManifest) {
manifest = ssrManifest;
}`;
}`,
};
}
return void 0;
},
};
}

View file

@ -46,7 +46,8 @@ export default function ({
},
load(id) {
if (id === '@my-ssr') {
return `
return {
code: `
import { App } from 'astro/app';
import fs from 'fs';
@ -92,7 +93,8 @@ export default function ({
createApp: (streaming) => new MyApp(manifest, streaming)
};
}
`;
`,
};
}
},
},