Skip to content

Commit

Permalink
islands hydrate themselves
Browse files Browse the repository at this point in the history
  • Loading branch information
sndrs committed Apr 22, 2023
1 parent 5c2fe05 commit f2f893d
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 68 deletions.
6 changes: 3 additions & 3 deletions src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ const routesESBuildConfig: esbuild.BuildOptions = {
.flat(),
write: false,
plugins: [
island_wrapper("ssr", site_dir),
island_wrapper("ssr", site_dir, base_path),
resolve_svelte_internal,
build_routes({ base_path }),
build_routes,
],
outdir: build_dir,
...baseESBuildConfig,
Expand All @@ -80,7 +80,7 @@ const islandsESBuildConfig: esbuild.BuildOptions = {
]
.flat(),
plugins: [
island_wrapper("dom", site_dir),
island_wrapper("dom", site_dir, base_path),
resolve_svelte_internal,
],
outdir: build_dir + "components/",
Expand Down
52 changes: 2 additions & 50 deletions src/esbuild_plugins/build_routes/get_route_html.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,18 @@
import { normalize } from "https://deno.land/[email protected]/path/mod.ts";
import { format } from "npm:prettier";

// dummy value to put the var name in scope
const component_path = "";

// this function is stringified inline in the page
// putting it here gives us type safety etc
export const hydrate_island = async (target: Element) => {
try {
const name = target.getAttribute("name");
const props = JSON.parse(target.getAttribute("props") ?? "{}");
const load = performance.now();

const Component =
(await import(component_path + name + ".island.js")).default;
console.group(name);
console.info(
`Loaded in %c${Math.round((performance.now() - load) * 1000) / 1000}ms`,
"color: orange",
);

const hydrate = performance.now();
new Component({ target, props, hydrate: true });
target.setAttribute("foraged", "");

console.info(
`Hydrated in %c${
Math.round((performance.now() - hydrate) * 1000) / 1000
}ms%c with`,
"color: orange",
"color: reset",
props,
);
console.groupEnd();
} catch (_) {
console.error(_);
}
};

interface TemplateOptions {
css: string;
head: string;
html: string;
hydrator: string;
}
const template = ({ css, head, html, hydrator }: TemplateOptions) => `
const template = ({ css, head, html }: TemplateOptions) => `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
${head}
<script type="module">${hydrator}</script>
<style>${css}</style>
</head>
<body>
Expand All @@ -61,23 +21,15 @@ const template = ({ css, head, html, hydrator }: TemplateOptions) => `
</html>
`;

const island_hydrator = (base = "") => `
const component_path = "${base}";
const hydrate_island = ${hydrate_island.toString()};
document.querySelectorAll("one-claw[name]").forEach(hydrate_island);
`;

export const get_route_html = ({ html, css, head, base_path }: {
export const get_route_html = ({ html, css, head }: {
html: string;
css: string;
head: string;
base_path?: string;
}) => {
const page = template({
css,
head,
html,
hydrator: island_hydrator(normalize(`/${base_path}/components/`)),
});

try {
Expand Down
8 changes: 3 additions & 5 deletions src/esbuild_plugins/build_routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ interface SSROutput {
css?: { code: string };
}

export const build_routes = (
{ base_path }: { base_path: string },
): Plugin => ({
export const build_routes: Plugin = {
name: "mononykus/build-routes",
setup(build) {
build.onEnd(async (result) => {
Expand All @@ -36,7 +34,7 @@ export const build_routes = (

await Deno.writeTextFile(
dist_path,
get_route_html({ html, css, head, base_path }),
get_route_html({ html, css, head }),
);
}

Expand All @@ -47,4 +45,4 @@ export const build_routes = (
);
});
},
});
};
59 changes: 49 additions & 10 deletions src/esbuild_plugins/islands.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import { normalize } from "https://deno.land/[email protected]/path/mod.ts";
import type { Plugin } from "https://deno.land/x/[email protected]/mod.js";
import { compile, preprocess } from "npm:svelte/compiler";

const filter = /\.svelte$/;
const name = "mononykus/svelte-islands";

export const island_wrapper = (mode: "ssr" | "dom", dir: string): Plugin => ({
export const island_wrapper = (
mode: "ssr" | "dom",
dir: string,
base_path: string,
): Plugin => ({
name,
setup(build) {
build.onLoad({ filter }, async ({ path }) => {
const filename = path.split(dir).at(-1) ?? "Undefined.svelte";
const source = await Deno.readTextFile(path);
const island = filename.match(/\/(\w+).island.svelte/);

const processed = island && mode === "ssr"
? (await preprocess(source, {
let processed = source;

if (island && mode === "ssr") {
const preprocessed = await preprocess(source, {
markup: ({ content }) => {
let processed = content;
const non_html = content.match(
Expand All @@ -25,19 +32,29 @@ export const island_wrapper = (mode: "ssr" | "dom", dir: string): Plugin => ({
for (const el of non_html) {
html = html.replace(el, "");
}
processed = non_html.join("") +
`<one-claw name="${
island[1]
}" props={JSON.stringify($$props)} style="display:contents;">${html.trim()}</one-claw>`;

const src = filename.replace(
"components/",
normalize(`/${base_path}/components/`),
).replace(/svelte$/, "js");

processed = non_html.join("") + `
<svelte:head>
<script type="module" src="${src}" />
</svelte:head>
<one-claw name="${
island[1]
}" props={JSON.stringify($$props)} style="display:contents;">${html.trim()}</one-claw>`;
}
return ({
code: processed,
});
},
})).code
: source;
});
processed = preprocessed.code;
}

const { js: { code } } = compile(processed, {
let { js: { code } } = compile(processed, {
generate: mode,
css: "injected",
cssHash: ({ hash, css }) => `◖${hash(css)}◗`,
Expand All @@ -46,6 +63,28 @@ export const island_wrapper = (mode: "ssr" | "dom", dir: string): Plugin => ({
filename,
});

if (island && mode === "dom") {
const name = island[1];
code += `
const load = performance.now();
const targets = document.querySelectorAll("one-claw[name='${name}']");
for (const target of targets) {
const props = JSON.parse(target.getAttribute("props") ?? "{}");
new ${name}_island({ target, props, hydrate: true });
console.info(
\`Hydrated %c${name}%c in %c\${
Math.round((performance.now() - load) * 1000) / 1000
\}ms%c with\`,
"color: orange",
"color: reset",
"color: orange",
"color: reset",
props,
);
}
`;
}

return ({ contents: code });
});
},
Expand Down

0 comments on commit f2f893d

Please sign in to comment.