diff --git a/.changeset/tidy-jars-listen.md b/.changeset/tidy-jars-listen.md new file mode 100644 index 000000000..dca1debd4 --- /dev/null +++ b/.changeset/tidy-jars-listen.md @@ -0,0 +1,7 @@ +--- +'@cloudfour/patterns': patch +--- + +Load `src/index-with-dependencies.scss` with `@use` rather than `@import`, which Dart Sass 3 removes. This was the last `@import` in the repo. + +Compiled output loses seven duplicate `@font-face` rules and nothing else. `@use` loads each stylesheet once, so the fonts configured at the top of the file are emitted a single time; under `@import` they came out twice, because `./index` loads `base/fonts` as well and an `@import` does not share the module graph with the file importing it. Every surviving rule is byte-for-byte one of the originals, in the same order, and all other CSS is byte-for-byte identical — 1,245 bytes smaller in the Storybook build. diff --git a/eslint.config.mjs b/eslint.config.mjs index 72af2b46f..9ed127713 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -28,13 +28,7 @@ export default [ languageOptions: { parserOptions: { tsconfigRootDir: import.meta.dirname, - projectService: { - // Our tsconfig covers `src`, because that is what ships. These files - // configure the tooling around it and belong to no project, so the type - // aware rules read them through an inferred one instead of failing to - // find them at all. - allowDefaultProject: ['vitest.config.mts', 'vitest.setup.*.ts'], - }, + projectService: true, }, }, }, diff --git a/src/components/button-swap/button-swap.browser.test.ts b/src/components/button-swap/button-swap.browser.test.ts index 7672ea017..7d077c055 100644 --- a/src/components/button-swap/button-swap.browser.test.ts +++ b/src/components/button-swap/button-swap.browser.test.ts @@ -7,12 +7,14 @@ import template from './button-swap.twig'; const body = () => page.elementLocator(document.body); -test('Swap UI state when clicked', async () => { +test('Render both states, exposing only the initial one', async () => { document.body.innerHTML = template(); // This package ships the markup for Button Swap but not the script that swaps it: - // that lives in the consuming site. So what this asserts is the initial state -- - // only the unsubscribed button is exposed, and it is not the slashed variant. + // that lives in the consuming site. So there is no swap to exercise here -- what + // the template has to get right is that both states are present and only the + // unsubscribed one reaches the accessibility tree. The snapshot covers the second + // half: `hidden` keeps the swapped state out of it. await expect.element(body()).toMatchAriaInlineSnapshot(` - status: Currently unsubscribed from notifications - button "Get notifications" @@ -21,6 +23,12 @@ test('Swap UI state when clicked', async () => { const firstBtn = page.getByRole('button'); await expect.element(firstBtn).toBeVisible(); await expect.element(firstBtn).not.toHaveClass('is-slashed'); + + const swapped = document.querySelector( + '.js-button-swap__swapped-button-wrapper', + ); + expect(swapped).not.toBeNull(); + expect(swapped).toHaveAttribute('hidden'); }); test('Set custom messages and labels', async () => { diff --git a/src/index-with-dependencies.scss b/src/index-with-dependencies.scss index d8ccdb293..f08129319 100644 --- a/src/index-with-dependencies.scss +++ b/src/index-with-dependencies.scss @@ -12,6 +12,9 @@ $dir: '/src/assets/fonts' ); -// stylelint-disable at-rule-disallowed-list -@import '../node_modules/@wordpress/block-library/build-style/style'; -@import './index'; +/// `@use` rather than `@import`, which Dart Sass 3 removes. Because `@use` loads each +/// stylesheet once, the `@font-face` rules from `base/fonts` above are emitted a single +/// time here; under `@import` they came out twice, since `./index` loads `base/fonts` +/// too and an `@import` does not share the module graph with the file importing it. +@use '../node_modules/@wordpress/block-library/build-style/style'; +@use './index'; diff --git a/tsconfig.json b/tsconfig.json index fae584e2c..74af31df5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,7 +24,14 @@ "skipLibCheck": true, "resolveJsonModule": true }, - "include": ["src"], + // `src` is what ships. The root-level globs pick up the tooling that configures the + // build and tests around it: those files belong to no project otherwise, so the + // type-aware lint rules either fail to find them or fall back to an inferred + // project that ignores the strictness settings above. Globs rather than a list, so + // the next root-level TypeScript file is covered without anyone remembering to add + // it. Nothing outside `ts-dist/src` reaches `dist`, so this does not widen what we + // publish. + "include": ["src", "*.ts", "*.mts", "*.cts"], "exclude": [ "src/prototypes", "src/vendor/highlight/demo/samples", diff --git a/twing/vite-plugin-twig.mjs b/twing/vite-plugin-twig.mjs index 054861ca6..153bafa07 100644 --- a/twing/vite-plugin-twig.mjs +++ b/twing/vite-plugin-twig.mjs @@ -64,15 +64,22 @@ export const twigInputs = export const twigPlugin = () => ({ name: 'cloudfour-twig', + /** @param {string} id */ resolveId(id) { if (id === VIRTUAL_ID) return RESOLVED_VIRTUAL_ID; }, + /** @param {string} id */ load(id) { if (id === RESOLVED_VIRTUAL_ID) return environmentModule; }, - transform(code, id) { + /** + * @param {string} _code The module source, which we never read: the transform + * rewrites `.twig` imports into a render wrapper generated from the id alone. + * @param {string} id + */ + transform(_code, id) { // Let `?raw` imports through untouched -- the environment module needs the source. if (!id.endsWith('.twig')) return;