Skip to content

Commit 052cda5

Browse files
committed
[🚀 Release] v0.2.0
1 parent f248118 commit 052cda5

File tree

3 files changed

+288
-0
lines changed

3 files changed

+288
-0
lines changed

index.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Adapter } from "@sveltejs/kit";
2+
3+
export interface AdapterOptions {
4+
pages?: string;
5+
assets?: string;
6+
fallback?: string;
7+
precompress?: boolean;
8+
strict?: boolean;
9+
policy?: string;
10+
viewport?: string;
11+
safearea?: boolean;
12+
platform?: string;
13+
}
14+
15+
export default function plugin(options?: AdapterOptions): Adapter;

index.js

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/* eslint-disable @typescript-eslint/ban-ts-comment */
2+
import path from "path";
3+
import glob from "tiny-glob";
4+
import replace from "replace-in-file";
5+
import { platforms } from "./platforms.js";
6+
7+
/** @type {import('.').default} */
8+
export default function (options) {
9+
return {
10+
name: "@ptkdev/sveltekit-cordova-adapter",
11+
12+
async adapt(builder) {
13+
if (!options?.fallback) {
14+
/** @type {string[]} */
15+
const dynamic_routes = [];
16+
17+
// this is a bit of a hack — it allows us to know whether there are dynamic
18+
// (i.e. prerender = false/'auto') routes without having dedicated API
19+
// surface area for it
20+
builder.createEntries((route) => {
21+
dynamic_routes.push(route.id);
22+
23+
return {
24+
id: "",
25+
filter: () => false,
26+
// eslint-disable-next-line @typescript-eslint/no-empty-function
27+
complete: () => {},
28+
};
29+
});
30+
31+
if (dynamic_routes.length > 0 && options?.strict !== false) {
32+
const prefix = path.relative(".", builder.config.kit.files.routes);
33+
const has_param_routes = dynamic_routes.some((route) => route.includes("["));
34+
const config_option =
35+
has_param_routes || JSON.stringify(builder.config.kit.prerender.entries) !== '["*"]'
36+
? ` - adjust the \`prerender.entries\` config option ${
37+
has_param_routes
38+
? "(routes with parameters are not part of entry points by default)"
39+
: ""
40+
} — see https://kit.svelte.dev/docs/configuration#prerender for more info.`
41+
: "";
42+
43+
builder.log.error(
44+
`@ptkdev/sveltekit-cordova-adapter: all routes must be fully prerenderable, but found the following routes that are dynamic:
45+
${dynamic_routes.map((id) => ` - ${path.posix.join(prefix, id)}`).join("\n")}
46+
47+
You have the following options:
48+
- set the \`fallback\` option — see https://github.com/sveltejs/kit/tree/master/packages/adapter-static#spa-mode for more info.
49+
- add \`export const prerender = true\` to your root \`+layout.js/.ts\` or \`+layout.server.js/.ts\` file. This will try to prerender all pages.
50+
- add \`export const prerender = true\` to any \`+server.js/ts\` files that are not fetched by page \`load\` functions.
51+
${config_option}
52+
- pass \`strict: false\` to \`adapter-static\` to ignore this error. Only do this if you are sure you don't need the routes in question in your final app, as they will be unavailable. See https://github.com/sveltejs/kit/tree/master/packages/adapter-static#strict for more info.
53+
54+
If this doesn't help, you may need to use a different adapter. @ptkdev/sveltekit-cordova-adapter can only be used for sites that don't need a server for dynamic rendering, and can run on just a static file server.
55+
See https://kit.svelte.dev/docs/page-options#prerender for more details`,
56+
);
57+
throw new Error("Encountered dynamic routes");
58+
}
59+
}
60+
61+
const platform = platforms.find((platform) => platform.test());
62+
63+
if (platform) {
64+
if (options) {
65+
builder.log.warn(
66+
`Detected ${platform.name}. Please remove adapter-static options to enable zero-config mode`,
67+
);
68+
} else {
69+
builder.log.info(`Detected ${platform.name}, using zero-config mode`);
70+
}
71+
}
72+
73+
const {
74+
pages = "build",
75+
assets = pages,
76+
fallback,
77+
precompress,
78+
} = options ?? platform?.defaults ?? /** @type {import('./index').AdapterOptions} */ ({});
79+
80+
builder.rimraf(assets);
81+
builder.rimraf(pages);
82+
83+
builder.writeClient(assets);
84+
builder.writePrerendered(pages);
85+
86+
const HTML_pages = await glob("**/*.html", {
87+
cwd: pages,
88+
dot: true,
89+
absolute: true,
90+
filesOnly: true,
91+
});
92+
93+
HTML_pages.forEach(async (path) => {
94+
let href = path.split("/").pop();
95+
96+
let regex_input = new RegExp(`href="/${href.replace(".html", "")}"`, "g");
97+
let regex_replace = `href="${`./${href}`}"`;
98+
99+
if (href === "index.html") {
100+
regex_input = new RegExp(`href="/"`, "g");
101+
regex_replace = `href="./index.html"`;
102+
}
103+
104+
await replace.sync({
105+
files: [`${pages}/**/*.html`],
106+
// @ts-ignore
107+
processor: (input) => input.replace(regex_input, regex_replace),
108+
});
109+
});
110+
111+
const HTML_assets = await glob("_app/**/*", {
112+
cwd: pages,
113+
dot: true,
114+
absolute: false,
115+
filesOnly: true,
116+
});
117+
118+
HTML_assets.forEach(async (path) => {
119+
let regex_input = new RegExp(`[^.](/_app/immutable)`, "g");
120+
let regex_replace = `./_app/immutable`;
121+
122+
await replace.sync({
123+
files: [`${pages}/**/*`],
124+
// @ts-ignore
125+
processor: (input) => input.replace(regex_input, regex_replace),
126+
});
127+
});
128+
129+
let regex_input = new RegExp(`</body>`, "g");
130+
let regex_replace = `<script src="cordova.js"></script></body>`;
131+
132+
await replace.sync({
133+
files: [`${pages}/**/*.html`],
134+
// @ts-ignore
135+
processor: (input) => input.replace(regex_input, regex_replace),
136+
});
137+
138+
regex_input = new RegExp(`http-equiv="content-security-policy" content=""`, "g");
139+
const policy =
140+
"default-src 'self' data: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;";
141+
regex_replace = `http-equiv="content-security-policy" content="${
142+
options?.policy ? options.policy : policy
143+
}"`;
144+
145+
await replace.sync({
146+
files: [`${pages}/**/*.html`],
147+
// @ts-ignore
148+
processor: (input) => input.replace(regex_input, regex_replace),
149+
});
150+
151+
regex_input = new RegExp(`name="viewport" content="width=device-width"`, "g");
152+
const viewport = "width=device-width, initial-scale=1.0, viewport-fit=cover";
153+
regex_replace = `name="viewport" content="${options?.viewport ? options.viewport : viewport}"`;
154+
155+
await replace.sync({
156+
files: [`${pages}/**/*.html`],
157+
// @ts-ignore
158+
processor: (input) => input.replace(regex_input, regex_replace),
159+
});
160+
161+
options?.safearea ?? true;
162+
163+
if (options?.safearea) {
164+
regex_input = new RegExp(`</head>`, "g");
165+
regex_replace = `<style>body { padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); }</style></head>`;
166+
167+
await replace.sync({
168+
files: [`${pages}/**/*.html`],
169+
// @ts-ignore
170+
processor: (input) => input.replace(regex_input, regex_replace),
171+
});
172+
}
173+
174+
if (fallback) {
175+
builder.generateFallback(path.join(pages, fallback));
176+
}
177+
178+
if (precompress) {
179+
builder.log.minor("Compressing assets and pages");
180+
if (pages === assets) {
181+
await builder.compress(assets);
182+
} else {
183+
await Promise.all([builder.compress(assets), builder.compress(pages)]);
184+
}
185+
}
186+
187+
if (pages === assets) {
188+
builder.log(`Wrote site to "${pages}"`);
189+
} else {
190+
builder.log(`Wrote pages to "${pages}" and assets to "${assets}"`);
191+
}
192+
193+
if (!options) {
194+
platform?.done(builder);
195+
}
196+
},
197+
};
198+
}

platforms.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import fs from "fs";
2+
3+
/**
4+
* @typedef {{
5+
* name: string;
6+
* test: () => boolean;
7+
* defaults: import('./index').AdapterOptions;
8+
* done: (builder: import('@sveltejs/kit').Builder) => void;
9+
* }}
10+
* Platform */
11+
12+
// This function is duplicated in adapter-vercel
13+
/** @param {import('@sveltejs/kit').Builder} builder */
14+
function static_vercel_config(builder) {
15+
/** @type {any[]} */
16+
const prerendered_redirects = [];
17+
18+
/** @type {Record<string, { path: string }>} */
19+
const overrides = {};
20+
21+
for (const [src, redirect] of builder.prerendered.redirects) {
22+
prerendered_redirects.push({
23+
src,
24+
headers: {
25+
Location: redirect.location,
26+
},
27+
status: redirect.status,
28+
});
29+
}
30+
31+
for (const [path, page] of builder.prerendered.pages) {
32+
if (path.endsWith("/") && path !== "/") {
33+
prerendered_redirects.push(
34+
{ src: path, dest: path.slice(0, -1) },
35+
{ src: path.slice(0, -1), status: 308, headers: { Location: path } },
36+
);
37+
38+
overrides[page.file] = { path: path.slice(1, -1) };
39+
} else {
40+
overrides[page.file] = { path: path.slice(1) };
41+
}
42+
}
43+
44+
return {
45+
version: 3,
46+
routes: [
47+
...prerendered_redirects,
48+
{
49+
src: `/${builder.getAppPath()}/immutable/.+`,
50+
headers: {
51+
"cache-control": "public, immutable, max-age=31536000",
52+
},
53+
},
54+
{
55+
handle: "filesystem",
56+
},
57+
],
58+
overrides,
59+
};
60+
}
61+
62+
/** @type {Platform[]} */
63+
export const platforms = [
64+
{
65+
name: "Vercel",
66+
test: () => !!process.env.VERCEL,
67+
defaults: {
68+
pages: ".vercel/output/static",
69+
},
70+
done: (builder) => {
71+
const config = static_vercel_config(builder);
72+
fs.writeFileSync(".vercel/output/config.json", JSON.stringify(config, null, " "));
73+
},
74+
},
75+
];

0 commit comments

Comments
 (0)