fix(editor): Remove invalid :global() from shipped design-system CSS (no-changelog) (#35144)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Alex Grozav 2026-08-03 10:32:37 +03:00 committed by GitHub
parent b2eb04aca0
commit 953fd3165c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 70 additions and 12 deletions

View File

@ -3,7 +3,6 @@
"name": "@n8n/design-system",
"version": "2.32.0",
"main": "src/index.ts",
"import": "src/index.ts",
"scripts": {
"dev": "pnpm run --dir=../storybook dev",
"clean": "rimraf dist .turbo",
@ -46,6 +45,7 @@
"autoprefixer": "^10.4.19",
"emojibase-data": "^17.0.0",
"eslint-plugin-storybook": "catalog:storybook",
"pinia": "catalog:frontend",
"postcss": "^8.4.38",
"sass": "^1.71.1",
"storybook": "catalog:storybook",
@ -90,7 +90,6 @@
"markdown-it-link-attributes": "catalog:",
"markdown-it-task-lists": "^2.1.1",
"parse-diff": "^0.11.1",
"pinia": "catalog:frontend",
"reka-ui": "^2.5.0",
"sanitize-html": "catalog:",
"vue": "catalog:frontend",

View File

@ -11,10 +11,15 @@
* Usage:
* <N8nButton variant="solid" class="n8n-button--success">Save</N8nButton>
* <N8nButton variant="ghost" class="n8n-button--highlight">Info</N8nButton>
*
* Do not wrap these selectors in `:global()`. This file is `@use`d from
* Button.vue's plain (non-module) style block, where selectors are already
* global, so `:global()` is never transformed away — it ships as an invalid
* pseudo-class and browsers drop the whole rule.
*/
/** @deprecated Use a semantic variant instead of success color */
:global(.n8n-button--success) {
.n8n-button--success {
--button--color--background: var(--color--success);
--button--color--background-hover: var(--color--success--shade-1);
--button--color--background-active: var(--color--success--shade-1);
@ -31,7 +36,7 @@
}
/** @deprecated Use a semantic variant instead of warning color */
:global(.n8n-button--warning) {
.n8n-button--warning {
--button--color--background: var(--color--warning);
--button--color--background-hover: var(--color--warning--shade-1);
--button--color--background-active: var(--color--warning--shade-1);

View File

@ -111,6 +111,7 @@ export { default as N8nResizeWrapper } from './N8nResizeWrapper';
export { default as N8nSelect } from './N8nSelect';
export { default as N8nSpinner } from './N8nSpinner';
export { default as N8nStatusDot } from './N8nStatusDot';
export { default as N8nStepper } from './N8nStepper/Stepper.vue';
export { default as N8nSticky } from './N8nSticky';
export { default as N8nResizeableSticky } from './N8nResizeableSticky';
export { default as N8nSuggestedActions } from './N8nSuggestedActions';

View File

@ -0,0 +1,55 @@
import { readdirSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';
/**
* `:global()` is a CSS-Modules construct. It is only transformed away in a
* `<style module>` or `<style scoped>` block; anywhere else it survives into
* the emitted CSS as an invalid pseudo-class, which browsers respond to by
* dropping the whole rule (and lightningcss by warning). A standalone `.scss`
* can't know which kind of block will `@use` it, so it must not use `:global()`
* at all — the caller decides, and getting it wrong fails silently.
*/
const srcDir = resolve(process.cwd(), 'src');
const styleBlock = /<style\b([^>]*)>([\s\S]*?)<\/style>/g;
/** Only selectors matter here, so comments explaining this very rule shouldn't trip it. */
const usesGlobal = (source: string) =>
source
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/(^|\s)\/\/.*$/gm, '$1')
.includes(':global(');
const allFiles = readdirSync(srcDir, { recursive: true, encoding: 'utf8' });
const collect = (extension: string) => {
const files = allFiles.filter((file) => file.endsWith(extension));
// A wrong srcDir would make the assertions below vacuously pass.
expect(files.length).toBeGreaterThan(0);
return files.map((file) => [file, readFileSync(resolve(srcDir, file), 'utf8')] as const);
};
describe(':global() placement', () => {
it('is not used in .vue style blocks that do not transform it', () => {
const offenders: string[] = [];
for (const [file, source] of collect('.vue')) {
for (const [, attrs, body] of source.matchAll(styleBlock)) {
const transformed = /\bmodule\b/.test(attrs) || /\bscoped\b/.test(attrs);
if (!transformed && usesGlobal(body)) {
offenders.push(`${file} — <style${attrs}>`);
}
}
}
expect(offenders).toEqual([]);
});
it('is not used in standalone .scss files', () => {
const offenders = collect('.scss')
.filter(([, source]) => usesGlobal(source))
.map(([file]) => file);
expect(offenders).toEqual([]);
});
});

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
import { computed, ref, shallowRef } from 'vue';
import { N8nButton, N8nIconButton, N8nInput, N8nText } from '@n8n/design-system';
import N8nStepper from '@n8n/design-system/components/N8nStepper/Stepper.vue';
import { N8nButton, N8nIconButton, N8nInput, N8nStepper, N8nText } from '@n8n/design-system';
import type { ChatIntegrationDescriptor, AgentIntegrationSettings } from '@n8n/api-types';
import { useI18n } from '@n8n/i18n';
import { useRootStore } from '@n8n/stores/useRootStore';

View File

@ -6,9 +6,9 @@ import {
N8nIcon,
N8nIconButton,
N8nInput,
N8nStepper,
N8nText,
} from '@n8n/design-system';
import N8nStepper from '@n8n/design-system/components/N8nStepper/Stepper.vue';
import AgentChannelSlackSetupSnapshots from './AgentChannelSlackSetupSnapshots.vue';
import { useI18n } from '@n8n/i18n';
import { useRootStore } from '@n8n/stores/useRootStore';

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { N8nButton, N8nText } from '@n8n/design-system';
import N8nStepper from '@n8n/design-system/components/N8nStepper/Stepper.vue';
import { N8nButton, N8nStepper, N8nText } from '@n8n/design-system';
import type { ChatIntegrationDescriptor, AgentIntegrationSettings } from '@n8n/api-types';
import { useI18n } from '@n8n/i18n';
import type { PermissionsRecord } from '@n8n/permissions';

View File

@ -5181,9 +5181,6 @@ importers:
parse-diff:
specifier: ^0.11.1
version: 0.11.1
pinia:
specifier: catalog:frontend
version: 2.2.4(typescript@6.0.2)(vue@3.5.26(typescript@6.0.2))
reka-ui:
specifier: ^2.5.0
version: 2.5.0(typescript@6.0.2)(vue@3.5.26(typescript@6.0.2))
@ -5272,6 +5269,9 @@ importers:
eslint-plugin-storybook:
specifier: catalog:storybook
version: 10.1.11(eslint@9.29.0(jiti@2.6.1))(storybook@10.1.11(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(prettier@3.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(utf-8-validate@5.0.10))(typescript@6.0.2)
pinia:
specifier: catalog:frontend
version: 2.2.4(typescript@6.0.2)(vue@3.5.26(typescript@6.0.2))
postcss:
specifier: 8.5.10
version: 8.5.10