diff --git a/packages/core/integration-tests/test/css-modules.js b/packages/core/integration-tests/test/css-modules.js index 26ace5c1642..ce5c73a9f1a 100644 --- a/packages/core/integration-tests/test/css-modules.js +++ b/packages/core/integration-tests/test/css-modules.js @@ -621,4 +621,52 @@ describe('css modules', () => { ); assert(contents.includes('.index {')); }); + + it('should support global css modules via boolean config', async function () { + let b = await bundle( + path.join(__dirname, '/integration/css-modules-global/a/index.js'), + {mode: 'production'}, + ); + let res = await run(b); + assert.deepEqual(res, 'C-gzXq_foo'); + + let contents = await outputFS.readFile( + b.getBundles().find(b => b.type === 'css').filePath, + 'utf8', + ); + assert(contents.includes('.C-gzXq_foo')); + assert(contents.includes('.x')); + }); + + it('should support global css modules via object config', async function () { + let b = await bundle( + path.join(__dirname, '/integration/css-modules-global/b/index.js'), + {mode: 'production'}, + ); + let res = await run(b); + assert.deepEqual(res, 'C-gzXq_foo'); + let contents = await outputFS.readFile( + b.getBundles().find(b => b.type === 'css').filePath, + 'utf8', + ); + assert(contents.includes('.C-gzXq_foo')); + assert(contents.includes('.x')); + }); + + it('should optimize away unused variables when dashedIdents option is used', async function () { + let b = await bundle( + path.join(__dirname, '/integration/css-modules-vars/index.js'), + {mode: 'production'}, + ); + let contents = await outputFS.readFile( + b.getBundles().find(b => b.type === 'css').filePath, + 'utf8', + ); + assert.equal( + contents.split('\n')[0], + ':root{--wGsoEa_color:red;--wGsoEa_font:Helvetica;--wGsoEa_theme-sizes-1\\/12:2;--wGsoEa_from-js:purple}body{font:var(--wGsoEa_font)}._4fY2uG_foo{color:var(--wGsoEa_color);width:var(--wGsoEa_theme-sizes-1\\/12);height:var(--height)}', + ); + let res = await run(b); + assert.deepEqual(res, ['_4fY2uG_foo', '--wGsoEa_from-js']); + }); }); diff --git a/packages/core/integration-tests/test/integration/css-modules-global/a/index.css b/packages/core/integration-tests/test/integration/css-modules-global/a/index.css new file mode 100644 index 00000000000..a2c2b35f533 --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-modules-global/a/index.css @@ -0,0 +1,3 @@ +.foo { + color: red; +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/css-modules-global/a/index.js b/packages/core/integration-tests/test/integration/css-modules-global/a/index.js new file mode 100644 index 00000000000..916bf8772cf --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-modules-global/a/index.js @@ -0,0 +1,4 @@ +var foo = require('./index.css'); +require('test/index.css'); + +output = foo.foo; diff --git a/packages/core/integration-tests/test/integration/css-modules-global/a/node_modules/test/index.css b/packages/core/integration-tests/test/integration/css-modules-global/a/node_modules/test/index.css new file mode 100644 index 00000000000..4f9c1a611af --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-modules-global/a/node_modules/test/index.css @@ -0,0 +1,3 @@ +.x { + color: red; +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/css-modules-global/a/node_modules/test/package.json b/packages/core/integration-tests/test/integration/css-modules-global/a/node_modules/test/package.json new file mode 100644 index 00000000000..2f37dc5b093 --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-modules-global/a/node_modules/test/package.json @@ -0,0 +1,3 @@ +{ + "name": "test" +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/css-modules-global/a/package.json b/packages/core/integration-tests/test/integration/css-modules-global/a/package.json new file mode 100644 index 00000000000..c12320b6d91 --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-modules-global/a/package.json @@ -0,0 +1,5 @@ +{ + "@parcel/transformer-css": { + "cssModules": true + } +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/css-modules-global/a/yarn.lock b/packages/core/integration-tests/test/integration/css-modules-global/a/yarn.lock new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/integration/css-modules-global/b/index.css b/packages/core/integration-tests/test/integration/css-modules-global/b/index.css new file mode 100644 index 00000000000..a2c2b35f533 --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-modules-global/b/index.css @@ -0,0 +1,3 @@ +.foo { + color: red; +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/css-modules-global/b/index.js b/packages/core/integration-tests/test/integration/css-modules-global/b/index.js new file mode 100644 index 00000000000..916bf8772cf --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-modules-global/b/index.js @@ -0,0 +1,4 @@ +var foo = require('./index.css'); +require('test/index.css'); + +output = foo.foo; diff --git a/packages/core/integration-tests/test/integration/css-modules-global/b/node_modules/test/index.css b/packages/core/integration-tests/test/integration/css-modules-global/b/node_modules/test/index.css new file mode 100644 index 00000000000..4f9c1a611af --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-modules-global/b/node_modules/test/index.css @@ -0,0 +1,3 @@ +.x { + color: red; +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/css-modules-global/b/node_modules/test/package.json b/packages/core/integration-tests/test/integration/css-modules-global/b/node_modules/test/package.json new file mode 100644 index 00000000000..2f37dc5b093 --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-modules-global/b/node_modules/test/package.json @@ -0,0 +1,3 @@ +{ + "name": "test" +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/css-modules-global/b/package.json b/packages/core/integration-tests/test/integration/css-modules-global/b/package.json new file mode 100644 index 00000000000..ab90e8d5c06 --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-modules-global/b/package.json @@ -0,0 +1,7 @@ +{ + "@parcel/transformer-css": { + "cssModules": { + "global": true + } + } +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/css-modules-global/b/yarn.lock b/packages/core/integration-tests/test/integration/css-modules-global/b/yarn.lock new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/integration/css-modules-vars/index.js b/packages/core/integration-tests/test/integration/css-modules-vars/index.js new file mode 100644 index 00000000000..779f1867c39 --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-modules-vars/index.js @@ -0,0 +1,4 @@ +var foo = require('./index.module.css'); +var vars = require('./vars.module.css'); + +output = [foo.foo, vars['--from-js']]; diff --git a/packages/core/integration-tests/test/integration/css-modules-vars/index.module.css b/packages/core/integration-tests/test/integration/css-modules-vars/index.module.css new file mode 100644 index 00000000000..f5c58018001 --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-modules-vars/index.module.css @@ -0,0 +1,5 @@ +.foo { + color: var(--color from "./vars.module.css"); + width: var(--theme-sizes-1\/12 from "./vars.module.css"); + height: var(--height from global); +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/css-modules-vars/package.json b/packages/core/integration-tests/test/integration/css-modules-vars/package.json new file mode 100644 index 00000000000..0f8b6aff7f2 --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-modules-vars/package.json @@ -0,0 +1,7 @@ +{ + "@parcel/transformer-css": { + "cssModules": { + "dashedIdents": true + } + } +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/css-modules-vars/vars.module.css b/packages/core/integration-tests/test/integration/css-modules-vars/vars.module.css new file mode 100644 index 00000000000..eda222dcca6 --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-modules-vars/vars.module.css @@ -0,0 +1,11 @@ +:root { + --color: red; + --font: Helvetica; + --theme-sizes-1\/12: 2; + --from-js: purple; + --unused: green; +} + +body { + font: var(--font); +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/css-modules-vars/yarn.lock b/packages/core/integration-tests/test/integration/css-modules-vars/yarn.lock new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/optimizers/css/package.json b/packages/optimizers/css/package.json index 4e136b7cf84..d8a364537ab 100644 --- a/packages/optimizers/css/package.json +++ b/packages/optimizers/css/package.json @@ -20,7 +20,7 @@ "parcel": "^2.5.0" }, "dependencies": { - "@parcel/css": "^1.8.2", + "@parcel/css": "^1.9.0", "@parcel/diagnostic": "2.5.0", "@parcel/plugin": "2.5.0", "@parcel/source-map": "^2.0.0", @@ -28,4 +28,4 @@ "browserslist": "^4.6.6", "nullthrows": "^1.1.1" } -} +} \ No newline at end of file diff --git a/packages/packagers/css/src/CSSPackager.js b/packages/packagers/css/src/CSSPackager.js index ae2fbbef49b..c602e9044bf 100644 --- a/packages/packagers/css/src/CSSPackager.js +++ b/packages/packagers/css/src/CSSPackager.js @@ -75,6 +75,35 @@ export default (new Packager({ return Promise.all([ asset, asset.getCode().then((css: string) => { + // Replace CSS variable references with resolved symbols. + if (asset.meta.hasReferences) { + let replacements = new Map(); + for (let dep of asset.getDependencies()) { + for (let [exported, {local}] of dep.symbols) { + let resolved = bundleGraph.getResolvedAsset(dep, bundle); + if (resolved) { + let resolution = bundleGraph.getSymbolResolution( + resolved, + exported, + bundle, + ); + if (resolution.symbol) { + replacements.set(local, resolution.symbol); + } + } + } + } + if (replacements.size > 0) { + let regex = new RegExp( + [...replacements.keys()].join('|'), + 'g', + ); + css = css.replace(regex, m => + escapeDashedIdent(replacements.get(m) || m), + ); + } + } + if (media.length) { return `@media ${media.join(', ')} {\n${css}\n}\n`; } @@ -251,3 +280,29 @@ async function processCSSModule( return [asset, content, sourceMap?.toBuffer()]; } + +function escapeDashedIdent(name) { + // https://drafts.csswg.org/cssom/#serialize-an-identifier + let res = ''; + for (let c of name) { + let code = c.codePointAt(0); + if (code === 0) { + res += '\ufffd'; + } else if ((code >= 0x1 && code <= 0x1f) || code === 0x7f) { + res += '\\' + code.toString(16) + ' '; + } else if ( + (code >= 48 /* '0' */ && code <= 57) /* '9' */ || + (code >= 65 /* 'A' */ && code <= 90) /* 'Z' */ || + (code >= 97 /* 'a' */ && code <= 122) /* 'z' */ || + code === 95 /* '_' */ || + code === 45 /* '-' */ || + code & 128 // non-ascii + ) { + res += c; + } else { + res += '\\' + c; + } + } + + return res; +} diff --git a/packages/transformers/css/package.json b/packages/transformers/css/package.json index a0b689d6608..17ede2ba053 100644 --- a/packages/transformers/css/package.json +++ b/packages/transformers/css/package.json @@ -20,7 +20,7 @@ "parcel": "^2.5.0" }, "dependencies": { - "@parcel/css": "^1.8.2", + "@parcel/css": "^1.9.0", "@parcel/diagnostic": "2.5.0", "@parcel/plugin": "2.5.0", "@parcel/source-map": "^2.0.0", @@ -28,4 +28,4 @@ "browserslist": "^4.6.6", "nullthrows": "^1.1.1" } -} +} \ No newline at end of file diff --git a/packages/transformers/css/src/CSSTransformer.js b/packages/transformers/css/src/CSSTransformer.js index 1917bf2b298..ece80fccbf6 100644 --- a/packages/transformers/css/src/CSSTransformer.js +++ b/packages/transformers/css/src/CSSTransformer.js @@ -49,14 +49,30 @@ export default (new Transformer({ targets, }); } else { + let cssModules = false; + if ( + asset.meta.type !== 'tag' && + asset.meta.cssModulesCompiled == null + ) { + let cssModulesConfig = config?.cssModules; + if ( + (asset.isSource && + (typeof cssModulesConfig === 'boolean' || + cssModulesConfig?.global)) || + /\.module\./.test(asset.filePath) + ) { + if (cssModulesConfig?.dashedIdents && !asset.isSource) { + cssModulesConfig.dashedIdents = false; + } + + cssModules = cssModulesConfig ?? true; + } + } + res = transform({ filename: path.relative(options.projectRoot, asset.filePath), code, - cssModules: - asset.meta.type !== 'tag' && - (config?.cssModules ?? - (asset.meta.cssModulesCompiled == null && - /\.module\./.test(asset.filePath))), + cssModules, analyzeDependencies: asset.meta.hasDependencies !== false, sourceMap: !!asset.env.sourceMap, drafts: config?.drafts, @@ -212,6 +228,22 @@ export default (new Transformer({ } } + if (res.references != null) { + let references = res.references; + for (let symbol in references) { + let reference = references[symbol]; + asset.addDependency({ + specifier: reference.specifier, + specifierType: 'esm', + symbols: new Map([ + [reference.name, {local: symbol, isWeak: false, loc: null}], + ]), + }); + + asset.meta.hasReferences = true; + } + } + assets.push({ type: 'js', content: depjs + js, diff --git a/yarn.lock b/yarn.lock index e38c484da5e..7429601df3c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2056,61 +2056,61 @@ dependencies: "@octokit/openapi-types" "^6.2.0" -"@parcel/css-darwin-arm64@1.8.2": - version "1.8.2" - resolved "https://registry.yarnpkg.com/@parcel/css-darwin-arm64/-/css-darwin-arm64-1.8.2.tgz#ac26b249e9f2ade2b674f51b8b1684901cf79449" - integrity sha512-p5etxX3kPCuEQcipjqH9yc5j0x5/Yc++uB4MvG/sFbRgL2gI2zUuRo9sIgqA21boOP8lE4bQgz1ovPD/W1hj+Q== +"@parcel/css-darwin-arm64@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@parcel/css-darwin-arm64/-/css-darwin-arm64-1.9.0.tgz#5a020c604249180afcf69ce0f6978b807e2011b3" + integrity sha512-f/guZseS2tNKtKw94LgpNTItZqdVA0mnznqPsmQaR5lSB+cM3IPrSV8cgOOpAS7Vwo9ggxuJartToxBBN+dWSw== -"@parcel/css-darwin-x64@1.8.2": - version "1.8.2" - resolved "https://registry.yarnpkg.com/@parcel/css-darwin-x64/-/css-darwin-x64-1.8.2.tgz#cf976783475e93f3feea09404bb946181b12bef6" - integrity sha512-c3xi5DXRZYec5db4KPTxp69eHbomOuasgZNiuPPOi80k7jlOwfzCFQs0h6/KwWvTcJrKEFsLl8BKJU/aX7mETw== +"@parcel/css-darwin-x64@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@parcel/css-darwin-x64/-/css-darwin-x64-1.9.0.tgz#b808c042ba71592901f787b68f0f4677833ff971" + integrity sha512-4SpuwiM/4ayOgKflqSLd87XT7YwyC3wd2QuzOOkasjbe38UU+tot/87l2lQYEB538YinLdfwFQuFLDY0x9MxgA== -"@parcel/css-linux-arm-gnueabihf@1.8.2": - version "1.8.2" - resolved "https://registry.yarnpkg.com/@parcel/css-linux-arm-gnueabihf/-/css-linux-arm-gnueabihf-1.8.2.tgz#e87ce228a81a5147e4d10c4b0c041beb4b28ad2a" - integrity sha512-+ih3+mMpwbwtOjr/XW5pP0frsV1PMN+Qz7jCAM84h8xX+8UE/1IR0UVi3EPa8wQiIlcVcEwszQ1MV2UHacvo/A== +"@parcel/css-linux-arm-gnueabihf@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@parcel/css-linux-arm-gnueabihf/-/css-linux-arm-gnueabihf-1.9.0.tgz#d97457f821867a4937453baf15caac3aa83704b1" + integrity sha512-KxCyX5fFvX5636Y8LSXwCxXMtIncgP7Lkw8nLsqd24C5YqMokmuOtAcHb/vQ9zQG6YiUWTv0MybqDuL7dBDfVw== -"@parcel/css-linux-arm64-gnu@1.8.2": - version "1.8.2" - resolved "https://registry.yarnpkg.com/@parcel/css-linux-arm64-gnu/-/css-linux-arm64-gnu-1.8.2.tgz#bedae0a7ff3f52003a6c4df0b20b65bced9dcc3a" - integrity sha512-jIoyXbjJ1trUHXtyJhi3hlF1ck6xM4CDyaY5N6eN+3+ovkdw6wxog9IiheYJ1jf9ellYevLvTF5kiYE9MiP04A== +"@parcel/css-linux-arm64-gnu@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@parcel/css-linux-arm64-gnu/-/css-linux-arm64-gnu-1.9.0.tgz#7dd9c5d8d91354cce6f70e1a59bc3bbe2fa86296" + integrity sha512-wZ6Gsn6l+lSuvRdfWoyr7TdY24l29eGCD8QhXcqA1ALnFI7+KOTMBJ6aV3tjWUjMw3sg5qkosMHVqlWZzvrgXw== -"@parcel/css-linux-arm64-musl@1.8.2": - version "1.8.2" - resolved "https://registry.yarnpkg.com/@parcel/css-linux-arm64-musl/-/css-linux-arm64-musl-1.8.2.tgz#be3b912c0127745dda70757e18d7e7628118076d" - integrity sha512-QVTc5a+HatoywIei3djKYmp3s5dbI2Q3QaYZf3gqhyjOkeC7bm6j5eeNzFO+wa5xtga5jdHkIuTRrJ/wCojKKw== +"@parcel/css-linux-arm64-musl@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@parcel/css-linux-arm64-musl/-/css-linux-arm64-musl-1.9.0.tgz#5c26ae07c372d615c67a53b9664d5c9ba6196eb7" + integrity sha512-N6n5HhMzcNR5oXWr0Md91gKYtuDhqDlp+aGDb3VT21uSCNLOvijOUz248v/VaPoRno1BPFYlMxn0fYYTTReB3A== -"@parcel/css-linux-x64-gnu@1.8.2": - version "1.8.2" - resolved "https://registry.yarnpkg.com/@parcel/css-linux-x64-gnu/-/css-linux-x64-gnu-1.8.2.tgz#b38a07f461fd45657676326ee50ee5b6abccfd2b" - integrity sha512-9r2tSfa6i3ZQ3a6C9XufJWuTv3LB7JYzxzEqsI35SSA8D/DrfAHMaIhqog5wSxKZRWmQxckh2wdT96eIIGHSGA== +"@parcel/css-linux-x64-gnu@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@parcel/css-linux-x64-gnu/-/css-linux-x64-gnu-1.9.0.tgz#029e4588956c5151da9d5746364e11381d09abed" + integrity sha512-QufawDkaiOjsh6jcZk/dgDBPMqBtIs+LGTOgcJDM6XL4mcbDNxO6VkDANssRUgPnbG66YYy419CUWFta9aeVOg== -"@parcel/css-linux-x64-musl@1.8.2": - version "1.8.2" - resolved "https://registry.yarnpkg.com/@parcel/css-linux-x64-musl/-/css-linux-x64-musl-1.8.2.tgz#e7ce666f76943bfb73998e0b3908c2636f239f99" - integrity sha512-5SetLWkxXRQ3NU6QwwbGf9tOmGW2m1cGt07Moybbe4RCXOY6R5wAYUtauZUp7pD/fJlE9mHge4jnNHKpVO9pvw== +"@parcel/css-linux-x64-musl@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@parcel/css-linux-x64-musl/-/css-linux-x64-musl-1.9.0.tgz#bf8a21a3cdd17e3d21f00021a57e67b020037843" + integrity sha512-s528buicSd83/5M5DN31JqwefZ8tqx4Jm97srkLDVBCZg+XEe9P0bO7q1Ngz5ZVFqfwvv8OYLPOtAtBmEppG3g== -"@parcel/css-win32-x64-msvc@1.8.2": - version "1.8.2" - resolved "https://registry.yarnpkg.com/@parcel/css-win32-x64-msvc/-/css-win32-x64-msvc-1.8.2.tgz#cbef99d007d19511c1d6bd0b0124d988f180d343" - integrity sha512-/EdW5Ejlnkvc/AYrAi/FmLNvM6a6eAx+A4Y7oW+8JSMvk6bYa2zmXi7XLU/QOQuH2VQa/3gIIMA+sYjPndvDpw== +"@parcel/css-win32-x64-msvc@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@parcel/css-win32-x64-msvc/-/css-win32-x64-msvc-1.9.0.tgz#e0d729c510b0f41a526b32843348a995c41c030d" + integrity sha512-L4s84iK4PXnO/SzZyTsazAuzadtEYLGHgi1dyKYxMMGCjToCDjuwsn5K8bykeewZxjoL7RaunQGqCBRt5dfB5Q== -"@parcel/css@^1.8.2": - version "1.8.2" - resolved "https://registry.yarnpkg.com/@parcel/css/-/css-1.8.2.tgz#98647159c8f1c7ce23675cd3c742dacbd55f73f5" - integrity sha512-3vTyKHy2LnZ3YJEut+UQPVIxsaY/mdGk7cDXtmvH4xR48Pd6rYzChHCMl4Ru2DUkCBpr0KCQRPZTdYcsJhUmIA== +"@parcel/css@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@parcel/css/-/css-1.9.0.tgz#23d13d097a0897215d224a09ffa634c96b4d52c7" + integrity sha512-egCetUQ1H6pgYxOIxVQ8X/YT5e8G0R8eq6aVaUHrqnZ7A8cc6FYgknl9XRmoy2Xxo9h1htrbzdaEShQ5gROwvw== dependencies: detect-libc "^1.0.3" optionalDependencies: - "@parcel/css-darwin-arm64" "1.8.2" - "@parcel/css-darwin-x64" "1.8.2" - "@parcel/css-linux-arm-gnueabihf" "1.8.2" - "@parcel/css-linux-arm64-gnu" "1.8.2" - "@parcel/css-linux-arm64-musl" "1.8.2" - "@parcel/css-linux-x64-gnu" "1.8.2" - "@parcel/css-linux-x64-musl" "1.8.2" - "@parcel/css-win32-x64-msvc" "1.8.2" + "@parcel/css-darwin-arm64" "1.9.0" + "@parcel/css-darwin-x64" "1.9.0" + "@parcel/css-linux-arm-gnueabihf" "1.9.0" + "@parcel/css-linux-arm64-gnu" "1.9.0" + "@parcel/css-linux-arm64-musl" "1.9.0" + "@parcel/css-linux-x64-gnu" "1.9.0" + "@parcel/css-linux-x64-musl" "1.9.0" + "@parcel/css-win32-x64-msvc" "1.9.0" "@parcel/source-map@^2.0.0": version "2.0.0"