@recranet/astro-workers-for-platforms-adapter 1.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.
- package/README.md +1 -0
- package/adapter.mjs +58 -0
- package/package.json +21 -0
- package/preview-entry.mjs +30 -0
- package/vite-plugin-astro-preview.mjs +53 -0
- package/worker-entry.mjs +26 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# astro-workers-for-platforms-adapter
|
package/adapter.mjs
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Custom Astro adapter for Cloudflare Workers for Platforms.
|
|
2
|
+
//
|
|
3
|
+
// Architecture:
|
|
4
|
+
// Request → Dispatch Worker → User Worker (worker-entry.mjs) → env.ASSETS.fetch()
|
|
5
|
+
//
|
|
6
|
+
// This adapter ensures Astro always emits a worker entry, even for fully static sites.
|
|
7
|
+
// While static assets could be uploaded to R2 and served by the dispatch worker,
|
|
8
|
+
// Workers for Platforms supports a native Static Assets binding (env.ASSETS) that
|
|
9
|
+
// serves files from the edge with per-tenant isolation — no R2 needed.
|
|
10
|
+
//
|
|
11
|
+
// The official @astrojs/cloudflare adapter cannot be used here because:
|
|
12
|
+
// - It requires wrangler as a peer dependency (we deploy via the CF API directly)
|
|
13
|
+
// - As of Astro 6 (PR #15478), it deletes the _worker.js output for fully static
|
|
14
|
+
// sites, which is the opposite of what Workers for Platforms needs
|
|
15
|
+
// - Its worker entrypoint is designed for Pages/Workers, not the dispatch namespace
|
|
16
|
+
// API with env.ASSETS binding and run_worker_first config
|
|
17
|
+
//
|
|
18
|
+
// Build flow:
|
|
19
|
+
// `astro build` triggers this adapter, which sets `ssr.noExternal: true` so Vite
|
|
20
|
+
// bundles all dependencies (including hono/utils/mime used in worker-entry.mjs)
|
|
21
|
+
// into a single dist/server/entry.mjs with no external imports. Static assets
|
|
22
|
+
// are prerendered into dist/client/.
|
|
23
|
+
//
|
|
24
|
+
// The build output (entry.mjs + static assets) is deployed by the astro-sandbox
|
|
25
|
+
// worker in studio via deploy.ts, which uses the Workers for Platforms 3-step asset
|
|
26
|
+
// upload API (create session → upload buckets → deploy worker with completion token).
|
|
27
|
+
export default function cloudflareWorkerScriptAdapter() {
|
|
28
|
+
return {
|
|
29
|
+
name: 'cloudflare-worker-script',
|
|
30
|
+
hooks: {
|
|
31
|
+
'astro:config:setup': ({ updateConfig }) => {
|
|
32
|
+
updateConfig({
|
|
33
|
+
vite: {
|
|
34
|
+
ssr: {
|
|
35
|
+
// Bundle all dependencies into the server entry (no external imports)
|
|
36
|
+
noExternal: true,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
'astro:config:done': ({ setAdapter }) => {
|
|
42
|
+
setAdapter({
|
|
43
|
+
name: 'cloudflare-worker-script',
|
|
44
|
+
serverEntrypoint: new URL('./worker-entry.mjs', import.meta.url),
|
|
45
|
+
previewEntrypoint: new URL('./preview-entry.mjs', import.meta.url).pathname,
|
|
46
|
+
entrypointResolution: 'auto',
|
|
47
|
+
supportedAstroFeatures: {
|
|
48
|
+
staticOutput: 'stable',
|
|
49
|
+
hybridOutput: { support: 'unsupported', message: '', suppress: 'all' },
|
|
50
|
+
serverOutput: { support: 'unsupported', message: '', suppress: 'all' },
|
|
51
|
+
sharpImageService: { support: 'unsupported', message: '', suppress: 'all' },
|
|
52
|
+
envGetSecret: 'stable',
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@recranet/astro-workers-for-platforms-adapter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": "./adapter.mjs",
|
|
6
|
+
"files": [
|
|
7
|
+
"adapter.mjs",
|
|
8
|
+
"worker-entry.mjs",
|
|
9
|
+
"preview-entry.mjs",
|
|
10
|
+
"vite-plugin-astro-preview.mjs"
|
|
11
|
+
],
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"astro": "^6.0.0"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"hono": "^4.12.7"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"publish": "npm publish . --access public"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { preview } from 'vite';
|
|
3
|
+
import { vitePluginAstroPreview } from './vite-plugin-astro-preview.mjs';
|
|
4
|
+
|
|
5
|
+
export default async function createPreviewServer({ client, host, port, headers, logger }) {
|
|
6
|
+
const clientDir = fileURLToPath(client);
|
|
7
|
+
|
|
8
|
+
const previewServer = await preview({
|
|
9
|
+
configFile: false,
|
|
10
|
+
appType: 'mpa',
|
|
11
|
+
build: { outDir: clientDir },
|
|
12
|
+
preview: { host, port, headers },
|
|
13
|
+
plugins: [vitePluginAstroPreview(clientDir)],
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const address = previewServer.resolvedUrls?.local?.[0] ?? `http://${host ?? 'localhost'}:${port}/`;
|
|
17
|
+
logger.info(`Preview server listening on ${address}`);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
host: host ?? 'localhost',
|
|
21
|
+
port,
|
|
22
|
+
closed: () =>
|
|
23
|
+
new Promise((resolve, reject) => {
|
|
24
|
+
previewServer.httpServer.addListener('close', resolve);
|
|
25
|
+
previewServer.httpServer.addListener('error', reject);
|
|
26
|
+
}),
|
|
27
|
+
server: previewServer.httpServer,
|
|
28
|
+
stop: previewServer.close.bind(previewServer),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Adapted from astro/dist/core/preview/vite-plugin-astro-preview.js
|
|
2
|
+
// Handles clean URL routing for static builds in Vite's MPA preview server.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
export function vitePluginAstroPreview(outDir) {
|
|
7
|
+
return {
|
|
8
|
+
name: 'astro:preview',
|
|
9
|
+
apply: 'serve',
|
|
10
|
+
configurePreviewServer(server) {
|
|
11
|
+
// Replace Vite's default 404 handler with one that serves 404.html if it exists
|
|
12
|
+
server.middlewares.use((req, res, next) => {
|
|
13
|
+
for (const middleware of server.middlewares.stack) {
|
|
14
|
+
if (middleware.handle.name === 'vite404Middleware') {
|
|
15
|
+
middleware.handle = (_req, _res) => {
|
|
16
|
+
const errorPagePath = join(outDir, '404.html');
|
|
17
|
+
if (fs.existsSync(errorPagePath)) {
|
|
18
|
+
_res.statusCode = 404;
|
|
19
|
+
_res.setHeader('Content-Type', 'text/html');
|
|
20
|
+
_res.end(fs.readFileSync(errorPagePath));
|
|
21
|
+
} else {
|
|
22
|
+
_res.statusCode = 404;
|
|
23
|
+
_res.end('Not Found');
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
next();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Post-middleware: rewrite clean URLs to their HTML files
|
|
32
|
+
return () => {
|
|
33
|
+
server.middlewares.use((req, _res, next) => {
|
|
34
|
+
const pathname = req.url?.split('?')[0];
|
|
35
|
+
if (pathname?.endsWith('/')) {
|
|
36
|
+
const htmlPath = join(outDir, pathname.slice(0, -1) + '.html');
|
|
37
|
+
if (fs.existsSync(htmlPath)) {
|
|
38
|
+
req.url = pathname.slice(0, -1) + '.html';
|
|
39
|
+
return next();
|
|
40
|
+
}
|
|
41
|
+
} else if (pathname && !pathname.includes('.')) {
|
|
42
|
+
const indexPath = join(outDir, pathname, 'index.html');
|
|
43
|
+
if (fs.existsSync(indexPath)) {
|
|
44
|
+
req.url = pathname + '/index.html';
|
|
45
|
+
return next();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
next();
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
package/worker-entry.mjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Worker entry point for Cloudflare Workers for Platforms.
|
|
2
|
+
// Serves static assets via the env.ASSETS binding provided by the dispatch namespace,
|
|
3
|
+
// with fixes for missing content-type headers and cache-control.
|
|
4
|
+
import { getMimeType } from 'hono/utils/mime';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
async fetch(request, env) {
|
|
8
|
+
const asset = await env.ASSETS.fetch(request);
|
|
9
|
+
|
|
10
|
+
// Clone into a mutable response so we can set headers.
|
|
11
|
+
const response = new Response(asset.body, asset);
|
|
12
|
+
|
|
13
|
+
// Prevent edge/browser caching — themes are redeployed in-place and must
|
|
14
|
+
// always reflect the latest version.
|
|
15
|
+
response.headers.set('cache-control', 'no-store');
|
|
16
|
+
|
|
17
|
+
// Workers for Platforms sometimes omits content-type on asset responses.
|
|
18
|
+
// Derive it from the file extension so browsers handle CSS, JS, etc. correctly.
|
|
19
|
+
const mimeType = getMimeType(new URL(request.url).pathname);
|
|
20
|
+
if (!response.headers.get('content-type') && mimeType) {
|
|
21
|
+
response.headers.set('content-type', mimeType);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return response;
|
|
25
|
+
},
|
|
26
|
+
};
|