@inkandswitch/patchwork 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/build-head.js +10 -0
- package/dist/client.d.ts +38 -0
- package/dist/global.css +1 -0
- package/dist/head.d.ts +1 -0
- package/dist/head.js +52 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +294 -0
- package/dist/loading.d.ts +5 -0
- package/dist/loading.js +125 -0
- package/dist/repo.d.ts +25 -0
- package/dist/repo.js +108 -0
- package/dist/router.d.ts +18 -0
- package/dist/router.js +132 -0
- package/dist/site-kit/html.d.ts +4 -0
- package/dist/site-kit/html.js +70 -0
- package/dist/site-kit/icons.d.ts +9 -0
- package/dist/site-kit/icons.js +36 -0
- package/dist/site-kit/index.d.ts +15 -0
- package/dist/site-kit/index.js +5 -0
- package/dist/site-kit/manifest.d.ts +3 -0
- package/dist/site-kit/manifest.js +29 -0
- package/dist/site-kit/netlify.d.ts +5 -0
- package/dist/site-kit/netlify.js +27 -0
- package/dist/site-kit/options.d.ts +67 -0
- package/dist/site-kit/options.js +9 -0
- package/dist/site-kit/sync-servers.d.ts +11 -0
- package/dist/site-kit/sync-servers.js +36 -0
- package/dist/types.d.ts +104 -0
- package/dist/types.js +1 -0
- package/dist/vite/config-plugin.d.ts +12 -0
- package/dist/vite/config-plugin.js +70 -0
- package/dist/vite/html-plugin.d.ts +11 -0
- package/dist/vite/html-plugin.js +51 -0
- package/dist/vite/icons.d.ts +4 -0
- package/dist/vite/icons.js +42 -0
- package/dist/vite/importmap-plugin.d.ts +4 -0
- package/dist/vite/importmap-plugin.js +83 -0
- package/dist/vite/manifest-plugin.d.ts +4 -0
- package/dist/vite/manifest-plugin.js +34 -0
- package/dist/vite/netlify-plugin.d.ts +9 -0
- package/dist/vite/netlify-plugin.js +29 -0
- package/dist/vite/patchwork-plugin.d.ts +43 -0
- package/dist/vite/patchwork-plugin.js +40 -0
- package/dist/vite/service-worker-plugin.d.ts +2 -0
- package/dist/vite/service-worker-plugin.js +49 -0
- package/package.json +55 -0
- package/src/client.d.ts +38 -0
- package/src/global.css +1 -0
- package/src/head.ts +14 -0
- package/src/index.ts +469 -0
- package/src/loading.ts +137 -0
- package/src/repo.ts +146 -0
- package/src/router.ts +195 -0
- package/src/site-kit/html.ts +100 -0
- package/src/site-kit/icons.ts +49 -0
- package/src/site-kit/index.ts +22 -0
- package/src/site-kit/manifest.ts +39 -0
- package/src/site-kit/netlify.ts +35 -0
- package/src/site-kit/options.ts +73 -0
- package/src/site-kit/sync-servers.ts +39 -0
- package/src/types.ts +135 -0
- package/src/vite/config-plugin.ts +88 -0
- package/src/vite/html-plugin.ts +60 -0
- package/src/vite/icons.ts +47 -0
- package/src/vite/importmap-plugin.ts +112 -0
- package/src/vite/manifest-plugin.ts +39 -0
- package/src/vite/netlify-plugin.ts +34 -0
- package/src/vite/patchwork-plugin.ts +67 -0
- package/src/vite/service-worker-plugin.ts +54 -0
- package/tsconfig.json +14 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { AutomergeUrl, DocHandle, Repo } from "@automerge/vanillajs/slim";
|
|
2
|
+
import type { AutomergeRepoKeyhive } from "@automerge/automerge-repo-keyhive";
|
|
3
|
+
import type { ModuleWatcher, HasPatchworkMetadata } from "@inkandswitch/patchwork-filesystem";
|
|
4
|
+
import type { AccountDoc } from "@inkandswitch/patchwork-plugins";
|
|
5
|
+
import type { ServiceWorkerRepoChannelListener, SyncStateDocMessage } from "@inkandswitch/patchwork-bootloader/types";
|
|
6
|
+
import type * as pluginsNS from "@inkandswitch/patchwork-plugins";
|
|
7
|
+
export type PluginsApi = typeof pluginsNS;
|
|
8
|
+
export type SignerIdentity = {
|
|
9
|
+
peerId: string;
|
|
10
|
+
verifyingKey: string;
|
|
11
|
+
};
|
|
12
|
+
export interface ServiceWorkerApi {
|
|
13
|
+
connectClassicSync: (server?: string) => Promise<void>;
|
|
14
|
+
subscribeToRepoChannel: (listener: ServiceWorkerRepoChannelListener) => Promise<() => void>;
|
|
15
|
+
subscribeSyncState: (documentId: string, listener: (update: SyncStateDocMessage) => void) => () => void;
|
|
16
|
+
}
|
|
17
|
+
export interface OpenOptions {
|
|
18
|
+
tool?: string;
|
|
19
|
+
type?: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface PatchworkOptions {
|
|
23
|
+
/**
|
|
24
|
+
* The site's default tool bundle — the tools every user of this site gets
|
|
25
|
+
* out of the box. Must collectively contribute at least a
|
|
26
|
+
* `patchwork:datatype` registration for `"account"` (typically the one
|
|
27
|
+
* supplied by `@inkandswitch/patchwork-frame`).
|
|
28
|
+
*
|
|
29
|
+
* Each entry is a *module-list source* and may be either:
|
|
30
|
+
* - an Automerge module-settings doc URL (`automerge:...`), which is
|
|
31
|
+
* live-reloaded, or
|
|
32
|
+
* - an HTTP(S) URL (absolute or site-relative, e.g. `/modules.json`) to a
|
|
33
|
+
* static JSON manifest of the shape `{ modules: string[], branches? }`,
|
|
34
|
+
* fetched once at boot.
|
|
35
|
+
*
|
|
36
|
+
* The module URLs *inside* either kind of source may themselves be Automerge
|
|
37
|
+
* folder docs or plain HTTP(S) bundles, so deployment targets can be freely
|
|
38
|
+
* mixed.
|
|
39
|
+
*
|
|
40
|
+
* Overridable at runtime with `localStorage.systemPackageListURL`.
|
|
41
|
+
*/
|
|
42
|
+
packageListURL?: string | string[];
|
|
43
|
+
/**
|
|
44
|
+
* `localStorage` key under which this site remembers which account document
|
|
45
|
+
* belongs to the current user. Defaults to `"patchworkAccountURL"`. Sites
|
|
46
|
+
* sharing an origin MUST use distinct keys so they do not clobber each
|
|
47
|
+
* other's accounts.
|
|
48
|
+
*/
|
|
49
|
+
accountKey?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Brand word for this site: appended to the document title as
|
|
52
|
+
* `"<doc> | <name>"` when a document is open (the separator is provided
|
|
53
|
+
* for you), and used to namespace this site's storage and peer ids.
|
|
54
|
+
*
|
|
55
|
+
* Defaults to the build-time `__SITE_NAME__` define, then `"patchwork"`.
|
|
56
|
+
*/
|
|
57
|
+
name?: string;
|
|
58
|
+
/** DOM id of the `<patchwork-view>` hosting the root tool. Defaults to "root". */
|
|
59
|
+
rootElementId?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Initialize keyhive for access control. The Repo then uses keyhive's network
|
|
62
|
+
* adapter, peerId and idFactory instead of a sharePolicy.
|
|
63
|
+
*/
|
|
64
|
+
keyhive?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Bring your own Repo. When provided, setup skips wasm initialization and
|
|
67
|
+
* repo creation entirely — you are responsible for having initialized
|
|
68
|
+
* automerge/subduction and wired the automerge-worker port yourself.
|
|
69
|
+
*/
|
|
70
|
+
repo?: Repo;
|
|
71
|
+
/** The keyhive instance accompanying a provided {@link PatchworkOptions.repo}. */
|
|
72
|
+
hive?: AutomergeRepoKeyhive;
|
|
73
|
+
/**
|
|
74
|
+
* How document navigation maps onto the URL. `"hash"` (the default) drives
|
|
75
|
+
* the root view from `location.hash`. `false` installs no router — call
|
|
76
|
+
* `patchwork.open()` directly.
|
|
77
|
+
*/
|
|
78
|
+
routing?: "hash" | false;
|
|
79
|
+
/**
|
|
80
|
+
* Milliseconds before the promise `setup()` returned rejects because boot
|
|
81
|
+
* hasn't finished — so a hang (service worker stuck, sync unreachable)
|
|
82
|
+
* surfaces as an error a site can show instead of blank space. The boot
|
|
83
|
+
* itself is not cancelled and may still complete in the background.
|
|
84
|
+
* Defaults to 30 seconds; `false` disables the deadline.
|
|
85
|
+
*/
|
|
86
|
+
timeout?: number | false;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The site runtime `setup()` resolves with — what a site assigns to
|
|
90
|
+
* `window.patchwork`. Plain fields: it exists only once setup has finished,
|
|
91
|
+
* so every member is always usable.
|
|
92
|
+
*/
|
|
93
|
+
export interface Patchwork {
|
|
94
|
+
readonly repo: Repo;
|
|
95
|
+
readonly hive?: AutomergeRepoKeyhive;
|
|
96
|
+
readonly account: DocHandle<AccountDoc>;
|
|
97
|
+
readonly signer?: SignerIdentity;
|
|
98
|
+
create<D>(type: string, init?: (doc: D) => void): Promise<DocHandle<D & HasPatchworkMetadata>>;
|
|
99
|
+
open(url: AutomergeUrl, options?: OpenOptions): void;
|
|
100
|
+
find<D>(url: AutomergeUrl): Promise<DocHandle<D>>;
|
|
101
|
+
readonly packages: ModuleWatcher;
|
|
102
|
+
readonly plugins: PluginsApi;
|
|
103
|
+
readonly sw: ServiceWorkerApi;
|
|
104
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import wasm from "vite-plugin-wasm";
|
|
3
|
+
import type { PatchworkVitePluginOptions } from "./patchwork-plugin.js";
|
|
4
|
+
/**
|
|
5
|
+
* Owns envPrefix, define (__SITE_NAME__/__KEYHIVE__/__KEYHIVE_SYNC_SERVER__),
|
|
6
|
+
* server/preview CORS defaults, worker format + the wasm plugin, and build
|
|
7
|
+
* defaults (firefox150 target, unminified, sourcemapped) — everything a site
|
|
8
|
+
* used to hand-write in its own vite.config.ts. Each is switched off
|
|
9
|
+
* individually via the matching `false` option.
|
|
10
|
+
*/
|
|
11
|
+
export declare function configPlugin(options?: PatchworkVitePluginOptions): Plugin;
|
|
12
|
+
export { wasm };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import wasm from "vite-plugin-wasm";
|
|
2
|
+
const CORS_HEADERS = { "Access-Control-Allow-Origin": "*" };
|
|
3
|
+
/**
|
|
4
|
+
* Owns envPrefix, define (__SITE_NAME__/__KEYHIVE__/__KEYHIVE_SYNC_SERVER__),
|
|
5
|
+
* server/preview CORS defaults, worker format + the wasm plugin, and build
|
|
6
|
+
* defaults (firefox150 target, unminified, sourcemapped) — everything a site
|
|
7
|
+
* used to hand-write in its own vite.config.ts. Each is switched off
|
|
8
|
+
* individually via the matching `false` option.
|
|
9
|
+
*/
|
|
10
|
+
export function configPlugin(options = {}) {
|
|
11
|
+
return {
|
|
12
|
+
name: "@patchwork/config",
|
|
13
|
+
config() {
|
|
14
|
+
const define = {
|
|
15
|
+
__KEYHIVE__: JSON.stringify(options.keyhive ?? false),
|
|
16
|
+
__KEYHIVE_SYNC_SERVER__: JSON.stringify(options.keyhiveSyncServer ?? false),
|
|
17
|
+
};
|
|
18
|
+
if (options.siteName) {
|
|
19
|
+
define.__SITE_NAME__ = JSON.stringify(options.siteName);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
envPrefix: ["VITE_", "PATCHWORK_"],
|
|
23
|
+
define,
|
|
24
|
+
server: options.server === false
|
|
25
|
+
? undefined
|
|
26
|
+
: {
|
|
27
|
+
headers: CORS_HEADERS,
|
|
28
|
+
...options.server,
|
|
29
|
+
},
|
|
30
|
+
preview: options.preview === false
|
|
31
|
+
? undefined
|
|
32
|
+
: {
|
|
33
|
+
port: process.env.PORT ? +process.env.PORT : 5173,
|
|
34
|
+
headers: CORS_HEADERS,
|
|
35
|
+
...options.preview,
|
|
36
|
+
},
|
|
37
|
+
worker: options.worker === false
|
|
38
|
+
? undefined
|
|
39
|
+
: {
|
|
40
|
+
format: options.worker?.format ?? "es",
|
|
41
|
+
plugins: () => [wasm()],
|
|
42
|
+
},
|
|
43
|
+
build: {
|
|
44
|
+
target: "firefox150",
|
|
45
|
+
minify: false,
|
|
46
|
+
sourcemap: true,
|
|
47
|
+
...options.build,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
// The shared automerge-worker's chunk imports bypass the page's service
|
|
52
|
+
// worker, so offline boot needs the browser's HTTP cache to serve them
|
|
53
|
+
// without revalidating. Content hashes make that safe; a new build gets
|
|
54
|
+
// new URLs. Production gets this from the generated _headers file — vite
|
|
55
|
+
// preview doesn't read that, so mirror it here.
|
|
56
|
+
configurePreviewServer(server) {
|
|
57
|
+
if (options.netlify === false ||
|
|
58
|
+
options.netlify?.immutableAssets === false) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
server.middlewares.use((req, res, next) => {
|
|
62
|
+
if (req.url?.startsWith("/assets/")) {
|
|
63
|
+
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
64
|
+
}
|
|
65
|
+
next();
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export { wasm };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import type { PatchworkVitePluginOptions } from "./patchwork-plugin.js";
|
|
3
|
+
/**
|
|
4
|
+
* Vite's html build step (`vite:build-html`) is a normal `transform` hook
|
|
5
|
+
* filtered on `id: /\.html$/` — it doesn't require a real file, just an id
|
|
6
|
+
* that *looks like* one under root (that's all `path.relative(root, id)`,
|
|
7
|
+
* which decides the output filename, cares about). So this is a plain
|
|
8
|
+
* virtual module: `resolveId` claims the one id we care about, `load`
|
|
9
|
+
* returns the generated string — nothing ever touches disk.
|
|
10
|
+
*/
|
|
11
|
+
export declare function htmlPlugin(options?: PatchworkVitePluginOptions): Plugin | null;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { buildHtml } from "../site-kit/html.js";
|
|
3
|
+
const GENERATED_PATH = "index.html";
|
|
4
|
+
/**
|
|
5
|
+
* Vite's html build step (`vite:build-html`) is a normal `transform` hook
|
|
6
|
+
* filtered on `id: /\.html$/` — it doesn't require a real file, just an id
|
|
7
|
+
* that *looks like* one under root (that's all `path.relative(root, id)`,
|
|
8
|
+
* which decides the output filename, cares about). So this is a plain
|
|
9
|
+
* virtual module: `resolveId` claims the one id we care about, `load`
|
|
10
|
+
* returns the generated string — nothing ever touches disk.
|
|
11
|
+
*/
|
|
12
|
+
export function htmlPlugin(options = {}) {
|
|
13
|
+
if (options.html === false)
|
|
14
|
+
return null;
|
|
15
|
+
const html = buildHtml(options);
|
|
16
|
+
let entryId;
|
|
17
|
+
return {
|
|
18
|
+
name: "@patchwork/html",
|
|
19
|
+
enforce: "pre",
|
|
20
|
+
config(config) {
|
|
21
|
+
const root = resolve(config.root ?? process.cwd());
|
|
22
|
+
entryId = resolve(root, GENERATED_PATH);
|
|
23
|
+
return {
|
|
24
|
+
build: { rollupOptions: { input: entryId } },
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
resolveId(source) {
|
|
28
|
+
if (source === entryId)
|
|
29
|
+
return entryId;
|
|
30
|
+
},
|
|
31
|
+
load(id) {
|
|
32
|
+
if (id === entryId)
|
|
33
|
+
return html;
|
|
34
|
+
},
|
|
35
|
+
configureServer: {
|
|
36
|
+
order: "pre",
|
|
37
|
+
handler(server) {
|
|
38
|
+
server.middlewares.use(async (req, res, next) => {
|
|
39
|
+
if (req.url !== "/" && req.url !== "/index.html") {
|
|
40
|
+
next();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const transformed = await server.transformIndexHtml(req.url, html, req.originalUrl);
|
|
44
|
+
res.statusCode = 200;
|
|
45
|
+
res.setHeader("Content-Type", "text/html");
|
|
46
|
+
res.end(transformed);
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import type { PatchworkVitePluginOptions } from "./patchwork-plugin.js";
|
|
3
|
+
/** Emits the rendered icon PNGs for build, and serves the same buffers for dev. */
|
|
4
|
+
export declare function iconsPlugin(options?: PatchworkVitePluginOptions): Plugin | null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { getIcons } from "../site-kit/icons.js";
|
|
3
|
+
/** Emits the rendered icon PNGs for build, and serves the same buffers for dev. */
|
|
4
|
+
export function iconsPlugin(options = {}) {
|
|
5
|
+
if (!options.icons)
|
|
6
|
+
return null;
|
|
7
|
+
const { source } = options.icons;
|
|
8
|
+
let sourcePath;
|
|
9
|
+
let isBuild = false;
|
|
10
|
+
return {
|
|
11
|
+
name: "@patchwork/icons",
|
|
12
|
+
configResolved(config) {
|
|
13
|
+
isBuild = config.command === "build";
|
|
14
|
+
sourcePath = resolve(config.root, source);
|
|
15
|
+
},
|
|
16
|
+
async buildStart() {
|
|
17
|
+
if (!isBuild)
|
|
18
|
+
return;
|
|
19
|
+
const icons = await getIcons(sourcePath);
|
|
20
|
+
for (const [fileName, buffer] of icons) {
|
|
21
|
+
this.emitFile({ type: "asset", fileName, source: buffer });
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
configureServer(server) {
|
|
25
|
+
server.middlewares.use(async (req, res, next) => {
|
|
26
|
+
const fileName = req.url?.replace(/^\//, "");
|
|
27
|
+
if (!fileName) {
|
|
28
|
+
next();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const icons = await getIcons(sourcePath);
|
|
32
|
+
const buffer = icons.get(fileName);
|
|
33
|
+
if (!buffer) {
|
|
34
|
+
next();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
res.setHeader("Content-Type", "image/png");
|
|
38
|
+
res.end(buffer);
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
/**
|
|
3
|
+
* bootloader owns resolving/emitting its own dependencies (it's the one that
|
|
4
|
+
* actually has automerge/keyhive/codemirror/solid/the other patchwork-*
|
|
5
|
+
* packages as real dependencies) — reused here rather than duplicated.
|
|
6
|
+
*/
|
|
7
|
+
import externals, { resolveExternal, emitWasmAssets, } from "@inkandswitch/patchwork-bootloader/externals";
|
|
8
|
+
export const builtins = externals.reduce((builtins, name) => ((builtins[name] = `/packages/${name}.js`), builtins), {});
|
|
9
|
+
// This package importmaps itself too, so tool code loaded at runtime can
|
|
10
|
+
// bare-import it just like the other @inkandswitch/patchwork-* packages.
|
|
11
|
+
builtins["@inkandswitch/patchwork"] = "/packages/@inkandswitch/patchwork.js";
|
|
12
|
+
/**
|
|
13
|
+
* Node resolves a package's own name from within its own source when its
|
|
14
|
+
* package.json has "exports" (self-reference) — the same mechanism
|
|
15
|
+
* @inkandswitch/patchwork-bootloader already relies on to list itself among
|
|
16
|
+
* its own externals. Anchoring at *this* file (which lives inside
|
|
17
|
+
* @inkandswitch/patchwork's own installed directory) makes that
|
|
18
|
+
* self-reference resolve to this package instead of bootloader's.
|
|
19
|
+
*/
|
|
20
|
+
const self = fileURLToPath(import.meta.url);
|
|
21
|
+
async function resolveSelf(name) {
|
|
22
|
+
const resolved = await this.resolve(name, self, { skipSelf: true });
|
|
23
|
+
if (!resolved) {
|
|
24
|
+
throw new Error(`@inkandswitch/patchwork: couldn't resolve "${name}" from its own package.`);
|
|
25
|
+
}
|
|
26
|
+
return resolved.id;
|
|
27
|
+
}
|
|
28
|
+
function resolveBuiltin(context, id) {
|
|
29
|
+
return id === "@inkandswitch/patchwork"
|
|
30
|
+
? resolveSelf.call(context, id)
|
|
31
|
+
: resolveExternal.call(context, id);
|
|
32
|
+
}
|
|
33
|
+
function createImportMap(options) {
|
|
34
|
+
const importmap = structuredClone(options?.importmap ?? { imports: {}, scopes: {} });
|
|
35
|
+
importmap.imports ??= {};
|
|
36
|
+
importmap.scopes ??= {};
|
|
37
|
+
Object.assign(importmap.imports, builtins);
|
|
38
|
+
return { importmap, builtins };
|
|
39
|
+
}
|
|
40
|
+
export function importmap(options) {
|
|
41
|
+
const { importmap, builtins } = createImportMap(options);
|
|
42
|
+
return {
|
|
43
|
+
name: "@patchwork/vite",
|
|
44
|
+
async buildStart() {
|
|
45
|
+
for (const [id, fileName] of Object.entries(builtins)) {
|
|
46
|
+
this.emitFile({
|
|
47
|
+
type: "chunk",
|
|
48
|
+
fileName: fileName.slice(1),
|
|
49
|
+
id: await resolveBuiltin(this, id),
|
|
50
|
+
preserveSignature: "strict",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// Emitted so the service worker can fetch them
|
|
54
|
+
emitWasmAssets.call(this);
|
|
55
|
+
},
|
|
56
|
+
async resolveId(id) {
|
|
57
|
+
if (id in builtins) {
|
|
58
|
+
// point the site's own imports at the same copy we emit as a chunk,
|
|
59
|
+
// otherwise rollup bundles a second one out of the site's node_modules
|
|
60
|
+
// and you end up with two automerges racing to init the same wasm
|
|
61
|
+
return resolveBuiltin(this, id);
|
|
62
|
+
}
|
|
63
|
+
if (id in importmap.imports) {
|
|
64
|
+
return { id: importmap.imports[id], external: true };
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
transformIndexHtml: {
|
|
68
|
+
order: "pre",
|
|
69
|
+
handler(html) {
|
|
70
|
+
return {
|
|
71
|
+
html,
|
|
72
|
+
tags: [
|
|
73
|
+
{
|
|
74
|
+
tag: "script",
|
|
75
|
+
attrs: { type: "importmap" },
|
|
76
|
+
children: JSON.stringify(importmap, null, 2),
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import type { PatchworkVitePluginOptions } from "./patchwork-plugin.js";
|
|
3
|
+
/** Emits manifest.webmanifest for build and serves it for dev, from the same generated object. */
|
|
4
|
+
export declare function manifestPlugin(options?: PatchworkVitePluginOptions): Plugin | null;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { buildManifest } from "../site-kit/manifest.js";
|
|
2
|
+
/** Emits manifest.webmanifest for build and serves it for dev, from the same generated object. */
|
|
3
|
+
export function manifestPlugin(options = {}) {
|
|
4
|
+
if (options.manifest === false)
|
|
5
|
+
return null;
|
|
6
|
+
const manifest = buildManifest(options);
|
|
7
|
+
const source = JSON.stringify(manifest, null, 2);
|
|
8
|
+
let isBuild = false;
|
|
9
|
+
return {
|
|
10
|
+
name: "@patchwork/manifest",
|
|
11
|
+
configResolved(config) {
|
|
12
|
+
isBuild = config.command === "build";
|
|
13
|
+
},
|
|
14
|
+
buildStart() {
|
|
15
|
+
if (!isBuild)
|
|
16
|
+
return;
|
|
17
|
+
this.emitFile({
|
|
18
|
+
type: "asset",
|
|
19
|
+
fileName: "manifest.webmanifest",
|
|
20
|
+
source,
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
configureServer(server) {
|
|
24
|
+
server.middlewares.use((req, res, next) => {
|
|
25
|
+
if (req.url !== "/manifest.webmanifest") {
|
|
26
|
+
next();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
res.setHeader("Content-Type", "application/manifest+json");
|
|
30
|
+
res.end(source);
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import type { PatchworkVitePluginOptions } from "./patchwork-plugin.js";
|
|
3
|
+
/**
|
|
4
|
+
* Netlify's _headers/_redirects are only meaningful on an actual Netlify
|
|
5
|
+
* deploy, so this only runs for the production build — no dev middleware.
|
|
6
|
+
* The Link header shares its sync-server/wasm-asset lists with html-plugin
|
|
7
|
+
* so the two never drift out of sync with each other.
|
|
8
|
+
*/
|
|
9
|
+
export declare function netlifyPlugin(options?: PatchworkVitePluginOptions): Plugin | null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { buildHeaders, REDIRECTS } from "../site-kit/netlify.js";
|
|
2
|
+
/**
|
|
3
|
+
* Netlify's _headers/_redirects are only meaningful on an actual Netlify
|
|
4
|
+
* deploy, so this only runs for the production build — no dev middleware.
|
|
5
|
+
* The Link header shares its sync-server/wasm-asset lists with html-plugin
|
|
6
|
+
* so the two never drift out of sync with each other.
|
|
7
|
+
*/
|
|
8
|
+
export function netlifyPlugin(options = {}) {
|
|
9
|
+
if (options.netlify === false)
|
|
10
|
+
return null;
|
|
11
|
+
const headers = buildHeaders(options);
|
|
12
|
+
let isBuild = false;
|
|
13
|
+
return {
|
|
14
|
+
name: "@patchwork/netlify",
|
|
15
|
+
configResolved(config) {
|
|
16
|
+
isBuild = config.command === "build";
|
|
17
|
+
},
|
|
18
|
+
buildStart() {
|
|
19
|
+
if (!isBuild)
|
|
20
|
+
return;
|
|
21
|
+
this.emitFile({ type: "asset", fileName: "_headers", source: headers });
|
|
22
|
+
this.emitFile({
|
|
23
|
+
type: "asset",
|
|
24
|
+
fileName: "_redirects",
|
|
25
|
+
source: REDIRECTS,
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Plugin, ServerOptions, PreviewOptions, BuildOptions } from "vite";
|
|
2
|
+
import type { PatchworkSiteOptions } from "../site-kit/options.js";
|
|
3
|
+
/**
|
|
4
|
+
* The patchwork vite plugin. A site's vite.config.ts can shrink down to
|
|
5
|
+
* `plugins: [patchwork({...})]` plus a single source icon file — this plugin
|
|
6
|
+
* owns the wasm plugin, the importmap/service-worker emission, the generated
|
|
7
|
+
* index.html/manifest.webmanifest/Netlify _headers+_redirects, the site's
|
|
8
|
+
* icon set, and vite's server/preview/worker/build/define config.
|
|
9
|
+
*
|
|
10
|
+
* Each generated piece is switched off individually by passing `false` for
|
|
11
|
+
* its option (`html: false`, `manifest: false`, `netlify: false`,
|
|
12
|
+
* `icons: false`, `server: false`, `preview: false`, `worker: false`).
|
|
13
|
+
*
|
|
14
|
+
* `server`/`preview`/`worker`/`build`/`define` are owned by this plugin's
|
|
15
|
+
* `config()` hook and driven entirely by these options — don't also set them
|
|
16
|
+
* in the site's own `defineConfig({...})` alongside `patchwork()`, since
|
|
17
|
+
* Vite's plugin-config merge order doesn't guarantee which one wins.
|
|
18
|
+
*
|
|
19
|
+
* Most of what this plugin does — icon rendering, and building the
|
|
20
|
+
* index.html/manifest.webmanifest/Netlify _headers content — has no vite
|
|
21
|
+
* dependency at all; see `../site-kit/index.ts` for those pieces reused by
|
|
22
|
+
* a different bundler adapter.
|
|
23
|
+
*/
|
|
24
|
+
export default function patchwork(options?: PatchworkVitePluginOptions): Plugin<any>[];
|
|
25
|
+
type Imports = {
|
|
26
|
+
[name: string]: string;
|
|
27
|
+
};
|
|
28
|
+
export type ImportMap = {
|
|
29
|
+
imports: Imports;
|
|
30
|
+
scopes?: {
|
|
31
|
+
[scope: string]: Imports;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
export type { PatchworkSiteOptions, PatchworkIconsOptions, PatchworkHtmlOptions, PatchworkNetlifyOptions, PatchworkSyncServersOptions, } from "../site-kit/options.js";
|
|
35
|
+
export interface PatchworkVitePluginOptions extends PatchworkSiteOptions {
|
|
36
|
+
importmap?: ImportMap;
|
|
37
|
+
server?: false | ServerOptions;
|
|
38
|
+
preview?: false | PreviewOptions;
|
|
39
|
+
worker?: false | {
|
|
40
|
+
format?: "es" | "iife";
|
|
41
|
+
};
|
|
42
|
+
build?: BuildOptions;
|
|
43
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { importmap } from "./importmap-plugin.js";
|
|
2
|
+
import { serviceworker } from "./service-worker-plugin.js";
|
|
3
|
+
import { configPlugin, wasm } from "./config-plugin.js";
|
|
4
|
+
import { iconsPlugin } from "./icons.js";
|
|
5
|
+
import { htmlPlugin } from "./html-plugin.js";
|
|
6
|
+
import { manifestPlugin } from "./manifest-plugin.js";
|
|
7
|
+
import { netlifyPlugin } from "./netlify-plugin.js";
|
|
8
|
+
/**
|
|
9
|
+
* The patchwork vite plugin. A site's vite.config.ts can shrink down to
|
|
10
|
+
* `plugins: [patchwork({...})]` plus a single source icon file — this plugin
|
|
11
|
+
* owns the wasm plugin, the importmap/service-worker emission, the generated
|
|
12
|
+
* index.html/manifest.webmanifest/Netlify _headers+_redirects, the site's
|
|
13
|
+
* icon set, and vite's server/preview/worker/build/define config.
|
|
14
|
+
*
|
|
15
|
+
* Each generated piece is switched off individually by passing `false` for
|
|
16
|
+
* its option (`html: false`, `manifest: false`, `netlify: false`,
|
|
17
|
+
* `icons: false`, `server: false`, `preview: false`, `worker: false`).
|
|
18
|
+
*
|
|
19
|
+
* `server`/`preview`/`worker`/`build`/`define` are owned by this plugin's
|
|
20
|
+
* `config()` hook and driven entirely by these options — don't also set them
|
|
21
|
+
* in the site's own `defineConfig({...})` alongside `patchwork()`, since
|
|
22
|
+
* Vite's plugin-config merge order doesn't guarantee which one wins.
|
|
23
|
+
*
|
|
24
|
+
* Most of what this plugin does — icon rendering, and building the
|
|
25
|
+
* index.html/manifest.webmanifest/Netlify _headers content — has no vite
|
|
26
|
+
* dependency at all; see `../site-kit/index.ts` for those pieces reused by
|
|
27
|
+
* a different bundler adapter.
|
|
28
|
+
*/
|
|
29
|
+
export default function patchwork(options) {
|
|
30
|
+
return [
|
|
31
|
+
wasm(),
|
|
32
|
+
configPlugin(options),
|
|
33
|
+
iconsPlugin(options),
|
|
34
|
+
htmlPlugin(options),
|
|
35
|
+
manifestPlugin(options),
|
|
36
|
+
netlifyPlugin(options),
|
|
37
|
+
importmap(options),
|
|
38
|
+
serviceworker(),
|
|
39
|
+
].filter((plugin) => plugin != null);
|
|
40
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { builtins } from "./importmap-plugin.js";
|
|
3
|
+
// Anchor resolution at this file (inside @inkandswitch/patchwork's own
|
|
4
|
+
// installed directory) rather than the site root — these worker specifiers
|
|
5
|
+
// are @inkandswitch/patchwork-bootloader's own exports, a transitive
|
|
6
|
+
// dependency of the site under strict pnpm, not resolvable from the site's
|
|
7
|
+
// own node_modules by bare specifier.
|
|
8
|
+
const self = fileURLToPath(import.meta.url);
|
|
9
|
+
// The service worker and the automerge shared worker are emitted as their
|
|
10
|
+
// own chunks. Their heavy imports are marked external and resolved to
|
|
11
|
+
// /packages/... URLs (both workers are created with type:"module", so the
|
|
12
|
+
// browser fetches those as regular network requests).
|
|
13
|
+
const workers = [
|
|
14
|
+
{
|
|
15
|
+
specifier: "@inkandswitch/patchwork-bootloader/service-worker",
|
|
16
|
+
fileName: "service-worker.js",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
specifier: "@inkandswitch/patchwork-bootloader/automerge-worker",
|
|
20
|
+
fileName: "automerge-worker.js",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
specifier: "@inkandswitch/patchwork-bootloader/module-loader-worker",
|
|
24
|
+
fileName: "module-loader-worker.js",
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
export function serviceworker() {
|
|
28
|
+
const entryIds = new Set();
|
|
29
|
+
return {
|
|
30
|
+
name: "@patchwork/service-worker",
|
|
31
|
+
enforce: "pre",
|
|
32
|
+
async buildStart() {
|
|
33
|
+
for (const { specifier, fileName } of workers) {
|
|
34
|
+
const resolved = await this.resolve(specifier, self);
|
|
35
|
+
entryIds.add(resolved.id);
|
|
36
|
+
this.emitFile({
|
|
37
|
+
type: "chunk",
|
|
38
|
+
id: resolved.id,
|
|
39
|
+
fileName,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
resolveId(source, importer) {
|
|
44
|
+
if (importer && entryIds.has(importer) && source in builtins) {
|
|
45
|
+
return { id: builtins[source], external: true };
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|