Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: improve support for Workers Assets beta #406

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .changeset/honest-ghosts-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
'@astrojs/cloudflare': minor
---

Adds experimental support for Cloudflare Workers Assets mode. To use this, update your settings as follows:

```diff
import cloudflare from '@astrojs/cloudflare';
import { defineConfig } from 'astro/config';

export default defineConfig({
output: 'server',
adapter: cloudflare({
+ experimental: {
+ cloudflare: {
+ workerAssets: true,
+ },
+ }
}),
});
```

Note: Currently Cloudflare Workers Assets mode, does not read any of `_headers`, `_redirects`, nor `_routes.json` files.
147 changes: 82 additions & 65 deletions packages/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ export type Options = {
/** Configuration persistence settings. Default '.wrangler/state/v3' */
persist?: boolean | { path: string };
};

/**
* Allow bundling cloudflare worker specific file types as importable modules. Defaults to true.
* When enabled, allows imports of '.wasm', '.bin', and '.txt' file types
Expand All @@ -72,6 +71,17 @@ export type Options = {
* for reference on how these file types are exported
*/
cloudflareModules?: boolean;
/**
* Lists all experimental features the adapter supports.
*/
experimental?: {
/**
* Enables support for Cloudflare Workers assets. Defaults to false.
*/
cloudflare?: {
workerAssets?: boolean;
};
Comment on lines +78 to +83
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the for a cloudflare field? The experimental option is for the cloudflare adapter, so I suppose there's no need for a cloudflare field. Is there something I miss?

Copy link
Member Author

@alexanderniebuhr alexanderniebuhr Oct 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cloudflare as to show this is a platform/host feature and not adapter or astro feature. Or do we not need that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

platform/host feature and not adapter or astro feature

Oh, I see the point now. However, astro feature VS platform feature is an implementation detail, and it shouldn't be passed down to the user. Other adapters mix astro features and platform features options, which works well.

};
};

function wrapWithSlashes(path: string): string {
Expand Down Expand Up @@ -117,12 +127,17 @@ export default function createIntegration(args?: Options): AstroIntegration {
addWatchFile,
addMiddleware,
}) => {
let clientURL = new URL(`.${wrapWithSlashes(config.base)}`, config.outDir);
if (args?.experimental?.cloudflare?.workerAssets) {
clientURL = new URL(`./assets/${wrapWithSlashes(config.base)}`, config.outDir);
}

updateConfig({
build: {
client: new URL(`.${wrapWithSlashes(config.base)}`, config.outDir),
client: clientURL,
server: new URL('./_worker.js/', config.outDir),
serverEntry: 'index.js',
redirects: false,
redirects: !!args?.experimental?.cloudflare?.workerAssets,
},
vite: {
plugins: [
Expand Down Expand Up @@ -289,79 +304,81 @@ export default function createIntegration(args?: Options): AstroIntegration {
}
}

let redirectsExists = false;
try {
const redirectsStat = await stat(new URL('./_redirects', _config.outDir));
if (redirectsStat.isFile()) {
redirectsExists = true;
if (!args?.experimental?.cloudflare?.workerAssets) {
let redirectsExists = false;
try {
const redirectsStat = await stat(new URL('./_redirects', _config.outDir));
if (redirectsStat.isFile()) {
redirectsExists = true;
}
} catch (error) {
redirectsExists = false;
}
} catch (error) {
redirectsExists = false;
}

const redirects: RouteData['segments'][] = [];
if (redirectsExists) {
const rl = createInterface({
input: createReadStream(new URL('./_redirects', _config.outDir)),
crlfDelay: Number.POSITIVE_INFINITY,
});
const redirects: RouteData['segments'][] = [];
if (redirectsExists) {
const rl = createInterface({
input: createReadStream(new URL('./_redirects', _config.outDir)),
crlfDelay: Number.POSITIVE_INFINITY,
});

for await (const line of rl) {
const parts = line.split(' ');
if (parts.length >= 2) {
const p = removeLeadingForwardSlash(parts[0])
.split('/')
.filter(Boolean)
.map((s: string) => {
const syntax = s
.replace(/\/:.*?(?=\/|$)/g, '/*')
// remove query params as they are not supported by cloudflare
.replace(/\?.*$/, '');
return getParts(syntax);
});
redirects.push(p);
for await (const line of rl) {
const parts = line.split(' ');
if (parts.length >= 2) {
const p = removeLeadingForwardSlash(parts[0])
.split('/')
.filter(Boolean)
.map((s: string) => {
const syntax = s
.replace(/\/:.*?(?=\/|$)/g, '/*')
// remove query params as they are not supported by cloudflare
.replace(/\?.*$/, '');
return getParts(syntax);
});
redirects.push(p);
}
}
}
}

let routesExists = false;
try {
const routesStat = await stat(new URL('./_routes.json', _config.outDir));
if (routesStat.isFile()) {
routesExists = true;
let routesExists = false;
try {
const routesStat = await stat(new URL('./_routes.json', _config.outDir));
if (routesStat.isFile()) {
routesExists = true;
}
} catch (error) {
routesExists = false;
}
} catch (error) {
routesExists = false;
}

if (!routesExists) {
await createRoutesFile(
_config,
logger,
routes,
pages,
redirects,
args?.routes?.extend?.include,
args?.routes?.extend?.exclude
);
}
if (!routesExists) {
await createRoutesFile(
_config,
logger,
routes,
pages,
redirects,
args?.routes?.extend?.include,
args?.routes?.extend?.exclude
);
}

const redirectRoutes: [RouteData, string][] = [];
for (const route of routes) {
if (route.type === 'redirect') redirectRoutes.push([route, '']);
}
const redirectRoutes: [RouteData, string][] = [];
for (const route of routes) {
if (route.type === 'redirect') redirectRoutes.push([route, '']);
}

const trueRedirects = createRedirectsFromAstroRoutes({
config: _config,
routeToDynamicTargetMap: new Map(Array.from(redirectRoutes)),
dir,
});
const trueRedirects = createRedirectsFromAstroRoutes({
config: _config,
routeToDynamicTargetMap: new Map(Array.from(redirectRoutes)),
dir,
});

if (!trueRedirects.empty()) {
try {
await appendFile(new URL('./_redirects', _config.outDir), trueRedirects.print());
} catch (error) {
logger.error('Failed to write _redirects file');
if (!trueRedirects.empty()) {
try {
await appendFile(new URL('./_redirects', _config.outDir), trueRedirects.print());
} catch (error) {
logger.error('Failed to write _redirects file');
}
}
}
},
Expand Down
8 changes: 7 additions & 1 deletion packages/cloudflare/test/fixtures/astro-env/astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ export default defineConfig({
},
},
},
adapter: cloudflare(),
adapter: cloudflare({
experimental: {
cloudflare: {
workerAssets: true,
},
}
}),
output: 'server',
});