From 790c78145b5cf5f3a4897c0cbde3643ad6f2a12a Mon Sep 17 00:00:00 2001 From: Ryan Turnquist Date: Tue, 27 Aug 2024 13:50:15 -0700 Subject: [PATCH] fix: hydrate glob imports --- .../build.expected.loading.0.html | 5 ++ .../build.expected.loading.1.html | 5 ++ .../build.expected.step-0.0.html | 5 ++ .../__snapshots__/dev.expected.loading.0.html | 5 ++ .../__snapshots__/dev.expected.loading.1.html | 5 ++ .../__snapshots__/dev.expected.step-0.0.html | 5 ++ .../isomorphic-glob-import/dev-server.mjs | 38 ++++++++++ .../isomorphic-glob-import/server.mjs | 15 ++++ .../src/components/layout-component.marko | 9 +++ .../isomorphic-glob-import/src/index.js | 9 +++ .../src/modules/class-component.marko | 20 ++++++ .../isomorphic-glob-import/src/template.marko | 13 ++++ .../isomorphic-glob-import/test.config.ts | 4 ++ src/glob-import-transform.ts | 71 +++++++++++++++++++ src/index.ts | 2 + 15 files changed, 211 insertions(+) create mode 100644 src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/build.expected.loading.0.html create mode 100644 src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/build.expected.loading.1.html create mode 100644 src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/build.expected.step-0.0.html create mode 100644 src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/dev.expected.loading.0.html create mode 100644 src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/dev.expected.loading.1.html create mode 100644 src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/dev.expected.step-0.0.html create mode 100644 src/__tests__/fixtures/isomorphic-glob-import/dev-server.mjs create mode 100644 src/__tests__/fixtures/isomorphic-glob-import/server.mjs create mode 100644 src/__tests__/fixtures/isomorphic-glob-import/src/components/layout-component.marko create mode 100644 src/__tests__/fixtures/isomorphic-glob-import/src/index.js create mode 100644 src/__tests__/fixtures/isomorphic-glob-import/src/modules/class-component.marko create mode 100644 src/__tests__/fixtures/isomorphic-glob-import/src/template.marko create mode 100644 src/__tests__/fixtures/isomorphic-glob-import/test.config.ts create mode 100644 src/glob-import-transform.ts diff --git a/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/build.expected.loading.0.html b/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/build.expected.loading.0.html new file mode 100644 index 0000000..2103a79 --- /dev/null +++ b/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/build.expected.loading.0.html @@ -0,0 +1,5 @@ +
+ Mounted: false Clicks: 0 +
\ No newline at end of file diff --git a/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/build.expected.loading.1.html b/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/build.expected.loading.1.html new file mode 100644 index 0000000..44c8061 --- /dev/null +++ b/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/build.expected.loading.1.html @@ -0,0 +1,5 @@ +
+ Mounted: true Clicks: 0 +
\ No newline at end of file diff --git a/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/build.expected.step-0.0.html b/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/build.expected.step-0.0.html new file mode 100644 index 0000000..13f0252 --- /dev/null +++ b/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/build.expected.step-0.0.html @@ -0,0 +1,5 @@ +
+ Mounted: true Clicks: 1 +
\ No newline at end of file diff --git a/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/dev.expected.loading.0.html b/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/dev.expected.loading.0.html new file mode 100644 index 0000000..2103a79 --- /dev/null +++ b/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/dev.expected.loading.0.html @@ -0,0 +1,5 @@ +
+ Mounted: false Clicks: 0 +
\ No newline at end of file diff --git a/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/dev.expected.loading.1.html b/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/dev.expected.loading.1.html new file mode 100644 index 0000000..44c8061 --- /dev/null +++ b/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/dev.expected.loading.1.html @@ -0,0 +1,5 @@ +
+ Mounted: true Clicks: 0 +
\ No newline at end of file diff --git a/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/dev.expected.step-0.0.html b/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/dev.expected.step-0.0.html new file mode 100644 index 0000000..13f0252 --- /dev/null +++ b/src/__tests__/fixtures/isomorphic-glob-import/__snapshots__/dev.expected.step-0.0.html @@ -0,0 +1,5 @@ +
+ Mounted: true Clicks: 1 +
\ No newline at end of file diff --git a/src/__tests__/fixtures/isomorphic-glob-import/dev-server.mjs b/src/__tests__/fixtures/isomorphic-glob-import/dev-server.mjs new file mode 100644 index 0000000..64b889d --- /dev/null +++ b/src/__tests__/fixtures/isomorphic-glob-import/dev-server.mjs @@ -0,0 +1,38 @@ +// In dev we'll start a Vite dev server in middleware mode, +// and forward requests to our http request handler. + +import { createServer } from "vite"; +import path from "path"; +import url from "url"; +import { createRequire } from "module"; + +// change to import once marko-vite is updated to ESM +const markoPlugin = createRequire(import.meta.url)("../../..").default; + +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); + +const devServer = await createServer({ + root: __dirname, + appType: "custom", + logLevel: "silent", + plugins: [markoPlugin()], + optimizeDeps: { force: true }, + server: { + middlewareMode: true, + watch: { + ignored: ["**/node_modules/**", "**/dist/**", "**/__snapshots__/**"], + }, + }, +}); + +export default devServer.middlewares.use(async (req, res, next) => { + try { + const { handler } = await devServer.ssrLoadModule( + path.join(__dirname, "./src/index.js") + ); + await handler(req, res, next); + } catch (err) { + devServer.ssrFixStacktrace(err); + return next(err); + } +}); diff --git a/src/__tests__/fixtures/isomorphic-glob-import/server.mjs b/src/__tests__/fixtures/isomorphic-glob-import/server.mjs new file mode 100644 index 0000000..410eee8 --- /dev/null +++ b/src/__tests__/fixtures/isomorphic-glob-import/server.mjs @@ -0,0 +1,15 @@ +// In production, simply start up the http server. +import path from 'path' +import url from 'url'; +import { createServer } from "http"; +import serve from "serve-handler"; +import { handler } from "./dist/index.mjs"; + +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); +const serveOpts = { public: path.resolve(__dirname, "dist") }; + +export default createServer(async (req, res) => { + await handler(req, res); + if (res.headersSent) return; + await serve(req, res, serveOpts); +}); diff --git a/src/__tests__/fixtures/isomorphic-glob-import/src/components/layout-component.marko b/src/__tests__/fixtures/isomorphic-glob-import/src/components/layout-component.marko new file mode 100644 index 0000000..49e7e8e --- /dev/null +++ b/src/__tests__/fixtures/isomorphic-glob-import/src/components/layout-component.marko @@ -0,0 +1,9 @@ + + + + Hello World + + + <${input.renderBody}/> + + \ No newline at end of file diff --git a/src/__tests__/fixtures/isomorphic-glob-import/src/index.js b/src/__tests__/fixtures/isomorphic-glob-import/src/index.js new file mode 100644 index 0000000..d3f5422 --- /dev/null +++ b/src/__tests__/fixtures/isomorphic-glob-import/src/index.js @@ -0,0 +1,9 @@ +import template from "./template.marko"; + +export function handler(req, res) { + if (req.url === "/") { + res.statusCode = 200; + res.setHeader("Content-Type", "text/html; charset=utf-8"); + template.render({}, res); + } +} diff --git a/src/__tests__/fixtures/isomorphic-glob-import/src/modules/class-component.marko b/src/__tests__/fixtures/isomorphic-glob-import/src/modules/class-component.marko new file mode 100644 index 0000000..9579382 --- /dev/null +++ b/src/__tests__/fixtures/isomorphic-glob-import/src/modules/class-component.marko @@ -0,0 +1,20 @@ +class { + onCreate() { + this.state = { + clickCount: 0, + mounted: false + }; + } + onMount() { + this.state.mounted = true; + } + + handleClick() { + this.state.clickCount++; + } +} + + + Mounted: ${state.mounted} + Clicks: ${state.clickCount} + diff --git a/src/__tests__/fixtures/isomorphic-glob-import/src/template.marko b/src/__tests__/fixtures/isomorphic-glob-import/src/template.marko new file mode 100644 index 0000000..5eb11ca --- /dev/null +++ b/src/__tests__/fixtures/isomorphic-glob-import/src/template.marko @@ -0,0 +1,13 @@ +static const modules = import.meta.glob('./modules/*.marko', { eager: true }) + +style { + div { color: green } +} + + + + + <${mod}/> + + + \ No newline at end of file diff --git a/src/__tests__/fixtures/isomorphic-glob-import/test.config.ts b/src/__tests__/fixtures/isomorphic-glob-import/test.config.ts new file mode 100644 index 0000000..9e9a321 --- /dev/null +++ b/src/__tests__/fixtures/isomorphic-glob-import/test.config.ts @@ -0,0 +1,4 @@ +export const ssr = true; +export async function steps() { + await page.click("#clickable"); +} diff --git a/src/glob-import-transform.ts b/src/glob-import-transform.ts new file mode 100644 index 0000000..c5a8f8b --- /dev/null +++ b/src/glob-import-transform.ts @@ -0,0 +1,71 @@ +import { types as t } from "@marko/compiler"; +import glob from "fast-glob"; +import path from "path"; + +type GlobArgs = [string | string[], { eager?: boolean; exhaustive?: boolean }]; + +const programGlobImports = new WeakMap, GlobArgs[]>(); + +export default { + MetaProperty(tag: t.NodePath) { + const memberExpression = tag.parentPath; + if ( + memberExpression.node.type === "MemberExpression" && + memberExpression.node.property.type === "Identifier" && + memberExpression.node.property.name === "glob" + ) { + const callExpression = memberExpression.parentPath; + if (callExpression?.node.type === "CallExpression") { + const args = ( + callExpression.get("arguments" as any) as t.NodePath[] + ).map((arg) => arg.evaluate().value) as GlobArgs; + if (args[1]?.eager) { + const program = tag.hub.file.path; + const existing = programGlobImports.get(program); + if (!existing) { + programGlobImports.set(program, [args]); + } else { + existing.push(args); + } + } + } + } + }, + Program: { + exit(tag: t.NodePath) { + const globImports = programGlobImports.get(tag); + if (!globImports) { + return; + } + + const { cwd, filename } = tag.hub.file.opts as { + cwd: string; + filename: string; + }; + const dir = path.dirname(filename); + const seen = new Set(); + + for (const [patterns, options] of globImports) { + const resolvedPatterns = Array.isArray(patterns) + ? patterns.map((p) => path.resolve(dir, p)) + : path.resolve(dir, patterns); + + const results = glob.globSync(resolvedPatterns, { + cwd, + absolute: true, + dot: !!options.exhaustive, + ignore: options.exhaustive + ? [] + : [path.join(cwd, "**/node_modules/**")], + }); + + for (const file of results) { + if (file.endsWith(".marko") && file !== filename && !seen.has(file)) { + seen.add(file); + tag.node.body.push(t.importDeclaration([], t.stringLiteral(file))); + } + } + } + }, + }, +}; diff --git a/src/index.ts b/src/index.ts index d12752f..c907127 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,6 +23,7 @@ import { } from "./render-assets-runtime"; import renderAssetsTransform from "./render-assets-transform"; import relativeAssetsTransform from "./relative-assets-transform"; +import globImportTransformer from "./glob-import-transform"; import { ReadOncePersistedStore } from "./read-once-persisted-store"; export namespace API { @@ -268,6 +269,7 @@ export default function markoPlugin(opts: Options = {}): vite.Plugin[] { if (!registeredTagLib) { registeredTagLib = true; compiler.taglib.register("@marko/vite", { + transform: globImportTransformer, "": { transformer: renderAssetsTransform }, "": { transformer: renderAssetsTransform }, "<*>": { transformer: relativeAssetsTransform },