UNPKG

@stylexswc/rs-compiler

Version:

NAPI-RS compiler for transform StyleX code

700 lines (545 loc) 20.4 kB
# NAPI-RS compiler for StyleX (\*\*unofficial) > Part of the > [StyleX SWC Plugin](https://github.com/Dwlad90/stylex-swc-plugin#readme) > workspace StyleX is a JavaScript library developed by Meta for defining styles optimized for user interfaces. You can find the [official StyleX repository](https://www.github.com/facebook/stylex) here. > [!WARNING] > This is an unofficial style compiler for StyleX. ## Overview This package provides an unofficial, high-performance NAPI-RS compiler for StyleX, a popular library from Meta for building optimized user interfaces. It is the top-level consumer crate that exposes the full StyleX pipeline to Node.js, leveraging SWC for parsing and transformation. > [!IMPORTANT] > The usage of StyleX does not change. All changes are internal. - Faster Build Times: By utilizing SWC instead of Babel, you can potentially experience significant speed improvements during StyleX processing. - Seamless Integration: This compiler seamlessly integrates with Next.js's default SWC Compiler, ensuring a smooth workflow. - Drop-in Replacement: Designed to be a drop-in replacement for the official StyleX Babel plugin, minimizing disruption to existing codebases. - Advanced Tooling Capabilities: NAPI-RS compiler unlocks access to StyleX metadata and source maps, enabling the creation of advanced plugins and tools for StyleX, ex. for creating a plugin for Webpack, Rollup, or other tools. ## Advantages of a `NAPI-RS` compiler versus a `SWC plugin` - Compability with SWC: under the hood, the NAPI-RS compiler uses SWC for parsing and transforming JavaScript code, ensuring compatibility with the latest ECMAScript features. - Direct Access to Node.js APIs: NAPI-RS allows you to directly access Node.js APIs from your Rust code, providing greater flexibility and control. - Improved Performance: NAPI-RS can often offer better performance than traditional Node.js addons, especially for computationally intensive tasks. - Simplified Development: NAPI-RS simplifies the process of developing Node.js addons in Rust, making it easier to create high-performance and efficient tools. ## Architecture - **Layer**: 9 — Compilers (top-level consumer) - **Depends on**: `stylex-ast`, `stylex-enums`, `stylex-logs`, `stylex-macros`, `stylex-regex`, `stylex-structures`, `stylex-transform`, `stylex-types`, `stylex-utils` - **Depended on by**: None (top-level entry point) ### Public API - `transform()` — Main entry point: takes source code + options, returns transformed output - `should_transform_file()` — File filtering based on path patterns - `normalize_rs_options()` — Options normalization and validation ### Modules - `enums` — Compiler-specific enum types - `structs` — Compiler-specific struct types - `utils::fn_parser` — Function argument parsing - `utils::metadata` — Build metadata handling - `utils::path_filter` — File path filtering logic ## Dependency Graph <details> <summary><h3>Dependency Graph</h3></summary> ```mermaid graph TD subgraph L0["Primitives"] stylex_constants["constants"] stylex_regex["regex"] stylex_styleq["styleq"] stylex_utils["utils"] end subgraph L1["Proc Macros"] stylex_macros["macros"] end subgraph L2["Domain Leaves"] stylex_enums["enums"] stylex_js["js"] stylex_logs["logs"] stylex_css_parser["css-parser"] stylex_path_resolver["path-resolver"] end subgraph L3["Core Data Structures"] stylex_structures["structures"] end subgraph L4["Type System"] stylex_types["types"] end subgraph L5["AST Foundations"] stylex_ast["ast"] end subgraph L6["Evaluation"] stylex_evaluator["evaluator"] end subgraph L7["CSS Processing"] stylex_css["css"] end subgraph L8["StyleX Transform"] stylex_transform["transform"] end subgraph L9["Compilers"] stylex_compiler_rs["rs-compiler"] end stylex_utils --> stylex_regex stylex_macros --> stylex_constants stylex_enums --> stylex_macros stylex_js --> stylex_constants stylex_js --> stylex_macros stylex_logs --> stylex_macros stylex_css_parser --> stylex_macros stylex_path_resolver --> stylex_macros stylex_structures --> stylex_constants stylex_structures --> stylex_enums stylex_structures --> stylex_macros stylex_types --> stylex_constants stylex_types --> stylex_enums stylex_types --> stylex_macros stylex_types --> stylex_structures stylex_types --> stylex_utils stylex_ast --> stylex_constants stylex_ast --> stylex_macros stylex_ast --> stylex_types stylex_ast --> stylex_utils stylex_evaluator --> stylex_ast stylex_evaluator --> stylex_constants stylex_evaluator --> stylex_js stylex_evaluator --> stylex_macros stylex_evaluator --> stylex_path_resolver stylex_evaluator --> stylex_types stylex_css --> stylex_ast stylex_css --> stylex_constants stylex_css --> stylex_css_parser stylex_css --> stylex_enums stylex_css --> stylex_evaluator stylex_css --> stylex_macros stylex_css --> stylex_regex stylex_css --> stylex_structures stylex_css --> stylex_types stylex_css --> stylex_utils stylex_transform --> stylex_ast stylex_transform --> stylex_constants stylex_transform --> stylex_css stylex_transform --> stylex_css_parser stylex_transform --> stylex_enums stylex_transform --> stylex_evaluator stylex_transform --> stylex_logs stylex_transform --> stylex_macros stylex_transform --> stylex_path_resolver stylex_transform --> stylex_regex stylex_transform --> stylex_structures stylex_transform --> stylex_styleq stylex_transform --> stylex_types stylex_transform --> stylex_utils stylex_compiler_rs --> stylex_ast stylex_compiler_rs --> stylex_enums stylex_compiler_rs --> stylex_logs stylex_compiler_rs --> stylex_macros stylex_compiler_rs --> stylex_regex stylex_compiler_rs --> stylex_structures stylex_compiler_rs --> stylex_transform stylex_compiler_rs --> stylex_types stylex_compiler_rs --> stylex_utils classDef l0 fill:#e8e8e8,stroke:#999,color:#333 classDef l1 fill:#dce8ff,stroke:#6699cc,color:#333 classDef l2 fill:#dcf5dc,stroke:#66aa66,color:#333 classDef l3 fill:#fff3dc,stroke:#cc9933,color:#333 classDef l4 fill:#ffe8dc,stroke:#cc6633,color:#333 classDef l5 fill:#f5dcff,stroke:#9933cc,color:#333 classDef l6 fill:#dcfff5,stroke:#33aaaa,color:#333 classDef l7 fill:#ffdcdc,stroke:#cc3333,color:#333 classDef l8 fill:#fffdc0,stroke:#aaaa33,color:#333 classDef l9 fill:#ffc0c0,stroke:#cc0000,color:#333 class stylex_constants,stylex_regex,stylex_styleq,stylex_utils l0 class stylex_macros l1 class stylex_enums,stylex_js,stylex_logs,stylex_css_parser,stylex_path_resolver l2 class stylex_structures l3 class stylex_types l4 class stylex_ast l5 class stylex_evaluator l6 class stylex_css l7 class stylex_transform l8 class stylex_compiler_rs l9 ``` </details> ## Installation To install the package, run the following command: ```bash npm install --save-dev @stylexswc/rs-compiler ``` ### Transformation Process Internally, this compiler takes your StyleX code and transforms it into a format optimized for further processing. ```ts var { transform } = require('@stylexswc/rs-compiler'); /// ...other logic const { code, metadata, sourcemap } = transform( filename, inputSourceCode, transformOptions ); /// ...other logic ``` ### Path Filtering > [!NOTE] > **New Feature:** The `include` and `exclude` options are exclusive to > this NAPI-RS compiler implementation and are not available in the official > StyleX Babel plugin. They provide powerful file filtering capabilities to > control which files are transformed. The compiler exports a `shouldTransformFile` function to determine whether a file should be transformed based on include/exclude patterns: ```ts import { shouldTransformFile } from '@stylexswc/rs-compiler'; const shouldTransform = shouldTransformFile( '/path/to/file.tsx', ['src/**/*.{ts,tsx}'], // include patterns (optional) ['**/*.test.*', '**/__tests__/**'] // exclude patterns (optional) ); if (shouldTransform) { // Transform the file } ``` #### Pattern Types - **Glob patterns** (strings): Use standard glob syntax to match file paths - `src/**/*.tsx` - All `.tsx` files in `src` directory and subdirectories - `**/*.test.*` - All test files - `**/node_modules/**` - All files in `node_modules` - **Regular expressions**: Use RegExp objects for complex pattern matching - `/\.test\./` - Files containing `.test.` - `/^src\/.*\.tsx$/` - `.tsx` files directly in the `src` directory **Advanced: Lookahead/Lookbehind Support** The Rust regex engine fully supports lookahead and lookbehind assertions, enabling sophisticated filtering patterns: - **Negative Lookahead** `(?!...)`: Match if NOT followed by pattern - `/node_modules(?!\/@stylexjs)/` - Exclude all node_modules except @stylexjs packages - `/\.tsx(?!\.test)/` - Match .tsx files that are NOT test files - **Positive Lookahead** `(?=...)`: Match if followed by pattern - `/.*\.test(?=\.tsx$)/` - Match only .test.tsx files - **Negative Lookbehind** `(?<!...)`: Match if NOT preceded by pattern - `/(?<!src\/).*\.tsx$/` - Exclude .tsx files not in src/ - **Positive Lookbehind** `(?<=...)`: Match if preceded by pattern - `/(?<=components\/).*\.tsx$/` - Match only .tsx files in components/ #### Filtering Rules 1. If `include` patterns are specified and not empty, files must match at least one pattern 2. If `exclude` patterns are specified, files matching any pattern are excluded 3. Exclude patterns take precedence over include patterns 4. All paths are matched relative to the current working directory #### Common Use Cases **Exclude all node_modules except specific packages:** ```ts // Exclude all node_modules except @stylexjs/open-props shouldTransformFile(filePath, undefined, [ /node_modules(?!\/@stylexjs\/open-props)/, ]); ``` **Transform only specific packages from node_modules:** ```ts shouldTransformFile( filePath, [ 'src/**/*.{ts,tsx}', 'node_modules/@stylexjs/open-props/**/*.js', 'node_modules/@my-org/design-system/**/*.js', ], ['**/*.test.*'] ); ``` **Exclude multiple node_modules packages except a few:** ```ts // Exclude all node_modules except @stylexjs packages shouldTransformFile(filePath, undefined, [/node_modules(?!\/@stylexjs)/]); ``` ### SWC Plugin Support > [!NOTE] >**New Feature:** The compiler now supports running SWC WASM plugins > before StyleX transformation. This allows you to chain transformations and > integrate custom SWC plugins seamlessly. The `transform` function accepts an optional `swcPlugins` array in the options object, allowing you to run SWC WASM plugins before the StyleX transformation: ```ts const { transform } = require('@stylexswc/rs-compiler'); const { code, metadata, map } = transform('Button.tsx', sourceCode, { dev: true, // Other StyleX options... // SWC plugins to run before StyleX transformation swcPlugins: [ // Plugin as [pluginPath, config] [ '/path/to/swc_plugin_theme.wasm', { themeName: 'my-theme', customOption: 'value', }, ], // You can chain multiple plugins [ '@swc/plugin-emotion', { sourceMap: true, }, ], ], }); ``` #### How It Works 1. **Plugin Execution Phase**: If `swcPlugins` are provided, the source code is first transformed using `@swc/core`'s `transformSync` with the specified WASM plugins 2. **StyleX Transformation Phase**: The plugin-transformed code is then passed to the StyleX compiler #### Plugin Configuration Each plugin in the `swcPlugins` array is a tuple of: - **Plugin Path** (string): Can be: - An absolute path to a `.wasm` file: `/path/to/plugin.wasm` - An npm package name: `@swc/plugin-emotion` - **Plugin Config** (object): Plugin-specific configuration options #### Example: Custom Theme Plugin ```ts transform(filename, code, { dev: true, swcPlugins: [ [ '/Users/me/plugins/swc_plugin_theme.wasm', { themeName: 'theme-name', themeConfig: { primaryColor: 'blue', spacing: 8, }, }, ], ], }); ``` #### Benefits - ✅ Chain multiple transformations seamlessly - ✅ Leverage the SWC plugin ecosystem - ✅ Custom preprocessing before StyleX transformation - ✅ Full compatibility with SWC WASM plugins - ✅ No additional build configuration needed ### Output The output from the compiler includes the transformed code, metadata about the generated styles, and an optional source map. ```json { "code": "import * as stylex from '@stylexjs/stylex';\nexport const styles = {\n default: {\n backgroundColor: \"xrkmrrc\",\n color: \"xju2f9n\",\n $$css: true\n }\n};\n", "metadata": { "stylex": { "styles": [ [ "xrkmrrc", { "ltr": ".xrkmrrc{background-color:red}", "rtl": null }, 3000 ], [ "xju2f9n", { "ltr": ".xju2f9n{color:blue}", "rtl": null }, 3000 ] ] } }, "map": "{\"version\":3,\"sources\":[\"<anon>\"],\"names\":[],\"mappings\":\"AACE;AACA;;;;;;EAKG\"}" } ``` ## Example Below is a simple example of input StyleX code: ```ts import * as stylex from '@stylexjs/stylex'; const styles = stylex.create({ root: { padding: 10, }, element: { backgroundColor: 'red', }, }); const styleProps = stylex.props(styles.root, styles.element); ``` Output code: ```ts import * as stylex from '@stylexjs/stylex'; const styleProps = { className: 'x7z7khe xrkmrrc', }; ``` ## Compatibility > [!IMPORTANT] > The current resolution of the `exports` field from > `package. json` is only partially supported, so if you encounter problems, > please open an > [issue](https://github.com/Dwlad90/stylex-swc-plugin/issues/new) with an > attached link to reproduce the problem. ## Configuration Options ### `injectStylexSideEffects` **Type:** `boolean` **Default:** `false` Automatically injects side-effect imports for `.stylex` and `.consts` files to prevent tree-shaking from removing them during bundling. #### Problem When using build tools that perform tree-shaking (like webpack, rollup, vite), imports from `.stylex` or `.consts` files may appear unused after StyleX transformation and get removed: ```ts // Before StyleX transformation import { colors } from './theme.stylex'; import { spacing } from './tokens.consts'; const styles = stylex.create({ root: { backgroundColor: colors.primary, // Uses colors padding: spacing.md, // Uses spacing }, }); // After StyleX transformation import { colors } from './theme.stylex'; // Appears unused! import { spacing } from './tokens.consts'; // Appears unused! const styles = { root: { backgroundColor: 'x1a2b3c', padding: 'x4d5e6f', $$css: true, }, }; ``` The bundler may remove these "unused" imports, but they're needed for other files to resolve the same StyleX/const references correctly. #### Solution When `injectStylexSideEffects: true`, the compiler automatically adds side-effect imports to preserve these modules: ```ts // After transformation with injectStylexSideEffects: true import { colors } from './theme.stylex'; import { spacing } from './tokens.consts'; import './theme.stylex'; // Side-effect import (prevents tree-shaking) import './tokens.consts'; // Side-effect import (prevents tree-shaking) const styles = { root: { backgroundColor: 'x1a2b3c', padding: 'x4d5e6f', $$css: true, }, }; ``` #### When to Use - ✅ **Use `true`** when your bundler runs StyleX transformation **before** other optimizations (recommended) - ✅ **Use `true`** with webpack's `loaderOrder: 'first'` option - ❌ **Use `false`** when StyleX runs **after** tree-shaking (e.g., webpack's `loaderOrder: 'last'`) > [!TIP] > This option is automatically enabled when using > `@stylexswc/webpack-plugin` with `loaderOrder: 'first'` (the default). ### `useRealFileForSource` **Type:** `boolean` **Default:** `true` Controls whether the compiler should read source files from disk for error reporting and source map generation. #### Behavior - **`true` (default)**: The compiler reads the actual source file from disk when generating error messages and source maps. This provides accurate line numbers and source context that match what you see in your editor. - **`false`**: The compiler uses the transformed AST representation for error reporting. This is useful when: - Working with in-memory transformations - Source files are not available on disk - You want faster compilation (skips file I/O) #### Example ```ts transform(filename, code, { use_real_file_for_source: true, // Use actual source files (default) dev: true, // ... other options }); ``` #### Use Cases **Use `true` (recommended for development):** - Local development with files on disk - Accurate error messages with real line numbers - Better debugging experience - Source maps match your actual files **Use `false` (for special cases):** - In-memory transformations without disk access - Virtual file systems - Performance optimization when error accuracy is less critical - Build pipelines where source files are not available > [!TIP] > Keep the default `true` value for most use cases. Only set it to > `false` if you have specific requirements for in-memory transformations or > performance-critical scenarios where file I/O is a bottleneck. > [!WARNING] > When `useRealFileForSource` is set to `false`, error messages may > report **incorrect line numbers**. The compiler will use the transformed AST > representation instead of the original source code, which can lead to line > number mismatches. This happens because: > > - The AST may have been modified by previous transformations > - Comments and whitespace are normalized in the AST > - The structure may differ from what's in your actual source file > > For accurate error reporting and debugging, always use > `useRealFileForSource: true` (the default) during development. ## Debug You can enable debug logging for the StyleX compiler using the `STYLEX_DEBUG` environment variable. This is useful for troubleshooting and understanding the internal processing of StyleX code. ### Log Levels The following log levels are available: - `error`: Only shows error messages - `warn`: Shows warnings and errors (default) - `info`: Shows informational messages, warnings, and errors - `debug`: Shows debug information and all above levels - `trace`: Shows very detailed execution information ### Usage Set the environment variable before running your build command: ```bash # Set to debug level STYLEX_DEBUG=debug npm run build # Set to trace for most verbose output STYLEX_DEBUG=trace npm run dev ``` For Windows Command Prompt: ```cmd set STYLEX_DEBUG=debug && npm run build ``` For PowerShell: ```powershell $env:STYLEX_DEBUG="debug"; npm run build ``` ## Error Handling The compiler produces clean, structured error messages with a branded `[StyleX]` prefix, replacing Rust's default panic boilerplate with user-friendly diagnostics in both the terminal and at the NAPI boundary. ### Error Format All StyleX errors follow this format in the terminal: ```bash [StyleX] message --> file:line:col [Stack trace]: internal/source/location #shown only when STYLEX_DEBUG >= info ``` Errors are color-coded for readability: | Category | Label | Color | | -------------------------- | ----------------- | ------------- | | Regular error | _(none)_ | Red prefix | | Unimplemented feature | `[UNIMPLEMENTED]` | Magenta label | | Internal unreachable state | `[UNREACHABLE]` | Blue label | ## License MIT — see [LICENSE](https://github.com/Dwlad90/stylex-swc-plugin/blob/develop/LICENSE)