Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: remote islands #2301

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/server/deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ export {
} from "https://esm.sh/@babel/[email protected]";
export { normalize } from "https://deno.land/[email protected]/path/posix/mod.ts";
export { assertSnapshot } from "https://deno.land/[email protected]/testing/snapshot.ts";
export {
Project,
ResolutionHosts,
} from "https://deno.land/x/[email protected]/mod.ts";
34 changes: 34 additions & 0 deletions src/server/fs_extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import {
contentType,
dirname,
extname,
fromFileUrl,
join,
Project,
ResolutionHosts,
SEPARATOR,
toFileUrl,
walk,
Expand Down Expand Up @@ -274,6 +277,16 @@ export async function extractRoutes(
});
}

for (const islandPath of findRemoteIslands(baseUrl)) {
const module = await import(islandPath);
const name = sanitizeIslandName(new URL(islandPath).pathname);
processedIslands.push({
name,
path: islandPath,
module,
});
}

for (const processedIsland of processedIslands) {
for (
const [exportName, exportedFunction] of Object.entries(
Expand Down Expand Up @@ -563,3 +576,24 @@ function getRoutesFromPlugins(plugins: Plugin[]): [string, RouteModule][] {
}];
});
}

function findRemoteIslands(baseUrl: string) {
const project = new Project({
resolutionHost: ResolutionHosts.deno,
});

["routes", "components", "islands"].forEach((directory) => {
const pathPattern = join(fromFileUrl(baseUrl), `${directory}/**/*.tsx`);
project.addSourceFilesAtPaths(pathPattern);
});

return project.getSourceFiles().flatMap((sourceFile) =>
sourceFile.getImportDeclarations().filter((importDeclaration) =>
importDeclaration.getTrailingCommentRanges().some((comment) =>
comment.getText().includes("FreshRemoteIsland")
)
).map((importDeclaration) =>
importDeclaration.getModuleSpecifier().getLiteralValue()
)
);
}
2 changes: 2 additions & 0 deletions tests/fixture/fresh.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import * as $intercept_args from "./routes/intercept_args.tsx";
import * as $island_json from "./routes/island_json.tsx";
import * as $islands_index from "./routes/islands/index.tsx";
import * as $islands_multiple_island_exports from "./routes/islands/multiple_island_exports.tsx";
import * as $islands_remoteIslands from "./routes/islands/remoteIslands.tsx";
import * as $islands_returning_null from "./routes/islands/returning_null.tsx";
import * as $islands_root_fragment from "./routes/islands/root_fragment.tsx";
import * as $islands_root_fragment_conditional_first from "./routes/islands/root_fragment_conditional_first.tsx";
Expand Down Expand Up @@ -147,6 +148,7 @@ const manifest = {
"./routes/islands/index.tsx": $islands_index,
"./routes/islands/multiple_island_exports.tsx":
$islands_multiple_island_exports,
"./routes/islands/remoteIslands.tsx": $islands_remoteIslands,
"./routes/islands/returning_null.tsx": $islands_returning_null,
"./routes/islands/root_fragment.tsx": $islands_root_fragment,
"./routes/islands/root_fragment_conditional_first.tsx":
Expand Down
14 changes: 14 additions & 0 deletions tests/fixture/routes/islands/remoteIslands.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useSignal } from "@preact/signals";
import Counter from "https://raw.githubusercontent.com/denoland/fresh/d19c22d5921bdf21de9a7d7bc043b8523ac81893/tests/fixture/islands/Counter.tsx"; //FreshRemoteIsland
import { default as RemoteCounter2 } from "https://deno.land/x/[email protected]/tests/fixture/islands/folder/Counter.tsx"; //FreshRemoteIsland
import { default as LocalCounter } from "../../islands/Counter.tsx";

export default function remoteIslandRoute() {
return (
<>
<Counter id="remoteCounter1" count={useSignal(13)} />
<RemoteCounter2 id="remoteCounter2" count={useSignal(37)} />
<LocalCounter id="localCounter" count={useSignal(42)} />
</>
);
}
62 changes: 34 additions & 28 deletions tests/islands_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,14 @@ import {

Deno.test("island tests", async (t) => {
await withPage(async (page, address) => {
async function counterTest(counterId: string, originalValue: number) {
const pElem = await page.waitForSelector(`#${counterId} > p`);

const value = await pElem?.evaluate((el) => el.textContent);
assert(value === `${originalValue}`, `${counterId} first value`);

await clickWhenListenerReady(page, `#b-${counterId}`);
await waitForText(page, `#${counterId} > p`, String(originalValue + 1));
}

await page.goto(`${address}/islands`);

await t.step("Ensure 5 islands on 1 page are revived", async () => {
await counterTest("counter1", 3);
await counterTest("counter2", 10);
await counterTest("folder-counter", 3);
await counterTest("subfolder-counter", 3);
await counterTest("kebab-case-file-counter", 5);
await counterTest("counter1", 3, page);
await counterTest("counter2", 10, page);
await counterTest("folder-counter", 3, page);
await counterTest("subfolder-counter", 3, page);
await counterTest("kebab-case-file-counter", 5, page);
});

await t.step("Ensure an island revive an img 'hash' path", async () => {
Expand All @@ -51,22 +41,12 @@ Deno.test("island tests", async (t) => {

Deno.test("multiple islands exported from one file", async (t) => {
await withPage(async (page, address) => {
async function counterTest(counterId: string, originalValue: number) {
const pElem = await page.waitForSelector(`#${counterId} > p`);

const value = await pElem?.evaluate((el) => el.textContent);
assert(value === `${originalValue}`, `${counterId} first value`);

await clickWhenListenerReady(page, `#b-${counterId}`);
await waitForText(page, `#${counterId} > p`, String(originalValue + 1));
}

await page.goto(`${address}/islands/multiple_island_exports`);

await t.step("Ensure 3 islands on 1 page are revived", async () => {
await counterTest("counter0", 4);
await counterTest("counter1", 3);
await counterTest("counter2", 10);
await counterTest("counter0", 4, page);
await counterTest("counter1", 3, page);
await counterTest("counter2", 10, page);
});
});
});
Expand All @@ -75,6 +55,18 @@ function withPage(fn: (page: Page, address: string) => Promise<void>) {
return withPageName("./tests/fixture/main.ts", fn);
}

Deno.test("remote islands", async (t) => {
await withPage(async (page, address) => {
await page.goto(`${address}/islands/remoteIslands`);

await t.step("Ensure 3 islands on 1 page are revived", async () => {
await counterTest("remoteCounter1", 13, page);
await counterTest("remoteCounter2", 37, page);
await counterTest("localCounter", 42, page);
});
});
});

Deno.test("island tests with </script>", async (t) => {
await withPage(async (page, address) => {
page.on("dialog", () => {
Expand Down Expand Up @@ -489,3 +481,17 @@ Deno.test("serves multiple islands in one file", async () => {
},
);
});

async function counterTest(
counterId: string,
originalValue: number,
page: Page,
) {
const pElem = await page.waitForSelector(`#${counterId} > p`);

const value = await pElem?.evaluate((el) => el.textContent);
assert(value === `${originalValue}`, `${counterId} first value`);

await clickWhenListenerReady(page, `#b-${counterId}`);
await waitForText(page, `#${counterId} > p`, String(originalValue + 1));
}
Loading