bun icon indicating copy to clipboard operation
bun copied to clipboard

Single file executable does not appear to work with plugins (at least not MDX)

Open JustinTulloss opened this issue 1 year ago • 3 comments

What version of Bun is running?

1.1.26+0a37423ba

What platform is your computer?

Darwin 23.6.0 arm64 arm

What steps can reproduce the bug?

The following files produce a different output when run directly vs when run as a compiled single-file executable.

preload-plugins.ts

import { plugin, type BunPlugin } from "bun";
import mdx from "@mdx-js/esbuild";

console.log("Loading plugins...");
plugin(mdx() as unknown as BunPlugin);

bunfig.toml

preload=["./preload-plugins.ts"]

index.tsx

import { embeddedFiles, serve } from "bun";
import { renderToStaticMarkup } from "react-dom/server"

import Post from "./test.mdx";

console.log("Hello via Bun!");
console.log(embeddedFiles);
console.log(renderToStaticMarkup(<Post />));

What is the expected behavior?

The built single file executable should output the same thing as running bun run index.tsx, except with different embedded plugins

Loading plugins...
Hello via Bun!
[]
<h1>Rendered contents of test.mdx</h1>

What do you see instead?

Hello via Bun!
[
  Blob (274 bytes) {
    name: "test-z9gtn511.mdx",
    type: "text/mdx"
  }
]
2729 | // https://infra.spec.whatwg.org/#ascii-tab-or-newline
2730 | // https://infra.spec.whatwg.org/#ascii-tab-or-newline
2731 | // https://infra.spec.whatwg.org/#ascii-tab-or-newline
2732 | // https://infra.spec.whatwg.org/#ascii-tab-or-newline
2733 | // https://infra.spec.whatwg.org/#ascii-tab-or-newline
2734 | // https://infra.spec.whatwg.org/#ascii-tab-or-newline
                   ^
error: Invalid tag: /$bunfs/root/test-z9gtn511.mdx

Additional information

You can get further by shlepping the bunfig.toml around with the executable, and even further by copying the executable along with bunfig.toml and preload-plugins.tsx but the build process still doesn't include the imports specified in preload-plugins.tsx so eventually it crashes.

JustinTulloss avatar Aug 25 '24 21:08 JustinTulloss

It does appear that you can get the necessary dependencies included by including preload-plugins.ts as one of the files that gets compiled, but the bunfig.toml includes a relative path that doesn't resolve within the SFE environment and the bunfig.toml file does not appear to be read if you include it in the SFE src args.

JustinTulloss avatar Aug 25 '24 21:08 JustinTulloss

I think the ideal fix here is for us to implement compile: true in Bun.build so that plugins are run ahead-of-time instead of at runtime.

If you want to inline the contents of .mdx files at compile time, you can do this:

import { mdx } from "./render.tsx" with { type: "macro" };

const Post = mdx("./test1.mdx");

console.log(Post);

and then render.tsx:

import { compile } from "@mdx-js/mdx";
import { readFileSync } from "fs";
import path from "path";
import React from "react";
import { renderToStaticMarkup } from "react-dom/server";
async function renderMDXToStaticMarkup(path) {
  const mdxContent = readFileSync(path, "utf8");

  const blob = await compile(mdxContent, {
    outputFormat: "program",
  }).then((code) => {
    return new Blob([code.value], { type: "application/javascript" });
  });
  const url = URL.createObjectURL(blob);
  try {
    const component = await import(url);
    return renderToStaticMarkup(React.createElement(component.default));
  } finally {
    URL.revokeObjectURL(url);
  }
}

export function mdx(filePath: string) {
  return renderMDXToStaticMarkup(path.join(process.cwd(), filePath));
}

If you actually do want to run the MDX plugin at runtime within the standalone executable, then something like this should work:

import { serve } from "bun";
import { renderToStaticMarkup } from "react-dom/server";
import { plugin, type BunPlugin } from "bun";
import mdx from "@mdx-js/esbuild";
import path from 'path';
console.log("Loading plugins...");
plugin(mdx() as unknown as BunPlugin);

const PostNamespace = await import(
  path.join(process.cwd(), "./test.mdx")
);
const Post = PostNamespace.default;
console.log(Post);
console.log("Hello via Bun!");
console.log(renderToStaticMarkup(<Post />));

Jarred-Sumner avatar Aug 27 '24 11:08 Jarred-Sumner

Another potential solution would be to add a —plugin flag to the build step to include those plugins. However there’s still the awkwardness of telling the executable when to run them.

I’ll precompile the mdx files, that should work for me!

Thank you

On Tue, Aug 27, 2024 at 4:33 AM Jarred Sumner @.***> wrote:

I think the ideal fix here is for us to implement compile: true so that plugins are run ahead-of-time instead of at runtime.

If you want to inline the contents of .mdx files at compile time, you can do this:

import { mdx } from "./render.tsx" with { type: "macro" }; const Post = mdx("./test1.mdx"); console.log(Post);

and then render.tsx:

import { compile } from @.***/mdx";import { readFileSync } from "fs";import path from "path";import React from "react";import { renderToStaticMarkup } from "react-dom/server";async function renderMDXToStaticMarkup(path) { const mdxContent = readFileSync(path, "utf8");

const blob = await compile(mdxContent, { outputFormat: "program", }).then((code) => { return new Blob([code.value], { type: "application/javascript" }); }); const url = URL.createObjectURL(blob); try { const component = await import(url); return renderToStaticMarkup(React.createElement(component.default)); } finally { URL.revokeObjectURL(url); }} export function mdx(filePath: string) { return renderMDXToStaticMarkup(path.join(process.cwd(), filePath));}

If you actually do want to run the MDX plugin at runtime within the standalone executable, then something like this should work:

import { serve } from "bun";import { renderToStaticMarkup } from "react-dom/server";import { plugin, type BunPlugin } from "bun";import mdx from @.***/esbuild";import path from 'path';console.log("Loading plugins...");plugin(mdx() as unknown as BunPlugin); const PostNamespace = await import( path.join(process.cwd(), "./test.mdx"));const Post = PostNamespace.default;console.log(Post);console.log("Hello via Bun!");console.log(renderToStaticMarkup(<Post />));

— Reply to this email directly, view it on GitHub https://github.com/oven-sh/bun/issues/13530#issuecomment-2312312260, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAFZNTVP6CTBESAIUFB42TZTRPXHAVCNFSM6AAAAABNC4DBQ2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGMJSGMYTEMRWGA . You are receiving this because you authored the thread.Message ID: @.***>

JustinTulloss avatar Aug 27 '24 14:08 JustinTulloss

Bun.build now supports a compile: true option, which unblocks build-time plugins ((https://github.com/oven-sh/bun/pull/21915). This will be part of the Bun v1.2.21 release.

Jarred-Sumner avatar Aug 20 '25 08:08 Jarred-Sumner