@astrojs/cloudflare 10.4.2 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,6 @@
1
1
  import { App } from 'astro/app';
2
+ import { setGetEnv } from 'astro/env/setup';
3
+ import { createGetEnv } from '../utils/env.js';
2
4
  export function createExports(manifest) {
3
5
  const app = new App(manifest);
4
6
  const fetch = async (request, env, context) => {
@@ -28,10 +30,14 @@ export function createExports(manifest) {
28
30
  caches: caches,
29
31
  ctx: {
30
32
  waitUntil: (promise) => context.waitUntil(promise),
31
- passThroughOnException: () => context.passThroughOnException(),
33
+ // Currently not available: https://developers.cloudflare.com/pages/platform/known-issues/#pages-functions
34
+ passThroughOnException: () => {
35
+ throw new Error('`passThroughOnException` is currently not available in Cloudflare Pages. See https://developers.cloudflare.com/pages/platform/known-issues/#pages-functions.');
36
+ },
32
37
  },
33
38
  },
34
39
  };
40
+ setGetEnv(createGetEnv(env));
35
41
  const response = await app.render(request, { routeData, locals });
36
42
  if (app.setCookieHeaders) {
37
43
  for (const setCookieHeader of app.setCookieHeaders(response)) {
package/dist/index.d.ts CHANGED
@@ -31,7 +31,7 @@ export type Options = {
31
31
  * Proxy configuration for the platform.
32
32
  */
33
33
  platformProxy?: {
34
- /** Toggle the proxy. Default `undefined`, which equals to `false`. */
34
+ /** Toggle the proxy. Default `undefined`, which equals to `true`. */
35
35
  enabled?: boolean;
36
36
  /** Path to the configuration file. Default `wrangler.toml`. */
37
37
  configPath?: string;
@@ -50,7 +50,5 @@ export type Options = {
50
50
  * for reference on how these file types are exported
51
51
  */
52
52
  cloudflareModules?: boolean;
53
- /** @deprecated - use `cloudflareModules`, which defaults to true. You can set `cloudflareModuleLoading: false` to disable */
54
- wasmModuleImports?: boolean;
55
53
  };
56
54
  export default function createIntegration(args?: Options): AstroIntegration;
package/dist/index.js CHANGED
@@ -1,94 +1,66 @@
1
1
  import { createReadStream } from 'node:fs';
2
- import { appendFile, rename, stat, unlink } from 'node:fs/promises';
2
+ import { appendFile, rename, stat } from 'node:fs/promises';
3
3
  import { createInterface } from 'node:readline/promises';
4
4
  import { appendForwardSlash, prependForwardSlash, removeLeadingForwardSlash, } from '@astrojs/internal-helpers/path';
5
5
  import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
6
6
  import { AstroError } from 'astro/errors';
7
- import { walk } from 'estree-walker';
8
- import MagicString from 'magic-string';
9
7
  import { getPlatformProxy } from 'wrangler';
10
8
  import { cloudflareModuleLoader, } from './utils/cloudflare-module-loader.js';
9
+ import { createGetEnv } from './utils/env.js';
11
10
  import { createRoutesFile, getParts } from './utils/generate-routes-json.js';
12
11
  import { setImageConfig } from './utils/image-config.js';
13
- import { mutateDynamicPageImportsInPlace, mutatePageMapInPlace } from './utils/index.js';
14
- import { NonServerChunkDetector } from './utils/non-server-chunk-detector.js';
12
+ function wrapWithSlashes(path) {
13
+ return prependForwardSlash(appendForwardSlash(path));
14
+ }
15
+ function setProcessEnv(config, env) {
16
+ const getEnv = createGetEnv(env);
17
+ if (config.experimental.env?.schema) {
18
+ for (const key of Object.keys(config.experimental.env.schema)) {
19
+ const value = getEnv(key);
20
+ if (value !== undefined) {
21
+ process.env[key] = value;
22
+ }
23
+ }
24
+ }
25
+ }
26
+ function createPlatformProxy(platformProxy) {
27
+ return getPlatformProxy({
28
+ configPath: platformProxy?.configPath ?? 'wrangler.toml',
29
+ experimentalJsonConfig: platformProxy?.experimentalJsonConfig ?? false,
30
+ persist: platformProxy?.persist ?? true,
31
+ });
32
+ }
15
33
  export default function createIntegration(args) {
16
34
  let _config;
17
- const cloudflareModulePlugin = cloudflareModuleLoader(args?.cloudflareModules ?? args?.wasmModuleImports ?? true);
18
- // Initialize the unused chunk analyzer as a shared state between hooks.
19
- // The analyzer is used on earlier hooks to collect information about used hooks on a Vite plugin
20
- // and then later after the full build to clean up unused chunks, so it has to be shared between them.
21
- const chunkAnalyzer = new NonServerChunkDetector();
35
+ const cloudflareModulePlugin = cloudflareModuleLoader(args?.cloudflareModules ?? true);
22
36
  return {
23
37
  name: '@astrojs/cloudflare',
24
38
  hooks: {
25
- 'astro:config:setup': ({ command, config, updateConfig, logger }) => {
39
+ 'astro:config:setup': ({ command, config, updateConfig, logger, addWatchFile }) => {
26
40
  updateConfig({
27
41
  build: {
28
- client: new URL(`.${prependForwardSlash(appendForwardSlash(config.base))}`, config.outDir),
42
+ client: new URL(`.${wrapWithSlashes(config.base)}`, config.outDir),
29
43
  server: new URL('./_worker.js/', config.outDir),
30
44
  serverEntry: 'index.js',
31
45
  redirects: false,
32
46
  },
33
47
  vite: {
34
- // load .wasm files as WebAssembly modules
35
48
  plugins: [
49
+ // https://developers.cloudflare.com/pages/functions/module-support/
50
+ // Allows imports of '.wasm', '.bin', and '.txt' file types
36
51
  cloudflareModulePlugin,
37
- chunkAnalyzer.getPlugin(),
38
- {
39
- name: 'dynamic-imports-analyzer',
40
- enforce: 'post',
41
- generateBundle(_, bundle) {
42
- let astrojsSSRVirtualEntryAST;
43
- const prerenderImports = [];
44
- let entryChunk;
45
- // Find all pages (ignore the ssr entrypoint) which are prerendered based on the dynamic imports of the prerender chunk
46
- for (const chunk of Object.values(bundle)) {
47
- if (chunk.type !== 'chunk')
48
- continue;
49
- if (chunk.name === '_@astrojs-ssr-virtual-entry') {
50
- astrojsSSRVirtualEntryAST = this.parse(chunk.code);
51
- entryChunk = chunk;
52
- continue;
53
- }
54
- const isPrerendered = chunk.dynamicImports.some((entry) => entry.includes('prerender'));
55
- if (isPrerendered) {
56
- prerenderImports.push(chunk.fileName);
57
- }
58
- }
59
- if (!astrojsSSRVirtualEntryAST)
60
- return;
61
- if (!entryChunk)
62
- return;
63
- const s = new MagicString(entryChunk.code);
64
- const constsToRemove = [];
65
- walk(astrojsSSRVirtualEntryAST, {
66
- leave(node) {
67
- // We are only looking for VariableDeclarations, since both (dynamic imports and pageMap) are declared as constants in the code
68
- if (node.type !== 'VariableDeclaration')
69
- return;
70
- if (!node.declarations[0] ||
71
- node.declarations[0].type !== 'VariableDeclarator')
72
- return;
73
- // This function will remove the dynamic imports from the entrypoint
74
- mutateDynamicPageImportsInPlace(node, prerenderImports, constsToRemove, s);
75
- // This function will remove the pageMap entries which are invalid now
76
- mutatePageMapInPlace(node, constsToRemove, s);
77
- },
78
- });
79
- entryChunk.code = s.toString();
80
- },
81
- },
82
52
  ],
83
53
  },
84
- image: setImageConfig(args?.imageService ?? 'DEFAULT', config.image, command, logger),
54
+ image: setImageConfig(args?.imageService ?? 'compile', config.image, command, logger),
85
55
  });
56
+ addWatchFile(new URL('./wrangler.toml', config.root));
57
+ addWatchFile(new URL('./wrangler.json', config.root));
86
58
  },
87
59
  'astro:config:done': ({ setAdapter, config }) => {
88
- _config = config;
89
60
  if (config.output === 'static') {
90
61
  throw new AstroError('[@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.');
91
62
  }
63
+ _config = config;
92
64
  setAdapter({
93
65
  name: '@astrojs/cloudflare',
94
66
  serverEntrypoint: '@astrojs/cloudflare/entrypoints/server.js',
@@ -107,16 +79,14 @@ export default function createIntegration(args) {
107
79
  isSharpCompatible: false,
108
80
  isSquooshCompatible: false,
109
81
  },
82
+ envGetSecret: 'experimental',
110
83
  },
111
84
  });
112
85
  },
113
86
  'astro:server:setup': async ({ server }) => {
114
- if (args?.platformProxy?.enabled === true) {
115
- const platformProxy = await getPlatformProxy({
116
- configPath: args.platformProxy.configPath ?? 'wrangler.toml',
117
- experimentalJsonConfig: args.platformProxy.experimentalJsonConfig ?? false,
118
- persist: args.platformProxy.persist ?? true,
119
- });
87
+ if ((args?.platformProxy?.enabled ?? true) === true) {
88
+ const platformProxy = await createPlatformProxy(args?.platformProxy);
89
+ setProcessEnv(_config, platformProxy.env);
120
90
  const clientLocalsSymbol = Symbol.for('astro.locals');
121
91
  server.middlewares.use(async function middleware(req, res, next) {
122
92
  Reflect.set(req, clientLocalsSymbol, {
@@ -126,7 +96,10 @@ export default function createIntegration(args) {
126
96
  caches: platformProxy.caches,
127
97
  ctx: {
128
98
  waitUntil: (promise) => platformProxy.ctx.waitUntil(promise),
129
- passThroughOnException: () => platformProxy.ctx.passThroughOnException(),
99
+ // Currently not available: https://developers.cloudflare.com/pages/platform/known-issues/#pages-functions
100
+ passThroughOnException: () => {
101
+ throw new AstroError('`passThroughOnException` is currently not available in Cloudflare Pages. See https://developers.cloudflare.com/pages/platform/known-issues/#pages-functions.');
102
+ },
130
103
  },
131
104
  },
132
105
  });
@@ -175,20 +148,6 @@ export default function createIntegration(args) {
175
148
  // @ts-expect-error
176
149
  vite.build.rollupOptions.output.banner ||=
177
150
  'globalThis.process ??= {}; globalThis.process.env ??= {};';
178
- // @ts-expect-error
179
- vite.build.rollupOptions.output.manualChunks = (id) => {
180
- if (id.includes('node_modules')) {
181
- if (id.indexOf('node_modules') !== -1) {
182
- const basic = id.toString().split('node_modules/')[1];
183
- const sub1 = basic.split('/')[0];
184
- if (sub1 !== '.pnpm') {
185
- return sub1.toString();
186
- }
187
- const name2 = basic.split('/')[1];
188
- return name2.split('@')[name2[0] === '@' ? 1 : 0].toString();
189
- }
190
- }
191
- };
192
151
  vite.build.rollupOptions.external = _config.vite.build?.rollupOptions?.external ?? [];
193
152
  // Cloudflare env is only available per request. This isn't feasible for code that access env vars
194
153
  // in a global way, so we shim their access as `process.env.*`. This is not the recommended way for users to access environment variables. But we'll add this for compatibility for chosen variables. Mainly to support `@astrojs/db`
@@ -208,8 +167,6 @@ export default function createIntegration(args) {
208
167
  vite.build ||= {};
209
168
  vite.build.rollupOptions ||= {};
210
169
  vite.build.rollupOptions.output ||= {};
211
- // @ts-expect-error
212
- vite.build.rollupOptions.output.manualChunks = undefined;
213
170
  }
214
171
  },
215
172
  'astro:build:done': async ({ pages, routes, dir, logger }) => {
@@ -289,18 +246,6 @@ export default function createIntegration(args) {
289
246
  logger.error('Failed to write _redirects file');
290
247
  }
291
248
  }
292
- // Get chunks from the bundle that are not needed on the server and delete them
293
- // Those modules are build only for prerendering routes.
294
- const chunksToDelete = chunkAnalyzer.getNonServerChunks();
295
- for (const chunk of chunksToDelete) {
296
- try {
297
- // Chunks are located on `./_worker.js` directory inside of the output directory
298
- await unlink(new URL(`./_worker.js/${chunk}`, _config.outDir));
299
- }
300
- catch (error) {
301
- logger.warn(`Issue while trying to delete unused file from server bundle: ${new URL(`./_worker.js/${chunk}`, _config.outDir).toString()}`);
302
- }
303
- }
304
249
  },
305
250
  },
306
251
  };
@@ -51,7 +51,7 @@ export function cloudflareModuleLoader(enabled) {
51
51
  return;
52
52
  }
53
53
  if (!enabled) {
54
- throw new Error(`Cloudflare module loading is experimental. The ${maybeExtension} module cannot be loaded unless you add \`wasmModuleImports: true\` to your astro config.`);
54
+ throw new Error(`Cloudflare module loading is experimental. The ${maybeExtension} module cannot be loaded unless you add \`cloudflareModules: true\` to your astro config.`);
55
55
  }
56
56
  const moduleLoader = renderers[moduleType];
57
57
  const filePath = id.replace(/\?\w+$/, '');
@@ -126,7 +126,10 @@ export function cloudflareModuleLoader(enabled) {
126
126
  for (const chunk of Object.values(bundle)) {
127
127
  const repls = chunk.name && replacementsByChunkName.get(chunk.name);
128
128
  for (const replacement of repls || []) {
129
- replacement.fileName = chunk.fileName;
129
+ if (!replacement.fileName) {
130
+ replacement.fileName = [];
131
+ }
132
+ replacement.fileName.push(chunk.fileName);
130
133
  }
131
134
  }
132
135
  },
@@ -140,11 +143,13 @@ export function cloudflareModuleLoader(enabled) {
140
143
  if (!replacement.fileName) {
141
144
  continue;
142
145
  }
143
- const repls = replacementsByFileName.get(replacement.fileName) || [];
144
- if (!repls.length) {
145
- replacementsByFileName.set(replacement.fileName, repls);
146
+ for (const fileName of replacement.fileName) {
147
+ const repls = replacementsByFileName.get(fileName) || [];
148
+ if (!repls.length) {
149
+ replacementsByFileName.set(fileName, repls);
150
+ }
151
+ repls.push(replacement);
146
152
  }
147
- repls.push(replacement);
148
153
  }
149
154
  for (const [fileName, repls] of replacementsByFileName.entries()) {
150
155
  const filepath = path.join(baseDir, '_worker.js', fileName);
@@ -0,0 +1,2 @@
1
+ import type { GetEnv } from 'astro/env/setup';
2
+ export declare const createGetEnv: (env: Record<string, unknown>) => GetEnv;
@@ -0,0 +1,11 @@
1
+ export const createGetEnv = (env) => (key) => {
2
+ const v = env[key];
3
+ if (typeof v === 'undefined' || typeof v === 'string') {
4
+ return v;
5
+ }
6
+ if (typeof v === 'boolean' || typeof v === 'number') {
7
+ // let astro:env handle the validation and transformation
8
+ return v.toString();
9
+ }
10
+ return undefined;
11
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@astrojs/cloudflare",
3
3
  "description": "Deploy your site to Cloudflare Workers/Pages",
4
- "version": "10.4.2",
4
+ "version": "11.0.0",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "author": "withastro",
@@ -39,10 +39,10 @@
39
39
  "wrangler": "^3.39.0"
40
40
  },
41
41
  "peerDependencies": {
42
- "astro": "^4.2.0"
42
+ "astro": "^4.10.3"
43
43
  },
44
44
  "devDependencies": {
45
- "astro": "^4.10.1",
45
+ "astro": "^4.10.3",
46
46
  "cheerio": "1.0.0-rc.12",
47
47
  "execa": "^8.0.1",
48
48
  "fast-glob": "^3.3.2",
@@ -1,8 +0,0 @@
1
- import type { Node } from 'estree-walker';
2
- import type MagicString from 'magic-string';
3
- export declare function mutatePageMapInPlace(node: Extract<Node, {
4
- type: 'VariableDeclaration';
5
- }>, constsToRemove: string[], s: MagicString): void;
6
- export declare function mutateDynamicPageImportsInPlace(node: Extract<Node, {
7
- type: 'VariableDeclaration';
8
- }>, prerenderImports: string[], constsToRemove: string[], s: MagicString): void;
@@ -1,55 +0,0 @@
1
- export function mutatePageMapInPlace(node, constsToRemove, s) {
2
- const declarator = node.declarations[0];
3
- if (declarator.id.type !== 'Identifier')
4
- return;
5
- if (declarator.id.name !== 'pageMap')
6
- return;
7
- if (!declarator.init || declarator.init.type !== 'NewExpression')
8
- return;
9
- if (!declarator.init.arguments[0] || declarator.init.arguments[0].type !== 'ArrayExpression')
10
- return;
11
- for (const arrayExpression of declarator.init.arguments[0].elements) {
12
- if (!arrayExpression || arrayExpression.type !== 'ArrayExpression')
13
- continue;
14
- if (!arrayExpression.elements[1] || arrayExpression.elements[1].type !== 'Identifier')
15
- continue;
16
- if (constsToRemove.includes(arrayExpression.elements[1].name)) {
17
- // @ts-expect-error - @types/estree seem to be wrong
18
- if (arrayExpression.start && arrayExpression.end) {
19
- // @ts-expect-error - @types/estree seem to be wrong
20
- s.remove(arrayExpression.start, arrayExpression.end);
21
- // We need to check if there are any leftover commas, which are not part of the `ArrayExpression` node
22
- // @ts-expect-error - @types/estree seem to be wrong
23
- const endChar = s.slice(arrayExpression.end, arrayExpression.end + 1);
24
- if (endChar.includes(',')) {
25
- // @ts-expect-error - @types/estree seem to be wrong
26
- s.remove(arrayExpression.end, arrayExpression.end + 1);
27
- }
28
- }
29
- }
30
- }
31
- }
32
- export function mutateDynamicPageImportsInPlace(node, prerenderImports, constsToRemove, s) {
33
- const declarator = node.declarations[0];
34
- if (declarator.id.type !== 'Identifier')
35
- return;
36
- if (!declarator.id.name.startsWith('_page'))
37
- return;
38
- if (!declarator.init || declarator.init.type !== 'ArrowFunctionExpression')
39
- return;
40
- if (!declarator.init.body || declarator.init.body.type !== 'ImportExpression')
41
- return;
42
- if (!declarator.init.body.source || declarator.init.body.source.type !== 'Literal')
43
- return;
44
- const sourceValue = declarator.init.body.source.value;
45
- if (typeof sourceValue !== 'string')
46
- return;
47
- if (prerenderImports.some((importItem) => sourceValue.includes(importItem))) {
48
- // @ts-expect-error - @types/estree seem to be wrong
49
- if (node.start && node.end) {
50
- constsToRemove.push(declarator.id.name);
51
- // @ts-expect-error - @types/estree seem to be wrong
52
- s.remove(node.start, node.end);
53
- }
54
- }
55
- }