facebook / stylex

StyleX is the styling system for ambitious user interfaces.
https://stylexjs.com
MIT License
8.41k stars 310 forks source link

[@stylexjs/esbuild-plugin] The esbuld-plugin does not embed variables in the TypeScript build. #454

Closed ueokande closed 9 months ago

ueokande commented 9 months ago

Describe the issue

When I use variables with the @stylexjs/esbuild-plugin, the plugin does not embed variables defined by stylex.defineVars() into the generated CSS. It works in the ESM build as expected, but it does not work well in the TypeScript build. Here is an example:

Built from ESM

:root{--xd1a5w:black;--x1h2un6e:white;}
@media (prefers-color-scheme: dark){:root{--xd1a5w:white;--x1h2un6e:black;}}
.xb26llm:not(#\#){background-color:var(--x1h2un6e)}
.x1bbqryo:not(#\#){color:var(--xd1a5w)}

Built from TypeScript

.x1e8mcya{background-color:var(--x1w3s4ik)}
.x9fir3{color:var(--x5x584n)}

Expected behavior

The @stylexjs/esbuild-plugin embed variabled defined by stylex.defineVars() in the TypeScript build.

Steps to reproduce

I reproduce it by the following versions:

  1. Build typescripts by the following configuration
// build.mjs
import esbuild from 'esbuild';
import stylexPlugin from '@stylexjs/esbuild-plugin';
import path from 'path';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

esbuild.build({
  entryPoints: [`src/index.ts`],
  outdir: `./build`,
  bundle: true,
  plugins: [
    stylexPlugin({
      dev: false,
      generatedCSSFileName: path.resolve(__dirname, `build/stylex.css`),
      stylexImports: ['@stylexjs/stylex'],
      unstable_moduleResolution: {
        type: 'commonJS',
        rootDir: __dirname,
      },
    }),
  ],
})
// src/index.ts
import * as stylex from '@stylexjs/stylex';
import { colors } from './tokens.stylex';

const styles = stylex.create({
  container: {
    color: colors.foreground,
    backgroundColor: colors.background,
  },
});

window.dummy = stylex.props(styles.container);
// src/tokens.stylex.ts
import * as stylex from '@stylexjs/stylex';

const DARK = '@media (prefers-color-scheme: dark)';

export const colors = stylex.defineVars({
  foreground: { default: 'black', [DARK]: 'white' },
  background: { default: 'white', [DARK]: 'black' },
});
  1. Compile and bundle TypeScript files by node build.mjs
  2. Check a generated CSS in the build directory

The full example project is available here:

Test case

No response

Additional comments

No response

nedjulius commented 9 months ago

hey, this is a good catch.

esbuild TS loader respects the tsconfig.json file and by default elides imports that are not referenced in the body of the importing module. StyleX babel plugin compiles away any references to stylex or variable calls:

import stylex from '@stylexjs/stylex'; // unused import
import { colors } from './vars.stylex'; // unused import

window.dummy = {
  className: "x9fir3 x1e8mcya"
}

esbuild traverses modules based on the imports of the entry module, and the variables file is not bundled due to the elision. to avoid this, you need to set verbatimModuleSyntax option to true in your tsconfig.json file:

{
  "compilerOptions": {
    "verbatimModuleSyntax": true
  }
}

this should definitely be documented or added as a tsconfig extension in StyleX

EDIT: the better option is mentioned below this comment. I also opened up a PR that sets the treeshakeCompensation to true in the esbuild plugin itself, so consumers won't have to do anything extra.

nmn commented 9 months ago

For variables, Stylex has an option called treeshakeCompensation that should ensure that the imports for variables are not removed due to tree-shaking. Turning that option on, should fix the issue as well.