@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.
Files changed (70) hide show
  1. package/build-head.js +10 -0
  2. package/dist/client.d.ts +38 -0
  3. package/dist/global.css +1 -0
  4. package/dist/head.d.ts +1 -0
  5. package/dist/head.js +52 -0
  6. package/dist/index.d.ts +37 -0
  7. package/dist/index.js +294 -0
  8. package/dist/loading.d.ts +5 -0
  9. package/dist/loading.js +125 -0
  10. package/dist/repo.d.ts +25 -0
  11. package/dist/repo.js +108 -0
  12. package/dist/router.d.ts +18 -0
  13. package/dist/router.js +132 -0
  14. package/dist/site-kit/html.d.ts +4 -0
  15. package/dist/site-kit/html.js +70 -0
  16. package/dist/site-kit/icons.d.ts +9 -0
  17. package/dist/site-kit/icons.js +36 -0
  18. package/dist/site-kit/index.d.ts +15 -0
  19. package/dist/site-kit/index.js +5 -0
  20. package/dist/site-kit/manifest.d.ts +3 -0
  21. package/dist/site-kit/manifest.js +29 -0
  22. package/dist/site-kit/netlify.d.ts +5 -0
  23. package/dist/site-kit/netlify.js +27 -0
  24. package/dist/site-kit/options.d.ts +67 -0
  25. package/dist/site-kit/options.js +9 -0
  26. package/dist/site-kit/sync-servers.d.ts +11 -0
  27. package/dist/site-kit/sync-servers.js +36 -0
  28. package/dist/types.d.ts +104 -0
  29. package/dist/types.js +1 -0
  30. package/dist/vite/config-plugin.d.ts +12 -0
  31. package/dist/vite/config-plugin.js +70 -0
  32. package/dist/vite/html-plugin.d.ts +11 -0
  33. package/dist/vite/html-plugin.js +51 -0
  34. package/dist/vite/icons.d.ts +4 -0
  35. package/dist/vite/icons.js +42 -0
  36. package/dist/vite/importmap-plugin.d.ts +4 -0
  37. package/dist/vite/importmap-plugin.js +83 -0
  38. package/dist/vite/manifest-plugin.d.ts +4 -0
  39. package/dist/vite/manifest-plugin.js +34 -0
  40. package/dist/vite/netlify-plugin.d.ts +9 -0
  41. package/dist/vite/netlify-plugin.js +29 -0
  42. package/dist/vite/patchwork-plugin.d.ts +43 -0
  43. package/dist/vite/patchwork-plugin.js +40 -0
  44. package/dist/vite/service-worker-plugin.d.ts +2 -0
  45. package/dist/vite/service-worker-plugin.js +49 -0
  46. package/package.json +55 -0
  47. package/src/client.d.ts +38 -0
  48. package/src/global.css +1 -0
  49. package/src/head.ts +14 -0
  50. package/src/index.ts +469 -0
  51. package/src/loading.ts +137 -0
  52. package/src/repo.ts +146 -0
  53. package/src/router.ts +195 -0
  54. package/src/site-kit/html.ts +100 -0
  55. package/src/site-kit/icons.ts +49 -0
  56. package/src/site-kit/index.ts +22 -0
  57. package/src/site-kit/manifest.ts +39 -0
  58. package/src/site-kit/netlify.ts +35 -0
  59. package/src/site-kit/options.ts +73 -0
  60. package/src/site-kit/sync-servers.ts +39 -0
  61. package/src/types.ts +135 -0
  62. package/src/vite/config-plugin.ts +88 -0
  63. package/src/vite/html-plugin.ts +60 -0
  64. package/src/vite/icons.ts +47 -0
  65. package/src/vite/importmap-plugin.ts +112 -0
  66. package/src/vite/manifest-plugin.ts +39 -0
  67. package/src/vite/netlify-plugin.ts +34 -0
  68. package/src/vite/patchwork-plugin.ts +67 -0
  69. package/src/vite/service-worker-plugin.ts +54 -0
  70. package/tsconfig.json +14 -0
@@ -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,4 @@
1
+ import type { Plugin } from "vite";
2
+ import type { PatchworkVitePluginOptions } from "./patchwork-plugin.js";
3
+ export declare const builtins: Record<string, string>;
4
+ export declare function importmap(options?: PatchworkVitePluginOptions): Plugin;
@@ -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,2 @@
1
+ import type { Plugin } from "vite";
2
+ export declare function serviceworker(): Plugin;
@@ -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
+ }