Skip to content

Commit

Permalink
fix: hydrate glob imports
Browse files Browse the repository at this point in the history
  • Loading branch information
rturnq committed Aug 27, 2024
1 parent 8de3e9d commit 790c781
Show file tree
Hide file tree
Showing 15 changed files with 211 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div
id="clickable"
>
Mounted: false Clicks: 0
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div
id="clickable"
>
Mounted: true Clicks: 0
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div
id="clickable"
>
Mounted: true Clicks: 1
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div
id="clickable"
>
Mounted: false Clicks: 0
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div
id="clickable"
>
Mounted: true Clicks: 0
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div
id="clickable"
>
Mounted: true Clicks: 1
</div>
38 changes: 38 additions & 0 deletions src/__tests__/fixtures/isomorphic-glob-import/dev-server.mjs
Original file line number Diff line number Diff line change
@@ -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);
}
});
15 changes: 15 additions & 0 deletions src/__tests__/fixtures/isomorphic-glob-import/server.mjs
Original file line number Diff line number Diff line change
@@ -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);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello World</title>
</head>
<body>
<${input.renderBody}/>
</body>
</html>
9 changes: 9 additions & 0 deletions src/__tests__/fixtures/isomorphic-glob-import/src/index.js
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class {
onCreate() {
this.state = {
clickCount: 0,
mounted: false
};
}
onMount() {
this.state.mounted = true;
}
handleClick() {
this.state.clickCount++;
}
}

<div#clickable onClick("handleClick")>
Mounted: ${state.mounted}
Clicks: ${state.clickCount}
</div>
13 changes: 13 additions & 0 deletions src/__tests__/fixtures/isomorphic-glob-import/src/template.marko
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
static const modules = import.meta.glob('./modules/*.marko', { eager: true })

style {
div { color: green }
}

<layout-component>
<div#app>
<for|_id, mod| in=modules>
<${mod}/>
</for>
</div>
</layout-component>
4 changes: 4 additions & 0 deletions src/__tests__/fixtures/isomorphic-glob-import/test.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const ssr = true;
export async function steps() {
await page.click("#clickable");
}
71 changes: 71 additions & 0 deletions src/glob-import-transform.ts
Original file line number Diff line number Diff line change
@@ -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<t.NodePath<t.Program>, GlobArgs[]>();

export default {
MetaProperty(tag: t.NodePath<t.MetaProperty>) {
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<t.Expression>[]
).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<t.Program>) {
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)));
}
}
}
},
},
};
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -268,6 +269,7 @@ export default function markoPlugin(opts: Options = {}): vite.Plugin[] {
if (!registeredTagLib) {
registeredTagLib = true;
compiler.taglib.register("@marko/vite", {
transform: globImportTransformer,
"<head>": { transformer: renderAssetsTransform },
"<body>": { transformer: renderAssetsTransform },
"<*>": { transformer: relativeAssetsTransform },
Expand Down

0 comments on commit 790c781

Please sign in to comment.