From 80681318c6cb0f612fcb5188933fdd20a8f474a3 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Mon, 22 Jul 2024 13:15:34 +0100 Subject: [PATCH] fix(actions): resolve actions when there aren't user actions (#11525) * fix(actions): pass path as input * revert code * chore: apply suggestions --- .changeset/new-melons-cross.md | 5 ++ packages/astro/src/actions/consts.ts | 3 ++ packages/astro/src/actions/index.ts | 60 ++++++++++++++++++--- packages/astro/src/actions/runtime/utils.ts | 6 ++- packages/astro/src/integrations/hooks.ts | 4 +- 5 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 .changeset/new-melons-cross.md diff --git a/.changeset/new-melons-cross.md b/.changeset/new-melons-cross.md new file mode 100644 index 0000000000..b2e30b3058 --- /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 ef6b87ca83..e1324f248d 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 f4ab24e2d4..f176988ff0 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 02961144b4..91f2859d45 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 48a9777e8c..33a7c7c0c5 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); }