@astrojs/cloudflare 0.0.0-cf-10-20240320162001 → 0.0.0-cf-10-solid-20240329084913

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,5 +1,6 @@
1
+ import { joinPaths } from '@astrojs/internal-helpers/path';
1
2
  import { baseService } from 'astro/assets';
2
- import { isESMImportedImage, isRemoteAllowed, joinPaths } from '../utils/assets.js';
3
+ import { isESMImportedImage, isRemoteAllowed } from '../utils/assets.js';
3
4
  const service = {
4
5
  ...baseService,
5
6
  getURL: (options, imageConfig) => {
@@ -1,4 +1,4 @@
1
- import type { CacheStorage as CFCacheStorage, Request as CFRequest, ExecutionContext } from '@cloudflare/workers-types';
1
+ import type { CacheStorage as CLOUDFLARE_CACHESTORAGE, Request as CLOUDFLARE_REQUEST, ExecutionContext } from '@cloudflare/workers-types';
2
2
  import type { SSRManifest } from 'astro';
3
3
  type Env = {
4
4
  ASSETS: {
@@ -6,17 +6,17 @@ type Env = {
6
6
  };
7
7
  ASTRO_STUDIO_APP_TOKEN?: string;
8
8
  };
9
- export interface AdvancedRuntime<T extends object = object> {
9
+ export interface Runtime<T extends object = object> {
10
10
  runtime: {
11
11
  waitUntil: (promise: Promise<any>) => void;
12
12
  env: Env & T;
13
- cf: CFRequest['cf'];
14
- caches: CFCacheStorage;
13
+ cf: CLOUDFLARE_REQUEST['cf'];
14
+ caches: CLOUDFLARE_CACHESTORAGE;
15
15
  };
16
16
  }
17
17
  export declare function createExports(manifest: SSRManifest): {
18
18
  default: {
19
- fetch: (request: Request & CFRequest, env: Env, context: ExecutionContext) => Promise<Response>;
19
+ fetch: (request: Request & CLOUDFLARE_REQUEST, env: Env, context: ExecutionContext) => Promise<Response>;
20
20
  };
21
21
  };
22
22
  export {};
@@ -8,6 +8,13 @@ export function createExports(manifest) {
8
8
  return env.ASSETS.fetch(request.url.replace(/\.html$/, ''));
9
9
  }
10
10
  const routeData = app.match(request);
11
+ if (!routeData) {
12
+ // https://developers.cloudflare.com/pages/functions/api-reference/#envassetsfetch
13
+ const asset = await env.ASSETS.fetch(request.url.replace(/index.html$/, '').replace(/\.html$/, ''));
14
+ if (asset.status !== 404) {
15
+ return asset;
16
+ }
17
+ }
11
18
  Reflect.set(request, Symbol.for('astro.clientAddress'), request.headers.get('cf-connecting-ip'));
12
19
  process.env.ASTRO_STUDIO_APP_TOKEN ??= (() => {
13
20
  if (typeof env.ASTRO_STUDIO_APP_TOKEN === 'string') {
package/dist/index.d.ts CHANGED
@@ -1,15 +1,48 @@
1
1
  import type { AstroIntegration } from 'astro';
2
- export type { AdvancedRuntime } from './entrypoints/server.advanced.js';
2
+ export type { Runtime } from './entrypoints/server.advanced.js';
3
3
  export type Options = {
4
+ /** Options for handling images. */
4
5
  imageService?: 'passthrough' | 'cloudflare' | 'compile';
5
- wasmModuleImports?: boolean;
6
+ /** Configuration for `_routes.json` generation. A _routes.json file controls when your Function is invoked. This file will include three different properties:
7
+ *
8
+ * - version: Defines the version of the schema. Currently there is only one version of the schema (version 1), however, we may add more in the future and aim to be backwards compatible.
9
+ * - include: Defines routes that will be invoked by Functions. Accepts wildcard behavior.
10
+ * - exclude: Defines routes that will not be invoked by Functions. Accepts wildcard behavior. `exclude` always take priority over `include`.
11
+ *
12
+ * Wildcards match any number of path segments (slashes). For example, `/users/*` will match everything after the `/users/` path.
13
+ *
14
+ */
15
+ routes?: {
16
+ /** Extend `_routes.json` */
17
+ extend: {
18
+ /** Paths which should be routed to the SSR function */
19
+ include?: {
20
+ /** Generally this is in pathname format, but does support wildcards, e.g. `/users`, `/products/*` */
21
+ pattern: string;
22
+ }[];
23
+ /** Paths which should be routed as static assets */
24
+ exclude?: {
25
+ /** Generally this is in pathname format, but does support wildcards, e.g. `/static`, `/assets/*`, `/images/avatar.jpg` */
26
+ pattern: string;
27
+ }[];
28
+ };
29
+ };
30
+ /**
31
+ * Proxy configuration for the platform.
32
+ */
6
33
  platformProxy?: {
34
+ /** Toggle the proxy. Default `undefined`, which equals to `false`. */
7
35
  enabled?: boolean;
36
+ /** Path to the configuration file. Default `wrangler.toml`. */
8
37
  configPath?: string;
38
+ /** Enable experimental support for JSON configuration. Default `false`. */
9
39
  experimentalJsonConfig?: boolean;
40
+ /** Configuration persistence settings. Default '.wrangler/state/v3' */
10
41
  persist?: boolean | {
11
42
  path: string;
12
43
  };
13
44
  };
45
+ /** Enable WebAssembly support */
46
+ wasmModuleImports?: boolean;
14
47
  };
15
48
  export default function createIntegration(args?: Options): AstroIntegration;
package/dist/index.js CHANGED
@@ -1,12 +1,11 @@
1
1
  import { createReadStream } from 'node:fs';
2
2
  import { appendFile, rename, stat } from 'node:fs/promises';
3
3
  import { createInterface } from 'node:readline/promises';
4
- import { inspect } from 'node:util';
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
7
  import { getPlatformProxy } from 'wrangler';
8
- import { removeLeadingForwardSlash } from './utils/assets.js';
9
- import createRoutesFile, { getParts } from './utils/generate-routes.js';
8
+ import { createRoutesFile, getParts } from './utils/generate-routes-json.js';
10
9
  import { setImageConfig } from './utils/image-config.js';
11
10
  import { wasmModuleLoader } from './utils/wasm-module-loader.js';
12
11
  export default function createIntegration(args) {
@@ -14,36 +13,24 @@ export default function createIntegration(args) {
14
13
  return {
15
14
  name: '@astrojs/cloudflare',
16
15
  hooks: {
17
- 'astro:config:setup': ({ command, config, updateConfig, logger, addDevToolbarApp, injectRoute, }) => {
16
+ 'astro:config:setup': ({ command, config, updateConfig, logger }) => {
18
17
  updateConfig({
19
18
  build: {
20
- client: new URL(`.${config.base}/`, config.outDir),
19
+ client: new URL(`.${prependForwardSlash(appendForwardSlash(config.base))}`, config.outDir),
21
20
  server: new URL('./_worker.js/', config.outDir),
22
21
  serverEntry: 'index.js',
22
+ redirects: false,
23
23
  },
24
24
  vite: {
25
25
  // load .wasm files as WebAssembly modules
26
26
  plugins: [
27
27
  wasmModuleLoader({
28
28
  disabled: !args?.wasmModuleImports,
29
- isHybrid: config.output === 'hybrid',
30
29
  }),
31
30
  ],
32
31
  },
33
32
  image: setImageConfig(args?.imageService ?? 'DEFAULT', config.image, command, logger),
34
33
  });
35
- // MARK: Dev Toolbar App
36
- if (command === 'dev') {
37
- addDevToolbarApp('@astrojs/cloudflare/dev-toolbar-app');
38
- injectRoute({
39
- pattern: '/_dev-toolbar-app/cloudflare/tabs',
40
- entrypoint: '@astrojs/cloudflare/dev-toolbar-app/tabs.astro',
41
- });
42
- injectRoute({
43
- pattern: '/_dev-toolbar-app/cloudflare/home',
44
- entrypoint: '@astrojs/cloudflare/dev-toolbar-app/test.astro',
45
- });
46
- }
47
34
  },
48
35
  'astro:config:done': ({ setAdapter, config }) => {
49
36
  _config = config;
@@ -71,24 +58,21 @@ export default function createIntegration(args) {
71
58
  },
72
59
  });
73
60
  },
74
- 'astro:server:setup': async ({ server, logger }) => {
61
+ 'astro:server:setup': async ({ server }) => {
75
62
  if (args?.platformProxy?.enabled === true) {
76
- const platform = await getPlatformProxy({
63
+ const platformProxy = await getPlatformProxy({
77
64
  configPath: args.platformProxy.configPath ?? 'wrangler.toml',
78
65
  experimentalJsonConfig: args.platformProxy.experimentalJsonConfig ?? false,
79
66
  persist: args.platformProxy.persist ?? true,
80
67
  });
81
- const tmpString = inspect(platform.env.D1);
82
- console.log(tmpString);
83
- console.log(platform.env.KV);
84
68
  const clientLocalsSymbol = Symbol.for('astro.locals');
85
69
  server.middlewares.use(async function middleware(req, res, next) {
86
70
  Reflect.set(req, clientLocalsSymbol, {
87
71
  runtime: {
88
- env: platform.env,
89
- cf: platform.cf,
90
- caches: platform.caches,
91
- ctx: platform.ctx,
72
+ env: platformProxy.env,
73
+ cf: platformProxy.cf,
74
+ caches: platformProxy.caches,
75
+ ctx: platformProxy.ctx,
92
76
  },
93
77
  });
94
78
  next();
@@ -113,26 +97,41 @@ export default function createIntegration(args) {
113
97
  vite.resolve.alias[alias.find] = alias.replacement;
114
98
  }
115
99
  }
100
+ vite.resolve.conditions ||= [];
101
+ vite.resolve.conditions.push('workerd', 'worker');
116
102
  vite.ssr ||= {};
117
- vite.ssr.noExternal = true;
118
- vite.ssr.external = _config?.vite?.ssr?.external ?? [];
119
103
  vite.ssr.target = 'webworker';
104
+ vite.ssr.noExternal = true;
105
+ vite.ssr.external = _config.vite.ssr?.external ?? [];
120
106
  vite.build ||= {};
121
107
  vite.build.rollupOptions ||= {};
122
108
  vite.build.rollupOptions.output ||= {};
123
- // @ts-ignore
124
- vite.build.rollupOptions.output.banner ||= 'globalThis.process ??= {};';
125
- vite.build.rollupOptions.external = _config?.vite?.build?.rollupOptions?.external ?? [];
109
+ // @ts-expect-error
110
+ vite.build.rollupOptions.output.banner ||=
111
+ 'globalThis.process ??= {}; globalThis.process.env ??= {};';
112
+ vite.build.rollupOptions.external = _config.vite.build?.rollupOptions?.external ?? [];
113
+ // Cloudflare env is only available per request. This isn't feasible for code that access env vars
114
+ // 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`
115
+ vite.define = {
116
+ 'process.env': 'process.env',
117
+ ...vite.define,
118
+ };
119
+ }
120
+ if (target === 'client') {
121
+ vite.resolve ||= {};
122
+ vite.resolve.conditions ||= [];
123
+ vite.resolve.conditions = vite.resolve.conditions.filter((c) => c !== 'workerd' && c !== 'worker');
126
124
  }
127
125
  },
128
- 'astro:build:done': async ({ routes, pages, dir }) => {
126
+ 'astro:build:done': async ({ pages, routes, dir, logger }) => {
127
+ const PLATFORM_FILES = ['_headers', '_redirects', '_routes.json'];
129
128
  if (_config.base !== '/') {
130
- for (const file of ['_headers', '_redirects', '_routes.json']) {
129
+ for (const file of PLATFORM_FILES) {
131
130
  try {
132
131
  await rename(new URL(file, _config.build.client), new URL(file, _config.outDir));
133
132
  }
134
133
  catch (e) {
135
- /* */
134
+ logger.error(`There was an error moving ${file} to the root of the output directory.`);
136
135
  }
137
136
  }
138
137
  }
@@ -180,7 +179,7 @@ export default function createIntegration(args) {
180
179
  routesExists = false;
181
180
  }
182
181
  if (!routesExists) {
183
- await createRoutesFile(_config, routes, pages, redirects);
182
+ await createRoutesFile(_config, logger, routes, pages, redirects, args?.routes?.extend?.include, args?.routes?.extend?.exclude);
184
183
  }
185
184
  const redirectRoutes = [];
186
185
  for (const route of routes) {
@@ -197,7 +196,7 @@ export default function createIntegration(args) {
197
196
  await appendFile(new URL('./_redirects', _config.outDir), trueRedirects.print());
198
197
  }
199
198
  catch (error) {
200
- // TODO
199
+ logger.error('Failed to write _redirects file');
201
200
  }
202
201
  }
203
202
  },
@@ -1,6 +1,5 @@
1
1
  import type { AstroConfig, ImageMetadata, RemotePattern } from 'astro';
2
2
  export declare function isESMImportedImage(src: ImageMetadata | string): src is ImageMetadata;
3
- export declare function isRemotePath(src: string): boolean;
4
3
  export declare function matchHostname(url: URL, hostname?: string, allowWildcard?: boolean): boolean;
5
4
  export declare function matchPort(url: URL, port?: string): boolean;
6
5
  export declare function matchProtocol(url: URL, protocol?: string): boolean;
@@ -8,7 +7,3 @@ export declare function matchPathname(url: URL, pathname?: string, allowWildcard
8
7
  export declare function matchPattern(url: URL, remotePattern: RemotePattern): boolean;
9
8
  export declare function isRemoteAllowed(src: string, { domains, remotePatterns, }: Partial<Pick<AstroConfig['image'], 'domains' | 'remotePatterns'>>): boolean;
10
9
  export declare function isString(path: unknown): path is string;
11
- export declare function removeTrailingForwardSlash(path: string): string;
12
- export declare function removeLeadingForwardSlash(path: string): string;
13
- export declare function trimSlashes(path: string): string;
14
- export declare function joinPaths(...paths: (string | undefined)[]): string;
@@ -1,9 +1,7 @@
1
+ import { isRemotePath } from '@astrojs/internal-helpers/path';
1
2
  export function isESMImportedImage(src) {
2
3
  return typeof src === 'object';
3
4
  }
4
- export function isRemotePath(src) {
5
- return /^(http|ftp|https|ws):?\/\//.test(src) || src.startsWith('data:');
6
- }
7
5
  export function matchHostname(url, hostname, allowWildcard) {
8
6
  if (!hostname) {
9
7
  return true;
@@ -68,26 +66,3 @@ export function isRemoteAllowed(src, { domains = [], remotePatterns = [], }) {
68
66
  export function isString(path) {
69
67
  return typeof path === 'string' || path instanceof String;
70
68
  }
71
- export function removeTrailingForwardSlash(path) {
72
- return path.endsWith('/') ? path.slice(0, path.length - 1) : path;
73
- }
74
- export function removeLeadingForwardSlash(path) {
75
- return path.startsWith('/') ? path.substring(1) : path;
76
- }
77
- export function trimSlashes(path) {
78
- return path.replace(/^\/|\/$/g, '');
79
- }
80
- export function joinPaths(...paths) {
81
- return paths
82
- .filter(isString)
83
- .map((path, i) => {
84
- if (i === 0) {
85
- return removeTrailingForwardSlash(path);
86
- }
87
- if (i === paths.length - 1) {
88
- return removeLeadingForwardSlash(path);
89
- }
90
- return trimSlashes(path);
91
- })
92
- .join('/');
93
- }
@@ -0,0 +1,9 @@
1
+ import type { AstroConfig, AstroIntegrationLogger, RouteData, RoutePart } from 'astro';
2
+ export declare function getParts(part: string): RoutePart[];
3
+ export declare function createRoutesFile(_config: AstroConfig, logger: AstroIntegrationLogger, routes: RouteData[], pages: {
4
+ pathname: string;
5
+ }[], redirects: RouteData['segments'][], includeExtends: {
6
+ pattern: string;
7
+ }[] | undefined, excludeExtends: {
8
+ pattern: string;
9
+ }[] | undefined): Promise<void>;
@@ -0,0 +1,205 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { posix } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { prependForwardSlash, removeLeadingForwardSlash, removeTrailingForwardSlash, } from '@astrojs/internal-helpers/path';
6
+ import glob from 'tiny-glob';
7
+ // Copied from https://github.com/withastro/astro/blob/3776ecf0aa9e08a992d3ae76e90682fd04093721/packages/astro/src/core/routing/manifest/create.ts#L45-L70
8
+ // We're not sure how to improve this regex yet
9
+ const ROUTE_DYNAMIC_SPLIT = /\[(.+?\(.+?\)|.+?)\]/;
10
+ const ROUTE_SPREAD = /^\.{3}.+$/;
11
+ export function getParts(part) {
12
+ const result = [];
13
+ part.split(ROUTE_DYNAMIC_SPLIT).map((str, i) => {
14
+ if (!str)
15
+ return;
16
+ const dynamic = i % 2 === 1;
17
+ const [, content] = dynamic ? /([^(]+)$/.exec(str) || [null, null] : [null, str];
18
+ if (!content || (dynamic && !/^(?:\.\.\.)?[\w$]+$/.test(content))) {
19
+ throw new Error('Parameter name must match /^[a-zA-Z0-9_$]+$/');
20
+ }
21
+ result.push({
22
+ content,
23
+ dynamic,
24
+ spread: dynamic && ROUTE_SPREAD.test(content),
25
+ });
26
+ });
27
+ return result;
28
+ }
29
+ async function writeRoutesFileToOutDir(_config, logger, include, exclude) {
30
+ try {
31
+ await writeFile(new URL('./_routes.json', _config.outDir), JSON.stringify({
32
+ version: 1,
33
+ include: include,
34
+ exclude: exclude,
35
+ }, null, 2), 'utf-8');
36
+ }
37
+ catch (error) {
38
+ logger.error("There was an error writing the '_routes.json' file to the output directory.");
39
+ }
40
+ }
41
+ function segmentsToCfSyntax(segments, _config) {
42
+ const pathSegments = [];
43
+ if (removeLeadingForwardSlash(removeTrailingForwardSlash(_config.base)).length > 0) {
44
+ pathSegments.push(removeLeadingForwardSlash(removeTrailingForwardSlash(_config.base)));
45
+ }
46
+ for (const segment of segments.flat()) {
47
+ if (segment.dynamic)
48
+ pathSegments.push('*');
49
+ else
50
+ pathSegments.push(segment.content);
51
+ }
52
+ return pathSegments;
53
+ }
54
+ class TrieNode {
55
+ children = new Map();
56
+ isEndOfPath = false;
57
+ hasWildcardChild = false;
58
+ }
59
+ class PathTrie {
60
+ root;
61
+ constructor() {
62
+ this.root = new TrieNode();
63
+ }
64
+ insert(path) {
65
+ let node = this.root;
66
+ for (const segment of path) {
67
+ if (segment === '*') {
68
+ node.hasWildcardChild = true;
69
+ break;
70
+ }
71
+ if (!node.children.has(segment)) {
72
+ node.children.set(segment, new TrieNode());
73
+ }
74
+ // biome-ignore lint/style/noNonNullAssertion: The `if` condition above ensures that the segment exists inside the map
75
+ node = node.children.get(segment);
76
+ }
77
+ node.isEndOfPath = true;
78
+ }
79
+ /**
80
+ * Depth-first search (dfs), traverses the "graph" segment by segment until the end or wildcard (*).
81
+ * It makes sure that all necessary paths are returned, but not paths with an existing wildcard prefix.
82
+ * e.g. if we have a path like /foo/* and /foo/bar, we only want to return /foo/*
83
+ */
84
+ dfs(node, path, allPaths) {
85
+ if (node.hasWildcardChild) {
86
+ allPaths.push([...path, '*']);
87
+ return;
88
+ }
89
+ if (node.isEndOfPath) {
90
+ allPaths.push([...path]);
91
+ }
92
+ for (const [segment, childNode] of node.children) {
93
+ this.dfs(childNode, [...path, segment], allPaths);
94
+ }
95
+ }
96
+ getAllPaths() {
97
+ const allPaths = [];
98
+ this.dfs(this.root, [], allPaths);
99
+ return allPaths;
100
+ }
101
+ }
102
+ export async function createRoutesFile(_config, logger, routes, pages, redirects, includeExtends, excludeExtends) {
103
+ const includePaths = [];
104
+ const excludePaths = [];
105
+ let hasPrerendered404 = false;
106
+ for (const route of routes) {
107
+ const convertedPath = segmentsToCfSyntax(route.segments, _config);
108
+ if (route.pathname === '/404' && route.prerender === true)
109
+ hasPrerendered404 = true;
110
+ switch (route.type) {
111
+ case 'page':
112
+ if (route.prerender === false)
113
+ includePaths.push(convertedPath);
114
+ break;
115
+ case 'endpoint':
116
+ if (route.prerender === false)
117
+ includePaths.push(convertedPath);
118
+ else
119
+ excludePaths.push(convertedPath);
120
+ break;
121
+ case 'redirect':
122
+ excludePaths.push(convertedPath);
123
+ break;
124
+ default:
125
+ /**
126
+ * We don't know the type, so we are conservative!
127
+ * Invoking the function on these is a safe-bet because
128
+ * the function will fallback to static asset fetching
129
+ */
130
+ includePaths.push(convertedPath);
131
+ break;
132
+ }
133
+ }
134
+ for (const page of pages) {
135
+ const pageSegments = removeLeadingForwardSlash(page.pathname)
136
+ .split(posix.sep)
137
+ .filter(Boolean)
138
+ .map((s) => {
139
+ return getParts(s);
140
+ });
141
+ excludePaths.push(segmentsToCfSyntax(pageSegments, _config));
142
+ }
143
+ if (existsSync(fileURLToPath(_config.publicDir))) {
144
+ const staticFiles = await glob(`${fileURLToPath(_config.publicDir)}/**/*`, {
145
+ cwd: fileURLToPath(_config.publicDir),
146
+ filesOnly: true,
147
+ dot: true,
148
+ });
149
+ for (const staticFile of staticFiles) {
150
+ if (['_headers', '_redirects', '_routes.json'].includes(staticFile))
151
+ continue;
152
+ const staticPath = staticFile;
153
+ const segments = removeLeadingForwardSlash(staticPath)
154
+ .split(posix.sep)
155
+ .filter(Boolean)
156
+ .map((s) => {
157
+ return getParts(s);
158
+ });
159
+ excludePaths.push(segmentsToCfSyntax(segments, _config));
160
+ }
161
+ }
162
+ /**
163
+ * All files in the `_config.build.assets` path, e.g. `_astro`
164
+ * are considered static assets and should not be handled by the function
165
+ * therefore we exclude a wildcard for that, e.g. `/_astro/*`
166
+ */
167
+ const assetsPath = segmentsToCfSyntax([
168
+ [{ content: _config.build.assets, dynamic: false, spread: false }],
169
+ [{ content: '', dynamic: true, spread: false }],
170
+ ], _config);
171
+ excludePaths.push(assetsPath);
172
+ for (const redirect of redirects) {
173
+ excludePaths.push(segmentsToCfSyntax(redirect, _config));
174
+ }
175
+ const includeTrie = new PathTrie();
176
+ for (const includePath of includePaths) {
177
+ includeTrie.insert(includePath);
178
+ }
179
+ const deduplicatedIncludePaths = includeTrie.getAllPaths();
180
+ const excludeTrie = new PathTrie();
181
+ for (const excludePath of excludePaths) {
182
+ excludeTrie.insert(excludePath);
183
+ }
184
+ const deduplicatedExcludePaths = excludeTrie.getAllPaths();
185
+ /**
186
+ * Cloudflare allows no more than 100 include/exclude rules combined
187
+ * https://developers.cloudflare.com/pages/functions/routing/#limits
188
+ */
189
+ const CLOUDFLARE_COMBINED_LIMIT = 100;
190
+ if (!hasPrerendered404 ||
191
+ deduplicatedIncludePaths.length + (includeExtends?.length ?? 0) > CLOUDFLARE_COMBINED_LIMIT ||
192
+ deduplicatedExcludePaths.length + (excludeExtends?.length ?? 0) > CLOUDFLARE_COMBINED_LIMIT) {
193
+ await writeRoutesFileToOutDir(_config, logger, ['/*'].concat(includeExtends?.map((entry) => entry.pattern) ?? []), deduplicatedExcludePaths
194
+ .map((path) => `${prependForwardSlash(path.join('/'))}`)
195
+ .concat(excludeExtends?.map((entry) => entry.pattern) ?? [])
196
+ .slice(0, 99));
197
+ }
198
+ else {
199
+ await writeRoutesFileToOutDir(_config, logger, deduplicatedIncludePaths
200
+ .map((path) => `${prependForwardSlash(path.join('/'))}`)
201
+ .concat(includeExtends?.map((entry) => entry.pattern) ?? []), deduplicatedExcludePaths
202
+ .map((path) => `${prependForwardSlash(path.join('/'))}`)
203
+ .concat(excludeExtends?.map((entry) => entry.pattern) ?? []));
204
+ }
205
+ }
@@ -8,7 +8,6 @@ import type { AstroConfig } from 'astro';
8
8
  * @param assetsDirectory - the folder name for the assets directory in the build directory. Usually '_astro'
9
9
  * @returns Vite plugin to load WASM tagged with '?module' as a WASM modules
10
10
  */
11
- export declare function wasmModuleLoader({ disabled, isHybrid, }: {
11
+ export declare function wasmModuleLoader({ disabled, }: {
12
12
  disabled: boolean;
13
- isHybrid: boolean;
14
13
  }): NonNullable<AstroConfig['vite']['plugins']>[number];
@@ -9,7 +9,7 @@ import * as path from 'node:path';
9
9
  * @param assetsDirectory - the folder name for the assets directory in the build directory. Usually '_astro'
10
10
  * @returns Vite plugin to load WASM tagged with '?module' as a WASM modules
11
11
  */
12
- export function wasmModuleLoader({ disabled, isHybrid, }) {
12
+ export function wasmModuleLoader({ disabled, }) {
13
13
  const postfix = '.wasm?module';
14
14
  let isDev = false;
15
15
  return {
@@ -24,6 +24,7 @@ export function wasmModuleLoader({ disabled, isHybrid, }) {
24
24
  assetsInclude: ['**/*.wasm?module'],
25
25
  build: {
26
26
  rollupOptions: {
27
+ // mark the wasm files as external so that they are not bundled and instead are loaded from the files
27
28
  external: [/^__WASM_ASSET__.+\.wasm$/i, /^__WASM_ASSET__.+\.wasm.mjs$/i],
28
29
  },
29
30
  },
@@ -71,9 +72,10 @@ export function wasmModuleLoader({ disabled, isHybrid, }) {
71
72
  return;
72
73
  if (!/__WASM_ASSET__/g.test(code))
73
74
  return;
75
+ const isPrerendered = Object.keys(chunk.modules).some((moduleId) => this.getModuleInfo(moduleId)?.meta?.astro?.pageOptions?.prerender === true);
74
76
  let final = code;
75
77
  // SSR
76
- if (isHybrid && chunk.exports.includes('prerender') && code.includes('prerender = false;')) {
78
+ if (!isPrerendered) {
77
79
  final = code.replaceAll(/__WASM_ASSET__([A-Za-z\d]+).wasm.mjs/g, (s, assetId) => {
78
80
  const fileName = this.getFileName(assetId).replace(/\.mjs$/, '');
79
81
  const relativePath = path
@@ -82,28 +84,8 @@ export function wasmModuleLoader({ disabled, isHybrid, }) {
82
84
  return `./${relativePath}`;
83
85
  });
84
86
  }
85
- // SSR
86
- if (!isHybrid && !chunk.exports.includes('prerender')) {
87
- final = code.replaceAll(/__WASM_ASSET__([A-Za-z\d]+).wasm.mjs/g, (s, assetId) => {
88
- const fileName = this.getFileName(assetId);
89
- const relativePath = path
90
- .relative(path.dirname(chunk.fileName), fileName)
91
- .replaceAll('\\', '/'); // fix windows paths for import
92
- return `./${relativePath}`;
93
- });
94
- }
95
- // SSG
96
- if (isHybrid && !chunk.exports.includes('prerender')) {
97
- final = code.replaceAll(/__WASM_ASSET__([A-Za-z\d]+).wasm.mjs/g, (s, assetId) => {
98
- const fileName = this.getFileName(assetId);
99
- const relativePath = path
100
- .relative(path.dirname(chunk.fileName), fileName)
101
- .replaceAll('\\', '/'); // fix windows paths for import
102
- return `./${relativePath}`;
103
- });
104
- }
105
87
  // SSG
106
- if (!isHybrid && chunk.exports.includes('prerender') && code.includes('prerender = true;')) {
88
+ if (isPrerendered) {
107
89
  final = code.replaceAll(/__WASM_ASSET__([A-Za-z\d]+).wasm.mjs/g, (s, assetId) => {
108
90
  const fileName = this.getFileName(assetId);
109
91
  const relativePath = path
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": "0.0.0-cf-10-20240320162001",
4
+ "version": "0.0.0-cf-10-solid-20240329084913",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "author": "withastro",
@@ -23,9 +23,6 @@
23
23
  "./entrypoints/server.directory.js": "./dist/entrypoints/server.directory.js",
24
24
  "./image-service": "./dist/entrypoints/image-service.js",
25
25
  "./image-endpoint": "./dist/entrypoints/image-endpoint.js",
26
- "./dev-toolbar-app": "./dist/entrypoints/dev-toolbar-app.js",
27
- "./dev-toolbar-app/tabs.astro": "./dist/dev-toolbar-app/tabs.astro",
28
- "./dev-toolbar-app/test.astro": "./dist/dev-toolbar-app/test.astro",
29
26
  "./package.json": "./package.json"
30
27
  },
31
28
  "files": [
@@ -33,31 +30,30 @@
33
30
  ],
34
31
  "dependencies": {
35
32
  "@astrojs/underscore-redirects": "^0.3.3",
36
- "@cloudflare/workers-types": "^4.20240314.0",
37
- "dotenv": "^16.4.5",
38
- "miniflare": "^3.20240314.0",
33
+ "@astrojs/internal-helpers": "0.3.0",
34
+ "@cloudflare/workers-types": "^4.20240320.1",
35
+ "miniflare": "^3.20240320.0",
36
+ "esbuild": "^0.19.5",
39
37
  "tiny-glob": "^0.2.9",
40
- "wrangler": "^3.35.0"
38
+ "wrangler": "^3.36.0"
41
39
  },
42
40
  "peerDependencies": {
43
41
  "astro": "^4.2.0"
44
42
  },
45
43
  "devDependencies": {
46
- "astro": "^4.5.7",
47
- "cheerio": "1.0.0-rc.12",
48
44
  "execa": "^8.0.1",
49
45
  "fast-glob": "^3.3.2",
50
46
  "strip-ansi": "^7.1.0",
51
- "rimraf": "^5.0.5",
52
- "copyfiles": "^2.4.1",
53
- "@astrojs/test-utils": "0.0.1",
54
- "astro-scripts": "0.0.14"
47
+ "astro": "^4.5.8",
48
+ "cheerio": "1.0.0-rc.12",
49
+ "astro-scripts": "0.0.14",
50
+ "@astrojs/test-utils": "0.0.1"
55
51
  },
56
52
  "publishConfig": {
57
53
  "provenance": true
58
54
  },
59
55
  "scripts": {
60
- "build": "tsc && copyfiles -u 1 src/dev-toolbar-app/*.astro dist/",
56
+ "build": "tsc",
61
57
  "test": "astro-scripts test \"test/**/*.test.js\""
62
58
  }
63
59
  }
@@ -1,20 +0,0 @@
1
- ---
2
- export const partial = true;
3
- ---
4
-
5
- <div>
6
- <button onclick="fetchHome(this)">Env</button>
7
- <button>D1</button>
8
- </div>
9
-
10
- <script is:inline>
11
- var fetchHome = function (element) {
12
- console.log(element.getRootNode());
13
- fetch('/_dev-toolbar-app/cloudflare/home')
14
- .then((res) => res.text())
15
- .then((html) => {
16
- const el = document.createRange().createContextualFragment(html);
17
- element.getRootNode().querySelector('#target').replaceChildren(el);
18
- });
19
- };
20
- </script>
@@ -1,15 +0,0 @@
1
- ---
2
- export const partial = true;
3
-
4
- const { env } = Astro.locals.runtime;
5
- ---
6
-
7
- <pre onclick="me(this)">
8
- {JSON.stringify(env, null, 2)}
9
- </pre>
10
-
11
- <script is:inline>
12
- var me = function (element) {
13
- console.log('me');
14
- };
15
- </script>
@@ -1,3 +0,0 @@
1
- import type { DevToolbarApp } from 'astro';
2
- declare const plugin: DevToolbarApp;
3
- export default plugin;
@@ -1,36 +0,0 @@
1
- const plugin = {
2
- id: 'cloudflare-app',
3
- name: 'Cloudflare DevToolbarApp',
4
- icon: '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M10.715 14.32H5.442l-.64-1.203L13.673 0l1.397.579-1.752 9.112h5.24l.648 1.192L10.719 24l-1.412-.54ZM4.091 5.448a.579.579 0 1 1 0-1.157.579.579 0 0 1 0 1.157m1.543 0a.579.579 0 1 1 0-1.157.579.579 0 0 1 0 1.157m1.544 0a.579.579 0 1 1 0-1.157.579.579 0 0 1 0 1.157m8.657-2.7h5.424l.772.771v16.975l-.772.772h-7.392l.374-.579h6.779l.432-.432V3.758l-.432-.432h-4.676l-.552 2.85h-.59l.529-2.877.108-.552ZM2.74 21.265l-.772-.772V3.518l.772-.771h7.677l-.386.579H2.98l-.432.432v16.496l.432.432h5.586l-.092.579zm1.157-1.93h3.28l-.116.58h-3.55l-.192-.193v-3.473l.578 1.158zm13.117 0 .579.58H14.7l.385-.58z"/></svg>',
5
- init(canvas, eventTarget) {
6
- const page = `
7
- <script>
8
- var loadTabs = function (element) {
9
- console.log(this);
10
- console.log(element);
11
- fetch('/_dev-toolbar-app/cloudflare/tabs')
12
- .then((res) => res.text())
13
- .then((html) => {
14
- const el = document.createRange().createContextualFragment(html);
15
- element.getRootNode().querySelector('#tabs').replaceChildren(el);
16
- });
17
- };
18
- </script>
19
- <div id="tabs"></div>
20
- <div id="target"></div>
21
- `;
22
- const pageEl = document.createRange().createContextualFragment(page);
23
- const cfWindow = document.createElement('astro-dev-toolbar-window');
24
- cfWindow.append(pageEl);
25
- canvas.appendChild(cfWindow);
26
- eventTarget.addEventListener('app-toggled', (event) => {
27
- // @ts-ignore
28
- if (event.detail.state === true) {
29
- console.log('The app is now enabled!');
30
- // @ts-ignore
31
- loadTabs(cfWindow);
32
- }
33
- });
34
- },
35
- };
36
- export default plugin;
@@ -1,21 +0,0 @@
1
- import type { AstroConfig, RouteData, RoutePart } from 'astro';
2
- /**
3
- * create _routes.json automatically
4
- * two approaches:
5
- * include everything and exclude static
6
- * if 404 is not prerendered, we need to include everything
7
- * include only ssr paths
8
- * we want to use as much wildcards as possible
9
- * assetsURL should be excluded via wildcard
10
- * _workers.js dir should not be in the routes anyway (cause it is the server bundle)
11
- * we would work based on routes (endpoint / page)
12
- * static files should be based on src rather than dist
13
- * static files should not include cloudflare special files
14
- * we need forwardslash
15
- * we need to add the base path
16
- * redirects from _redirects should be excluded
17
- */
18
- export declare const getParts: (part: string) => RoutePart[];
19
- export default function (_config: AstroConfig, routes: RouteData[], pages: {
20
- pathname: string;
21
- }[], redirects: RoutePart[][][]): Promise<void>;
@@ -1,199 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { writeFile } from 'node:fs/promises';
3
- import { posix } from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
- import glob from 'tiny-glob';
6
- import { removeLeadingForwardSlash, removeTrailingForwardSlash } from './assets.js';
7
- /**
8
- * create _routes.json automatically
9
- * two approaches:
10
- * include everything and exclude static
11
- * if 404 is not prerendered, we need to include everything
12
- * include only ssr paths
13
- * we want to use as much wildcards as possible
14
- * assetsURL should be excluded via wildcard
15
- * _workers.js dir should not be in the routes anyway (cause it is the server bundle)
16
- * we would work based on routes (endpoint / page)
17
- * static files should be based on src rather than dist
18
- * static files should not include cloudflare special files
19
- * we need forwardslash
20
- * we need to add the base path
21
- * redirects from _redirects should be excluded
22
- */
23
- export const getParts = (part) => {
24
- const result = [];
25
- part.split(/\[(.+?\(.+?\)|.+?)\]/).map((str, i) => {
26
- if (!str)
27
- return;
28
- const dynamic = i % 2 === 1;
29
- const [, content] = dynamic ? /([^(]+)$/.exec(str) || [null, null] : [null, str];
30
- if (!content || (dynamic && !/^(?:\.\.\.)?[\w$]+$/.test(content))) {
31
- throw new Error('Parameter name must match /^[a-zA-Z0-9_$]+$/');
32
- }
33
- result.push({
34
- content,
35
- dynamic,
36
- spread: dynamic && /^\.{3}.+$/.test(content),
37
- });
38
- });
39
- return result;
40
- };
41
- const segmentsToCfSyntax = (segments, _config) => {
42
- if (segments.length === 0)
43
- return ['', ''];
44
- const pathSegments = [removeLeadingForwardSlash(removeTrailingForwardSlash(_config.base))];
45
- for (const segment of segments.flat()) {
46
- if (segment.dynamic)
47
- pathSegments.push('*');
48
- else
49
- pathSegments.push(segment.content);
50
- }
51
- return pathSegments;
52
- };
53
- const deduplicateInPlace = (element, index, paths) => {
54
- const regexp = new RegExp(`${element.join('/').replace(/(\*\/)*\*$/g, '[*\\w\\/]+')}$`, 'gm');
55
- for (let i = index + 1; i < paths.length; i++) {
56
- if (regexp.test(paths[i].join('/'))) {
57
- paths.splice(i, 1);
58
- i--;
59
- }
60
- }
61
- };
62
- const sort = (first, second) => {
63
- // more segements should be sorted first
64
- if (second.length > first.length)
65
- return -1;
66
- if (first.length > second.length)
67
- return 1;
68
- // equal amount of segments, sort by specifity
69
- for (let i = 0; i < first.length; i++) {
70
- // if segment is equal, continue with next segment
71
- if (first[i] === second[i])
72
- continue;
73
- // wildcard segments should be sorted last
74
- if (first[i] === '*' && second[i] !== '*')
75
- return -1;
76
- if (first[i] !== '*' && second[i] === '*')
77
- return 1;
78
- }
79
- return 0;
80
- };
81
- export default async function (_config, routes, pages, redirects) {
82
- const includePaths = [];
83
- const excludePaths = [];
84
- let hasPrerendered404 = false;
85
- for (const route of routes) {
86
- const convertedPath = segmentsToCfSyntax(route.segments, _config);
87
- if (route.pathname === '/404' && route.prerender === true)
88
- hasPrerendered404 = true;
89
- if (route.type === 'page')
90
- if (route.prerender === false)
91
- includePaths.push(convertedPath);
92
- if (route.type === 'endpoint')
93
- if (route.prerender === false)
94
- includePaths.push(convertedPath);
95
- else
96
- excludePaths.push(convertedPath);
97
- if (route.type === 'redirect')
98
- excludePaths.push(convertedPath);
99
- }
100
- for (const page of pages) {
101
- const pageSegments = removeLeadingForwardSlash(page.pathname)
102
- .split(posix.sep)
103
- .filter(Boolean)
104
- .map((s) => {
105
- return getParts(s);
106
- });
107
- excludePaths.push(segmentsToCfSyntax(pageSegments, _config));
108
- }
109
- if (existsSync(fileURLToPath(_config.publicDir))) {
110
- const staticFiles = await glob(`${fileURLToPath(_config.publicDir)}/**/*`, {
111
- cwd: fileURLToPath(_config.publicDir),
112
- filesOnly: true,
113
- dot: true,
114
- });
115
- for (const staticFile of staticFiles) {
116
- if (['_headers', '_redirects', '_routes.json'].includes(staticFile))
117
- continue;
118
- const staticPath = staticFile;
119
- const segments = removeLeadingForwardSlash(staticPath)
120
- .split(posix.sep)
121
- .filter(Boolean)
122
- .map((s) => {
123
- return getParts(s);
124
- });
125
- excludePaths.push(segmentsToCfSyntax(segments, _config));
126
- }
127
- }
128
- const assetsPath = segmentsToCfSyntax([
129
- [{ content: _config.build.assets, dynamic: false, spread: false }],
130
- [{ content: '', dynamic: true, spread: false }],
131
- ], _config);
132
- excludePaths.push(assetsPath);
133
- const pagefindPath = segmentsToCfSyntax([
134
- [{ content: 'pagefind', dynamic: false, spread: false }],
135
- [{ content: '', dynamic: true, spread: false }],
136
- ], _config);
137
- excludePaths.push(pagefindPath);
138
- for (const redirect of redirects) {
139
- excludePaths.push(segmentsToCfSyntax(redirect, _config));
140
- }
141
- includePaths.sort(sort);
142
- excludePaths.sort(sort);
143
- console.log(includePaths);
144
- console.log('---');
145
- console.log(excludePaths);
146
- for (const [index, element] of includePaths.entries()) {
147
- deduplicateInPlace(element, index, includePaths);
148
- }
149
- for (const [index, element] of excludePaths.entries()) {
150
- console.log(element);
151
- deduplicateInPlace(element, index, excludePaths);
152
- }
153
- console.log('------');
154
- console.log(includePaths);
155
- console.log('---');
156
- console.log(excludePaths);
157
- console.log('------');
158
- console.log('INCLUDE', includePaths.length);
159
- console.log('---');
160
- console.log('EXCLUDE', excludePaths.length);
161
- if (!hasPrerendered404 ||
162
- includePaths.length > 100 ||
163
- includePaths.length > excludePaths.length) {
164
- try {
165
- await writeFile(new URL('./_routes.json', _config.outDir), JSON.stringify({
166
- version: 1,
167
- include: ['/*'],
168
- exclude: excludePaths.map((path) => path.join('/')).slice(0, 99),
169
- }, null, 2), 'utf-8');
170
- }
171
- catch (error) {
172
- // TODO
173
- }
174
- }
175
- else if (includePaths.length < excludePaths.length) {
176
- try {
177
- await writeFile(new URL('./_routes.json', _config.outDir), JSON.stringify({
178
- version: 1,
179
- include: includePaths.map((path) => path.join('/')),
180
- exclude: [],
181
- }, null, 2), 'utf-8');
182
- }
183
- catch (error) {
184
- // TODO
185
- }
186
- }
187
- else {
188
- try {
189
- await writeFile(new URL('./_routes.json', _config.outDir), JSON.stringify({
190
- version: 1,
191
- include: ['/*'],
192
- exclude: excludePaths.map((path) => path.join('/')).slice(0, 99),
193
- }, null, 2), 'utf-8');
194
- }
195
- catch (error) {
196
- // TODO
197
- }
198
- }
199
- }
@@ -1,2 +0,0 @@
1
- export declare const isNode: boolean;
2
- export declare function getProcessEnvProxy(): {};
@@ -1,10 +0,0 @@
1
- export const isNode = typeof process === 'object' && Object.prototype.toString.call(process) === '[object process]';
2
- export function getProcessEnvProxy() {
3
- return new Proxy({}, {
4
- get: (target, prop) => {
5
- console.warn(
6
- // NOTE: \0 prevents Vite replacement
7
- `Unable to access \`import.meta\0.env.${prop.toString()}\` on initialization as the Cloudflare platform only provides the environment variables per request. Please move the environment variable access inside a function that's only called after a request has been received.`);
8
- },
9
- });
10
- }