Open joshwcomeau opened 1 month ago
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.
@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.
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.
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
) -
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 ?
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.
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;
}
Surely not a perfect solution, but I confirm your workaround works :+1: 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 <>...</>
Steps to reproduce
Repro URL: https://github.com/joshwcomeau/pigmentcss-fs-issue
npm run dev
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
``` 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 ```npx @mui/envinfo
Search keywords: import, Node, fs, sandbox