extrojs 0.2.0 → 0.3.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/client.d.ts +7 -15
- package/dist/commands/build.js +1 -1
- package/dist/commands/dev.js +12 -29
- package/dist/config.d.ts +2 -2
- package/dist/core/asset.d.ts +10 -0
- package/dist/core/asset.js +10 -0
- package/dist/dev-assets.d.ts +3 -3
- package/dist/dev-assets.js +18 -16
- package/dist/exports/asset.d.ts +1 -0
- package/dist/exports/asset.js +1 -0
- package/dist/exports/link.d.ts +1 -0
- package/dist/exports/link.js +1 -0
- package/dist/exports/navigation.d.ts +2 -0
- package/dist/exports/navigation.js +1 -0
- package/dist/exports/runtime.d.ts +2 -0
- package/dist/exports/runtime.js +3 -0
- package/dist/load-config.d.ts +1 -1
- package/dist/paths.d.ts +1 -1
- package/dist/plugin/app-tree.d.ts +59 -0
- package/dist/plugin/app-tree.js +214 -0
- package/dist/plugin/asset-inventory.d.ts +24 -0
- package/dist/plugin/asset-inventory.js +9 -0
- package/dist/plugin/dev-reactions.d.ts +59 -0
- package/dist/plugin/dev-reactions.js +62 -0
- package/dist/plugin/emit-assets.d.ts +56 -0
- package/dist/plugin/emit-assets.js +47 -0
- package/dist/plugin/generators/html.d.ts +64 -0
- package/dist/plugin/generators/html.js +167 -0
- package/dist/plugin/generators/icons.d.ts +15 -0
- package/dist/plugin/generators/icons.js +16 -0
- package/dist/plugin/generators/public.d.ts +17 -0
- package/dist/plugin/generators/public.js +20 -0
- package/dist/plugin/icons.d.ts +5 -0
- package/dist/plugin/icons.js +20 -0
- package/dist/plugin/index.d.ts +31 -0
- package/dist/plugin/index.js +246 -0
- package/dist/plugin/internal.d.ts +14 -0
- package/dist/plugin/internal.js +6 -0
- package/dist/plugin/manifest.d.ts +29 -0
- package/dist/plugin/manifest.js +68 -0
- package/dist/plugin/public.d.ts +21 -0
- package/dist/plugin/public.js +69 -0
- package/dist/plugin/runtimes/clients/csui-mount.js +90 -0
- package/dist/plugin/runtimes/clients/dev-bridge.js +194 -0
- package/dist/plugin/runtimes/csui-mount.d.ts +18 -0
- package/dist/plugin/runtimes/csui-mount.js +21 -0
- package/dist/plugin/runtimes/dev-bridge.d.ts +22 -0
- package/dist/plugin/runtimes/dev-bridge.js +19 -0
- package/dist/plugin/runtimes/routes-module.d.ts +20 -0
- package/dist/plugin/runtimes/routes-module.js +51 -0
- package/dist/plugin/runtimes/runtime-module.d.ts +16 -0
- package/dist/plugin/runtimes/runtime-module.js +40 -0
- package/dist/plugin/surfaces.d.ts +37 -0
- package/dist/plugin/surfaces.js +67 -0
- package/dist/plugin/types/index.d.ts +9 -0
- package/dist/plugin/types/index.js +1 -0
- package/dist/plugin/utils/read-json.d.ts +1 -0
- package/dist/plugin/utils/read-json.js +8 -0
- package/dist/react/env.d.ts +13 -0
- package/dist/react/env.js +1 -0
- package/dist/router/build-tree.d.ts +46 -0
- package/dist/router/build-tree.js +56 -0
- package/dist/router/context.d.ts +13 -0
- package/dist/router/context.js +2 -0
- package/dist/router/create-router.d.ts +10 -0
- package/dist/router/create-router.js +126 -0
- package/dist/router/defaults.d.ts +24 -0
- package/dist/router/defaults.js +25 -0
- package/dist/router/error-boundary.d.ts +23 -0
- package/dist/router/error-boundary.js +21 -0
- package/dist/router/hooks.d.ts +18 -0
- package/dist/router/hooks.js +34 -0
- package/dist/router/index.d.ts +8 -0
- package/dist/router/index.js +7 -0
- package/dist/router/link.d.ts +305 -0
- package/dist/router/link.js +30 -0
- package/dist/router/match.d.ts +14 -0
- package/dist/router/match.js +27 -0
- package/dist/router/types.d.ts +55 -0
- package/dist/router/types.js +1 -0
- package/dist/types/index.d.ts +152 -0
- package/dist/types/index.js +1 -0
- package/package.json +39 -7
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { routeManifest } from "./app-tree.js";
|
|
2
|
+
/**
|
|
3
|
+
* A changed file under `src/app/background/` dirties background; under
|
|
4
|
+
* `src/app/content/` dirties content; anything else (shared code) dirties both.
|
|
5
|
+
* This is the only place that knows the `src/app` layout, so the CLI no longer
|
|
6
|
+
* hardcodes those paths.
|
|
7
|
+
*/
|
|
8
|
+
export function classifyScriptChange(changedPath) {
|
|
9
|
+
const p = changedPath.replace(/\\/g, "/");
|
|
10
|
+
const background = p.includes("/src/app/background/");
|
|
11
|
+
const content = p.includes("/src/app/content/");
|
|
12
|
+
if (!background && !content)
|
|
13
|
+
return { background: true, content: true };
|
|
14
|
+
return { background, content };
|
|
15
|
+
}
|
|
16
|
+
/** Union two dirty states (accumulating `change` events across one build). */
|
|
17
|
+
export function mergeDirty(a, b) {
|
|
18
|
+
return {
|
|
19
|
+
background: a.background || b.background,
|
|
20
|
+
content: a.content || b.content,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the accumulated dirty state at `BUNDLE_END`. No classified change
|
|
25
|
+
* (the initial build, or an `extro dev` restart) conservatively means both,
|
|
26
|
+
* matching the broadcast-on-first-build behavior.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveFlush(dirty) {
|
|
29
|
+
if (!dirty.background && !dirty.content) {
|
|
30
|
+
return { background: true, content: true };
|
|
31
|
+
}
|
|
32
|
+
return { background: dirty.background, content: dirty.content };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Diff two AppTrees into a {@link DevReaction}. A birth short-circuits the
|
|
36
|
+
* invalidate scan: the session has to restart regardless of any route delta.
|
|
37
|
+
*/
|
|
38
|
+
export function decideTreeReaction(prev, next, routables) {
|
|
39
|
+
if (!prev.scripts.background && next.scripts.background) {
|
|
40
|
+
return { kind: "restart", surface: "background" };
|
|
41
|
+
}
|
|
42
|
+
if (!prev.scripts.content && next.scripts.content) {
|
|
43
|
+
return { kind: "restart", surface: "content" };
|
|
44
|
+
}
|
|
45
|
+
for (const surface of routables) {
|
|
46
|
+
const had = (prev.surfaces[surface]?.routes.length ?? 0) > 0;
|
|
47
|
+
const has = (next.surfaces[surface]?.routes.length ?? 0) > 0;
|
|
48
|
+
if (has && !had)
|
|
49
|
+
return { kind: "restart", surface };
|
|
50
|
+
}
|
|
51
|
+
// The Route manifest IS what the routes module emits and is fully
|
|
52
|
+
// serializable, so its stable stringify is a faithful identity: invalidation
|
|
53
|
+
// can no longer drift from the contract (ADR 0005). This is what kills the
|
|
54
|
+
// historical HMR-drift bug class.
|
|
55
|
+
const changed = [];
|
|
56
|
+
for (const surface of routables) {
|
|
57
|
+
const key = (t) => JSON.stringify(routeManifest(t, surface));
|
|
58
|
+
if (key(prev) !== key(next))
|
|
59
|
+
changed.push(surface);
|
|
60
|
+
}
|
|
61
|
+
return changed.length ? { kind: "invalidate", surfaces: changed } : { kind: "noop" };
|
|
62
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ExtroConfig, ManifestV3 } from "../types/index.js";
|
|
2
|
+
import type { AppTree } from "./app-tree.js";
|
|
3
|
+
import type { AssetInventory } from "./asset-inventory.js";
|
|
4
|
+
import type { RoutableSurface } from "./surfaces.js";
|
|
5
|
+
/**
|
|
6
|
+
* Receives one finished asset at a time. Sync (Vite's `emitFile`) and async
|
|
7
|
+
* (`fs.writeFile`) sinks both fit — callers that await the seam will see
|
|
8
|
+
* any rejection from the async path.
|
|
9
|
+
*/
|
|
10
|
+
export type EmitSink = (fileName: string, source: string) => void | Promise<void>;
|
|
11
|
+
export interface AssetOptions {
|
|
12
|
+
tree: AppTree;
|
|
13
|
+
/**
|
|
14
|
+
* The build's discovered icons + Public assets. Both call paths
|
|
15
|
+
* (`generateBundle`, `writeDevAssets`) run `discoverAssets` once at their
|
|
16
|
+
* filesystem edge and pass the result in, so composition stays pure.
|
|
17
|
+
*/
|
|
18
|
+
inventory: AssetInventory;
|
|
19
|
+
pkg: {
|
|
20
|
+
name?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
version?: string;
|
|
23
|
+
};
|
|
24
|
+
config: ExtroConfig;
|
|
25
|
+
dev?: {
|
|
26
|
+
port: number;
|
|
27
|
+
signalPort: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface Artifacts {
|
|
31
|
+
manifest: ManifestV3;
|
|
32
|
+
html: Partial<Record<RoutableSurface, string>>;
|
|
33
|
+
/**
|
|
34
|
+
* The dev probe (`extro-dev.js`) the dev shells load to reveal the
|
|
35
|
+
* offline screen when the dev server is unreachable. Absent in prod
|
|
36
|
+
* builds and when there are no HTML surfaces to probe for.
|
|
37
|
+
*/
|
|
38
|
+
devProbe?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Pure projection from inputs to artifact strings: no filesystem access (the
|
|
42
|
+
* Asset inventory is discovered at the caller's edge and passed in). Exposed
|
|
43
|
+
* alongside `emitAssets` so tests can assert "given this tree + inventory, this
|
|
44
|
+
* is the manifest" without a sink and without staging files on disk.
|
|
45
|
+
*/
|
|
46
|
+
export declare function composeArtifacts(opts: AssetOptions): Artifacts;
|
|
47
|
+
/**
|
|
48
|
+
* Canonical emission of Extro's static outputs (manifest + per-surface HTML).
|
|
49
|
+
* Both the build (`generateBundle`) and dev (`writeDevAssets`) call paths go
|
|
50
|
+
* through here — the only difference between them is the sink.
|
|
51
|
+
*
|
|
52
|
+
* Icons live outside this seam: they are emitted once during build via
|
|
53
|
+
* Rollup's binary-asset path and are not needed in dev (the initial
|
|
54
|
+
* `viteBuild` already wrote them to disk).
|
|
55
|
+
*/
|
|
56
|
+
export declare function emitAssets(opts: AssetOptions, emit: EmitSink): Promise<void>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { generateManifest } from "./manifest.js";
|
|
2
|
+
import { DEV_PROBE_FILE, generateDevProbe, generateHTML, } from "./generators/html.js";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Public API
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
/**
|
|
7
|
+
* Pure projection from inputs to artifact strings: no filesystem access (the
|
|
8
|
+
* Asset inventory is discovered at the caller's edge and passed in). Exposed
|
|
9
|
+
* alongside `emitAssets` so tests can assert "given this tree + inventory, this
|
|
10
|
+
* is the manifest" without a sink and without staging files on disk.
|
|
11
|
+
*/
|
|
12
|
+
export function composeArtifacts(opts) {
|
|
13
|
+
const manifest = generateManifest(opts);
|
|
14
|
+
const surfaces = Object.keys(opts.tree.surfaces);
|
|
15
|
+
const html = {};
|
|
16
|
+
for (const surface of surfaces) {
|
|
17
|
+
html[surface] = generateHTML({
|
|
18
|
+
surface,
|
|
19
|
+
dev: opts.dev ? { port: opts.dev.port } : undefined,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const devProbe = opts.dev && surfaces.length > 0
|
|
23
|
+
? generateDevProbe({ port: opts.dev.port })
|
|
24
|
+
: undefined;
|
|
25
|
+
return { manifest, html, devProbe };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Canonical emission of Extro's static outputs (manifest + per-surface HTML).
|
|
29
|
+
* Both the build (`generateBundle`) and dev (`writeDevAssets`) call paths go
|
|
30
|
+
* through here — the only difference between them is the sink.
|
|
31
|
+
*
|
|
32
|
+
* Icons live outside this seam: they are emitted once during build via
|
|
33
|
+
* Rollup's binary-asset path and are not needed in dev (the initial
|
|
34
|
+
* `viteBuild` already wrote them to disk).
|
|
35
|
+
*/
|
|
36
|
+
export async function emitAssets(opts, emit) {
|
|
37
|
+
const { manifest, html, devProbe } = composeArtifacts(opts);
|
|
38
|
+
await emit("manifest.json", JSON.stringify(manifest, null, 2));
|
|
39
|
+
if (devProbe) {
|
|
40
|
+
await emit(DEV_PROBE_FILE, devProbe);
|
|
41
|
+
}
|
|
42
|
+
for (const [surface, body] of Object.entries(html)) {
|
|
43
|
+
if (!body)
|
|
44
|
+
continue;
|
|
45
|
+
await emit(`${surface}.html`, body);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
interface GenerateHTMLOptions {
|
|
2
|
+
surface: string;
|
|
3
|
+
dev?: {
|
|
4
|
+
port: number;
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The dev probe script emitted next to the HTML shells in the dev output
|
|
9
|
+
* dir. Referenced by the shells and reserved against Public assets, so the
|
|
10
|
+
* name lives here once.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEV_PROBE_FILE = "extro-dev.js";
|
|
13
|
+
/**
|
|
14
|
+
* @describe Generates the HTML shell for a surface. In dev mode, the shell
|
|
15
|
+
* points at the Vite dev server (with @vite/client for HMR) instead of the
|
|
16
|
+
* built bundle, and carries a hidden brand-styled "dev server offline"
|
|
17
|
+
* screen inside #root. The probe script (generateDevProbe) reveals it only
|
|
18
|
+
* when the dev server is unreachable; on a normal start it never paints,
|
|
19
|
+
* and React's createRoot replaces #root's children on mount.
|
|
20
|
+
*/
|
|
21
|
+
export declare function generateHTML({ surface, dev }: GenerateHTMLOptions): string;
|
|
22
|
+
export interface DevScreen {
|
|
23
|
+
title: string;
|
|
24
|
+
/** HTML body content (paragraphs, code, etc.). Inserted as-is. */
|
|
25
|
+
body: string;
|
|
26
|
+
/**
|
|
27
|
+
* When true, the screen fills the viewport and centers its card. Use for
|
|
28
|
+
* surfaces with a real viewport (options tab, sidepanel). When false
|
|
29
|
+
* (popup), the screen sizes to content with a fixed minimum.
|
|
30
|
+
*/
|
|
31
|
+
fill?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* When true, the screen ships with the `hidden` attribute and stays out
|
|
34
|
+
* of the first paint until something removes it (the dev probe, for the
|
|
35
|
+
* offline screen).
|
|
36
|
+
*/
|
|
37
|
+
hidden?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* @describe Renders a brand-styled "developer screen" card. Used as the
|
|
41
|
+
* pre-render inside #root for offline-dev-server fallback today, intended
|
|
42
|
+
* for reuse in other build-time / runtime dev panels.
|
|
43
|
+
*/
|
|
44
|
+
export declare const renderDevScreen: ({ title, body, fill, hidden, }: DevScreen) => string;
|
|
45
|
+
/**
|
|
46
|
+
* @describe Global styles for any `.extro-dev-screen` rendered in dev. Body
|
|
47
|
+
* is painted dark only while a visible screen is in the DOM (via `:has()`),
|
|
48
|
+
* so the user's app keeps default styling both before React mounts (screen
|
|
49
|
+
* still hidden) and after (screen wiped from #root).
|
|
50
|
+
*/
|
|
51
|
+
export declare const devScreenStyles: () => string;
|
|
52
|
+
/**
|
|
53
|
+
* @describe Generates the dev probe (`extro-dev.js`), emitted into the dev
|
|
54
|
+
* output dir next to the HTML shells. MV3 CSP bans inline scripts but allows
|
|
55
|
+
* 'self', so the probe loads from the extension origin and runs even when
|
|
56
|
+
* the Vite dev server is down. It reveals the hidden offline screen only
|
|
57
|
+
* when the @vite/client script fails to fetch, the one unambiguous signal
|
|
58
|
+
* that the server is unreachable; a failing user module while the server is
|
|
59
|
+
* up stays Vite's error overlay's problem.
|
|
60
|
+
*/
|
|
61
|
+
export declare const generateDevProbe: ({ port }: {
|
|
62
|
+
port: number;
|
|
63
|
+
}) => string;
|
|
64
|
+
export {};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dev probe script emitted next to the HTML shells in the dev output
|
|
3
|
+
* dir. Referenced by the shells and reserved against Public assets, so the
|
|
4
|
+
* name lives here once.
|
|
5
|
+
*/
|
|
6
|
+
export const DEV_PROBE_FILE = "extro-dev.js";
|
|
7
|
+
/**
|
|
8
|
+
* @describe Generates the HTML shell for a surface. In dev mode, the shell
|
|
9
|
+
* points at the Vite dev server (with @vite/client for HMR) instead of the
|
|
10
|
+
* built bundle, and carries a hidden brand-styled "dev server offline"
|
|
11
|
+
* screen inside #root. The probe script (generateDevProbe) reveals it only
|
|
12
|
+
* when the dev server is unreachable; on a normal start it never paints,
|
|
13
|
+
* and React's createRoot replaces #root's children on mount.
|
|
14
|
+
*/
|
|
15
|
+
export function generateHTML({ surface, dev }) {
|
|
16
|
+
const title = surface.charAt(0).toUpperCase() + surface.slice(1);
|
|
17
|
+
const scripts = dev
|
|
18
|
+
? `
|
|
19
|
+
<script src="./${DEV_PROBE_FILE}"></script>
|
|
20
|
+
<script type="module" src="http://localhost:${dev.port}/@vite/client"></script>
|
|
21
|
+
<script type="module" src="http://localhost:${dev.port}/@id/@vitejs/plugin-react/preamble"></script>
|
|
22
|
+
<script type="module" src="http://localhost:${dev.port}/@id/virtual:extro/runtime/${surface}"></script>
|
|
23
|
+
`
|
|
24
|
+
: `<script type="module" src="./${surface}.js"></script>`;
|
|
25
|
+
const rootContent = dev
|
|
26
|
+
? renderDevScreen({
|
|
27
|
+
title: "Dev server isn't running",
|
|
28
|
+
body: `
|
|
29
|
+
<p>Start it with <code>extro dev</code>, then reopen this surface.</p>
|
|
30
|
+
<p>Expected at <code>http://localhost:${dev.port}</code>.</p>
|
|
31
|
+
`,
|
|
32
|
+
// Popups size to content (no viewport to fill); everything else
|
|
33
|
+
// (options/sidepanel/tab) gets a full-viewport centered layout.
|
|
34
|
+
fill: surface !== "popup",
|
|
35
|
+
// Hidden until the probe proves the dev server is down, so a normal
|
|
36
|
+
// start never paints it (the screen used to flash for the moment
|
|
37
|
+
// between document load and React mount).
|
|
38
|
+
hidden: true,
|
|
39
|
+
})
|
|
40
|
+
: "";
|
|
41
|
+
return `
|
|
42
|
+
<!doctype html>
|
|
43
|
+
<html>
|
|
44
|
+
<head>
|
|
45
|
+
<title>${title}</title>
|
|
46
|
+
${dev ? devScreenStyles() : ""}
|
|
47
|
+
</head>
|
|
48
|
+
<body>
|
|
49
|
+
<div id="root">${rootContent}</div>
|
|
50
|
+
${scripts.trim()}
|
|
51
|
+
</body>
|
|
52
|
+
</html>
|
|
53
|
+
`.trim();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* @describe Renders a brand-styled "developer screen" card. Used as the
|
|
57
|
+
* pre-render inside #root for offline-dev-server fallback today, intended
|
|
58
|
+
* for reuse in other build-time / runtime dev panels.
|
|
59
|
+
*/
|
|
60
|
+
export const renderDevScreen = ({ title, body, fill = false, hidden = false, }) => {
|
|
61
|
+
const cls = `extro-dev-screen${fill ? " extro-dev-screen--fill" : ""}`;
|
|
62
|
+
return `
|
|
63
|
+
<div class="${cls}"${hidden ? " hidden" : ""}>
|
|
64
|
+
<div class="extro-dev-screen__card">
|
|
65
|
+
<span class="extro-dev-screen__tag">EXTRO</span>
|
|
66
|
+
<h1>${title}</h1>
|
|
67
|
+
${body.trim()}
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
`.trim();
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* @describe Global styles for any `.extro-dev-screen` rendered in dev. Body
|
|
74
|
+
* is painted dark only while a visible screen is in the DOM (via `:has()`),
|
|
75
|
+
* so the user's app keeps default styling both before React mounts (screen
|
|
76
|
+
* still hidden) and after (screen wiped from #root).
|
|
77
|
+
*/
|
|
78
|
+
export const devScreenStyles = () => `
|
|
79
|
+
<style>
|
|
80
|
+
body { margin: 0; }
|
|
81
|
+
body:has(.extro-dev-screen:not([hidden])) { background: #0a0a0a; }
|
|
82
|
+
.extro-dev-screen {
|
|
83
|
+
box-sizing: border-box;
|
|
84
|
+
min-width: 360px;
|
|
85
|
+
min-height: 240px;
|
|
86
|
+
padding: 24px 28px;
|
|
87
|
+
background: #0a0a0a;
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
justify-content: center;
|
|
91
|
+
opacity: 0;
|
|
92
|
+
animation: extro-dev-screen-in 0.18s forwards;
|
|
93
|
+
}
|
|
94
|
+
.extro-dev-screen[hidden] {
|
|
95
|
+
display: none;
|
|
96
|
+
}
|
|
97
|
+
.extro-dev-screen--fill {
|
|
98
|
+
min-height: 100vh;
|
|
99
|
+
}
|
|
100
|
+
.extro-dev-screen__card {
|
|
101
|
+
box-sizing: border-box;
|
|
102
|
+
width: 100%;
|
|
103
|
+
max-width: 480px;
|
|
104
|
+
display: flex;
|
|
105
|
+
flex-direction: column;
|
|
106
|
+
gap: 10px;
|
|
107
|
+
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
108
|
+
color: #e5e5e5;
|
|
109
|
+
}
|
|
110
|
+
.extro-dev-screen__tag {
|
|
111
|
+
align-self: flex-start;
|
|
112
|
+
font-size: 11px;
|
|
113
|
+
font-weight: 600;
|
|
114
|
+
letter-spacing: 0.08em;
|
|
115
|
+
padding: 3px 8px;
|
|
116
|
+
color: #0a0a0a;
|
|
117
|
+
background: #CC785C;
|
|
118
|
+
border-radius: 3px;
|
|
119
|
+
}
|
|
120
|
+
.extro-dev-screen h1 {
|
|
121
|
+
margin: 4px 0 0;
|
|
122
|
+
font-size: 15px;
|
|
123
|
+
font-weight: 600;
|
|
124
|
+
color: #fafafa;
|
|
125
|
+
}
|
|
126
|
+
.extro-dev-screen p {
|
|
127
|
+
margin: 0;
|
|
128
|
+
font-size: 13px;
|
|
129
|
+
line-height: 1.5;
|
|
130
|
+
color: #a3a3a3;
|
|
131
|
+
}
|
|
132
|
+
.extro-dev-screen code {
|
|
133
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
134
|
+
font-size: 12px;
|
|
135
|
+
padding: 1px 6px;
|
|
136
|
+
color: #CC785C;
|
|
137
|
+
background: #1a1a1a;
|
|
138
|
+
border-radius: 3px;
|
|
139
|
+
}
|
|
140
|
+
@keyframes extro-dev-screen-in { to { opacity: 1; } }
|
|
141
|
+
</style>
|
|
142
|
+
`.trim();
|
|
143
|
+
/**
|
|
144
|
+
* @describe Generates the dev probe (`extro-dev.js`), emitted into the dev
|
|
145
|
+
* output dir next to the HTML shells. MV3 CSP bans inline scripts but allows
|
|
146
|
+
* 'self', so the probe loads from the extension origin and runs even when
|
|
147
|
+
* the Vite dev server is down. It reveals the hidden offline screen only
|
|
148
|
+
* when the @vite/client script fails to fetch, the one unambiguous signal
|
|
149
|
+
* that the server is unreachable; a failing user module while the server is
|
|
150
|
+
* up stays Vite's error overlay's problem.
|
|
151
|
+
*/
|
|
152
|
+
export const generateDevProbe = ({ port }) => `
|
|
153
|
+
// Generated by Extro. Reveals the "dev server isn't running" screen when
|
|
154
|
+
// the Vite dev server can't be reached. Script fetch errors don't bubble,
|
|
155
|
+
// hence the capture-phase listener; this classic script runs before the
|
|
156
|
+
// module scripts below it can fail.
|
|
157
|
+
window.addEventListener(
|
|
158
|
+
"error",
|
|
159
|
+
(event) => {
|
|
160
|
+
const el = event.target;
|
|
161
|
+
if (!(el instanceof HTMLScriptElement)) return;
|
|
162
|
+
if (el.src !== "http://localhost:${port}/@vite/client") return;
|
|
163
|
+
document.querySelector(".extro-dev-screen")?.removeAttribute("hidden");
|
|
164
|
+
},
|
|
165
|
+
true,
|
|
166
|
+
);
|
|
167
|
+
`.trimStart();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PluginContextLike } from "../types/index.js";
|
|
2
|
+
interface EmitIconsOptions {
|
|
3
|
+
ctx: PluginContextLike;
|
|
4
|
+
root: string;
|
|
5
|
+
/** The recognized icon set from the Asset inventory (size -> "icons/16.png"). */
|
|
6
|
+
icons: Record<string, string> | null;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* @file generators/icons.ts
|
|
10
|
+
* @description Emits the recognized extension icons named by the Asset
|
|
11
|
+
* inventory. Discovery already decided which sizes exist, so this only reads
|
|
12
|
+
* bytes: what ships is exactly what `manifest.icons` references.
|
|
13
|
+
*/
|
|
14
|
+
export declare function emitIcons({ ctx, root, icons }: EmitIconsOptions): void;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* @file generators/icons.ts
|
|
5
|
+
* @description Emits the recognized extension icons named by the Asset
|
|
6
|
+
* inventory. Discovery already decided which sizes exist, so this only reads
|
|
7
|
+
* bytes: what ships is exactly what `manifest.icons` references.
|
|
8
|
+
*/
|
|
9
|
+
export function emitIcons({ ctx, root, icons }) {
|
|
10
|
+
if (!icons)
|
|
11
|
+
return;
|
|
12
|
+
for (const rel of Object.values(icons)) {
|
|
13
|
+
const source = fs.readFileSync(path.join(root, rel));
|
|
14
|
+
ctx.emitFile({ type: "asset", fileName: rel, source });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PluginContextLike } from "../types/index.js";
|
|
2
|
+
import { type PublicAssets } from "../public.js";
|
|
3
|
+
interface EmitPublicAssetsOptions {
|
|
4
|
+
ctx: PluginContextLike;
|
|
5
|
+
root: string;
|
|
6
|
+
/** The partitioned Public assets from the Asset inventory. */
|
|
7
|
+
publicAssets: PublicAssets;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* @file generators/public.ts
|
|
11
|
+
* @description Emits Public assets into the build output with their original
|
|
12
|
+
* names, from the partition the Asset inventory already computed. Mirrors
|
|
13
|
+
* `emitIcons`; the collision guard skips any file that would overwrite a
|
|
14
|
+
* generated output and warns instead.
|
|
15
|
+
*/
|
|
16
|
+
export declare function emitPublicAssets({ ctx, root, publicAssets }: EmitPublicAssetsOptions): void;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { PUBLIC_DIR } from "../public.js";
|
|
4
|
+
/**
|
|
5
|
+
* @file generators/public.ts
|
|
6
|
+
* @description Emits Public assets into the build output with their original
|
|
7
|
+
* names, from the partition the Asset inventory already computed. Mirrors
|
|
8
|
+
* `emitIcons`; the collision guard skips any file that would overwrite a
|
|
9
|
+
* generated output and warns instead.
|
|
10
|
+
*/
|
|
11
|
+
export function emitPublicAssets({ ctx, root, publicAssets }) {
|
|
12
|
+
const { files, conflicts } = publicAssets;
|
|
13
|
+
for (const conflict of conflicts) {
|
|
14
|
+
ctx.warn(`public/${conflict} collides with a generated output; skipping. Rename it to ship it.`);
|
|
15
|
+
}
|
|
16
|
+
for (const file of files) {
|
|
17
|
+
const source = fs.readFileSync(path.join(root, PUBLIC_DIR, file));
|
|
18
|
+
ctx.emitFile({ type: "asset", fileName: file, source });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* @file icons.ts
|
|
5
|
+
* @description Detects the icons for the extension.
|
|
6
|
+
*/
|
|
7
|
+
export function detectIcons(root) {
|
|
8
|
+
const iconsDir = path.join(root, "icons");
|
|
9
|
+
if (!fs.existsSync(iconsDir))
|
|
10
|
+
return undefined;
|
|
11
|
+
const sizes = ["16", "32", "48", "128"];
|
|
12
|
+
const icons = {};
|
|
13
|
+
for (const size of sizes) {
|
|
14
|
+
const file = path.join(iconsDir, `${size}.png`);
|
|
15
|
+
if (fs.existsSync(file)) {
|
|
16
|
+
icons[size] = `icons/${size}.png`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return Object.keys(icons).length ? icons : undefined;
|
|
20
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import type { ExtroConfig } from "../types/index.js";
|
|
3
|
+
interface ExtroPluginOptions {
|
|
4
|
+
root: string;
|
|
5
|
+
config?: ExtroConfig;
|
|
6
|
+
/**
|
|
7
|
+
* When true, only background/content are bundled. Manifest + HTML emission
|
|
8
|
+
* is skipped (writeDevAssets handles those separately during `extro dev`).
|
|
9
|
+
* Used by the dev build-watch sidecar.
|
|
10
|
+
*/
|
|
11
|
+
scriptsOnly?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* When set, wrap the background entry in the dev bridge so a service
|
|
14
|
+
* worker exists in dev mode (even if the user didn't write one) to
|
|
15
|
+
* receive rebuild signals from the CLI's WS server and forward Vite HMR
|
|
16
|
+
* events to content scripts.
|
|
17
|
+
*/
|
|
18
|
+
devBridge?: {
|
|
19
|
+
signalPort: number;
|
|
20
|
+
vitePort: number;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Called from the dev-server plugin's `handleHotUpdate` with a payload
|
|
24
|
+
* shaped like Vite's own HMR `update` event. The CLI uses this to
|
|
25
|
+
* broadcast HMR over the signal WS (avoiding Vite's HMR WS, whose
|
|
26
|
+
* origin check rejects chrome-extension:// service workers).
|
|
27
|
+
*/
|
|
28
|
+
broadcastHmr?: (payload: object) => void;
|
|
29
|
+
}
|
|
30
|
+
export declare function extro(options: ExtroPluginOptions): Plugin;
|
|
31
|
+
export {};
|