Skip to content

Commit 095abc9

Browse files
committed
Merge branch 'main' into nightly
2 parents 8576a23 + 3b69344 commit 095abc9

File tree

5 files changed

+289
-3
lines changed

5 files changed

+289
-3
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<!-- all-shields/header-badges:START -->
66

7-
[![v0.3.1-nightly.0](https://img.shields.io/badge/version-v0.3.1--nightly.0-lightgray.svg?style=flat&logo=)](https://github.com/ptkdev/sveltekit-cordova-adapter/blob/main/CHANGELOG.md) [![](https://img.shields.io/npm/v/@ptkdev/sveltekit-cordova-adapter?color=CC3534&logo=npm)](https://www.npmjs.com/package/@ptkdev/sveltekit-cordova-adapter) [![License: MIT](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat&logo=license)](https://github.com/ptkdev/sveltekit-cordova-adapter/blob/main/LICENSE.md) [![Language: TypeScript](https://img.shields.io/badge/language-typescript-blue.svg?style=flat&logo=typescript)](https://www.typescriptlang.org/) [![Framework: SvelteKit](https://img.shields.io/badge/powered%20by-sveltekit-ff531a.svg?style=flat&logo=svelte)](https://kit.svelte.dev/) [![ECMAScript: 2019](https://img.shields.io/badge/ES-9-F7DF1E.svg?style=flat&logo=javascript)](https://github.com/tc39/ecma262) [![Discord Server](https://discordapp.com/api/guilds/383373985666301975/embed.png)](https://discord.ptkdev.io)
7+
[![v0.3.0](https://img.shields.io/badge/version-v0.3.0-lightgray.svg?style=flat&logo=)](https://github.com/ptkdev/sveltekit-cordova-adapter/blob/main/CHANGELOG.md) [![](https://img.shields.io/npm/v/@ptkdev/sveltekit-cordova-adapter?color=CC3534&logo=npm)](https://www.npmjs.com/package/@ptkdev/sveltekit-cordova-adapter) [![License: MIT](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat&logo=license)](https://github.com/ptkdev/sveltekit-cordova-adapter/blob/main/LICENSE.md) [![Language: TypeScript](https://img.shields.io/badge/language-typescript-blue.svg?style=flat&logo=typescript)](https://www.typescriptlang.org/) [![Framework: SvelteKit](https://img.shields.io/badge/powered%20by-sveltekit-ff531a.svg?style=flat&logo=svelte)](https://kit.svelte.dev/) [![ECMAScript: 2019](https://img.shields.io/badge/ES-9-F7DF1E.svg?style=flat&logo=javascript)](https://github.com/tc39/ecma262) [![Discord Server](https://discordapp.com/api/guilds/383373985666301975/embed.png)](https://discord.ptkdev.io)
88

99
<!-- all-shields/header-badges:END -->
1010

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: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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 () => {
119+
let regex_input = new RegExp(`([^.])(/_app/immutable)`, "g");
120+
121+
await replace.sync({
122+
files: [`${pages}/**/*`],
123+
// @ts-ignore
124+
processor: (input) => input.replace(regex_input, "$1.$2"),
125+
});
126+
});
127+
128+
let regex_input = new RegExp(`</body>`, "g");
129+
let regex_replace = `<script src="cordova.js"></script></body>`;
130+
131+
await replace.sync({
132+
files: [`${pages}/**/*.html`],
133+
// @ts-ignore
134+
processor: (input) => input.replace(regex_input, regex_replace),
135+
});
136+
137+
regex_input = new RegExp(`http-equiv="content-security-policy" content=""`, "g");
138+
const policy = "";
139+
regex_replace = `http-equiv="content-security-policy" content="${
140+
options?.policy ? options.policy : policy
141+
}"`;
142+
143+
await replace.sync({
144+
files: [`${pages}/**/*.html`],
145+
// @ts-ignore
146+
processor: (input) => input.replace(regex_input, regex_replace),
147+
});
148+
149+
regex_input = new RegExp(`name="viewport" content="width=device-width"`, "g");
150+
const viewport = "width=device-width, initial-scale=1.0, viewport-fit=cover";
151+
regex_replace = `name="viewport" content="${options?.viewport ? options.viewport : viewport}"`;
152+
153+
await replace.sync({
154+
files: [`${pages}/**/*.html`],
155+
// @ts-ignore
156+
processor: (input) => input.replace(regex_input, regex_replace),
157+
});
158+
159+
options?.safearea === undefined ? (options.safearea = true) : "";
160+
161+
if (options?.safearea) {
162+
regex_input = new RegExp(`</head>`, "g");
163+
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>`;
164+
165+
await replace.sync({
166+
files: [`${pages}/**/*.html`],
167+
// @ts-ignore
168+
processor: (input) => input.replace(regex_input, regex_replace),
169+
});
170+
}
171+
172+
if (fallback) {
173+
builder.generateFallback(path.join(pages, fallback));
174+
}
175+
176+
if (precompress) {
177+
builder.log.minor("Compressing assets and pages");
178+
if (pages === assets) {
179+
await builder.compress(assets);
180+
} else {
181+
await Promise.all([builder.compress(assets), builder.compress(pages)]);
182+
}
183+
}
184+
185+
if (pages === assets) {
186+
builder.log(`Wrote site to "${pages}"`);
187+
} else {
188+
builder.log(`Wrote pages to "${pages}" and assets to "${assets}"`);
189+
}
190+
191+
if (!options) {
192+
platform?.done(builder);
193+
}
194+
},
195+
};
196+
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@ptkdev/sveltekit-cordova-adapter",
33
"displayName": "SvelteKit Adapter for Mobile Apps with Cordova or Ionic Capacitor",
44
"description": "Adapter for SvelteKit apps that prerenders your entire site as a collection of static files for use with Cordova or Ionic Capacitor (android/ios)",
5-
"version": "0.3.1-nightly.0",
5+
"version": "0.3.0",
66
"main": "./app/adapter/index.js",
77
"type": "module",
88
"exports": {
@@ -33,7 +33,7 @@
3333
"node": ">=14.0.0"
3434
},
3535
"scripts": {
36-
"build": "echo \"Compiling...\"",
36+
"build": "cp ./app/**/* . -r",
3737
"release": "npm run build",
3838
"test": "jest app",
3939
"docs": "git submodule update --recursive && markserv ./README.md",

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)