vanilla-extract-css / vanilla-extract

Zero-runtime Stylesheets-in-TypeScript
https://vanilla-extract.style
MIT License
9.55k stars 289 forks source link

@vanilla-extract/webpack-plugin - document is not defined (Next.js 13+) #1164

Open Dessader opened 1 year ago

Dessader commented 1 year ago

Describe the bug

An application and package has been created in the monorepository that contains the definition of base tokens that are implemented using vanilla extract. Rollup and the corresponding plugin - @vanilla-extract/rollup-plugin, are used to build this package.

This package should be used in applications and other local libraries that will implement UI components.

After building the package and trying to import the created variables into the consumer application, I get an error:

Uncaught ModuleBuildError: Module build failed (from ../../node_modules/@vanilla-extract/webpack-plugin/loader/dist/vanilla-extract-webpack-plugin-loader.cjs.js): NonErrorEmittedError: (Emitted value instead of an instance of Error) ReferenceError: document is not defined

I've looked at other issues in the repository with similar cases, but the proposed solutions are mostly based on the fact that you have to create a separate entry point for exporting style-related entities to get something like this:

import { A } from "@repo/design-system";
import { B } from "@repo/design-system/tokens"; 

However, in this case, it's probably not relevant, since nothing other than that is exported from the package.

I may be wrong somewhere.

Reproduction

https://github.com/Dessader/vanilla-extract-style-issue

System Info

System:
    OS: macOS 13.2.1
    CPU: (16) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 100.88 MB / 16.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 18.16.0 - ~/.nvm/versions/node/v18.16.0/bin/node
    Yarn: 1.22.19 - ~/.nvm/versions/node/v18.16.0/bin/yarn
    npm: 9.5.1 - ~/.nvm/versions/node/v18.16.0/bin/npm
  Browsers:
    Safari: 16.3

Used Package Manager

yarn

Logs

No response

Validations

Dessader commented 1 year ago

Small update.

When importing a component from a package that uses VE and including variables in its own styles, they work correctly in the consumed application.

However, there is still a problem where using variables directly from the package in the styles of the application itself causes an error.

The reproducible example given in the issue is also updated to demonstrate this.

Component definition:

// Box.tsx
import React from "react";
import { root } from "./Box.css";

export const Box = () => <div className={root}>External box</div>;
// Box.css.ts
import { style } from "@vanilla-extract/css";
import { cssVars } from "../vars";

export const root = style({
  padding: cssVars.spacing["spacing-2x"],
  background: "lightcoral",
});

Consumption of component:

import { homePageRoot } from "@/styles/app.css";
import { Box } from "@example-monorepo/design-system";

const HomePage = () => {
  return (
    <div className={homePageRoot}>
      Next App
      <Box />
    </div>
  );
};

export default HomePage;
dlineberger commented 1 year ago

I had the same error when attempting to integrate Storybook into my vanilla-based monorepo. I was able to resolve it by adding the entryFileNames and assetFileNames entries to my rollup.config.js as in this example.

Dessader commented 1 year ago

I had the same error when attempting to integrate Storybook into my vanilla-based monorepo. I was able to resolve it by adding the entryFileNames and assetFileNames entries to my rollup.config.js as in this example.

Could you please provide a reproducible example?

CinArb2 commented 1 year ago

I had a similar error, so basically when trying to import any css file into *.css.ts files throw errors in the new app directory. This css file is imported into your cssVars file when bundled. This is what I did on my project link Defining different entry points and adding this in the exports field on package.json solved my issue. I mostly followed what is done here I had to set treeshake on the entry point that handles globals selectors, then the css file can be imported directly into your layout page for example and the variable can be used in any css.ts file

Dessader commented 1 year ago

I had a similar error, so basically when trying to import any css file into *.css.ts files throw errors in the new app directory. This css file is imported into your cssVars file when bundled. This is what I did on my project link Defining different entry points and adding this in the exports field on package.json solved my issue. I mostly followed what is done here I had to set treeshake on the entry point that handles globals selectors, then the css file can be imported directly into your layout page for example and the variable can be used in any css.ts file

Thank you for taking the time to address this issue. I'm focused on another project right now, as soon as I have time to review your changes to my repository I'll look into it in more detail.

ArrayKnight commented 2 weeks ago

This seems to be the exact issue we're still struggling with.

We've got a design system being compiled with rollup. It outputs three main things as separated exports: all code (react & vanilla), vanilla only (contracts, tokens, and CSS util functions), and styles bundled into a single CSS file.

The design system package JSON specifies these in an exports map and everything resolves correctly in the app

The rollup config follows the prescribed configuration to rename output files so the app doesn't try to recompile anything in the design system dist. And the app does not transpile the design system

The React component files in the app consume the react exports and the static CSS. *.css.ts files in the app exclusively use the vanilla only exports to guarantee they do not incur any additional imported deps or code

The app is a NextJS app of version 14.2 (also started out on 14.0). We've tried both pages and app dirs, both with the same result, the OP error

We started out with our full Next config, but have been stripping things back to bare bones to try and isolate the error. We've yet to identify the root cause. At this point we've got a completely empty app with a single route, an empty Next config with only the VE plugin, and what feels like a correctly optimized design system build output, and yet...

ArrayKnight commented 2 weeks ago

@CinArb2 your example link doesn't seem to work anymore. There's no diff to view. Do you have time to recreate it here in this post?

ArrayKnight commented 2 weeks ago

@askoufis Any update on this issue? I'll post more specifics about our setup in a little bit. But I'm curious if there's any outstanding blocking issues or undocumented restrictions/specifics we should be aware of

ArrayKnight commented 2 weeks ago

The design system's rollup.config.js:

import path from 'path';
import json from '@rollup/plugin-json';
import { vanillaExtractPlugin } from '@vanilla-extract/rollup-plugin';
import { dts } from 'rollup-plugin-dts';
import esbuild from 'rollup-plugin-esbuild';
import { default as depsExternal } from 'rollup-plugin-node-externals';
import ts from 'typescript';

const CSS_IMPORT_REGEX = /^import '.+\.css';$/gm;

function loadCompilerOptions(tsconfig) {
  if (!tsconfig) return {};

  const configFile = ts.readConfigFile(tsconfig, ts.sys.readFile);

  const { options } = ts.parseJsonConfigFileContent(
    configFile.config,
    ts.sys,
    './'
  );

  return options;
}

const emittedCSSFiles = new Set();

function removeCssImports() {
  return {
    name: 'remove-css-imports',
    renderChunk(code, chunkInfo) {
      const output = code.replaceAll(CSS_IMPORT_REGEX, '');

      return {
        code: output,
        map: chunkInfo.map ?? null,
      };
    },
  };
}

// Bundle all of the .css files into a single "styles.css" file
function bundleCss() {
  return {
    name: 'bundle-css-emits',
    buildStart: () => {
      emittedCSSFiles.clear();
    },

    // Ran against every file
    renderChunk(code, chunkInfo) {
      const allImports = [
        ...code.matchAll(/import (?:.* from )?['"]([^;'"]*)['"];?/g),
      ];

      const dirname = path.dirname(chunkInfo.fileName);

      const output = allImports.reduce(
        (resultingCode, [importLine, moduleId]) => {
          if (emittedCSSFiles.has(path.posix.join(dirname, moduleId))) {
            return resultingCode.replace(importLine, '');
          }

          return resultingCode;
        },
        code
      );

      return {
        code: output,
        map: chunkInfo.map ?? null,
      };
    },
    generateBundle(_, bundle) {
      const bundleCode = Array.from(emittedCSSFiles.values())
        .map((file) => {
          const { name, fileName, source } = bundle[file];

          return `/* ${name} -> ${fileName} */\n` + source;
        })
        .join('\n\n');

      this.emitFile({
        type: 'asset',
        name: 'styles.css',
        source: bundleCode,
      });
    },
  };
}

// Bundle all of the .vanilla files into a single "vanilla" file
function bundleVanilla() {
  return {
    name: 'bundle-vanilla-emits',
    generateBundle(_, bundle) {
      const ext = bundle['index.d.ts'] ? 'd.ts' : 'js';

      this.emitFile({
        type: 'asset',
        fileName: `vanilla.${ext}`,
        source: bundle[`index.${ext}`].code
          .replace(/\/\/# sourceMappingURL.+/gi, '')
          .split('\n')
          .filter(
            (line) =>
              line.endsWith(`.vanilla.js';`) ||
              line.endsWith(`/types.js';`) ||
              line.endsWith(`'./utils/css.js';`) ||
              line.includes(`from './types/`)
          )
          .join('\n'),
      });
    },
  };
}

const compilerOptions = loadCompilerOptions('tsconfig.dist.json');

const commonPlugins = [
  vanillaExtractPlugin(),
  depsExternal(),
  esbuild(),
  json(),
];

export default [
  // Main processing and bundling
  {
    input: 'src/index.ts',
    plugins: [
      ...commonPlugins,
      removeCssImports(),
      bundleCss(),
      bundleVanilla(),
    ],
    output: [
      {
        dir: 'dist',
        exports: 'named',
        format: 'esm',
        preserveModules: true,
        sourcemap: true,
        // Change .css.js files to .vanilla.js so that they don't get re-processed by consumer's setup
        entryFileNames({ name }) {
          return `${name.replace(/\.css$/, '.vanilla')}.js`;
        },

        // Build out the assets folder with all of the css files.
        assetFileNames({ name }) {
          name = name
            .replace(/^src\//, 'assets/')
            .replace('.css.ts.vanilla', '');

          if (name.match(/\.css$/)) {
            emittedCSSFiles.add(name);
          }

          return name;
        },
      },
    ],
  },
  // Go through and create type definition files
  {
    input: 'src/index.ts',
    plugins: [
      ...commonPlugins,
      dts({
        compilerOptions: {
          ...compilerOptions,
          baseUrl: path.resolve(compilerOptions.baseUrl || '.'),
          declaration: true,
          emitDeclarationOnly: true,
          noEmit: false,
          noEmitOnError: true,
          target: ts.ScriptTarget.ESNext,
        },
      }),
      bundleVanilla(),
    ],
    output: [
      {
        dir: 'dist',
        format: 'esm',
        preserveModules: true,
        preserveModulesRoot: 'src',
        // Change .css.js files to vanilla.js so that they don't get re-processed by consumer's setup
        entryFileNames({ name }) {
          return `${name.replace(/\.css$/, '.vanilla')}.d.ts`;
        },
      },
    ],
  },
];

Produces a dist folder of this output:

image

Package.json exports:

image

The app's next.config.js:

import { createVanillaExtractPlugin } from '@vanilla-extract/next-plugin';

const withVanillaExtract = createVanillaExtractPlugin();

/**
 * @type {import('next').NextConfig}
 */
const nextConfig = {
};

export default withVanillaExtract(nextConfig);

In the app:

ArrayKnight commented 2 weeks ago

Figured out that having a CSS import inside of a *.css.ts file was what triggering this particular error

This was a brutal time tracking this down