@afonsograca/svelte-adapter 0.0.1
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/LICENSE +20 -0
- package/README.md +54 -0
- package/dist/__internal.d.ts +1 -0
- package/dist/__internal.js +1 -0
- package/dist/files/deploy.json +1 -0
- package/dist/files/handler.ts +161 -0
- package/dist/files/server/index.js +1 -0
- package/dist/files/server/manifest.js +2 -0
- package/dist/files/server.ts +7 -0
- package/dist/files/svelte.json +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +132 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright 2018-2025 the Deno authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Deno SvelteKit adapter
|
|
2
|
+
|
|
3
|
+
Official [Deno](https://deno.com/) adapter for
|
|
4
|
+
[SvelteKit](https://svelte.dev/docs/kit/introduction).
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
|
|
8
|
+
1. Install the adapter:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
deno install -D npm:@deno/svelte-adapter
|
|
12
|
+
# or
|
|
13
|
+
npm install -D @deno/svelte-adapter
|
|
14
|
+
# or
|
|
15
|
+
pnpm install -D @deno/svelte-adapter
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
2. Update your `svelte.config.js` file to use the adapter:
|
|
19
|
+
|
|
20
|
+
```diff
|
|
21
|
+
- import adapter from '@sveltejs/adapter-auto';
|
|
22
|
+
+ import adapter from "@deno/svelte-adapter";
|
|
23
|
+
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
|
24
|
+
|
|
25
|
+
/** @type {import('@sveltejs/kit').Config} */
|
|
26
|
+
const config = {
|
|
27
|
+
preprocess: vitePreprocess(),
|
|
28
|
+
kit: {
|
|
29
|
+
adapter: adapter(),
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default config;
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
3. Run the build:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
deno task build
|
|
40
|
+
# or
|
|
41
|
+
npm run build
|
|
42
|
+
# or
|
|
43
|
+
pnpm run build
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
4. Run the built server:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
deno run -A ./.deno-deploy/server.ts
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
MIT, see the [LICENSE](./LICENSE) file.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@deno/experimental-route-config";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@deno/experimental-route-config";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { Server } from "./server/index.js";
|
|
2
|
+
import { manifest } from "./server/manifest.js";
|
|
3
|
+
import {
|
|
4
|
+
applyConfig,
|
|
5
|
+
DeployConfig,
|
|
6
|
+
parseConfig,
|
|
7
|
+
} from "@deno/svelte-adapter/__internal";
|
|
8
|
+
|
|
9
|
+
interface IsrConfig {
|
|
10
|
+
pattern: RegExp;
|
|
11
|
+
expiration: number;
|
|
12
|
+
bypassToken: string | null;
|
|
13
|
+
allowQuery: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface SvelteData {
|
|
17
|
+
isr?: Array<
|
|
18
|
+
{
|
|
19
|
+
pattern: {
|
|
20
|
+
source: string;
|
|
21
|
+
flags: string;
|
|
22
|
+
};
|
|
23
|
+
expiration: number;
|
|
24
|
+
bypassToken: string | null;
|
|
25
|
+
allowQuery: string[];
|
|
26
|
+
}
|
|
27
|
+
>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ParsedSvelteData {
|
|
31
|
+
isr: IsrConfig[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function prepareServer(
|
|
35
|
+
rawSvelteData: SvelteData,
|
|
36
|
+
rawDeployConfig: DeployConfig,
|
|
37
|
+
cwd: string,
|
|
38
|
+
): Deno.ServeHandler {
|
|
39
|
+
const svelteData: { isr: IsrConfig[] } = {
|
|
40
|
+
isr: (rawSvelteData.isr ?? []).map((data) => {
|
|
41
|
+
return {
|
|
42
|
+
...data,
|
|
43
|
+
pattern: new RegExp(`${data.pattern.source}${data.pattern.flags}`),
|
|
44
|
+
};
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const deployConfig = parseConfig(rawDeployConfig, cwd);
|
|
49
|
+
const server = new Server(manifest);
|
|
50
|
+
|
|
51
|
+
const initialized = server.init({
|
|
52
|
+
env: Deno.env.toObject(),
|
|
53
|
+
// TODO: Svelte only supports sync
|
|
54
|
+
read(filePath) {
|
|
55
|
+
const file = Deno.openSync(filePath, { read: true });
|
|
56
|
+
return file.readable;
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const hasIsr = svelteData.isr.length > 0;
|
|
61
|
+
|
|
62
|
+
const initCache = hasIsr
|
|
63
|
+
? caches.open("svelte-isr") // TODO: Are caches global?
|
|
64
|
+
: Promise.resolve(null);
|
|
65
|
+
|
|
66
|
+
const handler: Deno.ServeHandler = async (req, info) => {
|
|
67
|
+
await initialized;
|
|
68
|
+
|
|
69
|
+
const url = new URL(req.url);
|
|
70
|
+
|
|
71
|
+
// Retrieve ISR cache if set for this route
|
|
72
|
+
const cache = await initCache;
|
|
73
|
+
let isr: IsrConfig | null = null;
|
|
74
|
+
if (hasIsr && cache !== null) {
|
|
75
|
+
isr = getIsrConfig(svelteData, req, url);
|
|
76
|
+
|
|
77
|
+
if (isr !== null) {
|
|
78
|
+
const key = toCacheKey(url, isr);
|
|
79
|
+
const cached = await cache.match(key);
|
|
80
|
+
if (cached !== undefined) return cached;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const responded = await applyConfig(deployConfig, url, cwd);
|
|
85
|
+
if (responded !== null) {
|
|
86
|
+
return responded;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const res = await server.respond(req, {
|
|
90
|
+
getClientAddress() {
|
|
91
|
+
if ("hostname" in info.remoteAddr) {
|
|
92
|
+
return info.remoteAddr.hostname;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// TODO: What to do for unix sockets?
|
|
96
|
+
return "127.0.0.1";
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Store ISR cache
|
|
101
|
+
if (isr !== null && cache !== null) {
|
|
102
|
+
const key = toCacheKey(url, isr);
|
|
103
|
+
const clone = res.clone();
|
|
104
|
+
clone.headers.set("Cache-Control", `max-age=${isr.expiration}`);
|
|
105
|
+
await cache.put(key, clone);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return res;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return handler;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getIsrConfig(
|
|
115
|
+
svelteData: ParsedSvelteData,
|
|
116
|
+
req: Request,
|
|
117
|
+
url: URL,
|
|
118
|
+
): IsrConfig | null {
|
|
119
|
+
for (let i = 0; i < svelteData.isr.length; i++) {
|
|
120
|
+
const data = svelteData.isr[i];
|
|
121
|
+
if (data.pattern.test(url.pathname)) {
|
|
122
|
+
let bypass: string | null = null;
|
|
123
|
+
|
|
124
|
+
// Check for bypass token in http headers
|
|
125
|
+
if (req.method === "GET" || req.method === "HEAD") {
|
|
126
|
+
bypass = req.headers.get("x-prerender-revalidate");
|
|
127
|
+
if (bypass !== null && bypass === data.bypassToken) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check for bypass token in Cookie
|
|
133
|
+
const cookies = req.headers.getSetCookie();
|
|
134
|
+
for (let i = 0; i < cookies.length; i++) {
|
|
135
|
+
const [key, value] = cookies[i].split("=");
|
|
136
|
+
if (key === "__prerender_bypass" && value === data.bypassToken) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return data;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function toCacheKey(url: URL, config: IsrConfig): Request {
|
|
149
|
+
const newUrl = new URL("http://c");
|
|
150
|
+
newUrl.pathname = url.pathname;
|
|
151
|
+
|
|
152
|
+
for (let i = 0; i < config.allowQuery.length; i++) {
|
|
153
|
+
const q = config.allowQuery[i];
|
|
154
|
+
const param = url.searchParams.get(q);
|
|
155
|
+
if (param !== null) {
|
|
156
|
+
newUrl.searchParams.append(q, param);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return new Request(newUrl);
|
|
161
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Server } from "@sveltejs/kit";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import rawDeployConfig from "./deploy.json" with { type: "json" };
|
|
2
|
+
import rawSvelteData from "./svelte.json" with { type: "json" };
|
|
3
|
+
import { prepareServer } from "./handler.ts";
|
|
4
|
+
|
|
5
|
+
const handler = prepareServer(rawSvelteData, rawDeployConfig, Deno.cwd());
|
|
6
|
+
|
|
7
|
+
Deno.serve(handler);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import fsp from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const OUT_DIR = ".deno-deploy";
|
|
4
|
+
export default function denoAdapter() {
|
|
5
|
+
return {
|
|
6
|
+
name: "@deno/svelte-adapter",
|
|
7
|
+
async adapt(builder) {
|
|
8
|
+
builder.rimraf(OUT_DIR);
|
|
9
|
+
const dirs = {
|
|
10
|
+
static: `${OUT_DIR}/static${builder.config.kit.paths.base}`,
|
|
11
|
+
server: `${OUT_DIR}/server`,
|
|
12
|
+
};
|
|
13
|
+
try {
|
|
14
|
+
await fsp.mkdir(dirs.server, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// ignore
|
|
18
|
+
}
|
|
19
|
+
builder.log.minor("Copying assets...");
|
|
20
|
+
builder.writeClient(dirs.static);
|
|
21
|
+
builder.writePrerendered(dirs.static);
|
|
22
|
+
builder.log.minor("Building server entry...");
|
|
23
|
+
builder.writeServer(dirs.server);
|
|
24
|
+
if (builder.hasServerInstrumentationFile()) {
|
|
25
|
+
builder.log.minor("Instrumenting server...");
|
|
26
|
+
builder.instrument({
|
|
27
|
+
entrypoint: `${dirs.server}/index.js`,
|
|
28
|
+
instrumentation: `${dirs.server}/instrumentation.server.js`,
|
|
29
|
+
module: {
|
|
30
|
+
exports: ["Server"],
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const staticFiles = [];
|
|
35
|
+
const redirects = [];
|
|
36
|
+
const rewrites = [];
|
|
37
|
+
for (const [pathname, data] of builder.prerendered.pages.entries()) {
|
|
38
|
+
staticFiles.push({
|
|
39
|
+
source: pathname,
|
|
40
|
+
destination: path.join(dirs.static, data.file),
|
|
41
|
+
});
|
|
42
|
+
// Add redirect
|
|
43
|
+
if (pathname !== "/") {
|
|
44
|
+
const trailing = pathname.endsWith("/");
|
|
45
|
+
redirects.push({
|
|
46
|
+
source: trailing ? pathname.slice(0, -1) : pathname + "/",
|
|
47
|
+
destination: pathname,
|
|
48
|
+
permanent: true,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const svelteData = {
|
|
53
|
+
isr: [],
|
|
54
|
+
};
|
|
55
|
+
// ISR
|
|
56
|
+
for (const page of builder.routes) {
|
|
57
|
+
// ISR cannot be use with prerendering together
|
|
58
|
+
if (page.prerender)
|
|
59
|
+
continue;
|
|
60
|
+
const isr = page.config.isr;
|
|
61
|
+
if (isr !== undefined) {
|
|
62
|
+
svelteData.isr.push({
|
|
63
|
+
pattern: {
|
|
64
|
+
source: page.pattern.source,
|
|
65
|
+
flags: page.pattern.flags,
|
|
66
|
+
},
|
|
67
|
+
expiration: isr.expiration ?? 604800,
|
|
68
|
+
bypassToken: isr.bypassToken ?? null,
|
|
69
|
+
allowQuery: isr.allowQuery ?? [],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const svelteMetaPath = path.join(OUT_DIR, "svelte.json");
|
|
74
|
+
await fsp.writeFile(svelteMetaPath, JSON.stringify(svelteData, null, 2), "utf-8");
|
|
75
|
+
staticFiles.push({
|
|
76
|
+
source: "/_app/immutable/:file*",
|
|
77
|
+
destination: ".deno-deploy/static/_app/immutable/:file*",
|
|
78
|
+
});
|
|
79
|
+
// Collect all remaining asset files
|
|
80
|
+
const assetDir = builder.config.kit.files.assets;
|
|
81
|
+
// TODO: What about generated assets
|
|
82
|
+
const assets = [];
|
|
83
|
+
await walk(assetDir, assets);
|
|
84
|
+
for (const asset of assets) {
|
|
85
|
+
const rel = path.relative(assetDir, asset);
|
|
86
|
+
staticFiles.push({
|
|
87
|
+
source: `/${rel.replace(/\\+/, "/")}`,
|
|
88
|
+
destination: path.join(dirs.static, rel),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const deploy = {
|
|
92
|
+
headers: [{
|
|
93
|
+
source: "/_app/immutable/:file*",
|
|
94
|
+
headers: [
|
|
95
|
+
{
|
|
96
|
+
key: "Cache-Control",
|
|
97
|
+
value: "public, immutable, max-age=31536000",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
}],
|
|
101
|
+
redirects,
|
|
102
|
+
rewrites,
|
|
103
|
+
staticFiles,
|
|
104
|
+
};
|
|
105
|
+
const out = path.join(OUT_DIR, "deploy.json");
|
|
106
|
+
await fsp.writeFile(out, JSON.stringify(deploy, null, 2), "utf-8");
|
|
107
|
+
const fileDir = path.join(import.meta.dirname, "files");
|
|
108
|
+
builder.copy(path.join(fileDir, "handler.ts"), path.join(OUT_DIR, "handler.ts"));
|
|
109
|
+
builder.copy(path.join(fileDir, "server.ts"), path.join(OUT_DIR, "server.ts"));
|
|
110
|
+
},
|
|
111
|
+
supports: {
|
|
112
|
+
read() {
|
|
113
|
+
// Deno Deploy V2 always supports reading from the file system
|
|
114
|
+
return true;
|
|
115
|
+
},
|
|
116
|
+
instrumentation() {
|
|
117
|
+
// Does it support SvelteKit's built-in observability features
|
|
118
|
+
return true;
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async function walk(dir, result) {
|
|
124
|
+
for (const entry of await fsp.readdir(dir, { withFileTypes: true })) {
|
|
125
|
+
if (entry.isDirectory()) {
|
|
126
|
+
await walk(path.join(dir, entry.name), result);
|
|
127
|
+
}
|
|
128
|
+
else if (entry.isFile()) {
|
|
129
|
+
result.push(path.join(dir, entry.name));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@afonsograca/svelte-adapter",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/afonsograca/svelte-adapter.git"
|
|
7
|
+
},
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/afonsograca/svelte-adapter/issues"
|
|
10
|
+
},
|
|
11
|
+
"author": "Afonso Graça",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"description": "Fork of the Deno adapter for SvelteKit",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "dist/index.js",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./__internal": {
|
|
22
|
+
"types": "./dist/__internal.d.ts",
|
|
23
|
+
"default": "./dist/__internal.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"fixture-deps": "cd test/fixture && npm ci",
|
|
28
|
+
"test": "deno test -A",
|
|
29
|
+
"build": "tsc && node tools/build.mjs",
|
|
30
|
+
"prepublishOnly": "npm run build",
|
|
31
|
+
"prepare": "svelte-kit sync && npm run build"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"Deno",
|
|
35
|
+
"Svelte",
|
|
36
|
+
"SvelteKit"
|
|
37
|
+
],
|
|
38
|
+
"files": [
|
|
39
|
+
"dist/"
|
|
40
|
+
],
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@sveltejs/kit": "^2.31.0"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@deno/experimental-route-config": "^0.0.5"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/deno": "^2.0.0",
|
|
49
|
+
"@types/node": "^22.10.7",
|
|
50
|
+
"expect": "^29.7.0",
|
|
51
|
+
"linkedom": "^0.18.6",
|
|
52
|
+
"typescript": "^5.7.3",
|
|
53
|
+
"urlpattern-polyfill": "^10.0.0"
|
|
54
|
+
}
|
|
55
|
+
}
|