From 4fa0dc61eb9fc8ba37f2799d0d0efc071a09b419 Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Wed, 11 Dec 2024 16:56:32 +0100 Subject: [PATCH 1/7] feat(actions): getActionPath() --- .changeset/wise-boxes-develop.md | 20 ++++++++++++++++ .../src/actions/runtime/virtual/shared.ts | 5 ++++ packages/astro/templates/actions.mjs | 24 ++++++++++++++----- packages/astro/test/actions.test.js | 16 +++++++++++++ .../actions/src/pages/get-action-path.astro | 6 +++++ 5 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 .changeset/wise-boxes-develop.md create mode 100644 packages/astro/test/fixtures/actions/src/pages/get-action-path.astro diff --git a/.changeset/wise-boxes-develop.md b/.changeset/wise-boxes-develop.md new file mode 100644 index 000000000000..9865dd54e326 --- /dev/null +++ b/.changeset/wise-boxes-develop.md @@ -0,0 +1,20 @@ +--- +'astro': minor +--- + +Exports a new `getActionPath()` helper from `astro:actions` + +In most cases, calling an action as `actions.like()` is enough. But sometimes it's not enough, for example: + +- You want to pass custom headers +- You want to call the actions endpoint without `fetch`, eg. using the `navigator.sendBeacon` API + +That's why you can now use the `getActionPath()` helper. Pass an action to it to get the pathname (prefixed by your `based` configuration) to the action: + +```ts +import { actions, getActionPath } from 'astro:actions' + +const path = getActionPath(actions.like) // '/_actions/like' +``` + +If you intent to use this server side, remember to supply a hostname (eg. using `Astro.site`). diff --git a/packages/astro/src/actions/runtime/virtual/shared.ts b/packages/astro/src/actions/runtime/virtual/shared.ts index 4067ad321dfd..459e135a472c 100644 --- a/packages/astro/src/actions/runtime/virtual/shared.ts +++ b/packages/astro/src/actions/runtime/virtual/shared.ts @@ -9,6 +9,7 @@ import type { MaybePromise, ActionAPIContext as _ActionAPIContext, } from '../utils.js'; +import type { ActionClient } from './server.js'; export type ActionAPIContext = _ActionAPIContext; export const ACTION_QUERY_PARAMS = _ACTION_QUERY_PARAMS; @@ -180,6 +181,10 @@ export function getActionQueryString(name: string) { return `?${searchParams.toString()}`; } +export function getActionPath(action: ActionClient) { + return `${import.meta.env.BASE_URL.replace(/\/$/, '')}/_actions/${new URLSearchParams(action.toString()).get(ACTION_QUERY_PARAMS.actionName)}`; +} + export type SerializedActionResult = | { type: 'data'; diff --git a/packages/astro/templates/actions.mjs b/packages/astro/templates/actions.mjs index 82a287448a22..fd14dee68bc7 100644 --- a/packages/astro/templates/actions.mjs +++ b/packages/astro/templates/actions.mjs @@ -1,4 +1,9 @@ -import { ActionError, deserializeActionResult, getActionQueryString } from 'astro:actions'; +import { + ActionError, + deserializeActionResult, + getActionQueryString, + getActionPath, +} from 'astro:actions'; const ENCODED_DOT = '%2E'; @@ -83,11 +88,18 @@ async function handleAction(param, path, context) { headers.set('Content-Length', '0'); } } - const rawResult = await fetch(`${import.meta.env.BASE_URL.replace(/\/$/, '')}/_actions/${path}`, { - method: 'POST', - body, - headers, - }); + const rawResult = await fetch( + getActionPath({ + toString() { + return path; + }, + }), + { + method: 'POST', + body, + headers, + }, + ); if (rawResult.status === 204) { return deserializeActionResult({ type: 'empty', status: 204 }); } diff --git a/packages/astro/test/actions.test.js b/packages/astro/test/actions.test.js index d8da4e72e308..05a9ae0f3437 100644 --- a/packages/astro/test/actions.test.js +++ b/packages/astro/test/actions.test.js @@ -564,6 +564,22 @@ it('Base path should be used', async () => { await devServer.stop(); }); +it('getActionPath() should return the right path', async () => { + const fixture = await loadFixture({ + root: './fixtures/actions/', + adapter: testAdapter(), + base: '/base', + }); + const devServer = await fixture.startDevServer(); + const res = await fixture.fetch('/base/get-action-path'); + + assert.equal(res.ok, true); + const html = await res.text(); + let $ = cheerio.load(html); + assert.equal($('[data-path]').text(), '/base/_actions/transformFormInput'); + await devServer.stop(); +}); + /** * Follow an expected redirect response. * diff --git a/packages/astro/test/fixtures/actions/src/pages/get-action-path.astro b/packages/astro/test/fixtures/actions/src/pages/get-action-path.astro new file mode 100644 index 000000000000..9fa69b0e2872 --- /dev/null +++ b/packages/astro/test/fixtures/actions/src/pages/get-action-path.astro @@ -0,0 +1,6 @@ +--- +import { actions, getActionPath } from "astro:actions" + +const path = getActionPath(actions.transformFormInput) +--- +

{path}

\ No newline at end of file From b79a6b050533f93fdb7134e720e2483438e759a6 Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Thu, 12 Dec 2024 14:49:59 +0100 Subject: [PATCH 2/7] feat: take trailing slash into account --- .../src/actions/runtime/virtual/shared.ts | 5 ---- packages/astro/templates/actions.mjs | 24 +++++++++---------- packages/astro/types/actions.d.ts | 4 ++++ 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/astro/src/actions/runtime/virtual/shared.ts b/packages/astro/src/actions/runtime/virtual/shared.ts index 36269eb4c41b..02cc07b52cd6 100644 --- a/packages/astro/src/actions/runtime/virtual/shared.ts +++ b/packages/astro/src/actions/runtime/virtual/shared.ts @@ -10,7 +10,6 @@ import type { MaybePromise, ActionAPIContext as _ActionAPIContext, } from '../utils.js'; -import type { ActionClient } from './server.js'; export type ActionAPIContext = _ActionAPIContext; export const ACTION_QUERY_PARAMS = _ACTION_QUERY_PARAMS; @@ -184,10 +183,6 @@ export function getActionQueryString(name: string) { return `?${searchParams.toString()}`; } -export function getActionPath(action: ActionClient) { - return `${import.meta.env.BASE_URL.replace(/\/$/, '')}/_actions/${new URLSearchParams(action.toString()).get(ACTION_QUERY_PARAMS.actionName)}`; -} - export type SerializedActionResult = | { type: 'data'; diff --git a/packages/astro/templates/actions.mjs b/packages/astro/templates/actions.mjs index 9b9415ea7d37..68c9164ed6af 100644 --- a/packages/astro/templates/actions.mjs +++ b/packages/astro/templates/actions.mjs @@ -3,7 +3,6 @@ import { deserializeActionResult, getActionQueryString, appendForwardSlash, - getActionPath, } from 'astro:actions'; const ENCODED_DOT = '%2E'; @@ -53,6 +52,17 @@ function toActionProxy(actionCallback = {}, aggregatedPath = '') { }); } +const SHOULD_APPEND_TRAILING_SLASH = '/** @TRAILING_SLASH@ **/'; + +/** @param {import('astro:actions').ActionClient} */ +export function getActionPath(action) { + let path = `${import.meta.env.BASE_URL.replace(/\/$/, '')}/_actions/${new URLSearchParams(action.toString()).get(ACTION_QUERY_PARAMS.actionName)}`; + if (SHOULD_APPEND_TRAILING_SLASH) { + path = appendForwardSlash(path); + } + return path; +} + /** * @param {*} param argument passed to the action when called server or client-side. * @param {string} path Built path to call action by path name. @@ -102,18 +112,6 @@ async function handleAction(param, path, context) { }, ); - const shouldAppendTrailingSlash = '/** @TRAILING_SLASH@ **/'; - let actionPath = import.meta.env.BASE_URL.replace(/\/$/, '') + '/_actions/' + path; - - if (shouldAppendTrailingSlash) { - actionPath = appendForwardSlash(actionPath); - } - - const rawResult = await fetch(actionPath, { - method: 'POST', - body, - headers, - }); if (rawResult.status === 204) { return deserializeActionResult({ type: 'empty', status: 204 }); } diff --git a/packages/astro/types/actions.d.ts b/packages/astro/types/actions.d.ts index 90187ebb9979..d30bd8bd99d0 100644 --- a/packages/astro/types/actions.d.ts +++ b/packages/astro/types/actions.d.ts @@ -1,3 +1,7 @@ declare module 'astro:actions' { export * from 'astro/actions/runtime/virtual/server.js'; + + export function getActionPath( + action: import('astro/actions/runtime/virtual/server.js').ActionClient, + ): string; } From aeec14ae89e520cfabf4c23e4046230f97fcaec6 Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Thu, 12 Dec 2024 14:55:16 +0100 Subject: [PATCH 3/7] fix --- packages/astro/src/actions/plugins.ts | 12 ++++++------ packages/astro/templates/actions.mjs | 3 ++- packages/astro/test/actions.test.js | 5 +++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/astro/src/actions/plugins.ts b/packages/astro/src/actions/plugins.ts index f5bd074dfcca..4c1b930c3d87 100644 --- a/packages/astro/src/actions/plugins.ts +++ b/packages/astro/src/actions/plugins.ts @@ -85,13 +85,13 @@ export function vitePluginActions({ code += `\nexport * from 'astro/actions/runtime/virtual/server.js';`; } else { code += `\nexport * from 'astro/actions/runtime/virtual/client.js';`; - code = code.replace( - "'/** @TRAILING_SLASH@ **/'", - JSON.stringify( - shouldAppendForwardSlash(settings.config.trailingSlash, settings.config.build.format), - ), - ); } + code = code.replace( + "'/** @TRAILING_SLASH@ **/'", + JSON.stringify( + shouldAppendForwardSlash(settings.config.trailingSlash, settings.config.build.format), + ), + ); return code; }, }; diff --git a/packages/astro/templates/actions.mjs b/packages/astro/templates/actions.mjs index 68c9164ed6af..e29a78f7352c 100644 --- a/packages/astro/templates/actions.mjs +++ b/packages/astro/templates/actions.mjs @@ -1,8 +1,9 @@ import { ActionError, + ACTION_QUERY_PARAMS, + appendForwardSlash, deserializeActionResult, getActionQueryString, - appendForwardSlash, } from 'astro:actions'; const ENCODED_DOT = '%2E'; diff --git a/packages/astro/test/actions.test.js b/packages/astro/test/actions.test.js index 407ff1c005d0..929a2d8d84a9 100644 --- a/packages/astro/test/actions.test.js +++ b/packages/astro/test/actions.test.js @@ -593,14 +593,15 @@ it('getActionPath() should return the right path', async () => { root: './fixtures/actions/', adapter: testAdapter(), base: '/base', + trailingSlash: 'always', }); const devServer = await fixture.startDevServer(); - const res = await fixture.fetch('/base/get-action-path'); + const res = await fixture.fetch('/base/get-action-path/'); assert.equal(res.ok, true); const html = await res.text(); let $ = cheerio.load(html); - assert.equal($('[data-path]').text(), '/base/_actions/transformFormInput'); + assert.equal($('[data-path]').text(), '/base/_actions/transformFormInput/'); await devServer.stop(); }); From b5fe5c53dc275217c602e1d922f4c7f4eae67b81 Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Thu, 12 Dec 2024 16:15:41 +0100 Subject: [PATCH 4/7] fix --- packages/astro/templates/actions.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/templates/actions.mjs b/packages/astro/templates/actions.mjs index e29a78f7352c..d10b2e3b3405 100644 --- a/packages/astro/templates/actions.mjs +++ b/packages/astro/templates/actions.mjs @@ -103,7 +103,7 @@ async function handleAction(param, path, context) { const rawResult = await fetch( getActionPath({ toString() { - return path; + return getActionQueryString(path); }, }), { From ae5f342aa44f7251b89e9aad9b4bbc1c1e8ca87f Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Tue, 17 Dec 2024 16:54:52 +0100 Subject: [PATCH 5/7] Update wise-boxes-develop.md --- .changeset/wise-boxes-develop.md | 38 +++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/.changeset/wise-boxes-develop.md b/.changeset/wise-boxes-develop.md index 9865dd54e326..e264a20a7b02 100644 --- a/.changeset/wise-boxes-develop.md +++ b/.changeset/wise-boxes-develop.md @@ -4,17 +4,39 @@ Exports a new `getActionPath()` helper from `astro:actions` -In most cases, calling an action as `actions.like()` is enough. But sometimes it's not enough, for example: +The `getActionPath()` utility accepts an action and returns a URL path so you can execute an action call as a `fetch()` operation directly. This allows you to provide details such as custom headers when you call your action. -- You want to pass custom headers -- You want to call the actions endpoint without `fetch`, eg. using the `navigator.sendBeacon` API +This example shows how to call a defined `like` action passing the `Authorization` header and the [`keepalive`](https://developer.mozilla.org/en-US/docs/Web/API/Request/keepalive) option: -That's why you can now use the `getActionPath()` helper. Pass an action to it to get the pathname (prefixed by your `based` configuration) to the action: - -```ts +```astro + ``` -If you intent to use this server side, remember to supply a hostname (eg. using `Astro.site`). +This example shows how to call the same `like` action using the [`sendBeacon`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon) API: + +```astro + +``` From 346895b5f9ccd0f527df509bda54b142d5ed0a67 Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Wed, 18 Dec 2024 08:48:46 +0100 Subject: [PATCH 6/7] Apply suggestions from code review Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com> --- .changeset/wise-boxes-develop.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.changeset/wise-boxes-develop.md b/.changeset/wise-boxes-develop.md index e264a20a7b02..705c412a0b7a 100644 --- a/.changeset/wise-boxes-develop.md +++ b/.changeset/wise-boxes-develop.md @@ -2,9 +2,11 @@ 'astro': minor --- -Exports a new `getActionPath()` helper from `astro:actions` +Adds a new `getActionPath()` helper available from `astro:actions` -The `getActionPath()` utility accepts an action and returns a URL path so you can execute an action call as a `fetch()` operation directly. This allows you to provide details such as custom headers when you call your action. +Astro 5.1 introduces a new helper function, `getActionPath()` to give you more flexibility when calling your action. + +Calling `getActionPath()` with your action returns its URL path so you can make a `fetch()` request with custom headers, or use your action with an API such as `navigator.sendBeacon()`. This example shows how to call a defined `like` action passing the `Authorization` header and the [`keepalive`](https://developer.mozilla.org/en-US/docs/Web/API/Request/keepalive) option: From a71b0e5bad45384a91b85c382bdbca9d5146f2dc Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Wed, 18 Dec 2024 13:59:29 +0000 Subject: [PATCH 7/7] Update .changeset/wise-boxes-develop.md Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com> --- .changeset/wise-boxes-develop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/wise-boxes-develop.md b/.changeset/wise-boxes-develop.md index 705c412a0b7a..5b7d0825e444 100644 --- a/.changeset/wise-boxes-develop.md +++ b/.changeset/wise-boxes-develop.md @@ -6,7 +6,7 @@ Adds a new `getActionPath()` helper available from `astro:actions` Astro 5.1 introduces a new helper function, `getActionPath()` to give you more flexibility when calling your action. -Calling `getActionPath()` with your action returns its URL path so you can make a `fetch()` request with custom headers, or use your action with an API such as `navigator.sendBeacon()`. +Calling `getActionPath()` with your action returns its URL path so you can make a `fetch()` request with custom headers, or use your action with an API such as `navigator.sendBeacon()`. Then, you can [handle the custom-formatted returned data](https://docs.astro.build/en/guides/actions/#handling-returned-data) as needed, just as if you had called an action directly. This example shows how to call a defined `like` action passing the `Authorization` header and the [`keepalive`](https://developer.mozilla.org/en-US/docs/Web/API/Request/keepalive) option: