diff --git a/packages/sharing-editor/src/export/html.spec.ts b/packages/sharing-editor/src/export/html.spec.ts index 214d8f5f7..068d1d576 100644 --- a/packages/sharing-editor/src/export/html.spec.ts +++ b/packages/sharing-editor/src/export/html.spec.ts @@ -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"; @@ -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({ @@ -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, }), }), @@ -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({ @@ -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, }), }), diff --git a/packages/sharing-editor/src/export/html.ts b/packages/sharing-editor/src/export/html.ts index 81f0302e1..6cac201d3 100644 --- a/packages/sharing-editor/src/export/html.ts +++ b/packages/sharing-editor/src/export/html.ts @@ -55,17 +55,13 @@ export function exportAsHtml(appData: AppData): string { name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> -