fix(unplugin): detect pre-compiled StyleX packages and prevent Next.js App Router runtime injection#1695
Conversation
…d for runtimeInjection in Next.js
|
Hi @JhaSourav07! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
|
@JhaSourav07 is attempting to deploy a commit to the Meta Open Source Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds runtime diagnostics to help users avoid CSS ordering conflicts when consuming StyleX libraries that ship precompiled CSS, and blocks an unsafe config combination in Next.js environments.
Changes:
- Detect StyleX dependencies that publish precompiled CSS and emit a detailed warning listing affected packages.
- Add a guard that errors/throws when
runtimeInjectionis enabled in a detected Next.js App Router environment. - Add Jest tests covering the new warning behavior and the
runtimeInjection+ Next guard.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| packages/@stylexjs/unplugin/src/core.js | Detects precompiled CSS in StyleX deps, changes package discovery to return {name, precompiled}, prints warning, and adds the Next + runtimeInjection guard. |
| packages/@stylexjs/unplugin/tests/unplugin.test.js | Adds tests for precompiled-package warning scenarios and for the Next + runtimeInjection guard behavior. |
| const found = new Map((explicitPackages || []).map((name) => [name, false])); | ||
|
|
||
| const pkgJsonPath = findNearestPackageJson(rootDir); | ||
| if (!pkgJsonPath) return Array.from(found); | ||
| if (!pkgJsonPath) return mapToPackageInfos(found); | ||
| const pkgDir = path.dirname(pkgJsonPath); | ||
| const pkgJson = readJSON(pkgJsonPath); | ||
| if (!pkgJson) return Array.from(found); | ||
| if (!pkgJson) return mapToPackageInfos(found); |
| if (precompiledPackages.length > 0) { | ||
| const packageList = precompiledPackages.map((p) => ` • ${p}`).join('\n'); | ||
| console.warn(` | ||
| [StyleX] ⚠️ Potential CSS ordering issue detected. | ||
|
|
||
| The following packages use StyleX and ship pre-compiled CSS: | ||
| ${packageList} | ||
|
|
||
| Because these packages are not being re-compiled by the StyleX plugin, both | ||
| the library CSS and your app-local CSS are emitted as separate files. They | ||
| share the same atomic class names (e.g. .xuxw1ft for white-space:nowrap) but | ||
| your app's CSS is loaded last and therefore unconditionally overrides the | ||
| library's CSS for any colliding rule — regardless of intended priority. | ||
|
|
||
| Recommended fixes (pick one): | ||
| 1. Publish the library without pre-compiled CSS and add it to Next.js | ||
| \`transpilePackages\` so a single compiler handles everything. | ||
| 2. Enable \`useCSSLayers: true\` in BOTH the library build and this plugin | ||
| so @layer order governs priority instead of stylesheet load order. | ||
| 3. Ensure the library <link> appears after your app <link> in <head> | ||
| (workaround only — this is fragile and not recommended long-term). | ||
| `); | ||
| } |
| const conditions = [ | ||
| exports['.'], | ||
| ...Object.values(exports).filter( | ||
| (v) => v !== null && typeof v === 'object', | ||
| ), | ||
| ]; |
…support explicit package detection
| function hasPrecompiledCss(manifest) { | ||
| if (!manifest || typeof manifest !== 'object') return false; | ||
|
|
||
| if (typeof manifest.style === 'string' && manifest.style.endsWith('.css')) { | ||
| return true; | ||
| } | ||
|
|
||
| const { exports } = manifest; | ||
| if (exports && typeof exports === 'object') { | ||
| if ( | ||
| (typeof exports.style === 'string' && exports.style.endsWith('.css')) || | ||
| (typeof exports.css === 'string' && exports.css.endsWith('.css')) | ||
| ) { | ||
| return true; | ||
| } | ||
| for (const val of Object.values(exports)) { | ||
| if (val && typeof val === 'object' && !Array.isArray(val)) { | ||
| if ( | ||
| (typeof val.style === 'string' && val.style.endsWith('.css')) || | ||
| (typeof val.css === 'string' && val.css.endsWith('.css')) | ||
| ) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| } |
| const isNextAppRouter = | ||
| !!process.env.NEXT_RUNTIME || | ||
| !!process.env.NEXT_PHASE || | ||
| (framework === 'webpack' && | ||
| !!( | ||
| process.env.npm_package_dependencies_next || | ||
| process.env.npm_package_devDependencies_next | ||
| )); | ||
|
|
||
| if (userOptions.runtimeInjection && isNextAppRouter) { | ||
| const msg = `[StyleX] ❌ \`runtimeInjection\` must not be used with the Next.js App Router. |
| } | ||
| }); | ||
|
|
||
| test('warns when a StyleX dep ships CSS via exports.style', () => { |
| } | ||
| }); | ||
|
|
||
| test('warns when a StyleX dep ships CSS via exports.css', () => { |
….js runtime injection warnings
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
packages/@stylexjs/unplugin/src/core.js:1
- The warning text includes a Next.js-specific recommendation (
transpilePackages) even when the plugin runs under non-Next frameworks (e.g. Rollup/Vite). This can be misleading for those users; consider tailoring the recommendations based onisNext/framework(e.g., show a bundler-agnostic “ensure the library is compiled by the same StyleX pipeline” tip, and only mentiontranspilePackageswhenisNextis true).
/**
| const warnedSet = (globalThis.__stylex_warned_packages = | ||
| globalThis.__stylex_warned_packages || new Set()); | ||
| const newWarnPackages = precompiledPackages.filter( | ||
| (name) => !warnedSet.has(name), | ||
| ); |
| function hasPrecompiledCss(manifest) { | ||
| if (!manifest || typeof manifest !== 'object') return false; | ||
|
|
||
| if (typeof manifest.style === 'string' && manifest.style.endsWith('.css')) { | ||
| return true; | ||
| } |
What changed / motivation ?
Addresses inconsistent CSS priority and ordering behavior when importing pre-compiled third-party StyleX libraries, as well as debugging issues caused by runtime style injection in Next.js.
Specifically, this PR introduces the following changes to
@stylexjs/unplugin:exportstree to detect pre-compiled CSS files (handles nested targets, subpaths, and conditional configurations).transpilePackages) or CSS Layers (useCSSLayers: true).explicitPackageseven if they are missing from the project's direct root dependencies (e.g. inside monorepos/workspaces).NEXT_RUNTIME,NEXT_PHASE) and webpack package contexts.runtimeInjection: truewithin Next.js by throwing in production and console-erroring in development.Linked PR/Issues
Fixes #1207
Additional Context
packages/@stylexjs/unplugin/__tests__/unplugin.test.jscover:explicitPackagesmissing fromdependencies.exports.style,exports.css, nested conditional exports, and direct subpath string exports).@stylexjs/unpluginare fully passing (yarn workspace @stylexjs/unplugin test).Pre-flight checklist
Contribution Guidelines