diff --git a/packages/cloudflare/src/index.ts b/packages/cloudflare/src/index.ts index 269b7a82..5c11fd04 100644 --- a/packages/cloudflare/src/index.ts +++ b/packages/cloudflare/src/index.ts @@ -2,18 +2,14 @@ import type { AstroConfig, AstroIntegration, RouteData } from 'astro'; import * as fs from 'node:fs'; import * as os from 'node:os'; -import { relative } from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; +import { fileURLToPath } from 'node:url'; import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects'; import { AstroError } from 'astro/errors'; -import esbuild from 'esbuild'; import glob from 'tiny-glob'; import { getAdapter } from './getAdapter.js'; import { deduplicatePatterns } from './utils/deduplicatePatterns.js'; import { prepareImageConfig } from './utils/image-config.js'; import { prependForwardSlash } from './utils/prependForwardSlash.js'; -import { rewriteWasmImportPath } from './utils/rewriteWasmImportPath.js'; -import { patchSharpBundle } from './utils/sharpBundlePatch.js'; import { wasmModuleLoader } from './utils/wasm-module-loader.js'; import { getPlatformProxy } from 'wrangler'; @@ -62,8 +58,6 @@ export default function createIntegration(args?: Options): AstroIntegration { let _config: AstroConfig; let _buildConfig: BuildConfig; - const SERVER_BUILD_FOLDER = '/$server_build/'; - return { name: '@astrojs/cloudflare', hooks: { @@ -71,8 +65,8 @@ export default function createIntegration(args?: Options): AstroIntegration { updateConfig({ build: { client: new URL(`.${config.base}`, config.outDir), - server: new URL(`.${SERVER_BUILD_FOLDER}`, config.outDir), - serverEntry: '_worker.mjs', + server: new URL('./_worker.js/', config.outDir), + serverEntry: 'index.js', redirects: false, }, vite: { @@ -80,7 +74,6 @@ export default function createIntegration(args?: Options): AstroIntegration { plugins: [ wasmModuleLoader({ disabled: !args?.wasmModuleImports, - assetsDirectory: config.build.assets, }), ], }, @@ -97,12 +90,6 @@ export default function createIntegration(args?: Options): AstroIntegration { '[@astrojs/cloudflare] `output: "server"` or `output: "hybrid"` is required to use this adapter. Otherwise, this adapter is not necessary to deploy a static site to Cloudflare.' ); } - - if (_config.base === SERVER_BUILD_FOLDER) { - throw new AstroError( - '[@astrojs/cloudflare] `base: "${SERVER_BUILD_FOLDER}"` is not allowed. Please change your `base` config to something else.' - ); - } }, 'astro:server:setup': async ({ server, logger }) => { if (args?.platformProxy?.enabled === true) { @@ -146,86 +133,22 @@ export default function createIntegration(args?: Options): AstroIntegration { (vite.resolve.alias as Record)[alias.find] = alias.replacement; } } + vite.ssr ||= {}; vite.ssr.target = 'webworker'; + vite.ssr.noExternal = true; + vite.ssr.external = _config.vite?.ssr?.external ?? []; - // Cloudflare env is only available per request. This isn't feasible for code that access env vars - // in a global way, so we shim their access as `process.env.*`. We will populate `process.env` later - // in its fetch handler. - vite.define = { - 'process.env': 'process.env', - ...vite.define, - }; + vite.build ||= {}; + vite.build.rollupOptions ||= {}; + vite.build.rollupOptions.output ||= {}; + // @ts-ignore + vite.build.rollupOptions.output.banner ||= 'globalThis.process ??= {};'; + + vite.build.rollupOptions.external = _config.vite?.build?.rollupOptions?.external ?? []; } }, 'astro:build:done': async ({ pages, routes, dir }) => { - const assetsUrl = new URL(_buildConfig.assets, _buildConfig.client); - - const entryPath = fileURLToPath(new URL(_buildConfig.serverEntry, _buildConfig.server)); - const entryUrl = new URL(_buildConfig.serverEntry, _config.outDir); - const buildPath = fileURLToPath(entryUrl); - // A URL for the final build path after renaming - const finalBuildUrl = pathToFileURL(buildPath.replace(/\.mjs$/, '.js')); - - const esbuildPlugins = []; - if (args?.imageService === 'compile') { - esbuildPlugins.push(patchSharpBundle()); - } - - if (args?.wasmModuleImports) { - esbuildPlugins.push( - rewriteWasmImportPath({ - relativePathToAssets: relative( - fileURLToPath(_buildConfig.client), - fileURLToPath(assetsUrl) - ), - }) - ); - } - - await esbuild.build({ - target: 'es2022', - platform: 'browser', - conditions: ['workerd', 'worker', 'browser'], - external: [ - 'node:assert', - 'node:async_hooks', - 'node:buffer', - 'node:crypto', - 'node:diagnostics_channel', - 'node:events', - 'node:path', - 'node:process', - 'node:stream', - 'node:string_decoder', - 'node:util', - 'cloudflare:*', - ], - entryPoints: [entryPath], - outfile: buildPath, - allowOverwrite: true, - format: 'esm', - bundle: true, - minify: _config.vite?.build?.minify !== false, - banner: { - js: `globalThis.process = { - argv: [], - env: {}, - };`, - }, - logOverride: { - 'ignored-bare-import': 'silent', - }, - plugins: esbuildPlugins, - }); - - // Rename to worker.js - await fs.promises.rename(buildPath, finalBuildUrl); - - // throw the server folder in the bin - const serverUrl = new URL(_buildConfig.server); - await fs.promises.rm(serverUrl, { recursive: true, force: true }); - // move cloudflare specific files to the root const cloudflareSpecialFiles = ['_headers', '_redirects', '_routes.json']; diff --git a/packages/cloudflare/src/utils/rewriteWasmImportPath.ts b/packages/cloudflare/src/utils/rewriteWasmImportPath.ts deleted file mode 100644 index c266f19b..00000000 --- a/packages/cloudflare/src/utils/rewriteWasmImportPath.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { basename } from 'node:path'; -import type esbuild from 'esbuild'; - -/** - * - * @param relativePathToAssets - relative path from the final location for the current esbuild output bundle, to the assets directory. - */ -export function rewriteWasmImportPath({ - relativePathToAssets, -}: { - relativePathToAssets: string; -}): esbuild.Plugin { - return { - name: 'wasm-loader', - setup(build) { - build.onResolve({ filter: /.*\.wasm.mjs$/ }, (args) => { - const updatedPath = [ - relativePathToAssets.replaceAll('\\', '/'), - basename(args.path).replace(/\.mjs$/, ''), - ].join('/'); - - return { - path: updatedPath, - external: true, // mark it as external in the bundle - }; - }); - }, - }; -} diff --git a/packages/cloudflare/src/utils/sharpBundlePatch.ts b/packages/cloudflare/src/utils/sharpBundlePatch.ts deleted file mode 100644 index ea9015be..00000000 --- a/packages/cloudflare/src/utils/sharpBundlePatch.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type esbuild from 'esbuild'; - -export function patchSharpBundle(): esbuild.Plugin { - return { - name: 'sharp-patch', - setup(build) { - build.onResolve({ filter: /^sharp/ }, (args) => ({ - path: args.path, - namespace: 'sharp-ns', - })); - - build.onLoad({ filter: /.*/, namespace: 'sharp-ns' }, (a) => { - return { - contents: JSON.stringify(''), - loader: 'json', - }; - }); - }, - }; -} diff --git a/packages/cloudflare/src/utils/wasm-module-loader.ts b/packages/cloudflare/src/utils/wasm-module-loader.ts index c186d264..0e7057de 100644 --- a/packages/cloudflare/src/utils/wasm-module-loader.ts +++ b/packages/cloudflare/src/utils/wasm-module-loader.ts @@ -13,10 +13,8 @@ import type { AstroConfig } from 'astro'; */ export function wasmModuleLoader({ disabled, - assetsDirectory, }: { disabled: boolean; - assetsDirectory: string; }): NonNullable[number] { const postfix = '.wasm?module'; let isDev = false; @@ -31,7 +29,11 @@ export function wasmModuleLoader({ // let vite know that file format and the magic import string is intentional, and will be handled in this plugin return { assetsInclude: ['**/*.wasm?module'], - build: { rollupOptions: { external: /^__WASM_ASSET__.+\.wasm\.mjs$/i } }, + build: { + rollupOptions: { + external: [/^__WASM_ASSET__.+\.wasm$/i, /^__WASM_ASSET__.+\.wasm.mjs$/i], + }, + }, }; }, @@ -50,10 +52,8 @@ export function wasmModuleLoader({ const data = fs.readFileSync(filePath); const base64 = data.toString('base64'); - const base64Module = ` -const wasmModule = new WebAssembly.Module(Uint8Array.from(atob("${base64}"), c => c.charCodeAt(0))); -export default wasmModule -`; + const base64Module = `const wasmModule = new WebAssembly.Module(Uint8Array.from(atob("${base64}"), c => c.charCodeAt(0)));export default wasmModule;`; + if (isDev) { // no need to wire up the assets in dev mode, just rewrite return base64Module; @@ -68,8 +68,8 @@ export default wasmModule // put it explicitly in the _astro assets directory with `fileName` rather than `name` so that // vite doesn't give it a random id in its name. We need to be able to easily rewrite from // the .mjs loader and the actual wasm asset later in the ESbuild for the worker - fileName: path.join(assetsDirectory, assetName), - source: fs.readFileSync(filePath), + fileName: assetName, + source: data, }); // however, by default, the SSG generator cannot import the .wasm as a module, so embed as a base64 string @@ -79,10 +79,7 @@ export default wasmModule code: base64Module, }); - return ` -import wasmModule from "__WASM_ASSET__${chunkId}.wasm.mjs"; -export default wasmModule; - `; + return `import wasmModule from "__WASM_ASSET__${chunkId}.wasm.mjs";export default wasmModule;`; }, // output original wasm file relative to the chunk @@ -91,13 +88,33 @@ export default wasmModule; if (!/__WASM_ASSET__/g.test(code)) return; - const final = code.replaceAll(/__WASM_ASSET__([A-Za-z\d]+).wasm.mjs/g, (s, assetId) => { - const fileName = this.getFileName(assetId); - const relativePath = path - .relative(path.dirname(chunk.fileName), fileName) - .replaceAll('\\', '/'); // fix windows paths for import - return `./${relativePath}`; - }); + const isPrerendered = Object.keys(chunk.modules).some( + (moduleId) => this.getModuleInfo(moduleId)?.meta?.astro?.pageOptions?.prerender === true + ); + + let final = code; + + // SSR + if (!isPrerendered) { + final = code.replaceAll(/__WASM_ASSET__([A-Za-z\d]+).wasm.mjs/g, (s, assetId) => { + const fileName = this.getFileName(assetId).replace(/\.mjs$/, ''); + const relativePath = path + .relative(path.dirname(chunk.fileName), fileName) + .replaceAll('\\', '/'); // fix windows paths for import + return `./${relativePath}`; + }); + } + + // SSG + if (isPrerendered) { + final = code.replaceAll(/__WASM_ASSET__([A-Za-z\d]+).wasm.mjs/g, (s, assetId) => { + const fileName = this.getFileName(assetId); + const relativePath = path + .relative(path.dirname(chunk.fileName), fileName) + .replaceAll('\\', '/'); // fix windows paths for import + return `./${relativePath}`; + }); + } return { code: final }; },