Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to import Node builtins #239

Open
joshwcomeau opened this issue Sep 16, 2024 · 7 comments
Open

Unable to import Node builtins #239

joshwcomeau opened this issue Sep 16, 2024 · 7 comments
Assignees
Labels
nextjs status: waiting for maintainer These issues haven't been looked at yet by a maintainer
Milestone

Comments

@joshwcomeau
Copy link

joshwcomeau commented Sep 16, 2024

Steps to reproduce

Repro URL:
https://github.com/joshwcomeau/pigmentcss-fs-issue

  1. Run npm run dev
  2. Note the issue in the terminal:

EvalError: Unable to import "fs/promises". Importing Node builtins is not supported in the sandbox.

Context

In my project, I'm using the fs/promises module to load MDX content. This obviously wouldn't work in-browser, but I'm doing this specifically inside a Server Component, within the next.js App Router. So none of this code is included in the client-side bundles.

It seems as though Pigment is unable to load any Node built-ins, but I don't think it has to; I don't think any of this stuff affects the generated CSS.

I also realize that this is likely an issue with @wyw-in-js, rather than Pigment CSS itself, but I wanted to highlight it here since that repo doesn't seem active.

Your environment

npx @mui/envinfo
  System:
    OS: macOS 14.5
  Binaries:
    Node: 20.16.0 - ~/.nvm/versions/node/v20.16.0/bin/node
    npm: 10.8.1 - ~/.nvm/versions/node/v20.16.0/bin/npm
    pnpm: 9.1.4 - ~/Library/pnpm/pnpm
  Browsers:
    Chrome: 128.0.6613.138
    Edge: Not Found
    Safari: 17.5

Search keywords: import, Node, fs, sandbox

@joshwcomeau joshwcomeau added the status: waiting for maintainer These issues haven't been looked at yet by a maintainer label Sep 16, 2024
@brijeshb42
Copy link
Contributor

Thanks for the report. I'll check this out. Ideally, if something is imported and not used in the css directly, wyw-in-js should tree-shake it before evalutating the code for css.

@brijeshb42 brijeshb42 self-assigned this Sep 17, 2024
@brijeshb42 brijeshb42 added this to the Road to v1 milestone Sep 17, 2024
@brijeshb42 brijeshb42 moved this to Backlog in Pigment CSS Sep 17, 2024
@brijeshb42
Copy link
Contributor

@joshwcomeau Could you share a relevant code snippet that shows how you are using the fs or any other node built-ins ? I think we might have to stub it but depends in the use-case.

@mathieuhasum
Copy link

Hey,
Just ran into this issue as well while refactoring a project using RSC and PigmentCSS.

One package (@jitl/notion-api) would always cause the dev server to hang forever (stuck at "Compiling..." without trace, which might or might not be related to this issue). Therefore I started to extract the few functionalities I needed from the library and recreate the helpers locally. One function (ensureImageDownloaded) relied on fs, path and other built-in modules.

Recreating a simpler version of it would cause the same error reported

unhandledRejection: Error: Unable to import "node:fs/promises". Importing Node builtins is not supported in the sandbox.

So to answer the question, an example of real-life use case here is using RSC to download remote images.

export async function ensureImageDownloaded(args: {
  url: string;
  filenamePrefix: string;
  directory: string;
}): Promise<string> {
  const { url, filenamePrefix, directory } = args; 

  // Check if file already exists with prefix
  const files = await readdir(directory);
  const existingFile = files.find((name) => name.startsWith(filenamePrefix));
  if (existingFile) {
    return existingFile;
  }

  // Download image
  const response = await fetch(url);
  const contentType = response.headers.get("content-type") || "image/png";
  const ext = contentType.split("/")[1];
  const filename = `${filenamePrefix}.${ext}`;
  const destPath = path.join(directory, filename);

  // Convert response to buffer and save
  const arrayBuffer = await response.arrayBuffer();
  const buffer = Buffer.from(arrayBuffer);
  await writeFile(destPath, buffer);

  return filename;
}

The repo linked by @joshwcomeau (https://github.com/joshwcomeau/pigmentcss-fs-issue) already provides a great minimal reproducible example as a starting point. You can swap content.helper.ts with any more complex use case like the one above.

@brijeshb42
Copy link
Contributor

Not sure if you have tried this with the latest versions of Next.js and Pigment CSS. I tried with @joshwcomeau's repo and I was getting a different error (after updating Nextjs to 15.0.2 and Pigment to 0.0.25) -

Screenshot 2024-11-04 at 7 28 33 PM

which meant that this one was not about sandbox. It wasn't able to find the file relative to itself. So as a workaround, I passed the root directory with src through an env var in next.config.mjs.

// next.config.js
import * as path from "path";
import { withPigment } from "@pigment-css/nextjs-plugin";

const nextConfig = {
  env: {
    DATA_DIR: path.join(process.cwd(), "src"),
  },
};

export default withPigment(nextConfig);

and in the file where you want to read it's contents, I did -

import * as path from "path";
import fs from "fs/promises";

export async function loadContent() {
  const data = await fs.readFile(
    path.join(process.env.DATA_DIR as string, "data.txt"),
    "utf-8",
  );
  return data;
}

This worked and the app built successfully. I am stating this as a solution as I am also doing the same thing for our Pigment CSS docs where we'll be authoring content in MDX and reading the contents through the fs module. Here's the code.

One thing to note here is that as long as your function where you are using built-in modules are not being called at the file scope, rather somewhere inside one of the next.js primitives, it should be fine. Here's an example -

function loadContent() {
}
const content = await loadContent();

export default function PageComponent() {
  return <div>{content}</div>;
}

This will throw an error because loadContent() is being called at the file scope.


function loadContent() {
}

export default function PageComponent() {
  const content = await loadContent();
  return <div>{content}</div>;
}

This should work as the actual call is inside another function.

@mathieuhasum Can you follow the above and see if it works for you ?

@mathieuhasum
Copy link

Thanks for the insights. I'll try again with this information and keep you updated.

If I can't identify the root cause, I'll create a reproducible example by stripping down the project I have. 🤔 Maybe there is a problem of loading from the wrong scope indeed.

For your information, as I was trying to debug yesterday I quickly swapped the package from PigmentCSS to Linaria. (From what I understand, they both leverage @wyw-in-js). And it did make the issue disappear. Will try to figure it out tonight or next weekend.

@brijeshb42
Copy link
Contributor

brijeshb42 commented Nov 4, 2024

I think I spoke too soon. The issue is still there during dev. What I stated above is valid for build command.
Let me look more into it. It hasn't been an issue for the Pigment docs yet.

Edit:

Moving the import to be dynamic and inside the function call worked. But this is a workaround and not a proper solution -

import * as path from "path";

export async function loadContent() {
  const fs = await import("fs/promises");
  const data = await fs.readFile(
    path.join(process.env.DATA_DIR as string, "data.txt"),
    "utf-8",
  );
  return data;
}

@mathieuhasum
Copy link

Surely not a perfect solution, but I confirm your workaround works 👍
Even for loading the functions from packages that were causing the dev server to get stuck.

export default async function PageComponent() {
  const blocks = await ...
  for (const block of blocks) {
    if (block.type === "image" ) {
      const { ensureImageDownloaded } = await import("@jitl/notion-api");
      await ensureImageDownloaded({
        url: block.image.file.url,
        directory: "public/images",
      ...
      }),
    }
  }
  return <>...</>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
nextjs status: waiting for maintainer These issues haven't been looked at yet by a maintainer
Projects
Status: Backlog
Development

No branches or pull requests

4 participants