fix(@astrojs/cloudflare): SSR split file renaming misses ts endpoints (#7568)

* fix bug, where ts files where not renamed correctly

* try to make rename logic more robust

* remove log

* update tests

* update changeset

* cleanup

* fix lint

* debug windows tests

* fix windows support

* fix cloudflare directory code

* use EventContext type

* improve for loop

* change changeset

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* change changeset

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

---------

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
This commit is contained in:
Alexander Niebuhr 2023-07-17 15:12:41 +02:00 committed by GitHub
parent 1f0d0b5863
commit 6ec040761e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 63 additions and 33 deletions

View file

@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': patch
---
Fix a bug where asset redirects caused Cloudflare error

View file

@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': patch
---
Fix bug where `.ts` files are not renamed to `.js`

View file

@ -3,7 +3,7 @@ import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'ast
import esbuild from 'esbuild'; import esbuild from 'esbuild';
import * as fs from 'fs'; import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import { dirname } from 'path'; import { sep } from 'path';
import glob from 'tiny-glob'; import glob from 'tiny-glob';
import { fileURLToPath, pathToFileURL } from 'url'; import { fileURLToPath, pathToFileURL } from 'url';
@ -21,15 +21,15 @@ interface BuildConfig {
export function getAdapter(isModeDirectory: boolean): AstroAdapter { export function getAdapter(isModeDirectory: boolean): AstroAdapter {
return isModeDirectory return isModeDirectory
? { ? {
name: '@astrojs/cloudflare', name: '@astrojs/cloudflare',
serverEntrypoint: '@astrojs/cloudflare/server.directory.js', serverEntrypoint: '@astrojs/cloudflare/server.directory.js',
exports: ['onRequest', 'manifest'], exports: ['onRequest', 'manifest'],
} }
: { : {
name: '@astrojs/cloudflare', name: '@astrojs/cloudflare',
serverEntrypoint: '@astrojs/cloudflare/server.advanced.js', serverEntrypoint: '@astrojs/cloudflare/server.advanced.js',
exports: ['default'], exports: ['default'],
}; };
} }
const SHIM = `globalThis.process = { const SHIM = `globalThis.process = {
@ -112,13 +112,12 @@ export default function createIntegration(args?: Options): AstroIntegration {
} }
if (isModeDirectory && _buildConfig.split) { if (isModeDirectory && _buildConfig.split) {
const entryPointsRouteData = [..._entryPoints.keys()];
const entryPointsURL = [..._entryPoints.values()]; const entryPointsURL = [..._entryPoints.values()];
const entryPaths = entryPointsURL.map((entry) => fileURLToPath(entry)); 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 await esbuild.build({
const { outputFiles } = await esbuild.build({
target: 'es2020', target: 'es2020',
platform: 'browser', platform: 'browser',
conditions: ['workerd', 'worker', 'browser'], conditions: ['workerd', 'worker', 'browser'],
@ -134,28 +133,44 @@ export default function createIntegration(args?: Options): AstroIntegration {
logOverride: { logOverride: {
'ignored-bare-import': 'silent', 'ignored-bare-import': 'silent',
}, },
write: false,
}); });
// loop through all bundled files and write them to the functions folder const outputFiles: Array<string> = (
for (const [index, outputFile] of outputFiles.entries()) { await glob(`**/*`, {
// we need to make sure the filename in the functions folder cwd: outputDir,
// matches to cloudflares routing capabilities (see their docs) filesOnly: true,
// 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); // move the files into the functions folder
const newFileDir = dirname(fileURLToPath(fileUrl)); // & make sure the file names match Cloudflare syntax for routing
if (!fs.existsSync(newFileDir)) { for (const outputFile of outputFiles) {
fs.mkdirSync(newFileDir, { recursive: true }); const path = outputFile.split(sep);
}
await fs.promises.writeFile(fileUrl, outputFile.contents); const finalSegments = path.map((segment) => segment
.replace(/(\_)(\w+)(\_)/g, (_, __, prop) => {
return `[${prop}]`;
})
.replace(/(\_\-\-\-)(\w+)(\_)/g, (_, __, prop) => {
return `[[${prop}]]`;
})
);
finalSegments[finalSegments.length - 1] = finalSegments[finalSegments.length - 1]
.replace('entry.', '')
.replace(/(.*)\.(\w+)\.(\w+)$/g, (_, fileName, __, newExt) => {
return `${fileName}.${newExt}`;
})
const finalDirPath = finalSegments.slice(0, -1).join(sep);
const finalPath = finalSegments.join(sep);
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 { } else {
const entryPath = fileURLToPath(new URL(_buildConfig.serverEntry, _buildConfig.server)); const entryPath = fileURLToPath(new URL(_buildConfig.serverEntry, _buildConfig.server));

View file

@ -24,7 +24,9 @@ export function createExports(manifest: SSRManifest) {
const { pathname } = new URL(request.url); const { pathname } = new URL(request.url);
// static assets fallback, in case default _routes.json is not used // static assets fallback, in case default _routes.json is not used
if (manifest.assets.has(pathname)) { 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 EventContext<unknown, string, unknown>['env']).ASSETS.fetch(request);
} }
let routeData = app.match(request, { matchNotFound: true }); let routeData = app.match(request, { matchNotFound: true });

View file

@ -36,6 +36,9 @@ describe('Cloudflare SSR split', () => {
expect(await fixture.pathExists('../functions/[person]/[car].js')).to.be.true; 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/files/[[path]].js')).to.be.true;
expect(await fixture.pathExists('../functions/[language]/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 () => { it('generates pre-rendered files', async () => {