From 847f308f2f70091c19a646af99d07462116dde98 Mon Sep 17 00:00:00 2001 From: Jodi Warren Date: Thu, 29 May 2025 12:46:45 +0100 Subject: [PATCH] v3.1.5: Fix #75 Add feature scoping --- changelog.md | 5 +- index.d.ts | 31 ++++++++- index.js | 6 +- lib/css.helper.js | 6 +- package.json | 2 +- test/styles/app.modules.css | 19 ++++++ test/styles/deep/styles/hello.modules.css | 1 + test/test.js | 76 +++++++++++++++++++++-- 8 files changed, 134 insertions(+), 12 deletions(-) diff --git a/changelog.md b/changelog.md index 65cb837..5e6f065 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,7 @@ -## V3.1.2 +## V3.1.5 +- fix [issue#75](https://github.com/indooorsman/esbuild-css-modules-plugin/issues/75) + +- ## V3.1.2 - fix [issue#74](https://github.com/indooorsman/esbuild-css-modules-plugin/issues/74) ## V3.1.1 diff --git a/index.d.ts b/index.d.ts index a744c54..32e810c 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2,7 +2,6 @@ import type { Plugin, PluginBuild } from 'esbuild'; import type { BundleOptions, CustomAtRules, - TransformOptions, } from 'lightningcss'; declare type EmitDtsConfig = Partial< @@ -138,6 +137,36 @@ declare interface BuildOptions module?: string; version: string; }; + /** + * Enforces usage of one or more id or class selectors for each rule. + * + * If you enable this option, Lightning CSS will throw an error for CSS rules + * that don't have at least one id or class selector, like div. This is useful + * because selectors like div are not scoped and affects all elements on the + * page. + * + * https://lightningcss.dev/css-modules.html#pure-mode + */ + pure?: boolean; + /** + * Turn off feature scoping for animations; + * + * https://lightningcss.dev/css-modules.html#turning-off-feature-scoping + */ + animation?: boolean; + /** + * Turn off feature scoping for custom idents: + * https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident; + * + * https://lightningcss.dev/css-modules.html#turning-off-feature-scoping + */ + customIdents?: boolean; + /** + * Turn off feature scoping for grid areas; + * + * https://lightningcss.dev/css-modules.html#turning-off-feature-scoping + */ + grid?: boolean; } declare function CssModulesPlugin(options?: BuildOptions): Plugin; diff --git a/index.js b/index.js index 135d565..b63420f 100644 --- a/index.js +++ b/index.js @@ -112,7 +112,7 @@ export const setup = (build, _options) => { const rpath = relative(buildRoot, path); const prefix = basename(rpath, extname(path)) .replace(/[^a-zA-Z0-9]/g, '-') - .replace(/^\-*/, ''); + .replace(/^-*/, ''); const suffix = patchedBuild.context.packageVersion?.replace(/[^a-zA-Z0-9]/g, '') ?? ''; const buildResult = CSSTransformer.getInstance(patchedBuild).bundle(path, { @@ -320,7 +320,9 @@ export const setup = (build, _options) => { /** @type {[string, string][]} */ const filesToBuild = []; warnMetafile(); - const cssOutputsMap = Object.entries(r.metafile?.outputs ?? {}).reduce((m, [o, { inputs }]) => { + + const cssOutputsMap = Object.entries(r.metafile?.outputs ?? {}) + .reduce(/** @type {(m: Record, o: any) => Record} */(m, [o, {inputs}]) => { const keys = Object.keys(inputs); if (keys.length === 1 && new RegExp(`^${pluginCssNamespace}:.+\.css$`).test(keys[0])) { m[keys[0].replace(`${pluginCssNamespace}:`, '')] = o; diff --git a/lib/css.helper.js b/lib/css.helper.js index f527b34..0155672 100644 --- a/lib/css.helper.js +++ b/lib/css.helper.js @@ -271,7 +271,11 @@ ${uniqNames.map(([o, l]) => ` "${o}": ${l}`).join(',\n')} filename: fullpath, cssModules: { dashedIdents: options?.dashedIndents, - pattern: options?.pattern ?? `${opt?.prefix ?? ''}__[local]_[hash]__${opt?.suffix ?? ''}` + pattern: options?.pattern ?? `${opt?.prefix ?? ''}__[local]_[hash]__${opt?.suffix ?? ''}`, + pure: options?.pure ?? false, + animation: options?.animation ?? true, + customIdents: options?.customIdents ?? true, + grid: options?.grid ?? true, }, drafts: { customMedia: true, diff --git a/package.json b/package.json index be4e5ec..14acbb8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "esbuild-css-modules-plugin", - "version": "3.1.4", + "version": "3.1.5", "description": "A esbuild plugin to bundle css modules into js(x)/ts(x), based on extremely fast [Lightning CSS](https://lightningcss.dev/)", "main": "./index.cjs", "module": "./index.js", diff --git a/test/styles/app.modules.css b/test/styles/app.modules.css index bb58f38..5bfd9b1 100644 --- a/test/styles/app.modules.css +++ b/test/styles/app.modules.css @@ -3,10 +3,15 @@ .hello_world { color: red; background: url("../components/world.jpg"); + + animation: 3s linear slide-in; + display: grid; + grid-template: "image text"; } .hello-world img { display: inline-block; + grid-area: image; } .hello-world:hover { @@ -16,3 +21,17 @@ .some-other-selector { background-image: url('../components/world.jpg'); } + +/* Should throw an error when `pure: true` */ +div { + border-radius: 20px; +} + +@keyframes slide-in { + from { + margin-left: -20%; + } + to { + margin-left: 100%; + } +} \ No newline at end of file diff --git a/test/styles/deep/styles/hello.modules.css b/test/styles/deep/styles/hello.modules.css index 8e2daf0..faaa5d3 100644 --- a/test/styles/deep/styles/hello.modules.css +++ b/test/styles/deep/styles/hello.modules.css @@ -1,4 +1,5 @@ .hello-text { composes: bg-red from "../../base.modules.css"; color: grey; + grid-area: text; } \ No newline at end of file diff --git a/test/test.js b/test/test.js index f4cef63..3665c1e 100644 --- a/test/test.js +++ b/test/test.js @@ -19,7 +19,8 @@ import cssModulesPlugin from '../index.js'; plugins: [ cssModulesPlugin({ inject: '#my-custom-element-with-shadow-dom', - emitDeclarationFile: true + emitDeclarationFile: true, + pattern: "__[hash]_[local]" }) ], metafile: true, @@ -56,7 +57,8 @@ import cssModulesPlugin from '../index.js'; document.head.appendChild(styleEle); } `; - } + }, + pattern: "__[hash]_[local]" }) ], logLevel: 'debug' @@ -85,7 +87,9 @@ import cssModulesPlugin from '../index.js'; cssModulesPlugin({ inject: false, namedExports: true, - filter: /\.css$/i + filter: /\.css$/i, + pattern: "__[hash]_[local]" + }) ], logLevel: 'debug', @@ -112,7 +116,8 @@ import cssModulesPlugin from '../index.js'; cssModulesPlugin({ inject: false, namedExports: true, - emitDeclarationFile: true + emitDeclarationFile: true, + pattern: "__[hash]_[local]" }) ], logLevel: 'debug', @@ -144,7 +149,8 @@ import cssModulesPlugin from '../index.js'; }, force: true, forceInlineImages: true, - inject: '#my-styles-container' + inject: '#my-styles-container', + pattern: "__[hash]_[local]" }) ], logLevel: 'debug', @@ -169,7 +175,8 @@ import cssModulesPlugin from '../index.js'; }, plugins: [ cssModulesPlugin({ - inject: true + inject: true, + pattern: "__[hash]_[local]" }) ], logLevel: 'debug', @@ -177,6 +184,63 @@ import cssModulesPlugin from '../index.js'; }); console.log('[test][esbuild:bundle:splitting] done, please check `test/dist/bundle-splitting`', '\n'); + try { + // testing pure: true + await esbuild.build({ + entryPoints: ['app.jsx'], + entryNames: '[name]-[hash]', + format: 'esm', + target: ['esnext'], + bundle: true, + minify: false, + publicPath: 'https://my.domain/static/', + external: ['react', 'react-dom'], + outdir: './dist/pure', + write: true, + loader: { + '.jpg': 'file' + }, + plugins: [ + cssModulesPlugin({ + pure: true, + pattern: "__[hash]_[local]" + }) + ], + metafile: true, + logLevel: 'debug' + }); + } catch (error) { + console.log('Should result in " [ERROR] A selector in CSS modules should contain at least one class or ID selector [plugin esbuild-css-modules-plugin]"`', '\n'); + } + + // testing feature scoping + await esbuild.build({ + entryPoints: ['app.jsx'], + entryNames: '[name]-[hash]', + format: 'esm', + target: ['esnext'], + bundle: true, + minify: false, + publicPath: 'https://my.domain/static/', + external: ['react', 'react-dom'], + outdir: './dist/feature-scoping', + write: true, + loader: { + '.jpg': 'file' + }, + plugins: [ + cssModulesPlugin({ + animation: false, + customIdents: false, + grid: false, + pattern: "__[hash]_[local]" + }) + ], + metafile: true, + logLevel: 'debug' + }); + console.log('Should result in " [ERROR] A selector in CSS modules should contain at least one class or ID selector [plugin esbuild-css-modules-plugin]"`', '\n'); + // testing no metafile & write false const r = await esbuild.build({ ...buildOptions,