diff --git a/.changeset/clever-pillows-boil.md b/.changeset/clever-pillows-boil.md new file mode 100644 index 0000000000000..8a35327675530 --- /dev/null +++ b/.changeset/clever-pillows-boil.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Excludes hoisted scripts and styles from Astro components imported with `?url` or `?raw` diff --git a/.changeset/new-melons-cross.md b/.changeset/new-melons-cross.md new file mode 100644 index 0000000000000..b2e30b30586a2 --- /dev/null +++ b/.changeset/new-melons-cross.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes a case where the build was failing when `experimental.actions` was enabled, an adapter was in use, and there were not actions inside the user code base. diff --git a/packages/astro/src/actions/consts.ts b/packages/astro/src/actions/consts.ts index ef6b87ca83fc4..e1324f248d198 100644 --- a/packages/astro/src/actions/consts.ts +++ b/packages/astro/src/actions/consts.ts @@ -1,3 +1,6 @@ export const VIRTUAL_MODULE_ID = 'astro:actions'; export const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID; export const ACTIONS_TYPES_FILE = 'actions.d.ts'; +export const VIRTUAL_INTERNAL_MODULE_ID = 'astro:internal-actions'; +export const RESOLVED_VIRTUAL_INTERNAL_MODULE_ID = '\0astro:internal-actions'; +export const NOOP_ACTIONS = '\0noop-actions'; diff --git a/packages/astro/src/actions/index.ts b/packages/astro/src/actions/index.ts index f4ab24e2d428b..f176988ff0c16 100644 --- a/packages/astro/src/actions/index.ts +++ b/packages/astro/src/actions/index.ts @@ -1,12 +1,25 @@ import fsMod from 'node:fs'; import type { Plugin as VitePlugin } from 'vite'; -import type { AstroIntegration } from '../@types/astro.js'; +import type { AstroIntegration, AstroSettings } from '../@types/astro.js'; import { ActionsWithoutServerOutputError } from '../core/errors/errors-data.js'; import { AstroError } from '../core/errors/errors.js'; import { isServerLikeOutput, viteID } from '../core/util.js'; -import { ACTIONS_TYPES_FILE, RESOLVED_VIRTUAL_MODULE_ID, VIRTUAL_MODULE_ID } from './consts.js'; +import { + ACTIONS_TYPES_FILE, + NOOP_ACTIONS, + RESOLVED_VIRTUAL_INTERNAL_MODULE_ID, + RESOLVED_VIRTUAL_MODULE_ID, + VIRTUAL_INTERNAL_MODULE_ID, + VIRTUAL_MODULE_ID, +} from './consts.js'; -export default function astroActions({ fs = fsMod }: { fs?: typeof fsMod }): AstroIntegration { +export default function astroActions({ + fs = fsMod, + settings, +}: { + fs?: typeof fsMod; + settings: AstroSettings; +}): AstroIntegration { return { name: VIRTUAL_MODULE_ID, hooks: { @@ -22,10 +35,7 @@ export default function astroActions({ fs = fsMod }: { fs?: typeof fsMod }): Ast ); params.updateConfig({ vite: { - define: { - 'import.meta.env.ACTIONS_PATH': stringifiedActionsImport, - }, - plugins: [vitePluginActions(fs)], + plugins: [vitePluginUserActions({ settings }), vitePluginActions(fs)], }, }); @@ -50,6 +60,42 @@ export default function astroActions({ fs = fsMod }: { fs?: typeof fsMod }): Ast }; } +/** + * This plugin is responsible to load the known file `actions/index.js` / `actions.js` + * If the file doesn't exist, it returns an empty object. + * @param settings + */ +export function vitePluginUserActions({ settings }: { settings: AstroSettings }): VitePlugin { + let resolvedActionsId: string; + return { + name: '@astro/plugin-actions', + async resolveId(id) { + if (id === NOOP_ACTIONS) { + return NOOP_ACTIONS; + } + if (id === VIRTUAL_INTERNAL_MODULE_ID) { + const resolvedModule = await this.resolve( + `${decodeURI(new URL('actions', settings.config.srcDir).pathname)}` + ); + + if (!resolvedModule) { + return NOOP_ACTIONS; + } + resolvedActionsId = resolvedModule.id; + return RESOLVED_VIRTUAL_INTERNAL_MODULE_ID; + } + }, + + load(id) { + if (id === NOOP_ACTIONS) { + return 'export const server = {}'; + } else if (id === RESOLVED_VIRTUAL_INTERNAL_MODULE_ID) { + return `export { server } from '${resolvedActionsId}';`; + } + }, + }; +} + const vitePluginActions = (fs: typeof fsMod): VitePlugin => ({ name: VIRTUAL_MODULE_ID, enforce: 'pre', diff --git a/packages/astro/src/actions/runtime/utils.ts b/packages/astro/src/actions/runtime/utils.ts index 02961144b4e27..91f2859d45e9a 100644 --- a/packages/astro/src/actions/runtime/utils.ts +++ b/packages/astro/src/actions/runtime/utils.ts @@ -12,14 +12,16 @@ export type MaybePromise = T | Promise; /** * Get server-side action based on the route path. - * Imports from `import.meta.env.ACTIONS_PATH`, which maps to + * Imports from the virtual module `astro:internal-actions`, which maps to * the user's `src/actions/index.ts` file at build-time. */ export async function getAction( path: string ): Promise<((param: unknown) => MaybePromise) | undefined> { const pathKeys = path.replace('/_actions/', '').split('.'); - let { server: actionLookup } = await import(import.meta.env.ACTIONS_PATH); + // @ts-expect-error virtual module + let { server: actionLookup } = await import('astro:internal-actions'); + for (const key of pathKeys) { if (!(key in actionLookup)) { return undefined; diff --git a/packages/astro/src/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts index 48a9777e8c89f..33a7c7c0c5ac1 100644 --- a/packages/astro/src/integrations/hooks.ts +++ b/packages/astro/src/integrations/hooks.ts @@ -119,7 +119,7 @@ export async function runHookConfigSetup({ } if (settings.config.experimental?.actions) { const { default: actionsIntegration } = await import('../actions/index.js'); - settings.config.integrations.push(actionsIntegration({ fs })); + settings.config.integrations.push(actionsIntegration({ fs, settings })); } let updatedConfig: AstroConfig = { ...settings.config }; @@ -230,9 +230,11 @@ export async function runHookConfigSetup({ const exts = (input.flat(Infinity) as string[]).map((ext) => `.${ext.replace(/^\./, '')}`); updatedSettings.pageExtensions.push(...exts); } + function addContentEntryType(contentEntryType: ContentEntryType) { updatedSettings.contentEntryTypes.push(contentEntryType); } + function addDataEntryType(dataEntryType: DataEntryType) { updatedSettings.dataEntryTypes.push(dataEntryType); } diff --git a/packages/astro/src/vite-plugin-astro-server/vite.ts b/packages/astro/src/vite-plugin-astro-server/vite.ts index 62bfa95d76734..eba9a81d1169e 100644 --- a/packages/astro/src/vite-plugin-astro-server/vite.ts +++ b/packages/astro/src/vite-plugin-astro-server/vite.ts @@ -2,6 +2,7 @@ import npath from 'node:path'; import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from '../core/constants.js'; import type { ModuleLoader, ModuleNode } from '../core/module-loader/index.js'; import { unwrapId } from '../core/util.js'; +import { hasSpecialQueries } from '../vite-plugin-utils/index.js'; import { isCSSRequest } from './util.js'; /** @@ -42,6 +43,10 @@ export async function* crawlGraph( if (id === entry.id) { scanned.add(id); + // NOTE: It may be worth revisiting if we can crawl direct imports of the module since + // `.importedModules` would also include modules that are dynamically watched, not imported. + // That way we no longer need the below `continue` skips. + // CSS requests `importedModules` are usually from `@import`, but we don't really need // to crawl into those as the `@import` code are already inlined into this `id`. // If CSS requests `importedModules` contain non-CSS files, e.g. Tailwind might add HMR @@ -50,6 +55,13 @@ export async function* crawlGraph( if (isCSSRequest(id)) { continue; } + // Some special Vite queries like `?url` or `?raw` are known to be a simple default export + // and doesn't have any imports to crawl. However, since they would `this.addWatchFile` the + // underlying module, our logic would crawl into them anyways which is incorrect as they + // don't take part in the final rendering, so we skip it here. + if (hasSpecialQueries(id)) { + continue; + } for (const importedModule of entry.importedModules) { if (!importedModule.id) continue; diff --git a/packages/astro/test/astro-basic.test.js b/packages/astro/test/astro-basic.test.js index 957be04fb6f2c..2cfa5d27fe804 100644 --- a/packages/astro/test/astro-basic.test.js +++ b/packages/astro/test/astro-basic.test.js @@ -162,7 +162,14 @@ describe('Astro basic build', () => { it('Handles importing .astro?raw correctly', async () => { const html = await fixture.readFile('/import-queries/raw/index.html'); const $ = cheerio.load(html); - assert.equal($('.raw-value').text(), '

Hello

\n'); + const rawValue = $('.raw-value').text(); + assert.match(rawValue, /

Hello<\/h1>/); + assert.match(rawValue, / +