diff --git a/.changeset/shiny-parrots-clap.md b/.changeset/shiny-parrots-clap.md new file mode 100644 index 000000000000..302870935d0f --- /dev/null +++ b/.changeset/shiny-parrots-clap.md @@ -0,0 +1,5 @@ +--- +'@astrojs/cloudflare': patch +--- + +fix bug where asset redirects caused Cloudflare error diff --git a/.changeset/sweet-bats-clap.md b/.changeset/sweet-bats-clap.md new file mode 100644 index 000000000000..ee3f1761076a --- /dev/null +++ b/.changeset/sweet-bats-clap.md @@ -0,0 +1,5 @@ +--- +'@astrojs/cloudflare': patch +--- + +fix bug where `.ts` files are not renamed to `.js` diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts index 3dc237b7274d..610a61df2171 100644 --- a/packages/integrations/cloudflare/src/index.ts +++ b/packages/integrations/cloudflare/src/index.ts @@ -3,7 +3,7 @@ import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'ast import esbuild from 'esbuild'; import * as fs from 'fs'; import * as os from 'os'; -import { dirname } from 'path'; +import { sep } from 'path'; import glob from 'tiny-glob'; import { fileURLToPath, pathToFileURL } from 'url'; @@ -21,15 +21,15 @@ interface BuildConfig { export function getAdapter(isModeDirectory: boolean): AstroAdapter { return isModeDirectory ? { - name: '@astrojs/cloudflare', - serverEntrypoint: '@astrojs/cloudflare/server.directory.js', - exports: ['onRequest', 'manifest'], - } + name: '@astrojs/cloudflare', + serverEntrypoint: '@astrojs/cloudflare/server.directory.js', + exports: ['onRequest', 'manifest'], + } : { - name: '@astrojs/cloudflare', - serverEntrypoint: '@astrojs/cloudflare/server.advanced.js', - exports: ['default'], - }; + name: '@astrojs/cloudflare', + serverEntrypoint: '@astrojs/cloudflare/server.advanced.js', + exports: ['default'], + }; } const SHIM = `globalThis.process = { @@ -104,13 +104,12 @@ export default function createIntegration(args?: Options): AstroIntegration { } if (isModeDirectory && _buildConfig.split) { - const entryPointsRouteData = [..._entryPoints.keys()]; const entryPointsURL = [..._entryPoints.values()]; const entryPaths = entryPointsURL.map((entry) => fileURLToPath(entry)); - const outputDir = fileURLToPath(new URL('.astro', _buildConfig.server)); + const outputUrl = new URL('$astro', _buildConfig.server) + const outputDir = fileURLToPath(outputUrl); - // NOTE: AFAIK, esbuild keeps the order of the entryPoints array - const { outputFiles } = await esbuild.build({ + await esbuild.build({ target: 'es2020', platform: 'browser', conditions: ['workerd', 'worker', 'browser'], @@ -126,28 +125,49 @@ export default function createIntegration(args?: Options): AstroIntegration { logOverride: { 'ignored-bare-import': 'silent', }, - write: false, }); - // loop through all bundled files and write them to the functions folder - for (const [index, outputFile] of outputFiles.entries()) { - // we need to make sure the filename in the functions folder - // matches to cloudflares routing capabilities (see their docs) - // IN: src/pages/[language]/files/[...path].astro - // OUT: [language]/files/[[path]].js - const fileName = entryPointsRouteData[index].component - .replace('src/pages/', '') - .replace('.astro', '.js') - .replace(/(\[\.\.\.)(\w+)(\])/g, (_match, _p1, p2) => { - return `[[${p2}]]`; - }); - - const fileUrl = new URL(fileName, functionsUrl); - const newFileDir = dirname(fileURLToPath(fileUrl)); - if (!fs.existsSync(newFileDir)) { - fs.mkdirSync(newFileDir, { recursive: true }); - } - await fs.promises.writeFile(fileUrl, outputFile.contents); + const outputFiles: Array = ( + await glob(`**/*`, { + cwd: outputDir, + filesOnly: true, + }) + ) + + // loop through all new bundled files and write them to the functions folder + for (const outputFile of outputFiles) { + + // split the path into an array + const path = outputFile.split(sep); + + // replace dynamic path with [path] + const pathWithDynamics = path.map((segment) => segment.replace(/(\_)(\w+)(\_)/g, (_, __, prop) => { + return `[${prop}]`; + })); + + // replace nested dynamic path with [[path]] + const pathWithNestedDynamics = pathWithDynamics.map((segment) => segment.replace(/(\_\-\-\-)(\w+)(\_)/g, (_, __, prop) => { + return `[[${prop}]]`; + })) + + // remove original file extension + const pathReversed = pathWithNestedDynamics.reverse(); + pathReversed[0] = pathReversed[0] + .replace('entry.', '') + .replace(/(.*)\.(\w+)\.(\w+)$/g, (_, fileName, oldExt, newExt) => { + return `${fileName}.${newExt}`; + }) + + const finalSegments = pathReversed.reverse(); + const finalDirPath = finalSegments.slice(0, -1).join('/'); + const finalPath = finalSegments.join('/'); + + const newDirUrl = new URL(finalDirPath, functionsUrl); + await fs.promises.mkdir(newDirUrl, { recursive: true }) + + const oldFileUrl = new URL(`$astro/${outputFile}`, outputUrl); + const newFileUrl = new URL(finalPath, functionsUrl); + await fs.promises.rename(oldFileUrl, newFileUrl); } } else { const entryPath = fileURLToPath(new URL(_buildConfig.serverEntry, _buildConfig.server)); diff --git a/packages/integrations/cloudflare/src/server.directory.ts b/packages/integrations/cloudflare/src/server.directory.ts index f9f71a33b5b8..25a13958df10 100644 --- a/packages/integrations/cloudflare/src/server.directory.ts +++ b/packages/integrations/cloudflare/src/server.directory.ts @@ -24,7 +24,13 @@ export function createExports(manifest: SSRManifest) { const { pathname } = new URL(request.url); // static assets fallback, in case default _routes.json is not used if (manifest.assets.has(pathname)) { - return next(request); + // we need this so the page does not error + // https://developers.cloudflare.com/pages/platform/functions/advanced-mode/#set-up-a-function + return (runtimeEnv.env as unknown & { + ASSETS: { + fetch: typeof fetch; + }; + }).ASSETS.fetch(request); } let routeData = app.match(request, { matchNotFound: true }); diff --git a/packages/integrations/cloudflare/test/directory-split.test.js b/packages/integrations/cloudflare/test/directory-split.test.js index 384543a4b896..6e6b0bfe29aa 100644 --- a/packages/integrations/cloudflare/test/directory-split.test.js +++ b/packages/integrations/cloudflare/test/directory-split.test.js @@ -36,6 +36,9 @@ describe('Cloudflare SSR split', () => { expect(await fixture.pathExists('../functions/[person]/[car].js')).to.be.true; expect(await fixture.pathExists('../functions/files/[[path]].js')).to.be.true; expect(await fixture.pathExists('../functions/[language]/files/[[path]].js')).to.be.true; + expect(await fixture.pathExists('../functions/trpc/[trpc].js')).to.be.true; + expect(await fixture.pathExists('../functions/javascript.js')).to.be.true; + expect(await fixture.pathExists('../functions/test.json.js')).to.be.true; }); it('generates pre-rendered files', async () => { diff --git a/packages/integrations/cloudflare/test/fixtures/split/src/pages/javascript.js b/packages/integrations/cloudflare/test/fixtures/split/src/pages/javascript.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/integrations/cloudflare/test/fixtures/split/src/pages/test.json.ts b/packages/integrations/cloudflare/test/fixtures/split/src/pages/test.json.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/integrations/cloudflare/test/fixtures/split/src/pages/trpc/[trpc].ts b/packages/integrations/cloudflare/test/fixtures/split/src/pages/trpc/[trpc].ts new file mode 100644 index 000000000000..e69de29bb2d1