Skip to content

Commit

Permalink
add builtin support for import_maps
Browse files Browse the repository at this point in the history
  • Loading branch information
tlgimenes committed Oct 19, 2024
1 parent 43cdaac commit 9f6c990
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 36 deletions.
13 changes: 1 addition & 12 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Loading React App</title>
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/[email protected]",
"react/": "https://esm.sh/[email protected]/",
"react-dom": "https://esm.sh/[email protected]",
"react-dom/": "https://esm.sh/[email protected]/",
"react-router-dom": "https://esm.sh/[email protected]"
}
}
</script>
<title>React App</title>
<script src="https://cdn.tailwindcss.com"></script>
<script type="module" src="index.js"></script>
</head>
Expand Down
40 changes: 22 additions & 18 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,31 +58,35 @@ const rerender = async () => {
const registerServiceWorker = async () => {
const waiting = Promise.withResolvers();

if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.register("sw.js");

if (registration.active) {
waiting.resolve();
} else if (registration.waiting || registration.installing) {
const worker = registration.waiting || registration.installing;

worker.addEventListener("statechange", (event) => {
if (event.target.state === "activated") {
waiting.resolve();
}
});
}
} catch (error) {
console.log("ServiceWorker is required to run this app: ", error);
if (!("serviceWorker" in navigator)) {
throw new Error("ServiceWorker is required to run this app");
}

try {
const registration = await navigator.serviceWorker.register("sw.js");

if (registration.active) {
waiting.resolve();
} else if (registration.waiting || registration.installing) {
const worker = registration.waiting || registration.installing;

worker.addEventListener("statechange", (event) => {
if (event.target.state === "activated") {
waiting.resolve();
}
});
}
} catch (error) {
console.error(error);

throw new Error("ServiceWorker is required to run this app");
}

return waiting.promise;
};

const main = async () => {
await registerServiceWorker();
await registerServiceWorker().catch(console.error);

const sdk = await getSDK();

Expand Down
126 changes: 120 additions & 6 deletions sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@ self.addEventListener("fetch", (event) => {
event.respondWith(route(event.request));
});

/**
* Get importmap from local cache, or fetch
*/
const getImportMapJSON = async () => {
const url = new URL("./deno.json", location.href);

try {
const response = await caches.match(url) || await fetch(url);
const importMap = await response.json();

return importMap;
} catch (error) {
console.error(error);
return {};
}
};

/**
* Adds a querystring to all imports in the code so we can bust the cache
*/
Expand Down Expand Up @@ -47,22 +64,118 @@ function addQuerystringToImportsBabelPlugin() {
source.startsWith("/");

if (isRelative) {
path.node.arguments[0].value = `${source}?ts=${Date.now()}`;
path.node.arguments[0].value = Babel.packages.types.stringLiteral(
`${source}?ts=${Date.now()}`,
);
}
}
},
},
};
}

/**
* Replaces all imports with the correct path from the import map
*/
function transformImportMapBabelPlugin(importMap) {
const prefixes = Object.keys(importMap.imports)
.filter((prefix) => prefix.endsWith("/"));

return {
visitor: {
ImportDeclaration(path) {
const source = path.node.source.value;

if (typeof source !== "string") {
return;
}

const isRelative = source.startsWith("./") ||
source.startsWith("../") ||
source.startsWith("/");

if (isRelative) {
return;
}

const fullMatch = importMap.imports[source];

if (fullMatch) {
path.node.source = Babel.packages.types.stringLiteral(
fullMatch,
);

return;
}

const prefix = prefixes.find((prefix) => source.startsWith(prefix));

if (prefix) {
const suffix = source.slice(prefix.length);
const fullMatch = importMap.imports[prefix] + suffix;

path.node.source = Babel.packages.types.stringLiteral(
fullMatch,
);
}
},
CallExpression(path) {
if (
path.node.callee.type === "Import" &&
path.node.arguments.length === 1 &&
path.node.arguments[0].type === "StringLiteral"
) {
const source = path.node.arguments[0].value;
const isRelative = source.startsWith("./") ||
source.startsWith("../") ||
source.startsWith("/");

if (isRelative) {
return;
}

const fullMatch = importMap.imports[source];

if (fullMatch) {
path.node.arguments[0].value = Babel.packages.types.stringLiteral(
fullMatch,
);

return;
}

const prefix = prefixes.find((prefix) => source.startsWith(prefix));

if (prefix) {
const suffix = source.slice(prefix.length);
const fullMatch = importMap.imports[prefix] + suffix;

path.node.arguments[0].value = Babel.packages.types.stringLiteral(
fullMatch,
);
}
}
},
},
};
}

const transpile = (code, path) =>
Babel.transform(code, {
const transpile = async (tsCode, path) => {
const importMap = await getImportMapJSON();

const { code } = Babel.transform(tsCode, {
code: true,
ast: false,
filename: new URL(path).pathname,
presets: [["react", { runtime: "automatic" }], "typescript"],
plugins: [addQuerystringToImportsBabelPlugin],
}).code;
plugins: [
addQuerystringToImportsBabelPlugin,
transformImportMapBabelPlugin(importMap),
],
});

return code;
};

/** If the request is a Typescript file, transpile it and change to text/javascript */
async function maybeTranspileResponse(request, response) {
Expand All @@ -74,7 +187,8 @@ async function maybeTranspileResponse(request, response) {

const headers = new Headers(response.headers);

const text = transpile(await response.text(), request.url);
const tsCode = await response.text();
const text = await transpile(tsCode, request.url);

headers.set("content-type", "text/javascript");
headers.set("cache-control", "no-store, no-cache");
Expand Down

0 comments on commit 9f6c990

Please sign in to comment.