Skip to content

Commit

Permalink
feat: Add support for enabling service worker feature (#1127)
Browse files Browse the repository at this point in the history
* feat: Add support for enabling service worker feature in sandpack-bundler

* fix: actually set suffix correctly

* fix

* wip

* wip

* create story

* refactor

* docs

* format

* update

* add example

---------

Co-authored-by: Danilo Woznica <danilowoz@gmail.com>
Co-authored-by: codesandbox-bot <codesandbot@codesandbox.io>
  • Loading branch information
3 people authored Jul 11, 2024
1 parent 0b09de6 commit 4e2b116
Show file tree
Hide file tree
Showing 18 changed files with 268 additions and 36 deletions.
2 changes: 1 addition & 1 deletion .codesandbox/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"command": "yarn workspace @codesandbox/sandpack-react build"
},
"dev:website-docs": {
"name": "Documentation",
"name": "Dev: website docs",
"command": "yarn dev:docs"
},
"dev:sandpack-react": {
Expand Down
1 change: 1 addition & 0 deletions sandpack-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@codesandbox/nodebox": "0.1.8",
"buffer": "^6.0.3",
"dequal": "^2.0.2",
"mime-db": "^1.52.0",
"outvariant": "1.4.0",
"static-browser-server": "1.0.3"
},
Expand Down
4 changes: 2 additions & 2 deletions sandpack-client/src/clients/node/client.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ export const findStartScriptPackageJson = (
}

if (item.type === TokenType.Command && commandNotFoundYet) {
command = item.value;
command = item.value!;
}

if (
item.type === TokenType.Argument ||
(!commandNotFoundYet && item.type === TokenType.Command)
) {
args.push(item.value);
args.push(item.value!);
}

// TODO: support TokenType.AND, TokenType.OR, TokenType.PIPE
Expand Down
104 changes: 101 additions & 3 deletions sandpack-client/src/clients/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,20 @@ import { SandpackClient } from "../base";

import Protocol from "./file-resolver-protocol";
import { IFrameProtocol } from "./iframe-protocol";
import type { SandpackRuntimeMessage } from "./types";
import { getTemplate } from "./utils";
import { EXTENSIONS_MAP } from "./mime";
import type { IPreviewRequestMessage, IPreviewResponseMessage } from "./types";
import { CHANNEL_NAME, type SandpackRuntimeMessage } from "./types";
import { getExtension, getTemplate } from "./utils";

const SUFFIX_PLACEHOLDER = "-{{suffix}}";

const BUNDLER_URL =
process.env.CODESANDBOX_ENV === "development"
? "http://localhost:3000/"
: `https://${process.env.PACKAGE_VERSION?.replace(
/\./g,
"-"
)}-sandpack.codesandbox.io/`;
)}${SUFFIX_PLACEHOLDER}-sandpack.codesandbox.io/`;

export class SandpackRuntime extends SandpackClient {
fileResolverProtocol?: Protocol;
Expand Down Expand Up @@ -62,6 +66,15 @@ export class SandpackRuntime extends SandpackClient {
`?cache=${Date.now()}`;
}

const suffixes: string[] = [];
if (options.experimental_enableServiceWorker) {
suffixes.push(Math.random().toString(36).slice(4));
}
this.bundlerURL = this.bundlerURL.replace(
SUFFIX_PLACEHOLDER,
suffixes.length ? `-${suffixes.join("-")}` : ""
);

this.bundlerState = undefined;
this.errors = [];
this.status = "initializing";
Expand Down Expand Up @@ -153,6 +166,89 @@ export class SandpackRuntime extends SandpackClient {
}
}
);

if (options.experimental_enableServiceWorker) {
this.serviceWorkerHandshake();
}
}

private serviceWorkerHandshake() {
const channel = new MessageChannel();

const iframeContentWindow = this.iframe.contentWindow;
if (!iframeContentWindow) {
throw new Error("Could not get iframe contentWindow");
}

const port = channel.port1;
port.onmessage = (evt: MessageEvent) => {
if (typeof evt.data === "object" && evt.data.$channel === CHANNEL_NAME) {
switch (evt.data.$type) {
case "preview/ready":
// no op for now
break;
case "preview/request":
this.handleWorkerRequest(evt.data, port);

break;
}
}
};

this.iframe.onload = () => {
const initMsg = {
$channel: CHANNEL_NAME,
$type: "preview/init",
};

iframeContentWindow.postMessage(initMsg, "*", [channel.port2]);
};
}

private handleWorkerRequest(
request: IPreviewRequestMessage,
port: MessagePort
) {
try {
const filepath = new URL(request.url, this.bundlerURL).pathname;

const headers: Record<string, string> = {};

const files = this.getFiles();
const body = files[filepath].code;

if (!headers["Content-Type"]) {
const extension = getExtension(filepath);
const foundMimetype = EXTENSIONS_MAP.get(extension);
if (foundMimetype) {
headers["Content-Type"] = foundMimetype;
}
}

const responseMessage: IPreviewResponseMessage = {
$channel: CHANNEL_NAME,
$type: "preview/response",
id: request.id,
headers,
status: 200,
body,
};

port.postMessage(responseMessage);
} catch (err) {
const responseMessage: IPreviewResponseMessage = {
$channel: CHANNEL_NAME,
$type: "preview/response",
id: request.id,
headers: {
"Content-Type": "text/html; charset=utf-8",
},
status: 404,
body: "File not found",
};

port.postMessage(responseMessage);
}
}

public setLocationURLIntoIFrame(): void {
Expand Down Expand Up @@ -240,6 +336,8 @@ export class SandpackRuntime extends SandpackClient {
hasFileResolver: Boolean(this.options.fileResolver),
disableDependencyPreprocessing:
this.sandboxSetup.disableDependencyPreprocessing,
experimental_enableServiceWorker:
this.options.experimental_enableServiceWorker,
template:
this.sandboxSetup.template ||
getTemplate(packageJSON, normalizedModules),
Expand Down
22 changes: 22 additions & 0 deletions sandpack-client/src/clients/runtime/mime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import mimeDB from "mime-db";

const extensionMap = new Map<string, string>();
const entries = Object.entries(mimeDB);
for (const [mimetype, entry] of entries) {
// eslint-disable-next-line
// @ts-ignore
if (!entry.extensions) {
continue;
}

// eslint-disable-next-line
// @ts-ignore
const extensions = entry.extensions as string[];
if (extensions.length) {
for (const ext of extensions) {
extensionMap.set(ext, mimetype);
}
}
}

export const EXTENSIONS_MAP = extensionMap;
19 changes: 19 additions & 0 deletions sandpack-client/src/clients/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export type SandpackRuntimeMessage = BaseSandpackMessage &
externalResources: string[];
hasFileResolver: boolean;
disableDependencyPreprocessing?: boolean;
experimental_enableServiceWorker?: boolean;
template?: string | SandpackTemplate;
showOpenInCodeSandbox: boolean;
showErrorScreen: boolean;
Expand Down Expand Up @@ -123,3 +124,21 @@ export type SandpackRuntimeMessage = BaseSandpackMessage &
};
}
);
export const CHANNEL_NAME = "$CSB_RELAY";

export interface IPreviewRequestMessage {
$channel: typeof CHANNEL_NAME;
$type: "preview/request";
id: string;
method: string;
url: string;
}

export interface IPreviewResponseMessage {
$channel: typeof CHANNEL_NAME;
$type: "preview/response";
id: string;
status: number;
headers: Record<string, string>;
body: string | Uint8Array;
}
10 changes: 10 additions & 0 deletions sandpack-client/src/clients/runtime/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,13 @@ export function getTemplate(

return undefined;
}

export function getExtension(filepath: string): string {
const parts = filepath.split(".");
if (parts.length <= 1) {
return "";
} else {
const ext = parts[parts.length - 1];
return ext;
}
}
5 changes: 5 additions & 0 deletions sandpack-client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ export interface ClientOptions {
* and unlock a few capabilities
*/
teamId?: string;

/**
* Enable the service worker feature for sandpack-bundler
*/
experimental_enableServiceWorker?: boolean;
}

export interface SandboxSetup {
Expand Down
1 change: 0 additions & 1 deletion sandpack-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@
"astring": "^1.8.4",
"babel-loader": "^7.1.5",
"rollup-plugin-filesize": "^10.0.0",
"rollup-plugin-preserve-directives": "^0.4.0",
"storybook": "^7.5.1",
"typescript": "^5.2.2"
},
Expand Down
4 changes: 1 addition & 3 deletions sandpack-react/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const commonjs = require("@rollup/plugin-commonjs");
const replace = require("@rollup/plugin-replace");
const typescript = require("@rollup/plugin-typescript");
const filesize = require("rollup-plugin-filesize");
const { preserveDirectives } = require("rollup-plugin-preserve-directives");

const pkg = require("./package.json");
const generateUnstyledTypes = require("./scripts/rollup-generate-unstyled-types");
Expand Down Expand Up @@ -32,8 +31,7 @@ const configBase = [
},
}),
typescript({ tsconfig: "./tsconfig.json" }),
filesize(),
preserveDirectives({ suppressPreserveModulesWarning: true })
filesize()
),
output: [
{
Expand Down
2 changes: 2 additions & 0 deletions sandpack-react/src/contexts/utils/useClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ export const useClient: UseClient = (
reactDevTools: state.reactDevTools,
customNpmRegistries: customSetup?.npmRegistries,
teamId,
experimental_enableServiceWorker:
!!options?.experimental_enableServiceWorker,
sandboxId,
}
);
Expand Down
2 changes: 1 addition & 1 deletion sandpack-react/src/contexts/utils/useFiles.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/**
* @jest-environment jsdom
*/
import type { SandpackBundlerFile } from "@codesandbox/sandpack-client/src";
import { renderHook, act } from "@testing-library/react-hooks";

import { VANILLA_TEMPLATE } from "../../templates";
import { getSandpackStateFromProps } from "../../utils/sandpackUtils";

import { useFiles } from "./useFiles";
import { SandpackBundlerFile } from "@codesandbox/sandpack-client/src";

describe(useFiles, () => {
it("should returns an initial state, which is the default template", () => {
Expand Down
32 changes: 32 additions & 0 deletions sandpack-react/src/presets/CustomSandpack.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,38 @@ export default {
title: "presets/Sandpack: custom",
};

export const ExperimentalServiceWorker: React.FC = () => {
return (
<Sandpack
files={{
"/public/logo.svg": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="-11.5 -10.23174 23 20.46348">
<title>React Logo</title>
<circle cx="0" cy="0" r="2.05" fill="#61dafb"/>
<g stroke="#61dafb" stroke-width="1" fill="none">
<ellipse rx="11" ry="4.2"/>
<ellipse rx="11" ry="4.2" transform="rotate(60)"/>
<ellipse rx="11" ry="4.2" transform="rotate(120)"/>
</g>
</svg>
`,
"/App.js": `export default function App() {
return (
<>
<h1>Hello React</h1>
<img width="100" src="/public/logo.svg" />
</>
);
}
`,
}}
options={{
experimental_enableServiceWorker: true,
}}
template="react"
/>
);
};

export const UsingSandpackLayout: React.FC = () => (
<SandpackProvider>
<SandpackLayout>
Expand Down
1 change: 1 addition & 0 deletions sandpack-react/src/presets/Sandpack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const Sandpack: SandpackInternal = ({
externalResources: options.externalResources,
logLevel: options.logLevel,
classes: options.classes,
experimental_enableServiceWorker: options.experimental_enableServiceWorker,
};

/**
Expand Down
1 change: 1 addition & 0 deletions sandpack-react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ export interface SandpackInternalOptions<
fileResolver?: FileResolver;
externalResources?: string[];
classes?: Record<string, string>;
experimental_enableServiceWorker?: boolean;
}

interface SandpackInternalProps<
Expand Down
3 changes: 2 additions & 1 deletion website/docs/src/pages/advanced-usage/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"client": { "title": "Sandpack Client", "label": "JS" },
"nodebox": { "title": "Nodebox", "label": "JS" },
"static": { "title": "Static", "label": "JS" },
"bundlers": "Experimental bundler (beta)"
"bundlers": "Experimental bundler (beta)",
"serving-static-files": "Serving static files (beta)"
}
Loading

0 comments on commit 4e2b116

Please sign in to comment.