Skip to content

Prevent non-module SCSS/CSS imports in JS/TS files #76744

@mirka

Description

@mirka

What problem does this address?

@wordpress/wp-build inlines all imported stylesheets into the JS bundle as runtime-injected <style> elements (with data-wp-hash deduplication). This means that if someone adds import './style.scss' in a JS file, it silently "works" at runtime, making it easy to miss that this is the wrong pattern.

In most packages, stylesheets should be imported through the package's root-level style.scss, not directly in JS. Importing in JS bypasses the standard stylesheet pipeline and causes the styles to be injected at runtime via document.head.appendChild. This has several problems:

  • Redundancy. If the stylesheet is already in the root style.scss chain, it gets loaded twice.
  • Missing from the stylesheet bundle. If the stylesheet is not in the root chain, it's absent from the CSS file that WordPress enqueues, and only loads through the runtime injection.
  • No RTL processing. Most packages rely on the build-time RTL transformation to generate -rtl.css stylesheets. Runtime-injected styles bypass this entirely, so any directional styles (margins, paddings, borders, etc.) will be broken in RTL locales.

I found three existing cases of this:

  • packages/block-editor/src/components/block-visibility/modal.js imports ./style.scss. This is redundant because the same file is already in the root packages/block-editor/src/style.scss.
  • packages/editor/src/components/collaborators-presence/index.tsx imports ./styles/collaborators-presence.scss, and list.tsx imports ./styles/collaborators-list.scss. These are not in the root stylesheet at all, so they're only loaded through the runtime injection.

What is your proposed solution?

  1. Fix the existing cases. Remove the SCSS imports from the JS/TS files above. For the collaborators-presence stylesheets, also add them to packages/editor/src/style.scss.

  2. Add a lint rule to prevent importing non-module .scss/.css files in JS/TS files. This should flag side-effect-only imports like import './style.scss' while allowing CSS module imports that have a binding (e.g., import styles from './style.module.scss'). The rule should apply to packages that use the root-stylesheet convention, and could be configured via an .eslintrc.js override to exclude packages like @wordpress/ui that intentionally import styles in JS.

  3. Consider restricting wp-build inline injection to CSS modules only. Currently the compileInlineStyle transform in wp-build processes all .css/.scss imports the same way. If it only applied the runtime injection to .module.css/.module.scss files, developers would notice that the styles are missing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    [Type] Code QualityIssues or PRs that relate to code quality

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions