Skip to content

Commit

Permalink
Update the HTML exporter for the new API (#1213)
Browse files Browse the repository at this point in the history
* Update the HTML exporter for the new API

* Replace mountable to browser in html exporter
  • Loading branch information
whitphx authored Jan 10, 2025
1 parent 1bbaa67 commit 96291fa
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 74 deletions.
148 changes: 82 additions & 66 deletions packages/sharing-editor/src/export/html.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { describe, it, expect } from "vitest";
import {
exportAsHtml,
escapeTextForJsTemplateLiteral,
} from "./html";
import { exportAsHtml, escapeTextForJsTemplateLiteral } from "./html";
import { JSDOM } from "jsdom";
import * as babelParser from "@babel/parser";
import { AppData } from "@stlite/sharing-common/dist";
Expand Down Expand Up @@ -31,59 +28,69 @@ describe("exportAsHtml", () => {
expect(rootDiv?.tagName).toEqual("DIV");

const linkTags = dom.head.getElementsByTagName("link");
expect(linkTags.length).toBe(1);
expect(linkTags[0].rel).toEqual("stylesheet");
expect(linkTags[0].href).toEqual(
`https://cdn.jsdelivr.net/npm/@stlite/mountable@${SELF_HOSTING_RUNTIME_VERSION}/build/stlite.css`,
);
expect(linkTags.length).toBe(0);

const scriptTags = dom.body.getElementsByTagName("script");
expect(scriptTags.length).toBe(2);

const stliteLoaderScriptTag = scriptTags[0];
expect(stliteLoaderScriptTag.src).toEqual(
`https://cdn.jsdelivr.net/npm/@stlite/mountable@${SELF_HOSTING_RUNTIME_VERSION}/build/stlite.js`,
);
expect(stliteLoaderScriptTag.text).toEqual("");
expect(scriptTags.length).toBe(1);

const appScriptTag = scriptTags[1];
const appScriptTag = scriptTags[0];
expect(appScriptTag.src).toEqual("");
expect(appScriptTag.type).toEqual("module");

const appScriptContent = appScriptTag.text;

const jsAstRoot = babelParser.parse(appScriptContent);
// The source code only includes `stlite.mount()`.
expect(jsAstRoot.program.body.length).toBe(1);
const jsAstRoot = babelParser.parse(appScriptContent, {
sourceType: "module",
});
// The source code contains an import and the `mount()` call.
expect(jsAstRoot.program.body.length).toBe(2);

const [stliteMount] = jsAstRoot.program.body;
const [importMount, mountCall] = jsAstRoot.program.body;

expect(stliteMount).toEqual(
expect(importMount).toEqual(
expect.objectContaining({
type: "ExpressionStatement",
expression: expect.objectContaining({
type: "CallExpression",
callee: expect.objectContaining({
type: "MemberExpression",
object: expect.objectContaining({
type: "ImportDeclaration",
source: expect.objectContaining({
type: "StringLiteral",
value: `https://cdn.jsdelivr.net/npm/@stlite/browser@${SELF_HOSTING_RUNTIME_VERSION}/build/stlite.js`,
}),
specifiers: [
expect.objectContaining({
type: "ImportSpecifier",
imported: expect.objectContaining({
type: "Identifier",
name: "stlite",
name: "mount",
}),
property: expect.objectContaining({
local: expect.objectContaining({
type: "Identifier",
name: "mount",
}),
}),
],
}),
);

expect(mountCall).toEqual(
expect.objectContaining({
type: "ExpressionStatement",
expression: expect.objectContaining({
type: "CallExpression",
callee: expect.objectContaining({
type: "Identifier",
name: "mount",
}),
}),
}),
);
if (
stliteMount.type !== "ExpressionStatement" ||
stliteMount.expression.type !== "CallExpression"
mountCall.type !== "ExpressionStatement" ||
mountCall.expression.type !== "CallExpression"
) {
throw new Error();
}

expect(stliteMount.expression.arguments.length).toBe(2);
const [mountOptions, mountTarget] = stliteMount.expression.arguments;
expect(mountCall.expression.arguments.length).toBe(2);
const [mountOptions, mountTarget] = mountCall.expression.arguments;

expect(mountTarget).toEqual(
expect.objectContaining({
Expand Down Expand Up @@ -156,7 +163,7 @@ describe("exportAsHtml", () => {
quasis: [
expect.objectContaining({
value: expect.objectContaining({
// @ts-ignore
// @ts-expect-error The content field is a discriminated union type and TypeScript cannot infer the text field exists
raw: appData.files["streamlit_app.py"].content.text,
}),
}),
Expand Down Expand Up @@ -197,59 +204,68 @@ describe("exportAsHtml", () => {
expect(rootDiv?.tagName).toEqual("DIV");

const linkTags = dom.head.getElementsByTagName("link");
expect(linkTags.length).toBe(1);
expect(linkTags[0].rel).toEqual("stylesheet");
expect(linkTags[0].href).toEqual(
`https://cdn.jsdelivr.net/npm/@stlite/mountable@${SELF_HOSTING_RUNTIME_VERSION}/build/stlite.css`,
);
expect(linkTags.length).toBe(0);

const scriptTags = dom.body.getElementsByTagName("script");
expect(scriptTags.length).toBe(2);

const stliteLoaderScriptTag = scriptTags[0];
expect(stliteLoaderScriptTag.src).toEqual(
`https://cdn.jsdelivr.net/npm/@stlite/mountable@${SELF_HOSTING_RUNTIME_VERSION}/build/stlite.js`,
);
expect(stliteLoaderScriptTag.text).toEqual("");
expect(scriptTags.length).toBe(1);

const appScriptTag = scriptTags[1];
const appScriptTag = scriptTags[0];
expect(appScriptTag.src).toEqual("");
expect(appScriptTag.type).toEqual("module");
const appScriptContent = appScriptTag.text;

const jsAstRoot = babelParser.parse(appScriptContent);
// The source code only includes `stlite.mount()` and the Base64 decoder function definition.
expect(jsAstRoot.program.body.length).toBe(2);
const jsAstRoot = babelParser.parse(appScriptContent, {
sourceType: "module",
});
// The source contains the import syntax, `mount()` call, and the Base64 decoder function definition.
expect(jsAstRoot.program.body.length).toBe(3);

const [stliteMount, funcDef] = jsAstRoot.program.body;
const [importMount, mountCall, funcDef] = jsAstRoot.program.body;

expect(stliteMount).toEqual(
expect(importMount).toEqual(
expect.objectContaining({
type: "ExpressionStatement",
expression: expect.objectContaining({
type: "CallExpression",
callee: expect.objectContaining({
type: "MemberExpression",
object: expect.objectContaining({
type: "ImportDeclaration",
source: expect.objectContaining({
type: "StringLiteral",
value: `https://cdn.jsdelivr.net/npm/@stlite/browser@${SELF_HOSTING_RUNTIME_VERSION}/build/stlite.js`,
}),
specifiers: [
expect.objectContaining({
type: "ImportSpecifier",
imported: expect.objectContaining({
type: "Identifier",
name: "stlite",
name: "mount",
}),
property: expect.objectContaining({
local: expect.objectContaining({
type: "Identifier",
name: "mount",
}),
}),
],
}),
);

expect(mountCall).toEqual(
expect.objectContaining({
type: "ExpressionStatement",
expression: expect.objectContaining({
type: "CallExpression",
callee: expect.objectContaining({
type: "Identifier",
name: "mount",
}),
}),
}),
);
if (
stliteMount.type !== "ExpressionStatement" ||
stliteMount.expression.type !== "CallExpression"
mountCall.type !== "ExpressionStatement" ||
mountCall.expression.type !== "CallExpression"
) {
throw new Error();
}

expect(stliteMount.expression.arguments.length).toBe(2);
const [mountOptions, mountTarget] = stliteMount.expression.arguments;
expect(mountCall.expression.arguments.length).toBe(2);
const [mountOptions, mountTarget] = mountCall.expression.arguments;

expect(mountTarget).toEqual(
expect.objectContaining({
Expand Down Expand Up @@ -322,7 +338,7 @@ describe("exportAsHtml", () => {
quasis: [
expect.objectContaining({
value: expect.objectContaining({
// @ts-ignore
// @ts-expect-error The content field is a discriminated union type and TypeScript cannot infer the text field exists
raw: appData.files["streamlit_app.py"].content.text,
}),
}),
Expand Down
12 changes: 4 additions & 8 deletions packages/sharing-editor/src/export/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,13 @@ export function exportAsHtml(appData: AppData): string {
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<title>stlite app</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@stlite/mountable@${SELF_HOSTING_RUNTIME_VERSION}/build/stlite.css"
/>
<title>Stlite app</title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.jsdelivr.net/npm/@stlite/mountable@${SELF_HOSTING_RUNTIME_VERSION}/build/stlite.js"></script>
<script>
stlite.mount(
<script type="module">
import { mount } from "https://cdn.jsdelivr.net/npm/@stlite/browser@${SELF_HOSTING_RUNTIME_VERSION}/build/stlite.js"
mount(
{
requirements: ${makeRequirementsLiteral(appData.requirements)},
entrypoint: ${makeEntrypointLiteral(appData.entrypoint)},
Expand Down

0 comments on commit 96291fa

Please sign in to comment.