diff --git a/packages/astro/e2e/fixtures/vite-virtual-modules/src/plugins/virtual.ts b/packages/astro/e2e/fixtures/vite-virtual-modules/src/plugins/virtual.ts index c0b359bcb334..c2eb56bc2d3e 100644 --- a/packages/astro/e2e/fixtures/vite-virtual-modules/src/plugins/virtual.ts +++ b/packages/astro/e2e/fixtures/vite-virtual-modules/src/plugins/virtual.ts @@ -4,15 +4,21 @@ const VIRTUAL_MODULE_ID = "virtual:dynamic.css"; const RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`; export default { - name: VIRTUAL_MODULE_ID, - resolveId(source) { - if (!source.startsWith(VIRTUAL_MODULE_ID)) return; - - return RESOLVED_VIRTUAL_MODULE_ID; - }, - load(id) { - if (!id.startsWith(RESOLVED_VIRTUAL_MODULE_ID)) return; - - return "body { background: red; }"; - }, + name: VIRTUAL_MODULE_ID, + resolveId: { + filter: { + id: new RegExp(`^${VIRTUAL_MODULE_ID}$`), + }, + handler() { + return RESOLVED_VIRTUAL_MODULE_ID; + }, + }, + load: { + filter: { + id: new RegExp(`^${RESOLVED_VIRTUAL_MODULE_ID}$`), + }, + handler() { + return "body { background: red; }"; + }, + }, } satisfies Plugin; diff --git a/packages/astro/src/actions/vite-plugin-actions.ts b/packages/astro/src/actions/vite-plugin-actions.ts index f3e937d98c9f..68a5d3053502 100644 --- a/packages/astro/src/actions/vite-plugin-actions.ts +++ b/packages/astro/src/actions/vite-plugin-actions.ts @@ -63,27 +63,30 @@ export function vitePluginActions({ return { name: VIRTUAL_MODULE_ID, enforce: 'pre', - async resolveId(id) { - if (id === VIRTUAL_MODULE_ID) { - return RESOLVED_VIRTUAL_MODULE_ID; - } - - if (id === OPTIONS_VIRTUAL_MODULE_ID) { - return RESOLVED_OPTIONS_VIRTUAL_MODULE_ID; - } - - if (id === ACTIONS_ENTRYPOINT_VIRTUAL_MODULE_ID) { - const resolvedModule = await this.resolve( - `${decodeURI(new URL('actions', settings.config.srcDir).pathname)}`, - ); - - if (!resolvedModule) { - return RESOLVED_NOOP_ENTRYPOINT_VIRTUAL_MODULE_ID; + resolveId: { + filter: { + id: new RegExp( + `^(${VIRTUAL_MODULE_ID}|${OPTIONS_VIRTUAL_MODULE_ID}|${ACTIONS_ENTRYPOINT_VIRTUAL_MODULE_ID})$`, + ), + }, + async handler(id) { + if (id === VIRTUAL_MODULE_ID) { + return RESOLVED_VIRTUAL_MODULE_ID; } - - resolvedActionsId = resolvedModule.id; - return ACTIONS_RESOLVED_ENTRYPOINT_VIRTUAL_MODULE_ID; - } + if (id === OPTIONS_VIRTUAL_MODULE_ID) { + return RESOLVED_OPTIONS_VIRTUAL_MODULE_ID; + } + if (id === ACTIONS_ENTRYPOINT_VIRTUAL_MODULE_ID) { + const resolvedModule = await this.resolve( + `${decodeURI(new URL('actions', settings.config.srcDir).pathname)}`, + ); + if (!resolvedModule) { + return RESOLVED_NOOP_ENTRYPOINT_VIRTUAL_MODULE_ID; + } + resolvedActionsId = resolvedModule.id; + return ACTIONS_RESOLVED_ENTRYPOINT_VIRTUAL_MODULE_ID; + } + }, }, async configureServer(server) { const filePresentOnStartup = await isActionsFilePresent(fs, settings.config.srcDir); @@ -97,35 +100,42 @@ export function vitePluginActions({ server.watcher.on('add', watcherCallback); server.watcher.on('change', watcherCallback); }, - async load(id) { - if (id === RESOLVED_VIRTUAL_MODULE_ID) { - if (this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client) { + load: { + filter: { + id: new RegExp( + `^(${RESOLVED_VIRTUAL_MODULE_ID}|${RESOLVED_NOOP_ENTRYPOINT_VIRTUAL_MODULE_ID}|${ACTIONS_RESOLVED_ENTRYPOINT_VIRTUAL_MODULE_ID}|${RESOLVED_OPTIONS_VIRTUAL_MODULE_ID})$`, + ), + }, + async handler(id) { + if (id === RESOLVED_VIRTUAL_MODULE_ID) { + if (this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client) { + return { + code: `export * from 'astro/actions/runtime/entrypoints/client.js';`, + }; + } return { - code: `export * from 'astro/actions/runtime/entrypoints/client.js';`, + code: `export * from 'astro/actions/runtime/entrypoints/server.js';`, }; } - return { - code: `export * from 'astro/actions/runtime/entrypoints/server.js';`, - }; - } - if (id === RESOLVED_NOOP_ENTRYPOINT_VIRTUAL_MODULE_ID) { - return { code: 'export const server = {}' }; - } + if (id === RESOLVED_NOOP_ENTRYPOINT_VIRTUAL_MODULE_ID) { + return { code: 'export const server = {}' }; + } - if (id === ACTIONS_RESOLVED_ENTRYPOINT_VIRTUAL_MODULE_ID) { - return { code: `export { server } from ${JSON.stringify(resolvedActionsId)};` }; - } + if (id === ACTIONS_RESOLVED_ENTRYPOINT_VIRTUAL_MODULE_ID) { + return { code: `export { server } from ${JSON.stringify(resolvedActionsId)};` }; + } - if (id === RESOLVED_OPTIONS_VIRTUAL_MODULE_ID) { - return { - code: ` + if (id === RESOLVED_OPTIONS_VIRTUAL_MODULE_ID) { + return { + code: ` export const shouldAppendTrailingSlash = ${JSON.stringify( shouldAppendForwardSlash(settings.config.trailingSlash, settings.config.build.format), )}; `, - }; - } + }; + } + }, }, }; } diff --git a/packages/astro/src/assets/consts.ts b/packages/astro/src/assets/consts.ts index 5fae641ae462..b1c522a9e58b 100644 --- a/packages/astro/src/assets/consts.ts +++ b/packages/astro/src/assets/consts.ts @@ -1,4 +1,5 @@ export const VIRTUAL_MODULE_ID = 'astro:assets'; +export const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID; export const VIRTUAL_SERVICE_ID = 'virtual:image-service'; export const VALID_INPUT_FORMATS = [ 'jpeg', diff --git a/packages/astro/src/assets/fonts/vite-plugin-fonts.ts b/packages/astro/src/assets/fonts/vite-plugin-fonts.ts index e999512eeeec..227c64c39329 100644 --- a/packages/astro/src/assets/fonts/vite-plugin-fonts.ts +++ b/packages/astro/src/assets/fonts/vite-plugin-fonts.ts @@ -296,20 +296,26 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin { } }); }, - resolveId(id) { - if (id === VIRTUAL_MODULE_ID) { + resolveId: { + filter: { + id: new RegExp(`^(${VIRTUAL_MODULE_ID})$`), + }, + handler() { return RESOLVED_VIRTUAL_MODULE_ID; - } + }, }, - load(id) { - if (id === RESOLVED_VIRTUAL_MODULE_ID) { + load: { + filter: { + id: new RegExp(`^(${RESOLVED_VIRTUAL_MODULE_ID})$`), + }, + handler() { return { code: ` export const internalConsumableMap = new Map(${JSON.stringify(Array.from(internalConsumableMap?.entries() ?? []))}); export const consumableMap = new Map(${JSON.stringify(Array.from(consumableMap?.entries() ?? []))}); `, }; - } + }, }, async buildEnd() { if (sync || settings.config.experimental.fonts!.length === 0) { diff --git a/packages/astro/src/assets/vite-plugin-assets.ts b/packages/astro/src/assets/vite-plugin-assets.ts index 97f9cf89379a..a60d9489254e 100644 --- a/packages/astro/src/assets/vite-plugin-assets.ts +++ b/packages/astro/src/assets/vite-plugin-assets.ts @@ -12,8 +12,14 @@ import { removeQueryString, } from '../core/path.js'; import { normalizePath } from '../core/viteUtils.js'; +import { isAstroServerEnvironment } from '../environments.js'; import type { AstroSettings } from '../types/astro.js'; -import { VALID_INPUT_FORMATS, VIRTUAL_MODULE_ID, VIRTUAL_SERVICE_ID } from './consts.js'; +import { + RESOLVED_VIRTUAL_MODULE_ID, + VALID_INPUT_FORMATS, + VIRTUAL_MODULE_ID, + VIRTUAL_SERVICE_ID, +} from './consts.js'; import { fontsPlugin } from './fonts/vite-plugin-fonts.js'; import type { ImageTransform } from './types.js'; import { getAssetsPrefix } from './utils/getAssetsPrefix.js'; @@ -22,9 +28,6 @@ import { emitImageMetadata, hashTransform, propsToFilename } from './utils/node. import { getProxyCode } from './utils/proxy.js'; import { makeSvgComponent } from './utils/svg.js'; import { createPlaceholderURL, stringifyPlaceholderURL } from './utils/url.js'; -import { isAstroServerEnvironment } from '../environments.js'; - -const resolvedVirtualModuleId = '\0' + VIRTUAL_MODULE_ID; const assetRegex = new RegExp(`\\.(${VALID_INPUT_FORMATS.join('|')})`, 'i'); const assetRegexEnds = new RegExp(`\\.(${VALID_INPUT_FORMATS.join('|')})$`, 'i'); @@ -127,19 +130,27 @@ export default function assets({ fs, settings, sync, logger }: Options): vite.Pl config(_, env) { isBuild = env.command === 'build'; }, - async resolveId(id, _importer) { - if (id === VIRTUAL_SERVICE_ID) { - if (isAstroServerEnvironment(this.environment)) { - return await this.resolve(settings.config.image.service.entrypoint); + resolveId: { + filter: { + id: new RegExp(`^(${VIRTUAL_SERVICE_ID}|${VIRTUAL_MODULE_ID})$`), + }, + async handler(id) { + if (id === VIRTUAL_SERVICE_ID) { + if (isAstroServerEnvironment(this.environment)) { + return await this.resolve(settings.config.image.service.entrypoint); + } + return await this.resolve('astro/assets/services/noop'); } - return await this.resolve('astro/assets/services/noop'); - } - if (id === VIRTUAL_MODULE_ID) { - return resolvedVirtualModuleId; - } + if (id === VIRTUAL_MODULE_ID) { + return RESOLVED_VIRTUAL_MODULE_ID; + } + }, }, - load(id) { - if (id === resolvedVirtualModuleId) { + load: { + filter: { + id: new RegExp(`^(${RESOLVED_VIRTUAL_MODULE_ID})$`), + }, + handler() { return { code: ` export { getConfiguredImageService, isLocalService } from "astro/assets"; @@ -186,7 +197,7 @@ export default function assets({ fs, settings, sync, logger }: Options): vite.Pl export const getFontData = createGetFontData(fontsMod); `, }; - } + }, }, buildStart() { if (!isBuild) return; @@ -231,8 +242,11 @@ export default function assets({ fs, settings, sync, logger }: Options): vite.Pl configResolved(viteConfig) { resolvedConfig = viteConfig; }, - async load(id) { - if (assetRegex.test(id)) { + load: { + filter: { + id: assetRegex, + }, + async handler(id) { if (!globalThis.astroAsset.referencedImages) globalThis.astroAsset.referencedImages = new Set(); @@ -283,7 +297,7 @@ export default function assets({ fs, settings, sync, logger }: Options): vite.Pl code: `export default ${JSON.stringify(imageMetadata)}`, }; } - } + }, }, }, fontsPlugin({ settings, sync, logger }), diff --git a/packages/astro/src/container/vite-plugin-container.ts b/packages/astro/src/container/vite-plugin-container.ts index 2098ff0866e2..77d19534e3dd 100644 --- a/packages/astro/src/container/vite-plugin-container.ts +++ b/packages/astro/src/container/vite-plugin-container.ts @@ -1,15 +1,18 @@ import type * as vite from 'vite'; -const virtualModuleId = 'astro:container'; +const VIRTUAL_MODULE_ID = 'astro:container'; export default function astroContainer(): vite.Plugin { return { - name: 'astro:container', + name: VIRTUAL_MODULE_ID, enforce: 'pre', - resolveId(id) { - if (id === virtualModuleId) { + resolveId: { + filter: { + id: new RegExp(`^(${VIRTUAL_MODULE_ID})$`), + }, + handler() { return this.resolve('astro/virtual-modules/container.js'); - } + }, }, }; } diff --git a/packages/astro/src/content/vite-plugin-content-assets.ts b/packages/astro/src/content/vite-plugin-content-assets.ts index 7e56784e7523..829f032d24d6 100644 --- a/packages/astro/src/content/vite-plugin-content-assets.ts +++ b/packages/astro/src/content/vite-plugin-content-assets.ts @@ -31,40 +31,45 @@ export function astroContentAssetPropagationPlugin({ return { name: 'astro:content-asset-propagation', enforce: 'pre', - async resolveId(id, importer, opts) { - if (hasContentFlag(id, CONTENT_IMAGE_FLAG)) { - const [base, query] = id.split('?'); - const params = new URLSearchParams(query); - const importerParam = params.get('importer'); + resolveId: { + filter: { + id: new RegExp(`(?:\\?|&)(?:${CONTENT_IMAGE_FLAG}|${CONTENT_RENDER_FLAG})(?:&|=|$)`), + }, + async handler(id, importer, opts) { + if (hasContentFlag(id, CONTENT_IMAGE_FLAG)) { + const [base, query] = id.split('?'); + const params = new URLSearchParams(query); + const importerParam = params.get('importer'); - const importerPath = importerParam - ? fileURLToPath(new URL(importerParam, settings.config.root)) - : importer; + const importerPath = importerParam + ? fileURLToPath(new URL(importerParam, settings.config.root)) + : importer; - const resolved = await this.resolve(base, importerPath, { skipSelf: true, ...opts }); - if (!resolved) { - throw new AstroError({ - ...AstroErrorData.ImageNotFound, - message: AstroErrorData.ImageNotFound.message(base), - }); + const resolved = await this.resolve(base, importerPath, { skipSelf: true, ...opts }); + if (!resolved) { + throw new AstroError({ + ...AstroErrorData.ImageNotFound, + message: AstroErrorData.ImageNotFound.message(base), + }); + } + return resolved; } - return resolved; - } - if (hasContentFlag(id, CONTENT_RENDER_FLAG)) { - const base = id.split('?')[0]; + if (hasContentFlag(id, CONTENT_RENDER_FLAG)) { + const base = id.split('?')[0]; - for (const { extensions, handlePropagation = true } of settings.contentEntryTypes) { - if (handlePropagation && extensions.includes(extname(base))) { - return this.resolve(`${base}?${PROPAGATED_ASSET_FLAG}`, importer, { - skipSelf: true, - ...opts, - }); + for (const { extensions, handlePropagation = true } of settings.contentEntryTypes) { + if (handlePropagation && extensions.includes(extname(base))) { + return this.resolve(`${base}?${PROPAGATED_ASSET_FLAG}`, importer, { + skipSelf: true, + ...opts, + }); + } } + // Resolve to the base id (no content flags) + // if Astro doesn't need to handle propagation. + return this.resolve(base, importer, { skipSelf: true, ...opts }); } - // Resolve to the base id (no content flags) - // if Astro doesn't need to handle propagation. - return this.resolve(base, importer, { skipSelf: true, ...opts }); - } + }, }, configureServer(server) { if (!isRunnableDevEnvironment(server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr])) { @@ -75,44 +80,48 @@ export function astroContentAssetPropagationPlugin({ server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr] as RunnableDevEnvironment, ); }, - async transform(_, id) { - if (hasContentFlag(id, PROPAGATED_ASSET_FLAG)) { - const basePath = id.split('?')[0]; - let stringifiedLinks: string, stringifiedStyles: string; + transform: { + filter: { + id: new RegExp(`(?:\\?|&)${PROPAGATED_ASSET_FLAG}(?:&|=|$)`), + }, + async handler(_, id) { + if (hasContentFlag(id, PROPAGATED_ASSET_FLAG)) { + const basePath = id.split('?')[0]; + let stringifiedLinks: string, stringifiedStyles: string; - // We can access the server in dev, - // so resolve collected styles and scripts here. - if (isAstroServerEnvironment(this.environment) && devModuleLoader) { - if (!devModuleLoader.getModuleById(basePath)?.ssrModule) { - await devModuleLoader.import(basePath); - } - const { - styles, - urls, - crawledFiles: styleCrawledFiles, - } = await getStylesForURL(basePath, devModuleLoader.getSSREnvironment()); + // We can access the server in dev, + // so resolve collected styles and scripts here. + if (isAstroServerEnvironment(this.environment) && devModuleLoader) { + if (!devModuleLoader.getModuleById(basePath)?.ssrModule) { + await devModuleLoader.import(basePath); + } + const { + styles, + urls, + crawledFiles: styleCrawledFiles, + } = await getStylesForURL(basePath, devModuleLoader.getSSREnvironment()); - // Register files we crawled to be able to retrieve the rendered styles and scripts, - // as when they get updated, we need to re-transform ourselves. - // We also only watch files within the user source code, as changes in node_modules - // are usually also ignored by Vite. - for (const file of styleCrawledFiles) { - if (!file.includes('node_modules')) { - this.addWatchFile(file); + // Register files we crawled to be able to retrieve the rendered styles and scripts, + // as when they get updated, we need to re-transform ourselves. + // We also only watch files within the user source code, as changes in node_modules + // are usually also ignored by Vite. + for (const file of styleCrawledFiles) { + if (!file.includes('node_modules')) { + this.addWatchFile(file); + } } - } - stringifiedLinks = JSON.stringify([...urls]); - stringifiedStyles = JSON.stringify(styles.map((s) => s.content)); - } else { - // Otherwise, use placeholders to inject styles and scripts - // during the production bundle step. - // @see the `astro:content-build-plugin` below. - stringifiedLinks = JSON.stringify(LINKS_PLACEHOLDER); - stringifiedStyles = JSON.stringify(STYLES_PLACEHOLDER); - } + stringifiedLinks = JSON.stringify([...urls]); + stringifiedStyles = JSON.stringify(styles.map((s) => s.content)); + } else { + // Otherwise, use placeholders to inject styles and scripts + // during the production bundle step. + // @see the `astro:content-build-plugin` below. + stringifiedLinks = JSON.stringify(LINKS_PLACEHOLDER); + stringifiedStyles = JSON.stringify(STYLES_PLACEHOLDER); + } - const code = ` + const code = ` async function getMod() { return import(${JSON.stringify(basePath)}); } @@ -121,10 +130,11 @@ export function astroContentAssetPropagationPlugin({ const defaultMod = { __astroPropagation: true, getMod, collectedLinks, collectedStyles, collectedScripts: [] }; export default defaultMod; `; - // ^ Use a default export for tools like Markdoc - // to catch the `__astroPropagation` identifier - return { code, map: { mappings: '' } }; - } + // ^ Use a default export for tools like Markdoc + // to catch the `__astroPropagation` identifier + return { code, map: { mappings: '' } }; + } + }, }, }; } diff --git a/packages/astro/src/content/vite-plugin-content-imports.ts b/packages/astro/src/content/vite-plugin-content-imports.ts index ab34ac0ca92c..6782c965fd92 100644 --- a/packages/astro/src/content/vite-plugin-content-imports.ts +++ b/packages/astro/src/content/vite-plugin-content-imports.ts @@ -91,28 +91,32 @@ export function astroContentImportPlugin({ // Get symlinks once at build start symlinks = await getSymlinkedContentCollections({ contentDir, logger, fs }); }, - async transform(_, viteId) { - if (hasContentFlag(viteId, DATA_FLAG)) { - // By default, Vite will resolve symlinks to their targets. We need to reverse this for - // content entries, so we can get the path relative to the content directory. - const fileId = reverseSymlink({ - entry: viteId.split('?')[0] ?? viteId, - contentDir, - symlinks, - }); - // Data collections don't need to rely on the module cache. - // This cache only exists for the `render()` function specific to content. - const { id, data, collection, _internal } = await getDataEntryModule({ - fileId, - entryConfigByExt: dataEntryConfigByExt, - contentDir, - config: settings.config, - fs, - pluginContext: this, - shouldEmitFile, - }); - - const code = ` + transform: { + filter: { + id: new RegExp(`(?:\\?|&)(?:${DATA_FLAG}|${CONTENT_FLAG})(?:&|=|$)`), + }, + async handler(_, viteId) { + if (hasContentFlag(viteId, DATA_FLAG)) { + // By default, Vite will resolve symlinks to their targets. We need to reverse this for + // content entries, so we can get the path relative to the content directory. + const fileId = reverseSymlink({ + entry: viteId.split('?')[0] ?? viteId, + contentDir, + symlinks, + }); + // Data collections don't need to rely on the module cache. + // This cache only exists for the `render()` function specific to content. + const { id, data, collection, _internal } = await getDataEntryModule({ + fileId, + entryConfigByExt: dataEntryConfigByExt, + contentDir, + config: settings.config, + fs, + pluginContext: this, + shouldEmitFile, + }); + + const code = ` export const id = ${JSON.stringify(id)}; export const collection = ${JSON.stringify(collection)}; export const data = ${stringifyEntryData(data, settings.buildOutput === 'server')}; @@ -122,20 +126,20 @@ export const _internal = { rawData: ${JSON.stringify(_internal.rawData)}, }; `; - return code; - } else if (hasContentFlag(viteId, CONTENT_FLAG)) { - const fileId = reverseSymlink({ entry: viteId.split('?')[0], contentDir, symlinks }); - const { id, slug, collection, body, data, _internal } = await getContentEntryModule({ - fileId, - entryConfigByExt: contentEntryConfigByExt, - contentDir, - config: settings.config, - fs, - pluginContext: this, - shouldEmitFile, - }); - - const code = ` + return code; + } else if (hasContentFlag(viteId, CONTENT_FLAG)) { + const fileId = reverseSymlink({ entry: viteId.split('?')[0], contentDir, symlinks }); + const { id, slug, collection, body, data, _internal } = await getContentEntryModule({ + fileId, + entryConfigByExt: contentEntryConfigByExt, + contentDir, + config: settings.config, + fs, + pluginContext: this, + shouldEmitFile, + }); + + const code = ` export const id = ${JSON.stringify(id)}; export const collection = ${JSON.stringify(collection)}; export const slug = ${JSON.stringify(slug)}; @@ -147,8 +151,9 @@ export const _internal = { rawData: ${JSON.stringify(_internal.rawData)}, };`; - return { code, map: { mappings: '' } }; - } + return { code, map: { mappings: '' } }; + } + }, }, configureServer(viteServer) { viteServer.watcher.on('all', async (event, entry) => { @@ -200,12 +205,21 @@ export const _internal = { if (settings.contentEntryTypes.some((t) => t.getRenderModule)) { plugins.push({ name: 'astro:content-render-imports', - async transform(contents, viteId) { - const contentRenderer = getContentRendererByViteId(viteId, settings); - if (!contentRenderer) return; - - const fileId = viteId.split('?')[0]; - return contentRenderer.bind(this)({ viteId, contents, fileUrl: pathToFileURL(fileId) }); + transform: { + filter: { + id: { + include: settings.contentEntryTypes + .filter((t) => t.getRenderModule) + .map((t) => new RegExp(`\\.(${t.extensions.map((e) => e.slice(1)).join('|')})$`)), + }, + }, + async handler(contents, viteId) { + const contentRenderer = getContentRendererByViteId(viteId, settings); + if (!contentRenderer) return; + + const fileId = viteId.split('?')[0]; + return contentRenderer.bind(this)({ viteId, contents, fileUrl: pathToFileURL(fileId) }); + }, }, }); } diff --git a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts index 0135a1e7fcee..8965f68b5f8c 100644 --- a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts +++ b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts @@ -1,10 +1,11 @@ -import { dataToEsm } from '@rollup/pluginutils'; import nodeFs from 'node:fs'; import { fileURLToPath } from 'node:url'; +import { dataToEsm } from '@rollup/pluginutils'; import { normalizePath, type Plugin, type ViteDevServer } from 'vite'; import { ASTRO_VITE_ENVIRONMENT_NAMES } from '../core/constants.js'; import { AstroError, AstroErrorData } from '../core/errors/index.js'; import { rootRelativePath } from '../core/viteUtils.js'; +import { isAstroClientEnvironment } from '../environments.js'; import type { AstroSettings } from '../types/astro.js'; import type { AstroPluginMetadata } from '../vite-plugin-astro/index.js'; import { createDefaultAstroMetadata } from '../vite-plugin-astro/metadata.js'; @@ -12,6 +13,7 @@ import { ASSET_IMPORTS_FILE, ASSET_IMPORTS_RESOLVED_STUB_ID, ASSET_IMPORTS_VIRTUAL_ID, + CONTENT_MODULE_FLAG, CONTENT_RENDER_FLAG, DATA_STORE_VIRTUAL_ID, MODULES_IMPORTS_FILE, @@ -23,7 +25,6 @@ import { } from './consts.js'; import { getDataStoreFile } from './content-layer.js'; import { getContentPaths, isDeferredModule } from './utils.js'; -import { isAstroClientEnvironment } from '../environments.js'; interface AstroContentVirtualModPluginParams { settings: AstroSettings; @@ -67,106 +68,120 @@ export function astroContentVirtualModPlugin({ invalidateDataStore(devServer); } }, - async resolveId(id, importer) { - if (id === VIRTUAL_MODULE_ID) { - // Live content config can't import the virtual module directly, - // because it would create a circular dependency from the collection exports. - // Instead, we resolve the config util module, because that's all that it should use anyway. - if (liveConfig && importer && liveConfig === normalizePath(importer)) { - return this.resolve('astro/virtual-modules/live-config', importer, { - skipSelf: true, - }); + resolveId: { + filter: { + id: new RegExp( + `^(${VIRTUAL_MODULE_ID}|${DATA_STORE_VIRTUAL_ID}|${MODULES_MJS_ID}|${ASSET_IMPORTS_VIRTUAL_ID})$|(?:\\?|&)${CONTENT_MODULE_FLAG}(?:&|=|$)`, + ), + }, + async handler(id, importer) { + if (id === VIRTUAL_MODULE_ID) { + // Live content config can't import the virtual module directly, + // because it would create a circular dependency from the collection exports. + // Instead, we resolve the config util module, because that's all that it should use anyway. + if (liveConfig && importer && liveConfig === normalizePath(importer)) { + return this.resolve('astro/virtual-modules/live-config', importer, { + skipSelf: true, + }); + } + return RESOLVED_VIRTUAL_MODULE_ID; } - return RESOLVED_VIRTUAL_MODULE_ID; - } - if (id === DATA_STORE_VIRTUAL_ID) { - return RESOLVED_DATA_STORE_VIRTUAL_ID; - } - - if (isDeferredModule(id)) { - const [, query] = id.split('?'); - const params = new URLSearchParams(query); - const fileName = params.get('fileName'); - let importPath = undefined; - if (fileName && URL.canParse(fileName, settings.config.root.toString())) { - importPath = fileURLToPath(new URL(fileName, settings.config.root)); + if (id === DATA_STORE_VIRTUAL_ID) { + return RESOLVED_DATA_STORE_VIRTUAL_ID; } - if (importPath) { - return await this.resolve(`${importPath}?${CONTENT_RENDER_FLAG}`); + + if (isDeferredModule(id)) { + const [, query] = id.split('?'); + const params = new URLSearchParams(query); + const fileName = params.get('fileName'); + let importPath = undefined; + if (fileName && URL.canParse(fileName, settings.config.root.toString())) { + importPath = fileURLToPath(new URL(fileName, settings.config.root)); + } + if (importPath) { + return await this.resolve(`${importPath}?${CONTENT_RENDER_FLAG}`); + } } - } - if (id === MODULES_MJS_ID) { - const modules = new URL(MODULES_IMPORTS_FILE, settings.dotAstroDir); - if (fs.existsSync(modules)) { - return fileURLToPath(modules); + if (id === MODULES_MJS_ID) { + const modules = new URL(MODULES_IMPORTS_FILE, settings.dotAstroDir); + if (fs.existsSync(modules)) { + return fileURLToPath(modules); + } + return MODULES_MJS_VIRTUAL_ID; } - return MODULES_MJS_VIRTUAL_ID; - } - if (id === ASSET_IMPORTS_VIRTUAL_ID) { - const assetImportsFile = new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir); - if (fs.existsSync(assetImportsFile)) { - return fileURLToPath(assetImportsFile); + if (id === ASSET_IMPORTS_VIRTUAL_ID) { + const assetImportsFile = new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir); + if (fs.existsSync(assetImportsFile)) { + return fileURLToPath(assetImportsFile); + } + return ASSET_IMPORTS_RESOLVED_STUB_ID; } - return ASSET_IMPORTS_RESOLVED_STUB_ID; - } + }, }, - async load(id) { - if (id === RESOLVED_VIRTUAL_MODULE_ID) { - const isClient = isAstroClientEnvironment(this.environment); - const code = await generateContentEntryFile({ - settings, - fs, - isClient, - }); - - const astro = createDefaultAstroMetadata(); - astro.propagation = 'in-tree'; - return { - code, - meta: { - astro, - } satisfies AstroPluginMetadata, - }; - } - if (id === RESOLVED_DATA_STORE_VIRTUAL_ID) { - if (!fs.existsSync(dataStoreFile)) { - return { code: 'export default new Map()' }; - } - const jsonData = await fs.promises.readFile(dataStoreFile, 'utf-8'); + load: { + filter: { + id: new RegExp( + `^(${RESOLVED_VIRTUAL_MODULE_ID}|${RESOLVED_DATA_STORE_VIRTUAL_ID}|${ASSET_IMPORTS_RESOLVED_STUB_ID}|${MODULES_MJS_VIRTUAL_ID})$`, + ), + }, + async handler(id) { + if (id === RESOLVED_VIRTUAL_MODULE_ID) { + const isClient = isAstroClientEnvironment(this.environment); + const code = await generateContentEntryFile({ + settings, + fs, + isClient, + }); - try { - const parsed = JSON.parse(jsonData); + const astro = createDefaultAstroMetadata(); + astro.propagation = 'in-tree'; return { - code: dataToEsm(parsed, { - compact: true, - }), - map: { mappings: '' }, + code, + meta: { + astro, + } satisfies AstroPluginMetadata, }; - } catch (err) { - const message = 'Could not parse JSON file'; - this.error({ message, id, cause: err }); } - } + if (id === RESOLVED_DATA_STORE_VIRTUAL_ID) { + if (!fs.existsSync(dataStoreFile)) { + return { code: 'export default new Map()' }; + } + const jsonData = await fs.promises.readFile(dataStoreFile, 'utf-8'); + + try { + const parsed = JSON.parse(jsonData); + return { + code: dataToEsm(parsed, { + compact: true, + }), + map: { mappings: '' }, + }; + } catch (err) { + const message = 'Could not parse JSON file'; + this.error({ message, id, cause: err }); + } + } - if (id === ASSET_IMPORTS_RESOLVED_STUB_ID) { - const assetImportsFile = new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir); - return { - code: fs.existsSync(assetImportsFile) - ? fs.readFileSync(assetImportsFile, 'utf-8') - : 'export default new Map()', - }; - } + if (id === ASSET_IMPORTS_RESOLVED_STUB_ID) { + const assetImportsFile = new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir); + 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); - return { - code: fs.existsSync(modules) - ? fs.readFileSync(modules, 'utf-8') - : 'export default new Map()', - }; - } + if (id === MODULES_MJS_VIRTUAL_ID) { + const modules = new URL(MODULES_IMPORTS_FILE, settings.dotAstroDir); + return { + code: fs.existsSync(modules) + ? fs.readFileSync(modules, 'utf-8') + : 'export default new Map()', + }; + } + }, }, configureServer(server) { diff --git a/packages/astro/src/core/build/plugins/plugin-component-entry.ts b/packages/astro/src/core/build/plugins/plugin-component-entry.ts index c06670a4b98e..f992404ad7d8 100644 --- a/packages/astro/src/core/build/plugins/plugin-component-entry.ts +++ b/packages/astro/src/core/build/plugins/plugin-component-entry.ts @@ -57,13 +57,19 @@ export function pluginComponentEntry(internals: BuildInternals): VitePlugin { }); } }, - async resolveId(id) { - if (id.startsWith(astroEntryPrefix)) { + resolveId: { + filter: { + id: new RegExp(`^${astroEntryPrefix}`), + }, + handler(id) { return id; - } + }, }, - async load(id) { - if (id.startsWith(astroEntryPrefix)) { + load: { + filter: { + id: new RegExp(`^${astroEntryPrefix}`), + }, + async handler(id) { const componentId = id.slice(astroEntryPrefix.length); const exportNames = componentToExportNames.get(componentId); if (exportNames) { @@ -71,7 +77,7 @@ export function pluginComponentEntry(internals: BuildInternals): VitePlugin { code: `export { ${exportNames.join(', ')} } from ${JSON.stringify(componentId)}`, }; } - } + }, }, }; } diff --git a/packages/astro/src/core/build/plugins/plugin-css.ts b/packages/astro/src/core/build/plugins/plugin-css.ts index d4163c8b278c..4b9d93ceeeb4 100644 --- a/packages/astro/src/core/build/plugins/plugin-css.ts +++ b/packages/astro/src/core/build/plugins/plugin-css.ts @@ -12,6 +12,7 @@ import { getPageDataByViteID, getPageDatasByClientOnlyID } from '../internal.js' import type { PageBuildData, StaticBuildOptions, StylesheetAsset } from '../types.js'; import { shouldInlineAsset } from './util.js'; import { ASTRO_VITE_ENVIRONMENT_NAMES } from '../../constants.js'; +import { CSS_LANGS_RE } from '../../viteUtils.js'; /***** ASTRO PLUGIN *****/ @@ -53,8 +54,11 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] { ); }, - transform(_code, id) { - if (isCSSRequest(id)) { + transform: { + filter: { + id: CSS_LANGS_RE, + }, + handler(_code, id) { // In prerender, don't rebundle CSS that was already bundled in SSR. // Return an empty string here to prevent it. if (this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender) { @@ -65,7 +69,7 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] { } } cssModulesInBundles.add(id); - } + }, }, async generateBundle(_outputOptions, bundle) { diff --git a/packages/astro/src/core/build/plugins/plugin-noop.ts b/packages/astro/src/core/build/plugins/plugin-noop.ts index 01135c49e8a2..544ba1417e2a 100644 --- a/packages/astro/src/core/build/plugins/plugin-noop.ts +++ b/packages/astro/src/core/build/plugins/plugin-noop.ts @@ -10,15 +10,21 @@ const RESOLVED_NOOP_MODULE_ID = '\0' + NOOP_MODULE_ID; export function pluginNoop(): vite.Plugin { return { name: 'plugin-noop', - resolveId(id) { - if (id === NOOP_MODULE_ID) { + resolveId: { + filter: { + id: new RegExp(`^${NOOP_MODULE_ID}$`), + }, + handler() { return RESOLVED_NOOP_MODULE_ID; - } + }, }, - load(id) { - if (id === RESOLVED_NOOP_MODULE_ID) { + load: { + filter: { + id: new RegExp(`^${RESOLVED_NOOP_MODULE_ID}$`), + }, + handler() { return 'export const noop = {};'; - } + }, }, generateBundle(_options, bundle) { // Delete this bundle so that its not written out to disk. diff --git a/packages/astro/src/core/build/plugins/plugin-ssr.ts b/packages/astro/src/core/build/plugins/plugin-ssr.ts index bfca10c5b7e6..e5e0d0d1af7a 100644 --- a/packages/astro/src/core/build/plugins/plugin-ssr.ts +++ b/packages/astro/src/core/build/plugins/plugin-ssr.ts @@ -20,20 +20,26 @@ function vitePluginAdapter(adapter: AstroAdapter): VitePlugin { applyToEnvironment(environment) { return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr; }, - resolveId(id) { - if (id === ADAPTER_VIRTUAL_MODULE_ID) { + resolveId: { + filter: { + id: new RegExp(`^${ADAPTER_VIRTUAL_MODULE_ID}$`), + }, + handler() { return RESOLVED_ADAPTER_VIRTUAL_MODULE_ID; - } + }, }, - async load(id) { - if (id === RESOLVED_ADAPTER_VIRTUAL_MODULE_ID) { + load: { + filter: { + id: new RegExp(`^${RESOLVED_ADAPTER_VIRTUAL_MODULE_ID}$`), + }, + async handler() { const adapterEntrypointStr = JSON.stringify(adapter.serverEntrypoint); return { code: `export * from ${adapterEntrypointStr}; import * as _serverEntrypoint from ${adapterEntrypointStr}; export default _serverEntrypoint.default;`, }; - } + }, }, }; } @@ -50,20 +56,26 @@ function vitePluginAdapterConfig(adapter: AstroAdapter): VitePlugin { applyToEnvironment(environment) { return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr; }, - resolveId(id) { - if (id === ADAPTER_CONFIG_VIRTUAL_MODULE_ID) { + resolveId: { + filter: { + id: new RegExp(`^${ADAPTER_CONFIG_VIRTUAL_MODULE_ID}$`), + }, + handler() { return RESOLVED_ADAPTER_CONFIG_VIRTUAL_MODULE_ID; - } + }, }, - load(id) { - if (id === RESOLVED_ADAPTER_CONFIG_VIRTUAL_MODULE_ID) { + load: { + filter: { + id: new RegExp(`^${RESOLVED_ADAPTER_CONFIG_VIRTUAL_MODULE_ID}$`), + }, + handler() { return { code: `export const args = ${adapter.args ? JSON.stringify(adapter.args, null, 2) : 'undefined'}; export const exports = ${adapter.exports ? JSON.stringify(adapter.exports) : 'undefined'}; export const adapterFeatures = ${adapter.adapterFeatures ? JSON.stringify(adapter.adapterFeatures, null, 2) : 'undefined'}; export const serverEntrypoint = ${JSON.stringify(adapter.serverEntrypoint)};`, }; - } + }, }, }; } @@ -75,13 +87,19 @@ function vitePluginSSR(internals: BuildInternals, adapter: AstroAdapter): VitePl applyToEnvironment(environment) { return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr; }, - resolveId(id) { - if (id === SSR_VIRTUAL_MODULE_ID) { + resolveId: { + filter: { + id: new RegExp(`^${SSR_VIRTUAL_MODULE_ID}$`), + }, + handler() { return RESOLVED_SSR_VIRTUAL_MODULE_ID; - } + }, }, - load(id) { - if (id === RESOLVED_SSR_VIRTUAL_MODULE_ID) { + load: { + filter: { + id: new RegExp(`^${RESOLVED_SSR_VIRTUAL_MODULE_ID}$`), + }, + handler() { const exports: string[] = []; if (adapter.exports) { @@ -99,7 +117,7 @@ function vitePluginSSR(internals: BuildInternals, adapter: AstroAdapter): VitePl return { code: `import _exports from 'astro/entrypoints/legacy';\n${exports.join('\n')}`, }; - } + }, }, async generateBundle(_opts, bundle) { // Add assets from this SSR chunk as well. diff --git a/packages/astro/src/core/middleware/vite-plugin.ts b/packages/astro/src/core/middleware/vite-plugin.ts index cab3082c7bcb..efb588803074 100644 --- a/packages/astro/src/core/middleware/vite-plugin.ts +++ b/packages/astro/src/core/middleware/vite-plugin.ts @@ -30,8 +30,11 @@ export function vitePluginMiddleware({ settings }: { settings: AstroSettings }): environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender ); }, - async resolveId(id) { - if (id === MIDDLEWARE_MODULE_ID) { + resolveId: { + filter: { + id: new RegExp(`^${MIDDLEWARE_MODULE_ID}$`), + }, + async handler() { const middlewareId = await this.resolve( `${decodeURI(settings.config.srcDir.pathname)}${MIDDLEWARE_PATH_SEGMENT_NAME}`, ); @@ -44,26 +47,28 @@ export function vitePluginMiddleware({ settings }: { settings: AstroSettings }): } else { return NOOP_MIDDLEWARE; } - } - if (id === NOOP_MIDDLEWARE) { - return NOOP_MIDDLEWARE; - } + }, }, - async load(id) { - if (id === NOOP_MIDDLEWARE) { - if (!userMiddlewareIsPresent && settings.config.i18n?.routing === 'manual') { - throw new AstroError(MissingMiddlewareForInternationalization); - } - return { code: 'export const onRequest = (_, next) => next()' }; - } else if (id === MIDDLEWARE_RESOLVED_MODULE_ID) { - if (!userMiddlewareIsPresent && settings.config.i18n?.routing === 'manual') { - throw new AstroError(MissingMiddlewareForInternationalization); + load: { + filter: { + id: new RegExp(`^(${NOOP_MIDDLEWARE}|${MIDDLEWARE_RESOLVED_MODULE_ID})$`), + }, + async handler(id) { + if (id === NOOP_MIDDLEWARE) { + if (!userMiddlewareIsPresent && settings.config.i18n?.routing === 'manual') { + throw new AstroError(MissingMiddlewareForInternationalization); + } + return { code: 'export const onRequest = (_, next) => next()' }; } + if (id === MIDDLEWARE_RESOLVED_MODULE_ID) { + if (!userMiddlewareIsPresent && settings.config.i18n?.routing === 'manual') { + throw new AstroError(MissingMiddlewareForInternationalization); + } - const preMiddleware = createMiddlewareImports(settings.middlewares.pre, 'pre'); - const postMiddleware = createMiddlewareImports(settings.middlewares.post, 'post'); + const preMiddleware = createMiddlewareImports(settings.middlewares.pre, 'pre'); + const postMiddleware = createMiddlewareImports(settings.middlewares.post, 'post'); - const code = ` + const code = ` ${ userMiddlewareIsPresent ? `import { onRequest as userOnRequest } from '${resolvedMiddlewareId}';` @@ -79,8 +84,9 @@ export const onRequest = sequence( ); `.trim(); - return { code }; - } + return { code }; + } + }, }, }; } diff --git a/packages/astro/src/core/server-islands/vite-plugin-server-islands.ts b/packages/astro/src/core/server-islands/vite-plugin-server-islands.ts index b4317082063c..421e9160b143 100644 --- a/packages/astro/src/core/server-islands/vite-plugin-server-islands.ts +++ b/packages/astro/src/core/server-islands/vite-plugin-server-islands.ts @@ -26,96 +26,110 @@ export function vitePluginServerIslands({ settings }: AstroPluginOptions): ViteP configureServer(server) { ssrEnvironment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr]; }, - resolveId(name) { - if (name === SERVER_ISLAND_MANIFEST) { + resolveId: { + filter: { + id: new RegExp(`^${SERVER_ISLAND_MANIFEST}$`), + }, + handler() { return RESOLVED_SERVER_ISLAND_MANIFEST; - } + }, }, - load(id) { - if (id === RESOLVED_SERVER_ISLAND_MANIFEST) { + load: { + filter: { + id: new RegExp(`^${RESOLVED_SERVER_ISLAND_MANIFEST}$`), + }, + handler() { return { - code: ` - export const serverIslandMap = ${serverIslandPlaceholderMap};\n\nexport const serverIslandNameMap = ${serverIslandPlaceholderNameMap}; - `, + code: `export const serverIslandMap = ${serverIslandPlaceholderMap};\n\nexport const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`, }; - } + }, }, - async transform(_code, id) { - // We run the transform for all file extensions to support transformed files, eg. mdx - const info = this.getModuleInfo(id); + transform: { + filter: { + id: { + include: [ + // Allows server islands in astro and mdx files + /\.(astro|mdx)$/, + new RegExp(`^${RESOLVED_SERVER_ISLAND_MANIFEST}$`), + ], + }, + }, + async handler(_code, id) { + const info = this.getModuleInfo(id); - const astro = info ? (info.meta.astro as AstroPluginMetadata['astro']) : undefined; + const astro = info ? (info.meta.astro as AstroPluginMetadata['astro']) : undefined; - if (astro) { - for (const comp of astro.serverComponents) { - if (!serverIslandNameMap.has(comp.resolvedPath)) { - if (!settings.adapter) { - throw new AstroError(AstroErrorData.NoAdapterInstalledServerIslands); - } - let name = comp.localName; - let idx = 1; + if (astro) { + for (const comp of astro.serverComponents) { + if (!serverIslandNameMap.has(comp.resolvedPath)) { + if (!settings.adapter) { + throw new AstroError(AstroErrorData.NoAdapterInstalledServerIslands); + } + let name = comp.localName; + let idx = 1; - while (true) { - // Name not taken, let's use it. - if (!serverIslandMap.has(name)) { - break; + while (true) { + // Name not taken, let's use it. + if (!serverIslandMap.has(name)) { + break; + } + // Increment a number onto the name: Avatar -> Avatar1 + name += idx++; } - // Increment a number onto the name: Avatar -> Avatar1 - name += idx++; - } - // Append the name map, for prod - serverIslandNameMap.set(comp.resolvedPath, name); - serverIslandMap.set(name, comp.resolvedPath); + // Append the name map, for prod + serverIslandNameMap.set(comp.resolvedPath, name); + serverIslandMap.set(name, comp.resolvedPath); - // Build mode - if (command === 'build') { - let referenceId = this.emitFile({ - type: 'chunk', - id: comp.specifier, - importer: id, - name: comp.localName, - }); - referenceIdMap.set(comp.resolvedPath, referenceId); + // Build mode + if (command === 'build') { + let referenceId = this.emitFile({ + type: 'chunk', + id: comp.specifier, + importer: id, + name: comp.localName, + }); + referenceIdMap.set(comp.resolvedPath, referenceId); + } } } } - } - - if (serverIslandNameMap.size > 0 && serverIslandMap.size > 0 && ssrEnvironment) { - // In dev, we need to clear the module graph so that Vite knows to re-transform - // the module with the new island information. - const mod = ssrEnvironment.moduleGraph.getModuleById(RESOLVED_SERVER_ISLAND_MANIFEST); - if (mod) { - ssrEnvironment.moduleGraph.invalidateModule(mod); - } - } - if (id === RESOLVED_SERVER_ISLAND_MANIFEST) { - if (command === 'build' && settings.buildOutput) { - const hasServerIslands = serverIslandNameMap.size > 0; - // Error if there are server islands but no adapter provided. - if (hasServerIslands && settings.buildOutput !== 'server') { - throw new AstroError(AstroErrorData.NoAdapterInstalledServerIslands); + if (serverIslandNameMap.size > 0 && serverIslandMap.size > 0 && ssrEnvironment) { + // In dev, we need to clear the module graph so that Vite knows to re-transform + // the module with the new island information. + const mod = ssrEnvironment.moduleGraph.getModuleById(RESOLVED_SERVER_ISLAND_MANIFEST); + if (mod) { + ssrEnvironment.moduleGraph.invalidateModule(mod); } } - if (serverIslandNameMap.size > 0 && serverIslandMap.size > 0) { - let mapSource = 'new Map([\n\t'; - for (let [name, path] of serverIslandMap) { - mapSource += `\n\t['${name}', () => import('${path}')],`; + if (id === RESOLVED_SERVER_ISLAND_MANIFEST) { + if (command === 'build' && settings.buildOutput) { + const hasServerIslands = serverIslandNameMap.size > 0; + // Error if there are server islands but no adapter provided. + if (hasServerIslands && settings.buildOutput !== 'server') { + throw new AstroError(AstroErrorData.NoAdapterInstalledServerIslands); + } } - mapSource += ']);'; - return { - code: ` + if (serverIslandNameMap.size > 0 && serverIslandMap.size > 0) { + let mapSource = 'new Map([\n\t'; + for (let [name, path] of serverIslandMap) { + mapSource += `\n\t['${name}', () => import('${path}')],`; + } + mapSource += ']);'; + + return { + code: ` export const serverIslandMap = ${mapSource}; \n\nexport const serverIslandNameMap = new Map(${JSON.stringify(Array.from(serverIslandNameMap.entries()), null, 2)}); `, - }; + }; + } } - } + }, }, renderChunk(code, chunk) { diff --git a/packages/astro/src/core/session/vite-plugin.ts b/packages/astro/src/core/session/vite-plugin.ts index bb76357c708e..e8f26c96680c 100644 --- a/packages/astro/src/core/session/vite-plugin.ts +++ b/packages/astro/src/core/session/vite-plugin.ts @@ -14,14 +14,20 @@ export function vitePluginSessionDriver({ settings }: { settings: AstroSettings name: VIRTUAL_SESSION_DRIVER_ID, enforce: 'pre', - async resolveId(id) { - if (id === VIRTUAL_SESSION_DRIVER_ID) { + resolveId: { + filter: { + id: new RegExp(`^${VIRTUAL_SESSION_DRIVER_ID}$`), + }, + handler() { return RESOLVED_VIRTUAL_SESSION_DRIVER_ID; - } + }, }, - async load(id) { - if (id === RESOLVED_VIRTUAL_SESSION_DRIVER_ID) { + load: { + filter: { + id: new RegExp(`^${RESOLVED_VIRTUAL_SESSION_DRIVER_ID}$`), + }, + async handler() { if (settings.config.session) { let sessionDriver: string; if (settings.config.session.driver === 'fs') { @@ -51,7 +57,7 @@ export function vitePluginSessionDriver({ settings }: { settings: AstroSettings } else { return { code: 'export default null;' }; } - } + }, }, }; } diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts index 54f8d57abc52..8b96af135e3a 100644 --- a/packages/astro/src/core/util.ts +++ b/packages/astro/src/core/util.ts @@ -17,6 +17,7 @@ export function isObject(value: unknown): value is Record { export function isURL(value: unknown): value is URL { return Object.prototype.toString.call(value) === '[object URL]'; } + /** Check if a file is a markdown file based on its extension */ export function isMarkdownFile(fileId: string, option?: { suffix?: string }): boolean { if (hasSpecialQueries(fileId)) { diff --git a/packages/astro/src/core/viteUtils.ts b/packages/astro/src/core/viteUtils.ts index 0fd7c4cb7faa..301edacb15fd 100644 --- a/packages/astro/src/core/viteUtils.ts +++ b/packages/astro/src/core/viteUtils.ts @@ -68,3 +68,7 @@ export async function resolveIdToUrl(loader: ModuleLoader, id: string, root?: UR } return VALID_ID_PREFIX + resultId; } + +// https://github.com/vitejs/vite/blob/2f9428d1ffd988e30cb253d5bb84844fb1654e86/packages/vite/src/node/constants.ts#L108 +// Used by isCSSRequest() under the hood +export const CSS_LANGS_RE = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/; diff --git a/packages/astro/src/env/vite-plugin-env.ts b/packages/astro/src/env/vite-plugin-env.ts index ad82618b9a1d..64d693e970c8 100644 --- a/packages/astro/src/env/vite-plugin-env.ts +++ b/packages/astro/src/env/vite-plugin-env.ts @@ -1,6 +1,7 @@ import { readFileSync } from 'node:fs'; import type { Plugin } from 'vite'; import { AstroError, AstroErrorData } from '../core/errors/index.js'; +import { isAstroClientEnvironment } from '../environments.js'; import type { AstroSettings } from '../types/astro.js'; import { CLIENT_VIRTUAL_MODULE_ID, @@ -15,7 +16,6 @@ import type { EnvLoader } from './env-loader.js'; import { type InvalidVariable, invalidVariablesToError } from './errors.js'; import type { EnvSchema } from './schema.js'; import { getEnvFieldType, validateEnvVariable } from './validators.js'; -import { isAstroClientEnvironment } from '../environments.js'; interface AstroEnvPluginParams { settings: AstroSettings; @@ -50,53 +50,70 @@ export function astroEnv({ settings, sync, envLoader }: AstroEnvPluginParams): P populated = true; }, - resolveId(id) { - if (id === CLIENT_VIRTUAL_MODULE_ID) { - return RESOLVED_CLIENT_VIRTUAL_MODULE_ID; - } - if (id === SERVER_VIRTUAL_MODULE_ID) { - return RESOLVED_SERVER_VIRTUAL_MODULE_ID; - } - if (id === INTERNAL_VIRTUAL_MODULE_ID) { - return RESOLVED_INTERNAL_VIRTUAL_MODULE_ID; - } + resolveId: { + filter: { + id: new RegExp( + `^(${CLIENT_VIRTUAL_MODULE_ID}|${SERVER_VIRTUAL_MODULE_ID}|${INTERNAL_VIRTUAL_MODULE_ID})$`, + ), + }, + handler(id) { + if (id === CLIENT_VIRTUAL_MODULE_ID) { + return RESOLVED_CLIENT_VIRTUAL_MODULE_ID; + } + if (id === SERVER_VIRTUAL_MODULE_ID) { + return RESOLVED_SERVER_VIRTUAL_MODULE_ID; + } + if (id === INTERNAL_VIRTUAL_MODULE_ID) { + return RESOLVED_INTERNAL_VIRTUAL_MODULE_ID; + } + }, }, - load(id) { - if (id === RESOLVED_INTERNAL_VIRTUAL_MODULE_ID) { - return { code: `export const schema = ${JSON.stringify(schema)};` }; - } - - if (id === RESOLVED_SERVER_VIRTUAL_MODULE_ID && isAstroClientEnvironment(this.environment)) { - throw new AstroError({ - ...AstroErrorData.ServerOnlyModule, - message: AstroErrorData.ServerOnlyModule.message(SERVER_VIRTUAL_MODULE_ID), - }); - } + load: { + filter: { + id: new RegExp( + `^(${RESOLVED_CLIENT_VIRTUAL_MODULE_ID}|${RESOLVED_SERVER_VIRTUAL_MODULE_ID}|${RESOLVED_INTERNAL_VIRTUAL_MODULE_ID})$`, + ), + }, + handler(id) { + if (id === RESOLVED_INTERNAL_VIRTUAL_MODULE_ID) { + return { code: `export const schema = ${JSON.stringify(schema)};` }; + } - if (id === RESOLVED_CLIENT_VIRTUAL_MODULE_ID || id === RESOLVED_SERVER_VIRTUAL_MODULE_ID) { - const loadedEnv = envLoader.get(); - - const validatedVariables = validatePublicVariables({ - schema, - loadedEnv, - validateSecrets, - sync, - }); - const { client, server } = getTemplates({ - schema, - validatedVariables, - // In dev, we inline process.env to avoid freezing it - loadedEnv: isBuild ? null : loadedEnv, - }); - - if (id === RESOLVED_CLIENT_VIRTUAL_MODULE_ID) { - return { code: client }; + if ( + id === RESOLVED_SERVER_VIRTUAL_MODULE_ID && + isAstroClientEnvironment(this.environment) + ) { + throw new AstroError({ + ...AstroErrorData.ServerOnlyModule, + message: AstroErrorData.ServerOnlyModule.message(SERVER_VIRTUAL_MODULE_ID), + }); } - if (id === RESOLVED_SERVER_VIRTUAL_MODULE_ID) { - return { code: server }; + if (id === RESOLVED_CLIENT_VIRTUAL_MODULE_ID || id === RESOLVED_SERVER_VIRTUAL_MODULE_ID) { + const loadedEnv = envLoader.get(); + + const validatedVariables = validatePublicVariables({ + schema, + loadedEnv, + validateSecrets, + sync, + }); + const { client, server } = getTemplates({ + schema, + validatedVariables, + // In dev, we inline process.env to avoid freezing it + loadedEnv: isBuild ? null : loadedEnv, + }); + + if (id === RESOLVED_CLIENT_VIRTUAL_MODULE_ID) { + return { code: client }; + } + + if (id === RESOLVED_SERVER_VIRTUAL_MODULE_ID) { + return { code: server }; + } } - } + }, }, }; } diff --git a/packages/astro/src/env/vite-plugin-import-meta-env.ts b/packages/astro/src/env/vite-plugin-import-meta-env.ts index 4bf6fd5b1f18..d92d3f20bfda 100644 --- a/packages/astro/src/env/vite-plugin-import-meta-env.ts +++ b/packages/astro/src/env/vite-plugin-import-meta-env.ts @@ -1,8 +1,8 @@ import { transform } from 'esbuild'; import MagicString from 'magic-string'; import type * as vite from 'vite'; -import { createFilter, isCSSRequest } from 'vite'; import type { EnvLoader } from './env-loader.js'; +import { CSS_LANGS_RE } from '../core/viteUtils.js'; import { isAstroClientEnvironment } from '../environments.js'; interface EnvPluginOptions { @@ -73,7 +73,6 @@ export function importMetaEnv({ envLoader }: EnvPluginOptions): vite.Plugin { let isDev: boolean; let devImportMetaEnvPrepend: string; let viteConfig: vite.ResolvedConfig; - const filter = createFilter(null, ['**/*.html', '**/*.htm', '**/*.json']); return { name: 'astro:vite-plugin-env', config(_, { command }) { @@ -100,68 +99,70 @@ export function importMetaEnv({ envLoader }: EnvPluginOptions): vite.Plugin { } }, - transform(source, id) { - if ( - isAstroClientEnvironment(this.environment) || - !source.includes('import.meta.env') || - !filter(id) || - isCSSRequest(id) || - viteConfig.assetsInclude(id) - ) { - return; - } - // Find matches for *private* env and do our own replacement. - // Env is retrieved before process.env is populated by astro:env - // so that import.meta.env is first replaced by values, not process.env - privateEnv ??= envLoader.getPrivateEnv(); + transform: { + filter: { + id: { + exclude: [/.*\.(html|htm|json)$/, CSS_LANGS_RE], + }, + code: /import\.meta\.env/, + }, + handler(source, id) { + if (isAstroClientEnvironment(this.environment) || viteConfig.assetsInclude(id)) { + return; + } + // Find matches for *private* env and do our own replacement. + // Env is retrieved before process.env is populated by astro:env + // so that import.meta.env is first replaced by values, not process.env + privateEnv ??= envLoader.getPrivateEnv(); + + // In dev, we can assign the private env vars to `import.meta.env` directly for performance + if (isDev) { + const s = new MagicString(source); + + if (!devImportMetaEnvPrepend) { + devImportMetaEnvPrepend = `Object.assign(import.meta.env,{`; + for (const key in privateEnv) { + devImportMetaEnvPrepend += `${key}:${privateEnv[key]},`; + } + devImportMetaEnvPrepend += '});'; + } + s.prepend(devImportMetaEnvPrepend); - // In dev, we can assign the private env vars to `import.meta.env` directly for performance - if (isDev) { - const s = new MagicString(source); + return { + code: s.toString(), + map: s.generateMap({ hires: 'boundary' }), + }; + } - if (!devImportMetaEnvPrepend) { - devImportMetaEnvPrepend = `Object.assign(import.meta.env,{`; + // In build, use esbuild to perform replacements. Compute the default defines for esbuild here as a + // separate object as it could be extended by `import.meta.env` later. + if (!defaultDefines) { + defaultDefines = {}; for (const key in privateEnv) { - devImportMetaEnvPrepend += `${key}:${privateEnv[key]},`; + defaultDefines[`import.meta.env.${key}`] = privateEnv[key]; } - devImportMetaEnvPrepend += '});'; } - s.prepend(devImportMetaEnvPrepend); - return { - code: s.toString(), - map: s.generateMap({ hires: 'boundary' }), - }; - } + let defines = defaultDefines; - // In build, use esbuild to perform replacements. Compute the default defines for esbuild here as a - // separate object as it could be extended by `import.meta.env` later. - if (!defaultDefines) { - defaultDefines = {}; - for (const key in privateEnv) { - defaultDefines[`import.meta.env.${key}`] = privateEnv[key]; - } - } - - let defines = defaultDefines; - - // If reference the `import.meta.env` object directly, we want to inject private env vars - // into Vite's injected `import.meta.env` object. To do this, we use `Object.assign` and keeping - // the `import.meta.env` identifier so Vite sees it. - if (importMetaEnvOnlyRe.test(source)) { - const references = getReferencedPrivateKeys(source, privateEnv); - let replacement = `(Object.assign(import.meta.env,{`; - for (const key of references.values()) { - replacement += `${key}:${privateEnv[key]},`; + // If reference the `import.meta.env` object directly, we want to inject private env vars + // into Vite's injected `import.meta.env` object. To do this, we use `Object.assign` and keeping + // the `import.meta.env` identifier so Vite sees it. + if (importMetaEnvOnlyRe.test(source)) { + const references = getReferencedPrivateKeys(source, privateEnv); + let replacement = `(Object.assign(import.meta.env,{`; + for (const key of references.values()) { + replacement += `${key}:${privateEnv[key]},`; + } + replacement += '}))'; + defines = { + ...defaultDefines, + 'import.meta.env': replacement, + }; } - replacement += '}))'; - defines = { - ...defaultDefines, - 'import.meta.env': replacement, - }; - } - return replaceDefine(source, id, defines, viteConfig); + return replaceDefine(source, id, defines, viteConfig); + }, }, }; } diff --git a/packages/astro/src/i18n/vite-plugin-i18n.ts b/packages/astro/src/i18n/vite-plugin-i18n.ts index 791ed9d75187..5df1a740269d 100644 --- a/packages/astro/src/i18n/vite-plugin-i18n.ts +++ b/packages/astro/src/i18n/vite-plugin-i18n.ts @@ -3,7 +3,7 @@ import { AstroError } from '../core/errors/errors.js'; import { AstroErrorData } from '../core/errors/index.js'; import type { AstroSettings } from '../types/astro.js'; -const virtualModuleId = 'astro:i18n'; +const VIRTUAL_MODULE_ID = 'astro:i18n'; type AstroInternationalization = { settings: AstroSettings; @@ -14,13 +14,16 @@ export default function astroInternationalization({ }: AstroInternationalization): vite.Plugin { const { i18n } = settings.config; return { - name: 'astro:i18n', + name: VIRTUAL_MODULE_ID, enforce: 'pre', - resolveId(id) { - if (id === virtualModuleId) { + resolveId: { + filter: { + id: new RegExp(`^${VIRTUAL_MODULE_ID}$`), + }, + handler() { if (i18n === undefined) throw new AstroError(AstroErrorData.i18nNotEnabled); return this.resolve('astro/virtual-modules/i18n.js'); - } + }, }, }; } diff --git a/packages/astro/src/manifest/serialized.ts b/packages/astro/src/manifest/serialized.ts index 197bb3dc6df9..2ee6e05d768b 100644 --- a/packages/astro/src/manifest/serialized.ts +++ b/packages/astro/src/manifest/serialized.ts @@ -39,14 +39,20 @@ export function serializedManifestPlugin({ name: SERIALIZED_MANIFEST_ID, enforce: 'pre', - resolveId(id) { - if (id === SERIALIZED_MANIFEST_ID) { + resolveId: { + filter: { + id: new RegExp(`^${SERIALIZED_MANIFEST_ID}$`), + }, + handler() { return SERIALIZED_MANIFEST_RESOLVED_ID; - } + }, }, - async load(id) { - if (id === SERIALIZED_MANIFEST_RESOLVED_ID) { + load: { + filter: { + id: new RegExp(`^${SERIALIZED_MANIFEST_RESOLVED_ID}$`), + }, + async handler() { let manifestData: string; if (command === 'build' && !sync) { // Emit placeholder token that will be replaced by plugin-manifest.ts in build:post @@ -82,7 +88,7 @@ export function serializedManifestPlugin({ export { manifest }; `; return { code }; - } + }, }, }; } diff --git a/packages/astro/src/manifest/virtual-module.ts b/packages/astro/src/manifest/virtual-module.ts index cb6145ca7c20..f0c488425d13 100644 --- a/packages/astro/src/manifest/virtual-module.ts +++ b/packages/astro/src/manifest/virtual-module.ts @@ -11,18 +11,27 @@ const RESOLVED_VIRTUAL_CLIENT_ID = '\0' + VIRTUAL_CLIENT_ID; export default function virtualModulePlugin(): Plugin { return { name: 'astro-manifest-plugin', - resolveId(id) { - // Resolve the virtual module - if (VIRTUAL_SERVER_ID === id) { - return RESOLVED_VIRTUAL_SERVER_ID; - } else if (VIRTUAL_CLIENT_ID === id) { - return RESOLVED_VIRTUAL_CLIENT_ID; - } + resolveId: { + filter: { + id: new RegExp(`^(${VIRTUAL_SERVER_ID}|${VIRTUAL_CLIENT_ID})$`), + }, + handler(id) { + if (id === VIRTUAL_SERVER_ID) { + return RESOLVED_VIRTUAL_SERVER_ID; + } + if (id === VIRTUAL_CLIENT_ID) { + return RESOLVED_VIRTUAL_CLIENT_ID; + } + }, }, - async load(id) { - if (id === RESOLVED_VIRTUAL_CLIENT_ID) { - // There's nothing wrong about using `/client` on the server - const code = ` + load: { + filter: { + id: new RegExp(`^(${RESOLVED_VIRTUAL_SERVER_ID}|${RESOLVED_VIRTUAL_CLIENT_ID})$`), + }, + handler(id) { + if (id === RESOLVED_VIRTUAL_CLIENT_ID) { + // There's nothing wrong about using `/client` on the server + const code = ` import { manifest } from '${SERIALIZED_MANIFEST_ID}' import { fromRoutingStrategy } from 'astro/app'; @@ -46,17 +55,16 @@ const build = { export { base, i18n, trailingSlash, site, compressHTML, build }; `; - return { code }; - } - // server - else if (id == RESOLVED_VIRTUAL_SERVER_ID) { - if (this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client) { - throw new AstroError({ - ...AstroErrorData.ServerOnlyModule, - message: AstroErrorData.ServerOnlyModule.message(VIRTUAL_SERVER_ID), - }); + return { code }; } - const code = ` + if (id == RESOLVED_VIRTUAL_SERVER_ID) { + if (this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client) { + throw new AstroError({ + ...AstroErrorData.ServerOnlyModule, + message: AstroErrorData.ServerOnlyModule.message(VIRTUAL_SERVER_ID), + }); + } + const code = ` import { manifest } from '${SERIALIZED_MANIFEST_ID}' import { fromRoutingStrategy } from "astro/app"; @@ -102,8 +110,9 @@ export { }; `; - return { code }; - } + return { code }; + } + }, }, }; } diff --git a/packages/astro/src/prefetch/vite-plugin-prefetch.ts b/packages/astro/src/prefetch/vite-plugin-prefetch.ts index 8ab23503cc61..7e43f9330c45 100644 --- a/packages/astro/src/prefetch/vite-plugin-prefetch.ts +++ b/packages/astro/src/prefetch/vite-plugin-prefetch.ts @@ -1,8 +1,8 @@ import type * as vite from 'vite'; import type { AstroSettings } from '../types/astro.js'; -const virtualModuleId = 'astro:prefetch'; -const resolvedVirtualModuleId = '\0' + virtualModuleId; +const VIRTUAL_MODULE_ID = 'astro:prefetch'; +const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID; const prefetchInternalModuleFsSubpath = 'astro/dist/prefetch/index.js'; const prefetchCode = `import { init } from 'astro/virtual-modules/prefetch.js';init()`; @@ -31,22 +31,31 @@ export default function astroPrefetch({ settings }: { settings: AstroSettings }) return { name: 'astro:prefetch', - async resolveId(id) { - if (id === virtualModuleId) { + resolveId: { + filter: { + id: new RegExp(`^${VIRTUAL_MODULE_ID}$`), + }, + handler() { if (!prefetch) throwPrefetchNotEnabledError(); - return resolvedVirtualModuleId; - } + return RESOLVED_VIRTUAL_MODULE_ID; + }, }, - load(id) { - if (id === resolvedVirtualModuleId) { + load: { + filter: { + id: new RegExp(`^${RESOLVED_VIRTUAL_MODULE_ID}$`), + }, + handler() { if (!prefetch) throwPrefetchNotEnabledError(); return { code: `export { prefetch } from "astro/virtual-modules/prefetch.js";` }; - } + }, }, - transform(code, id) { - // NOTE: Handle replacing the specifiers even if prefetch is disabled so View Transitions - // can import the internal module and not hit runtime issues. - if (id.includes(prefetchInternalModuleFsSubpath)) { + transform: { + filter: { + // NOTE: Handle replacing the specifiers even if prefetch is disabled so View Transitions + // can import the internal module and not hit runtime issues. + id: new RegExp(`${prefetchInternalModuleFsSubpath}`), + }, + handler(code) { // We perform a simple replacement with padding so that the code offset is not changed and // we don't have to generate a sourcemap. This has the assumption that the replaced string // will always be shorter than the search string to work. @@ -64,7 +73,7 @@ export default function astroPrefetch({ settings }: { settings: AstroSettings }) `${JSON.stringify(settings.config.experimental.clientPrerender)}`.padEnd(33), ); return { code, map: null }; - } + }, }, }; } diff --git a/packages/astro/src/toolbar/vite-plugin-dev-toolbar.ts b/packages/astro/src/toolbar/vite-plugin-dev-toolbar.ts index 66989a4e3a5e..e0c205d9be5c 100644 --- a/packages/astro/src/toolbar/vite-plugin-dev-toolbar.ts +++ b/packages/astro/src/toolbar/vite-plugin-dev-toolbar.ts @@ -3,8 +3,8 @@ import { telemetry } from '../events/index.js'; import { eventAppToggled } from '../events/toolbar.js'; import type { AstroPluginOptions } from '../types/astro.js'; -const PRIVATE_VIRTUAL_MODULE_ID = 'astro:toolbar:internal'; -const resolvedPrivateVirtualModuleId = '\0' + PRIVATE_VIRTUAL_MODULE_ID; +const VIRTUAL_MODULE_ID = 'astro:toolbar:internal'; +const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID; export default function astroDevToolbar({ settings, logger }: AstroPluginOptions): vite.Plugin { let telemetryTimeout: ReturnType; @@ -19,10 +19,13 @@ export default function astroDevToolbar({ settings, logger }: AstroPluginOptions }, }; }, - resolveId(id) { - if (id === PRIVATE_VIRTUAL_MODULE_ID) { - return resolvedPrivateVirtualModuleId; - } + resolveId: { + filter: { + id: new RegExp(`^${VIRTUAL_MODULE_ID}$`), + }, + handler() { + return RESOLVED_VIRTUAL_MODULE_ID; + }, }, configureServer(server) { server.hot.on('astro:devtoolbar:error:load', (args) => { @@ -56,8 +59,11 @@ export default function astroDevToolbar({ settings, logger }: AstroPluginOptions }, 200); }); }, - async load(id) { - if (id === resolvedPrivateVirtualModuleId) { + load: { + filter: { + id: new RegExp(`^${RESOLVED_VIRTUAL_MODULE_ID}$`), + }, + handler() { return { code: ` export const loadDevToolbarApps = async () => { @@ -115,7 +121,7 @@ export default function astroDevToolbar({ settings, logger }: AstroPluginOptions } `, }; - } + }, }, }; } diff --git a/packages/astro/src/transitions/vite-plugin-transitions.ts b/packages/astro/src/transitions/vite-plugin-transitions.ts index c2ec27e77ba4..d8a270cbb04e 100644 --- a/packages/astro/src/transitions/vite-plugin-transitions.ts +++ b/packages/astro/src/transitions/vite-plugin-transitions.ts @@ -1,10 +1,10 @@ import type * as vite from 'vite'; import type { AstroSettings } from '../types/astro.js'; -const virtualModuleId = 'astro:transitions'; -const resolvedVirtualModuleId = '\0' + virtualModuleId; -const virtualClientModuleId = 'astro:transitions/client'; -const resolvedVirtualClientModuleId = '\0' + virtualClientModuleId; +const VIRTUAL_MODULE_ID = 'astro:transitions'; +const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID; +const VIRTUAL_CLIENT_MODULE_ID = 'astro:transitions/client'; +const RESOLVED_VIRTUAL_CLIENT_MODULE_ID = '\0' + VIRTUAL_CLIENT_MODULE_ID; // The virtual module for the astro:transitions namespace export default function astroTransitions({ settings }: { settings: AstroSettings }): vite.Plugin { @@ -17,26 +17,35 @@ export default function astroTransitions({ settings }: { settings: AstroSettings }, }; }, - async resolveId(id) { - if (id === virtualModuleId) { - return resolvedVirtualModuleId; - } - if (id === virtualClientModuleId) { - return resolvedVirtualClientModuleId; - } + resolveId: { + filter: { + id: new RegExp(`^(${VIRTUAL_MODULE_ID}|${VIRTUAL_CLIENT_MODULE_ID})$`), + }, + handler(id) { + if (id === VIRTUAL_MODULE_ID) { + return RESOLVED_VIRTUAL_MODULE_ID; + } + if (id === VIRTUAL_CLIENT_MODULE_ID) { + return RESOLVED_VIRTUAL_CLIENT_MODULE_ID; + } + }, }, - load(id) { - if (id === resolvedVirtualModuleId) { - return { - code: ` + load: { + filter: { + id: new RegExp(`^(${RESOLVED_VIRTUAL_MODULE_ID}|${RESOLVED_VIRTUAL_CLIENT_MODULE_ID})$`), + }, + handler(id) { + if (id === RESOLVED_VIRTUAL_MODULE_ID) { + return { + code: ` export * from "astro/virtual-modules/transitions.js"; export { default as ClientRouter } from "astro/components/ClientRouter.astro"; `, - }; - } - if (id === resolvedVirtualClientModuleId) { - return { - code: ` + }; + } + if (id === RESOLVED_VIRTUAL_CLIENT_MODULE_ID) { + return { + code: ` export { navigate, supportsViewTransitions, transitionEnabledOnThisPage } from "astro/virtual-modules/transitions-router.js"; export * from "astro/virtual-modules/transitions-types.js"; export { @@ -47,14 +56,18 @@ export default function astroTransitions({ settings }: { settings: AstroSettings } from "astro/virtual-modules/transitions-events.js"; export { swapFunctions } from "astro/virtual-modules/transitions-swap-functions.js"; `, - }; - } + }; + } + }, }, - transform(code, id) { - if (id.includes('ClientRouter.astro') && id.endsWith('.ts')) { + transform: { + filter: { + id: /ClientRouter\.astro.*\.ts$/, + }, + handler(code) { const prefetchDisabled = settings.config.prefetch === false; return code.replace('__PREFETCH_DISABLED__', JSON.stringify(prefetchDisabled)); - } + }, }, }; } diff --git a/packages/astro/src/vite-plugin-adapter-config/index.ts b/packages/astro/src/vite-plugin-adapter-config/index.ts index 208d44ec75bc..1335651124e5 100644 --- a/packages/astro/src/vite-plugin-adapter-config/index.ts +++ b/packages/astro/src/vite-plugin-adapter-config/index.ts @@ -1,6 +1,6 @@ import type { Plugin as VitePlugin } from 'vite'; -import type { AstroSettings } from '../types/astro.js'; import { isAstroServerEnvironment } from '../environments.js'; +import type { AstroSettings } from '../types/astro.js'; const VIRTUAL_CLIENT_ID = 'virtual:astro:adapter-config/client'; const RESOLVED_VIRTUAL_CLIENT_ID = '\0' + VIRTUAL_CLIENT_ID; @@ -8,13 +8,19 @@ const RESOLVED_VIRTUAL_CLIENT_ID = '\0' + VIRTUAL_CLIENT_ID; export function vitePluginAdapterConfig(settings: AstroSettings): VitePlugin { return { name: 'astro:adapter-config', - resolveId(id) { - if (id === VIRTUAL_CLIENT_ID) { + resolveId: { + filter: { + id: new RegExp(`^${VIRTUAL_CLIENT_ID}$`), + }, + handler() { return RESOLVED_VIRTUAL_CLIENT_ID; - } + }, }, - load(id) { - if (id === RESOLVED_VIRTUAL_CLIENT_ID) { + load: { + filter: { + id: new RegExp(`^${RESOLVED_VIRTUAL_CLIENT_ID}$`), + }, + handler() { // During SSR, return empty headers to avoid any runtime issues if (isAstroServerEnvironment(this.environment)) { return { @@ -36,7 +42,7 @@ export function vitePluginAdapterConfig(settings: AstroSettings): VitePlugin { return { code: `export const internalFetchHeaders = ${JSON.stringify(internalFetchHeaders)};`, }; - } + }, }, }; } diff --git a/packages/astro/src/vite-plugin-app/index.ts b/packages/astro/src/vite-plugin-app/index.ts index 5c1a4bb2e169..a635da81dfb1 100644 --- a/packages/astro/src/vite-plugin-app/index.ts +++ b/packages/astro/src/vite-plugin-app/index.ts @@ -6,11 +6,14 @@ export function vitePluginApp(): vite.Plugin { return { name: 'astro:app', - async resolveId(id) { - if (id === ASTRO_DEV_APP_ID) { + resolveId: { + filter: { + id: new RegExp(`^${ASTRO_DEV_APP_ID}$`), + }, + handler() { const url = new URL('./createAstroServerApp.js', import.meta.url); - return await this.resolve(url.toString()); - } + return this.resolve(url.toString()); + }, }, }; } diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts index 9160cf99401b..5c8cea9a9686 100644 --- a/packages/astro/src/vite-plugin-astro-server/plugin.ts +++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts @@ -160,13 +160,6 @@ export default function createVitePluginAstroServer({ }); }; }, - transform(code, id, opts = {}) { - if (opts.ssr) return; - if (!id.includes('vite/dist/client/client.mjs')) return; - - // Replace the Vite overlay with ours - return patchOverlay(code); - }, }; } @@ -176,12 +169,14 @@ export function createVitePluginAstroServerClient(): vite.Plugin { applyToEnvironment(environment) { return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client; }, - transform(code, id, opts = {}) { - if (opts.ssr) return; - if (!id.includes('vite/dist/client/client.mjs')) return; - - // Replace the Vite overlay with ours - return patchOverlay(code); + transform: { + filter: { + id: /vite\/dist\/client\/client\.mjs/, + }, + handler(code) { + // Replace the Vite overlay with ours + return patchOverlay(code); + }, }, }; } diff --git a/packages/astro/src/vite-plugin-astro-server/util.ts b/packages/astro/src/vite-plugin-astro-server/util.ts index 4331c4ee449f..d2d0c1df98fc 100644 --- a/packages/astro/src/vite-plugin-astro-server/util.ts +++ b/packages/astro/src/vite-plugin-astro-server/util.ts @@ -1,7 +1,7 @@ import { isCSSRequest } from 'vite'; -const rawRE = /(?:\?|&)raw(?:&|$)/; -const inlineRE = /(?:\?|&)inline\b/; +export const rawRE = /(?:\?|&)raw(?:&|$)/; +export const inlineRE = /(?:\?|&)inline\b/; export const isBuildableCSSRequest = (request: string): boolean => isCSSRequest(request) && !rawRE.test(request) && !inlineRE.test(request); diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 31e07a0a295f..0e302c10fdd1 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -4,15 +4,15 @@ import type * as vite from 'vite'; import { defaultClientConditions, defaultServerConditions, normalizePath } from 'vite'; import { ASTRO_VITE_ENVIRONMENT_NAMES } from '../core/constants.js'; import type { Logger } from '../core/logger/core.js'; +import { isAstroServerEnvironment } from '../environments.js'; import type { AstroSettings } from '../types/astro.js'; import type { AstroConfig } from '../types/public/config.js'; -import { hasSpecialQueries, normalizeFilename } from '../vite-plugin-utils/index.js'; +import { normalizeFilename, specialQueriesRE } from '../vite-plugin-utils/index.js'; import { type CompileAstroResult, compileAstro } from './compile.js'; import { handleHotUpdate } from './hmr.js'; import { parseAstroRequest } from './query.js'; import type { PluginMetadata as AstroPluginMetadata, CompileMetadata } from './types.js'; import { loadId } from './utils.js'; -import { isAstroServerEnvironment } from '../environments.js'; export { getAstroMetadata } from './metadata.js'; export type { AstroPluginMetadata }; @@ -41,256 +41,283 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl const notAstroComponent = (component: HydratedComponent) => !component.resolvedPath.endsWith('.astro'); - const prePlugin: vite.Plugin = { - name: 'astro:build', - enforce: 'pre', // run transforms before other plugins can - async configEnvironment(name, viteConfig, opts) { - viteConfig.resolve ??= {}; - // Emulate Vite default fallback for `resolve.conditions` if not set - if (viteConfig.resolve.conditions == null) { - if (viteConfig.consumer === 'client' || name === 'client' || opts.isSsrTargetWebworker) { - viteConfig.resolve.conditions = [...defaultClientConditions]; - } else { - viteConfig.resolve.conditions = [...defaultServerConditions]; - } - } - viteConfig.resolve.conditions.push('astro'); - }, - async configResolved(viteConfig) { - const toolbarEnabled = await settings.preferences.get('devToolbar.enabled'); - // Initialize `compile` function to simplify usage later - compile = (code, filename) => { - return compileAstro({ - compileProps: { - astroConfig: config, - viteConfig, - toolbarEnabled, - filename, - source: code, + return [ + { + name: 'astro:build:css-hmr', + enforce: 'pre', + applyToEnvironment(environment) { + return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client; + }, + transform: { + filter: { + id: { + include: [/(?:\?|&)astro(?:&|=|$)/], + // ignore astro file sub-requests, e.g. Foo.astro?astro&type=script&index=0&lang.ts + exclude: [specialQueriesRE], }, - astroFileToCompileMetadata, - logger, - }); - }; - }, - configureServer(_server) { - server = _server; - // Make sure deleted files are removed from the compile metadata to save memory - server.watcher.on('unlink', (filename) => { - astroFileToCompileMetadata.delete(filename); - }); - }, - buildStart() { - astroFileToCompileMetadata = new Map(); + }, + async handler(_source, id) { + const parsedId = parseAstroRequest(id); + // Special edge case handling for Vite 6 beta, the style dependencies need to be registered here take affect + // TODO: Remove this when Vite fixes it (https://github.com/vitejs/vite/pull/18103) + const astroFilename = normalizePath(normalizeFilename(parsedId.filename, config.root)); + const compileMetadata = astroFileToCompileMetadata.get(astroFilename); + if (compileMetadata && parsedId.query.type === 'style' && parsedId.query.index != null) { + const result = compileMetadata.css[parsedId.query.index]; - // Share the `astroFileToCompileMetadata` across the same Astro config as Astro performs - // multiple builds and its hoisted scripts analyzer requires the compile metadata from - // previous builds. Ideally this should not be needed when we refactor hoisted scripts analysis. - if (astroFileToCompileMetadataWeakMap.has(config)) { - astroFileToCompileMetadata = astroFileToCompileMetadataWeakMap.get(config)!; - } else { - astroFileToCompileMetadataWeakMap.set(config, astroFileToCompileMetadata); - } + // Register dependencies from preprocessing this style + result.dependencies?.forEach((dep) => this.addWatchFile(dep)); + } + }, + }, }, - async load(id) { - const parsedId = parseAstroRequest(id); - const query = parsedId.query; - if (!query.astro) { - return null; - } + { + name: 'astro:build', + enforce: 'pre', // run transforms before other plugins can + async configEnvironment(name, viteConfig, opts) { + viteConfig.resolve ??= {}; + // Emulate Vite default fallback for `resolve.conditions` if not set + if (viteConfig.resolve.conditions == null) { + if (viteConfig.consumer === 'client' || name === 'client' || opts.isSsrTargetWebworker) { + viteConfig.resolve.conditions = [...defaultClientConditions]; + } else { + viteConfig.resolve.conditions = [...defaultServerConditions]; + } + } + viteConfig.resolve.conditions.push('astro'); + }, + async configResolved(viteConfig) { + const toolbarEnabled = await settings.preferences.get('devToolbar.enabled'); + // Initialize `compile` function to simplify usage later + compile = (code, filename) => { + return compileAstro({ + compileProps: { + astroConfig: config, + viteConfig, + toolbarEnabled, + filename, + source: code, + }, + astroFileToCompileMetadata, + logger, + }); + }; + }, + configureServer(_server) { + server = _server; + // Make sure deleted files are removed from the compile metadata to save memory + server.watcher.on('unlink', (filename) => { + astroFileToCompileMetadata.delete(filename); + }); + }, + buildStart() { + astroFileToCompileMetadata = new Map(); - // Astro scripts and styles virtual module code comes from the main Astro compilation - // through the metadata from `astroFileToCompileMetadata`. It should always exist as Astro - // modules are compiled first, then its virtual modules. - const filename = normalizePath(normalizeFilename(parsedId.filename, config.root)); - let compileMetadata = astroFileToCompileMetadata.get(filename); - if (!compileMetadata) { - // If `compileMetadata` doesn't exist in dev, that means the virtual module may have been invalidated. - // We try to re-compile the main Astro module (`filename`) first before retrieving the metadata again. - if (server) { - const code = await loadId(server.pluginContainer, filename); - // `compile` should re-set `filename` in `astroFileToCompileMetadata` - if (code != null) await compile(code, filename); + // Share the `astroFileToCompileMetadata` across the same Astro config as Astro performs + // multiple builds and its hoisted scripts analyzer requires the compile metadata from + // previous builds. Ideally this should not be needed when we refactor hoisted scripts analysis. + if (astroFileToCompileMetadataWeakMap.has(config)) { + astroFileToCompileMetadata = astroFileToCompileMetadataWeakMap.get(config)!; + } else { + astroFileToCompileMetadataWeakMap.set(config, astroFileToCompileMetadata); } + }, + load: { + filter: { + id: /(?:\?|&)astro(?:&|=|$)/, + }, + async handler(id) { + const parsedId = parseAstroRequest(id); + const query = parsedId.query; - compileMetadata = astroFileToCompileMetadata.get(filename); - } - // If the metadata still doesn't exist, that means the virtual modules are somehow compiled first, - // throw an error and we should investigate it. - if (!compileMetadata) { - throw new Error( - `No cached compile metadata found for "${id}". The main Astro module "${filename}" should have ` + - `compiled and filled the metadata first, before its virtual modules can be requested.`, - ); - } + // Astro scripts and styles virtual module code comes from the main Astro compilation + // through the metadata from `astroFileToCompileMetadata`. It should always exist as Astro + // modules are compiled first, then its virtual modules. + const filename = normalizePath(normalizeFilename(parsedId.filename, config.root)); + let compileMetadata = astroFileToCompileMetadata.get(filename); + if (!compileMetadata) { + // If `compileMetadata` doesn't exist in dev, that means the virtual module may have been invalidated. + // We try to re-compile the main Astro module (`filename`) first before retrieving the metadata again. + if (server) { + const code = await loadId(server.pluginContainer, filename); + // `compile` should re-set `filename` in `astroFileToCompileMetadata` + if (code != null) await compile(code, filename); + } - switch (query.type) { - case 'style': { - if (typeof query.index === 'undefined') { - throw new Error(`Requests for Astro CSS must include an index.`); + compileMetadata = astroFileToCompileMetadata.get(filename); } - - const result = compileMetadata.css[query.index]; - if (!result) { - throw new Error(`No Astro CSS at index ${query.index}`); + // If the metadata still doesn't exist, that means the virtual modules are somehow compiled first, + // throw an error and we should investigate it. + if (!compileMetadata) { + throw new Error( + `No cached compile metadata found for "${id}". The main Astro module "${filename}" should have ` + + `compiled and filled the metadata first, before its virtual modules can be requested.`, + ); } - // Register dependencies from preprocessing this style - result.dependencies?.forEach((dep) => this.addWatchFile(dep)); + switch (query.type) { + case 'style': { + if (typeof query.index === 'undefined') { + throw new Error(`Requests for Astro CSS must include an index.`); + } - return { - code: result.code, - // `vite.cssScopeTo` is a Vite feature that allows this CSS to be treeshaken - // if the Astro component's default export is not used - meta: result.isGlobal - ? undefined - : { - vite: { - cssScopeTo: [filename, 'default'], - }, - }, - }; - } - case 'script': { - if (typeof query.index === 'undefined') { - throw new Error(`Requests for scripts must include an index`); - } - // SSR script only exists to make them appear in the module graph. - if (isAstroServerEnvironment(this.environment)) { - return { - code: `/* client script, empty in SSR: ${id} */`, - }; - } + const result = compileMetadata.css[query.index]; + if (!result) { + throw new Error(`No Astro CSS at index ${query.index}`); + } - const script = compileMetadata.scripts[query.index]; - if (!script) { - throw new Error(`No script at index ${query.index}`); - } + // Register dependencies from preprocessing this style + result.dependencies?.forEach((dep) => this.addWatchFile(dep)); - if (script.type === 'external') { - const src = script.src; - if (src.startsWith('/') && !isBrowserPath(src)) { - const publicDir = config.publicDir.pathname.replace(/\/$/, '').split('/').pop() + '/'; - throw new Error( - `\n\n