Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import {
getViewportCheckboxState,
getHideEverywhereCheckboxState,
} from './utils';
import './style.scss';

const DEFAULT_VIEWPORT_CHECKBOX_VALUES = {
[ BLOCK_VISIBILITY_VIEWPORTS.mobile.key ]: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import { unlock } from '../../lock-unlock';
import { getAvatarUrl } from '../collaborators-overlay/get-avatar-url';
import { getAvatarBorderColor } from '../collab-sidebar/utils';
import { createCursorRegistry } from '../collaborators-overlay/cursor-registry';

import './styles/collaborators-presence.scss';
import { CollaboratorsOverlay } from '../collaborators-overlay';

const { useActiveCollaborators } = unlock( privateApis );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import { getAvatarUrl } from '../collaborators-overlay/get-avatar-url';
import { getAvatarBorderColor } from '../collab-sidebar/utils';
import { type CursorRegistry } from '../collaborators-overlay/cursor-registry';

import './styles/collaborators-list.scss';

interface CollaboratorsListProps {
activeCollaborators: PostEditorAwarenessState[];
popoverAnchor?: HTMLElement | null;
Expand Down
2 changes: 2 additions & 0 deletions packages/editor/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
@use "./components/collab-sidebar/style.scss" as *;
@use "./components/collaborators-presence/avatar/styles.scss" as *;
@use "./components/collaborators-presence/avatar-group/styles.scss" as *;
@use "./components/collaborators-presence/styles/collaborators-list.scss" as *;
@use "./components/collaborators-presence/styles/collaborators-presence.scss" as *;
@use "./components/collapsible-block-toolbar/style.scss" as *;
@use "./components/block-visibility/style.scss" as *;
@use "./components/blog-title/style.scss" as *;
Expand Down
4 changes: 4 additions & 0 deletions packages/eslint-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### New Features

- Added [`no-non-module-stylesheet-imports`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/eslint-plugin/docs/rules/no-non-module-stylesheet-imports.md) rule to prevent importing non-module stylesheets from JavaScript files ([#77984](https://github.com/WordPress/gutenberg/pull/77984)).

## 25.1.0 (2026-04-29)

### New Features
Expand Down
7 changes: 6 additions & 1 deletion packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,9 @@ The granular rulesets will not define any environment globals. As such, if they
| [no-dom-globals-in-react-fc](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-dom-globals-in-react-fc.md) | Disallow use of DOM globals in the render cycle of a React function component. | |
| [components-no-missing-40px-size-prop](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/components-no-missing-40px-size-prop.md) | Disallow missing `__next40pxDefaultSize` prop on `@wordpress/components` components. | ✓ |
| [components-no-unsafe-button-disabled](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/components-no-unsafe-button-disabled.md) | Disallow using `disabled` on Button without `accessibleWhenDisabled`. | ✓ |
| [no-unsafe-render-order](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unsafe-render-order.md) | Prevent unsafe `render` composition orders that silently remove semantics. | ✓ |
| [no-unsafe-render-order](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unsafe-render-order.md) | Prevent unsafe `render` composition orders that silently remove semantics. | ✓ |
| [no-i18n-in-save](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-i18n-in-save.md) | Disallow translation functions in block save methods. | |
| [no-non-module-stylesheet-imports](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-non-module-stylesheet-imports.md) | Disallow importing non-module stylesheets from JavaScript files. | |
| [no-unmerged-classname](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unmerged-classname.md) | Disallow unmerged `className` in components that spread rest props. | |
| [no-unguarded-get-range-at](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md) | Disallow the usage of unguarded `getRangeAt` calls. | ✓ |
| [no-unsafe-wp-apis](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unsafe-wp-apis.md) | Disallow the usage of unsafe APIs from `@wordpress/*` packages | ✓ |
Expand All @@ -146,6 +147,7 @@ If you are upgrading from a previous version that used `.eslintrc.*` files:

1. Replace your `.eslintrc.*` file with an `eslint.config.mjs` file.
2. Change `extends` arrays to import + spread:

```js
// Old (.eslintrc.js)
module.exports = {
Expand All @@ -156,14 +158,17 @@ If you are upgrading from a previous version that used `.eslintrc.*` files:
import wordpress from '@wordpress/eslint-plugin';
export default [ ...wordpress.configs.recommended ];
```

3. Convert `overrides` to separate config objects with `files` patterns.
4. Replace `env` with `languageOptions.globals` using the [`globals`](https://www.npmjs.com/package/globals) package.
5. Delete your `.eslintignore` file and move patterns into an `ignores` config object.
6. Update rule prefixes in inline comments: `eslint-comments/*` has been renamed to `@eslint-community/eslint-comments/*`. For example:

```diff
- /* eslint-disable eslint-comments/no-unlimited-disable */
+ /* eslint-disable @eslint-community/eslint-comments/no-unlimited-disable */
```

7. Remove any `/* eslint-env */` comments — they are no longer supported in ESLint v10. Use `languageOptions.globals` in your config instead.

For a comprehensive walkthrough with examples and troubleshooting, see the [Gutenberg ESLint v10 migration guide](https://github.com/WordPress/gutenberg/blob/HEAD/docs/how-to-guides/eslint-v10-migration.md). See also the [ESLint migration guide](https://eslint.org/docs/latest/use/configure/migration-guide) for general flat config details.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# No Non-Module Stylesheet Imports (no-non-module-stylesheet-imports)

Non-module stylesheets imported from JavaScript are injected at runtime,
bypassing the package stylesheet bundle. This can duplicate styles, omit them
from the enqueued stylesheet, and skip build-time processing such as RTL
generation.

Use a package stylesheet entry point, such as `src/style.scss`, for non-module
stylesheets. CSS modules may still be imported from JavaScript.

## Rule details

Examples of **incorrect** code for this rule:

```js
import './style.scss';
```

```js
import styles from './style.css';
```

Examples of **correct** code for this rule:

```scss
@use './components/example/style.scss' as *;
```

```js
import styles from './style.module.css';
```

```js
import './style.module.css';
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { RuleTester } from 'eslint';
import rule from '../no-non-module-stylesheet-imports';

const ruleTester = new RuleTester( {
languageOptions: {
sourceType: 'module',
ecmaVersion: 6,
},
} );

ruleTester.run( 'no-non-module-stylesheet-imports', rule, {
valid: [
{ code: "import './component';" },
{ code: "import styles from './style.module.css';" },
{ code: "import styles from './style.module.scss';" },
{ code: "import styles from './style.module.sass';" },
{ code: "import theme from './style.module.css?inline';" },
{ code: "import './style.module.css';" },
{ code: "import './style.module.scss?inline';" },
],
invalid: [
{
code: "import './style.css';",
errors: [ { messageId: 'noNonModuleStylesheet' } ],
},
{
code: "import './style.scss';",
errors: [ { messageId: 'noNonModuleStylesheet' } ],
},
{
code: "import './style.sass';",
errors: [ { messageId: 'noNonModuleStylesheet' } ],
},
{
code: "import styles from './style.scss';",
errors: [ { messageId: 'noNonModuleStylesheet' } ],
},
],
} );
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const STYLESHEET_EXTENSIONS = /\.(?:css|scss|sass)$/i;
const MODULE_STYLESHEET_EXTENSIONS = /\.module\.(?:css|scss|sass)$/i;

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'problem',
docs: {
description:
'Disallow importing non-module stylesheets from JavaScript files.',
url: 'https://github.com/WordPress/gutenberg/blob/HEAD/packages/eslint-plugin/docs/rules/no-non-module-stylesheet-imports.md',
},
schema: [],
messages: {
noNonModuleStylesheet:
'Import non-module stylesheets through the package stylesheet entry point instead of JavaScript. If you want to import from JavaScript, use a CSS module.',
},
},
create( context ) {
return {
ImportDeclaration( node ) {
if ( typeof node.source.value !== 'string' ) {
return;
}

const importPath = stripQueryAndHash( node.source.value );
if ( ! STYLESHEET_EXTENSIONS.test( importPath ) ) {
return;
}

if ( MODULE_STYLESHEET_EXTENSIONS.test( importPath ) ) {
return;
}

context.report( {
node,
messageId: 'noNonModuleStylesheet',
} );
},
};
},
};

/**
* @param {string} importPath Import source path.
* @return {string} Import source path without query or hash suffixes.
*/
function stripQueryAndHash( importPath ) {
return importPath.replace( /[?#].*$/, '' );
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ import {
type WPKeycodeModifier,
} from '@wordpress/keycodes';

/**
* Internal dependencies
*/
import './style.scss';

interface KeyCombination {
/** Modifier for cross-platform display (e.g. 'primary', 'primaryShift', 'shift'). */
modifier?: WPKeycodeModifier;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import { RectangleStencil } from './stencils/rectangle-stencil';
import { DimmingOverlay } from './overlays/dimming-overlay';
import { GridOverlay } from './overlays/grid-overlay';
import { ViewportProvider, useViewport } from './viewport-provider';
import './cropper.scss';

/** Threshold for comparing normalized crop rect values. */
const CROP_RECT_EPSILON = 1e-6;
Expand Down
1 change: 1 addition & 0 deletions packages/media-editor/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
@use "./components/media-editor-modal/style.scss" as *;
@use "./components/media-editor-canvas/style.scss" as *;
@use "./components/media-editor-toolbar/style.scss" as *;
@use "./components/media-editor-keyboard-shortcuts-modal/style.scss" as *;
@use "./components/rotation-ruler/style.scss" as *;
13 changes: 13 additions & 0 deletions tools/eslint/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,19 @@ export default dedupePlugins( [
},
},

// Override: Package source files — non-module stylesheets should be
// bundled through package stylesheet entry points, not runtime injected.
{
files: [ 'packages/*/src/**/*.[tj]s?(x)', 'routes/**/*.[tj]s?(x)' ],
ignores: [
...developmentFiles,
'**/*.@(android|ios|native).[tj]s?(x)',
],
rules: {
'@wordpress/no-non-module-stylesheet-imports': 'error',
},
},

// Override: Package source files — forbid raw SVG elements.
{
files: [ 'packages/**/*.js' ],
Expand Down
Loading
Loading