Skip to content
This repository has been archived by the owner on Sep 22, 2024. It is now read-only.

support minimal mdx cases and add test fixture #159

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions lib/plugins/mdx/deno.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"lock": false,
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"tasks": {
"check:types": "deno check **/*.ts && deno check **/*.tsx",
"coverage": "rm -rf coverage && deno test -A --parallel --no-check --coverage && deno coverage --html",
"fixture": "deno run -A --watch=static/,routes/ tests/fixture/dev.ts",
"ok": "deno fmt --check && deno lint && deno task check:types && deno task test",
"test": "deno test -A --parallel --no-check"
},
"importMap": "./.vscode/import_map.json"
}
16 changes: 8 additions & 8 deletions lib/plugins/mdx/plugin.ts → lib/plugins/mdx/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import type { Plugin, PluginRoute } from "../../deps/$fresh/server.ts";
import type { NetzoState } from "../../mod.ts";
import { mdxPathsToRoutes, scanForMDXFiles } from "./utils.ts";

// deno-lint-ignore ban-types
export type MdxConfig = {};
export type MdxConfig = {
configLocation: string;
Copy link
Contributor

@miguelrk miguelrk Mar 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should look into using the same property across plugins. The unocss plugin uses url for this same value which should also be set to import.meta.url).

I have no preference really, but I think URL is more indicative than location (matches the url in import.meta.url). Maybe configURL would be best of both worlds? And we could change this also for the unocss plugin

};

// deno-lint-ignore ban-types
export type MdxState = {};
Expand All @@ -14,14 +15,13 @@ export type MdxState = {};
* @param {MdxConfig} - configuration options for the plugin
* @returns {Plugin} - a Plugin for Deno Fresh
*/
export const mdx = async (_config: MdxConfig): Promise<
Plugin<NetzoState>
> => {
const mdxFiles = await scanForMDXFiles("routes");
const routes: PluginRoute[] = await mdxPathsToRoutes(mdxFiles);
export async function mdx(config: MdxConfig): Promise<Plugin<NetzoState>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit here, maybe keep arrow syntax here for consistency with the other plugins.

export const mdx = async (config: MdxConfig): Promise<Plugin<NetzoState>> => {}

const routesDir = new URL("./routes", config.configLocation).pathname;
const mdxFiles = await scanForMDXFiles(routesDir);
const routes: PluginRoute[] = await mdxPathsToRoutes(mdxFiles, routesDir);

return {
name: "mdx",
routes,
};
};
}
3 changes: 3 additions & 0 deletions lib/plugins/mdx/tests/fixture/components/Bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Bar() {
return <div>Bar</div>;
}
39 changes: 39 additions & 0 deletions lib/plugins/mdx/tests/fixture/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"lock": false,
"tasks": {
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
"cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run --unstable -A -",
"manifest": "deno task cli manifest $(pwd)",
"start": "deno run -A --watch=static/,routes/ dev.ts",
"build": "deno run -A dev.ts build",
"preview": "deno run -A main.ts",
"update": "deno run -A -r https://fresh.deno.dev/update ."
},
"lint": {
"rules": {
"tags": [
"fresh",
"recommended"
]
}
},
"exclude": [
"**/_fresh/*"
],
"imports": {
"$fresh/": "https://deno.land/x/[email protected]/",
"preact": "https://esm.sh/[email protected]",
"preact/": "https://esm.sh/[email protected]/",
"@preact/signals": "https://esm.sh/*@preact/[email protected]",
"@preact/signals-core": "https://esm.sh/*@preact/[email protected]",
"tailwindcss": "npm:[email protected]",
"tailwindcss/": "npm:/[email protected]/",
"tailwindcss/plugin": "npm:/[email protected]/plugin.js",
"$std/": "https://deno.land/[email protected]/"
},
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"nodeModulesDir": true
}
8 changes: 8 additions & 0 deletions lib/plugins/mdx/tests/fixture/dev.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env -S deno run -A --watch=static/,routes/

import dev from "$fresh/dev.ts";
import config from "./fresh.config.ts";

import "$std/dotenv/load.ts";

await dev(import.meta.url, "./main.ts", config);
7 changes: 7 additions & 0 deletions lib/plugins/mdx/tests/fixture/fresh.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from "$fresh/server.ts";
import tailwind from "$fresh/plugins/tailwind.ts";
import { mdx } from "../../mod.ts";

export default defineConfig({
plugins: [await mdx({ configLocation: import.meta.url }), tailwind()],
});
19 changes: 19 additions & 0 deletions lib/plugins/mdx/tests/fixture/fresh.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// DO NOT EDIT. This file is generated by Fresh.
// This file SHOULD be checked into source version control.
// This file is automatically updated during development when running `dev.ts`.

import * as $_app from "./routes/_app.tsx";
import * as $Counter from "./islands/Counter.tsx";
import { type Manifest } from "$fresh/server.ts";

const manifest = {
routes: {
"./routes/_app.tsx": $_app,
},
islands: {
"./islands/Counter.tsx": $Counter,
},
baseUrl: import.meta.url,
} satisfies Manifest;

export default manifest;
20 changes: 20 additions & 0 deletions lib/plugins/mdx/tests/fixture/islands/Counter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Signal } from "@preact/signals";
import { IS_BROWSER } from "$fresh/runtime.ts";

interface CounterProps {
count: Signal<number>;
}

export default function Counter({ count }: CounterProps) {
return (
<div>
<p>{count}</p>
<button onClick={() => count.value -= 1} disabled={!IS_BROWSER}>
-1
</button>
<button onClick={() => count.value += 1} disabled={!IS_BROWSER}>
+1
</button>
</div>
);
}
13 changes: 13 additions & 0 deletions lib/plugins/mdx/tests/fixture/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference no-default-lib="true" />
/// <reference lib="dom" />
/// <reference lib="dom.iterable" />
/// <reference lib="dom.asynciterable" />
/// <reference lib="deno.ns" />

import "$std/dotenv/load.ts";

import { start } from "$fresh/server.ts";
import manifest from "./fresh.gen.ts";
import config from "./fresh.config.ts";

await start(manifest, config);
16 changes: 16 additions & 0 deletions lib/plugins/mdx/tests/fixture/routes/_app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type PageProps } from "$fresh/server.ts";
export default function App({ Component }: PageProps) {
return (
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>fixture</title>
{/* <link rel="stylesheet" href="/styles.css" /> */}
</head>
<body>
<Component />
</body>
</html>
);
}
18 changes: 18 additions & 0 deletions lib/plugins/mdx/tests/fixture/routes/foo.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: foobar
---

import Foo from "../components/Bar.tsx"
import { useSignal } from "https://esm.sh/*@preact/[email protected]";
import Counter from "../islands/Counter.tsx";

# Hello

This is rendered from mdx!

* one
* two
* three

<Foo />
<Counter count={useSignal(3)} />
Binary file added lib/plugins/mdx/tests/fixture/static/favicon.ico
Binary file not shown.
3 changes: 3 additions & 0 deletions lib/plugins/mdx/tests/fixture/static/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
7 changes: 7 additions & 0 deletions lib/plugins/mdx/tests/fixture/tailwind.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type Config } from "tailwindcss";

export default {
content: [
"{routes,islands,components}/**/*.{ts,tsx}",
],
} satisfies Config;
83 changes: 76 additions & 7 deletions lib/plugins/mdx/utils.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,105 @@
import { compile } from "npm:@mdx-js/mdx";
import { type MdxjsEsm } from "npm:[email protected]";
import { default as remarkFrontmatter } from "npm:[email protected]";
import { default as remarkGfm } from "npm:[email protected]";
import { type PluginRoute } from "../../deps/$fresh/server.ts";
import { walk } from "../../deps/std/fs/walk.ts";
import { visit } from "npm:[email protected]";
import { Root } from "npm:@types/[email protected]";
import { Project } from "npm:[email protected]";
import { join, toFileUrl } from "../../deps/std/path/mod.ts";
import { ImportDeclaration } from "npm:@types/[email protected]";

export async function scanForMDXFiles(_directory: string): Promise<string[]> {
export async function scanForMDXFiles(directory: string): Promise<string[]> {
const files: string[] = [];
for await (
const entry of walk(
"/home/mrk/repos/netzo/templates/minimal/routes",
{ includeDirs: false, exts: [".mdx"] },
)
const entry of walk(directory, { includeDirs: false, exts: [".mdx"] })
) {
files.push(entry.path);
}
return files;
}

export function mdxPathsToRoutes(mdxPaths: string[]): Promise<PluginRoute[]> {
export function mdxPathsToRoutes(
mdxPaths: string[],
routesDir: string,
): Promise<PluginRoute[]> {
return Promise.all(mdxPaths.map(async (path: string) => {
const routePath = path.split("/routes/").pop()!.replace(".mdx", "");

const file = Deno.readTextFileSync(path);
const compiled = await compile(file, {
remarkPlugins: [remarkGfm, remarkFrontmatter],
rehypePlugins: [rehypeLogger],
remarkPlugins: [remarkGfm, remarkFrontmatter, [
remarkAbsoluteImportPaths,
{ basePath: routesDir },
], remarkLogger],
jsxImportSource: "preact",
});

const result = compiled.toString();
// console.log({ file, result });
const code = await import("data:text/javascript," + result);
const comp = code.default;

return { path: routePath, component: comp } satisfies PluginRoute;
}));
}

type RemarkAbsoluteImportPathsOptions = {
basePath?: string;
};

function remarkAbsoluteImportPaths(
options: Readonly<RemarkAbsoluteImportPathsOptions>,
): (tree: Root) => void {
const basePath = options.basePath || "";

return function (tree: Root) {
visit(tree, "mdxjsEsm", (node: MdxjsEsm) => {
const project = new Project({
useInMemoryFileSystem: true,
});

const sourceFile = project.createSourceFile("tempFile.ts", node.value);
const imports = sourceFile.getImportDeclarations();

imports.forEach((importDeclaration) => {
const originalSpecifier = importDeclaration.getModuleSpecifierValue();
if (originalSpecifier.startsWith("http")) return;
const updatedPath = join(basePath, originalSpecifier);

const newSpecifier = toFileUrl(updatedPath).href;

if (node.data?.estree) {
const estreeBody = node.data.estree.body as ImportDeclaration[];
const importNode = estreeBody.find((n) =>
n.type === "ImportDeclaration" &&
n.source.type === "Literal" &&
n.source.value === originalSpecifier
);
if (importNode) {
importNode.source.value = newSpecifier;
importNode.source.raw = `"${newSpecifier}"`;
}
}
});
});
};
}

function remarkLogger(): (tree: Root) => undefined {
return function (tree: Root) {
visit(tree, "root", (node: Root) => {
// console.log(node);
});
};
}

function rehypeLogger(): (tree: Root) => undefined {
return function (tree: Root) {
visit(tree, "root", (node: Element) => {
// console.log(node);
});
};
}
Loading