From 8af5b7427461506d08d582e6134428bd1602645b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20MANCA?= Date: Wed, 1 Jun 2022 16:04:24 +0200 Subject: [PATCH] feat(css): implement @layer and @import rules --- examples/css/README.md | 202 +++++++++++++++--- examples/css/index.html | 6 + examples/css/style.css | 21 +- examples/css/style3.css | 4 + examples/css/webpack.config.js | 8 + examples/css/webpack.lock | 6 + .../css_family_Open_Sans_6f42d275465c16986db8 | 7 + ...4dVJWUgsjZ0B4gaVc_f3d8ca9f27ed4eaad40e.ttf | Bin 0 -> 31380 bytes ...es_base_draggable_acb29bf51808e282c28e.css | 12 ++ lib/css/CssModulesPlugin.js | 9 + lib/css/CssParser.js | 90 +++++++- lib/css/walkCssTokens.js | 121 +++++++++++ .../CssImportDecoratorDependency.js | 112 ++++++++++ lib/dependencies/CssImportDependency.js | 23 +- lib/util/internalSerializables.js | 2 + test/configCases/css/_imports/index.js | 19 ++ .../css/_imports/style-imported1.css | 3 + .../css/_imports/style-imported2.css | 3 + .../css/_imports/style-imported3.css | 3 + test/configCases/css/_imports/style.css | 7 + .../css/_imports/style2-imported.css | 4 + test/configCases/css/_imports/style2.css | 19 ++ test/configCases/css/_imports/test.config.js | 8 + .../css/_imports/webpack.config.js | 8 + .../css/css-modules-in-node/index.js | 41 ++-- test/configCases/css/css-modules/index.js | 41 ++-- .../css/css-modules/style.module.css | 6 + test/configCases/css/css-modules/use-style.js | 4 +- test/walkCssTokens.unittest.js | 118 ++++++++++ 29 files changed, 830 insertions(+), 77 deletions(-) create mode 100644 examples/css/style3.css create mode 100644 examples/css/webpack.lock create mode 100644 examples/css/webpack.lock.data/https_fonts.googleapis.com/css_family_Open_Sans_6f42d275465c16986db8 create mode 100644 examples/css/webpack.lock.data/https_fonts.gstatic.com/s_opensans_v29_memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVc_f3d8ca9f27ed4eaad40e.ttf create mode 100644 examples/css/webpack.lock.data/https_unpkg.com/jquery-ui_1.13.1_themes_base_draggable_acb29bf51808e282c28e.css create mode 100644 lib/dependencies/CssImportDecoratorDependency.js create mode 100644 test/configCases/css/_imports/index.js create mode 100644 test/configCases/css/_imports/style-imported1.css create mode 100644 test/configCases/css/_imports/style-imported2.css create mode 100644 test/configCases/css/_imports/style-imported3.css create mode 100644 test/configCases/css/_imports/style.css create mode 100644 test/configCases/css/_imports/style2-imported.css create mode 100644 test/configCases/css/_imports/style2.css create mode 100644 test/configCases/css/_imports/test.config.js create mode 100644 test/configCases/css/_imports/webpack.config.js diff --git a/examples/css/README.md b/examples/css/README.md index 0d2411cb7ae..77e585c598b 100644 --- a/examples/css/README.md +++ b/examples/css/README.md @@ -13,12 +13,31 @@ document.getElementsByTagName("main")[0].className = main; ```javascript @import "style-imported.css"; -@import "https://fonts.googleapis.com/css?family=Open+Sans"; +@import "https://fonts.googleapis.com/css?family=Open+Sans" screen; +@import "https://unpkg.com/jquery-ui@1.13.1/themes/base/draggable.css" supports(touch-action: none); +@import url( "style3.css" ) layer( base ) supports( font-weight: bold ) screen and (min-width: 1024px); + +@layer base, special; body { background: green; font-family: "Open Sans"; } + +@layer special { + .item { + color: rebeccapurple; + } +} + +@layer base { + .item { + color: black; + border: 5px solid black; + font-size: 1.3em; + padding: .5em; + } +} ``` # dist/output.js @@ -39,6 +58,19 @@ body { module.exports = __webpack_require__.p + "89a353e9c515885abd8e.png"; +/***/ }), + +/***/ 5: +/*!****************************************************************************************************************!*\ + !*** https://fonts.gstatic.com/s/opensans/v29/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVc.ttf ***! + \****************************************************************************************************************/ +/*! default exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__.p, module, __webpack_require__.* */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +module.exports = __webpack_require__.p + "8b49cef9eef7a6b1c4cb.ttf"; + /***/ }) /******/ }); @@ -392,12 +424,12 @@ var __webpack_exports__ = {}; /*! runtime requirements: __webpack_require__, __webpack_require__.r, __webpack_exports__, __webpack_require__.e, __webpack_require__.* */ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _style_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./style.css */ 1); -/* harmony import */ var _style2_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./style2.css */ 5); -/* harmony import */ var _style_module_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./style.module.css */ 6); +/* harmony import */ var _style2_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./style2.css */ 8); +/* harmony import */ var _style_module_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./style.module.css */ 9); -__webpack_require__.e(/*! import() */ 1).then(__webpack_require__.bind(__webpack_require__, /*! ./lazy-style.css */ 7)); +__webpack_require__.e(/*! import() */ 1).then(__webpack_require__.bind(__webpack_require__, /*! ./lazy-style.css */ 10)); document.getElementsByTagName("main")[0].className = _style_module_css__WEBPACK_IMPORTED_MODULE_2__.main; @@ -410,7 +442,6 @@ document.getElementsByTagName("main")[0].className = _style_module_css__WEBPACK_ # dist/output.css ```javascript -@import url("https://fonts.googleapis.com/css?family=Open+Sans"); .img { width: 150px; height: 150px; @@ -418,43 +449,95 @@ document.getElementsByTagName("main")[0].className = _style_module_css__WEBPACK_ } +@media screen { + @font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-stretch: normal; + src: url(8b49cef9eef7a6b1c4cb.ttf) format('truetype'); + } +} + +@supports(touch-action: none) { + /*! + * jQuery UI Draggable 1.13.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + .ui-draggable-handle { + -ms-touch-action: none; + touch-action: none; + } +} + +@layer base { + @supports(font-weight: bold) { + @media screen and (min-width: 1024px) { + body { + font-weight: bold; + text-decoration: underline; + } + } + } +} + +@layer base, special; + body { background: green; font-family: "Open Sans"; } +@layer special { + .item { + color: rebeccapurple; + } +} + +@layer base { + .item { + color: black; + border: 5px solid black; + font-size: 1.3em; + padding: .5em; + } +} + body { background: red; } :root { - --app-6-large: 72px; + --app-9-large: 72px; } -.app-6-main { - font-size: var(--app-6-large); +.app-9-main { + font-size: var(--app-9-large); color: darkblue; } @media (min-width: 1024px) { - .app-6-main { + .app-9-main { color: green; } } @supports (display: grid) { - .app-6-main { + .app-9-main { display: grid } } -head{--webpack-app-0:_4,_2,_1,_5,large%main/_6;} +head{--webpack-app-0:_2,_4,_6,_7,_1,_8,large%main/_9;} ``` ## production ```javascript -@import url("https://fonts.googleapis.com/css?family=Open+Sans"); .img { width: 150px; height: 150px; @@ -462,11 +545,64 @@ head{--webpack-app-0:_4,_2,_1,_5,large%main/_6;} } +@media screen { + @font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-stretch: normal; + src: url(8b49cef9eef7a6b1c4cb.ttf) format('truetype'); + } +} + +@supports(touch-action: none) { + /*! + * jQuery UI Draggable 1.13.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + .ui-draggable-handle { + -ms-touch-action: none; + touch-action: none; + } +} + +@layer base { + @supports(font-weight: bold) { + @media screen and (min-width: 1024px) { + body { + font-weight: bold; + text-decoration: underline; + } + } + } +} + +@layer base, special; + body { background: green; font-family: "Open Sans"; } +@layer special { + .item { + color: rebeccapurple; + } +} + +@layer base { + .item { + color: black; + border: 5px solid black; + font-size: 1.3em; + padding: .5em; + } +} + body { background: red; } @@ -492,7 +628,7 @@ body { } } -head{--webpack-app-179:_548,_431,_258,_268,b%D/_491;} +head{--webpack-app-179:_431,_572,_863,_252,_258,_268,b%D/_491;} ``` # dist/1.output.css @@ -502,7 +638,7 @@ body { color: blue; } -head{--webpack-app-1:_7;} +head{--webpack-app-1:_10;} ``` # Info @@ -510,16 +646,18 @@ head{--webpack-app-1:_7;} ## Unoptimized ``` -assets by chunk 17 KiB (name: main) - asset output.js 16.5 KiB [emitted] (name: main) - asset output.css 516 bytes [emitted] (name: main) -asset 89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: images/file.png] (auxiliary name: main) -asset 1.output.css 49 bytes [emitted] -Entrypoint main 17 KiB (14.6 KiB) = output.js 16.5 KiB output.css 516 bytes 1 auxiliary asset -chunk (runtime: main) output.js, output.css (main) 218 bytes (javascript) 454 bytes (css) 14.6 KiB (asset) 42 bytes (css-import) 10 KiB (runtime) [entry] [rendered] +assets by info 45.2 KiB [immutable] + asset 8b49cef9eef7a6b1c4cb.ttf 30.6 KiB [emitted] [immutable] [from: https://fonts.gstatic.com/s/opensans/v29/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVc.ttf] (auxiliary name: main) + asset 89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: images/file.png] (auxiliary name: main) +assets by chunk 18.4 KiB (name: main) + asset output.js 17.1 KiB [emitted] (name: main) + asset output.css 1.29 KiB [emitted] (name: main) +asset 1.output.css 50 bytes [emitted] +Entrypoint main 18.4 KiB (45.2 KiB) = output.js 17.1 KiB output.css 1.29 KiB 2 auxiliary assets +chunk (runtime: main) output.js, output.css (main) 260 bytes (javascript) 1.39 KiB (css) 45.2 KiB (asset) 10 KiB (runtime) [entry] [rendered] > ./example.js main + dependent modules 84 bytes (javascript) 45.2 KiB (asset) 1.39 KiB (css) [dependent] 9 modules runtime modules 10 KiB 9 modules - dependent modules 42 bytes (javascript) 14.6 KiB (asset) 454 bytes (css) 42 bytes (css-import) [dependent] 6 modules ./example.js 176 bytes [built] [code generated] [no exports] [used exports unknown] @@ -530,30 +668,32 @@ chunk (runtime: main) 1.output.css 23 bytes [no exports] [used exports unknown] import() ./lazy-style.css ./example.js 4:0-26 -webpack 5.78.0 compiled successfully +webpack 5.79.0 compiled successfully ``` ## Production mode ``` -assets by chunk 4.38 KiB (name: main) - asset output.js 3.88 KiB [emitted] [minimized] (name: main) - asset output.css 514 bytes [emitted] (name: main) -asset 89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: images/file.png] (auxiliary name: main) +assets by info 45.2 KiB [immutable] + asset 8b49cef9eef7a6b1c4cb.ttf 30.6 KiB [emitted] [immutable] [from: https://fonts.gstatic.com/s/opensans/v29/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVc.ttf] (auxiliary name: main) + asset 89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: images/file.png] (auxiliary name: main) +assets by chunk 5.22 KiB (name: main) + asset output.js 3.94 KiB [emitted] [minimized] (name: main) + asset output.css 1.29 KiB [emitted] (name: main) asset 159.output.css 53 bytes [emitted] -Entrypoint main 4.38 KiB (14.6 KiB) = output.js 3.88 KiB output.css 514 bytes 1 auxiliary asset +Entrypoint main 5.22 KiB (45.2 KiB) = output.js 3.94 KiB output.css 1.29 KiB 2 auxiliary assets chunk (runtime: main) 159.output.css 23 bytes > ./lazy-style.css ./example.js 4:0-26 ./lazy-style.css 23 bytes [built] [code generated] [no exports] import() ./lazy-style.css ./example.js 4:0-26 -chunk (runtime: main) output.js, output.css (main) 218 bytes (javascript) 454 bytes (css) 14.6 KiB (asset) 42 bytes (css-import) 10 KiB (runtime) [entry] [rendered] +chunk (runtime: main) output.js, output.css (main) 260 bytes (javascript) 1.39 KiB (css) 45.2 KiB (asset) 10 KiB (runtime) [entry] [rendered] > ./example.js main + dependent modules 84 bytes (javascript) 45.2 KiB (asset) 1.39 KiB (css) [dependent] 9 modules runtime modules 10 KiB 9 modules - dependent modules 42 bytes (javascript) 14.6 KiB (asset) 454 bytes (css) 42 bytes (css-import) [dependent] 6 modules ./example.js 176 bytes [built] [code generated] [no exports] [no exports used] entry ./example.js main -webpack 5.78.0 compiled successfully +webpack 5.79.0 compiled successfully ``` diff --git a/examples/css/index.html b/examples/css/index.html index 9b3f06397ab..2212111ec25 100644 --- a/examples/css/index.html +++ b/examples/css/index.html @@ -5,6 +5,12 @@
Hello World

+
+ I am displayed in color: rebeccapurple because the + special layer comes after the base layer. My + black border, font-size, and padding come from the + base layer. +
diff --git a/examples/css/style.css b/examples/css/style.css index 8b855420284..b0e9e3d26e8 100644 --- a/examples/css/style.css +++ b/examples/css/style.css @@ -1,7 +1,26 @@ @import "style-imported.css"; -@import "https://fonts.googleapis.com/css?family=Open+Sans"; +@import "https://fonts.googleapis.com/css?family=Open+Sans" screen; +@import "https://unpkg.com/jquery-ui@1.13.1/themes/base/draggable.css" supports(touch-action: none); +@import url( "style3.css" ) layer( base ) supports( font-weight: bold ) screen and (min-width: 1024px); + +@layer base, special; body { background: green; font-family: "Open Sans"; } + +@layer special { + .item { + color: rebeccapurple; + } +} + +@layer base { + .item { + color: black; + border: 5px solid black; + font-size: 1.3em; + padding: .5em; + } +} diff --git a/examples/css/style3.css b/examples/css/style3.css new file mode 100644 index 00000000000..cebb7af0436 --- /dev/null +++ b/examples/css/style3.css @@ -0,0 +1,4 @@ +body { + font-weight: bold; + text-decoration: underline; +} diff --git a/examples/css/webpack.config.js b/examples/css/webpack.config.js index 93ef7f910e6..e1ad7704678 100644 --- a/examples/css/webpack.config.js +++ b/examples/css/webpack.config.js @@ -3,6 +3,14 @@ module.exports = { uniqueName: "app" }, experiments: { + buildHttp: { + allowedUris: [ + "https://fonts.googleapis.com", + "https://fonts.gstatic.com", + "https://unpkg.com" + ], + frozen: false + }, css: true } }; diff --git a/examples/css/webpack.lock b/examples/css/webpack.lock new file mode 100644 index 00000000000..579dc3820a5 --- /dev/null +++ b/examples/css/webpack.lock @@ -0,0 +1,6 @@ +{ + "https://fonts.googleapis.com/css?family=Open+Sans": { "integrity": "sha512-N58gP8WenQgxVpqofshprDTqvAh3tZ/OkhNgKd2uCcRAk6LXLVB8echZKWx78ETx6P9+C1KJfDYMuy9zpBYAfg==", "contentType": "text/css; charset=utf-8" }, + "https://fonts.gstatic.com/s/opensans/v29/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVc.ttf": { "integrity": "sha512-PUea/AZwAh372fk4PYolSDpxSWNtgsJ0bzdL6LBVIxn+sRMONrSs3Po7qVk8veWuuJwZq0TZePsXUblXAZCz9w==", "contentType": "font/ttf" }, + "https://unpkg.com/jquery-ui@1.13.1/themes/base/draggable.css": { "integrity": "sha512-ID6NYyrTEGxfWaVutJAR/C+668CmUVWh8htwDW3NuBfdFpezO0ANTzD4Hl2G7HO7BrK1B+OwzauU0vG2f6gSCw==", "contentType": "text/css; charset=utf-8" }, + "version": 1 +} diff --git a/examples/css/webpack.lock.data/https_fonts.googleapis.com/css_family_Open_Sans_6f42d275465c16986db8 b/examples/css/webpack.lock.data/https_fonts.googleapis.com/css_family_Open_Sans_6f42d275465c16986db8 new file mode 100644 index 00000000000..ff64d990c5c --- /dev/null +++ b/examples/css/webpack.lock.data/https_fonts.googleapis.com/css_family_Open_Sans_6f42d275465c16986db8 @@ -0,0 +1,7 @@ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-stretch: normal; + src: url(https://fonts.gstatic.com/s/opensans/v29/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVc.ttf) format('truetype'); +} diff --git a/examples/css/webpack.lock.data/https_fonts.gstatic.com/s_opensans_v29_memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVc_f3d8ca9f27ed4eaad40e.ttf b/examples/css/webpack.lock.data/https_fonts.gstatic.com/s_opensans_v29_memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVc_f3d8ca9f27ed4eaad40e.ttf new file mode 100644 index 0000000000000000000000000000000000000000..913cf11e7548fe5272f78600ed7d717a6130475e GIT binary patch literal 31380 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 { switch (mode) { @@ -88,8 +88,10 @@ const explainMode = mode => { return "parsing css rule content (local)"; case CSS_MODE_AT_IMPORT_EXPECT_URL: return "parsing @import (expecting url)"; + case CSS_MODE_AT_IMPORT_EXPECT_LAYER: + return "parsing @import (expecting optionally layer)"; case CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS: - return "parsing @import (expecting optionally supports or media query)"; + return "parsing @import (expecting optionally supports)"; case CSS_MODE_AT_IMPORT_EXPECT_MEDIA: return "parsing @import (expecting optionally media query)"; case CSS_MODE_AT_OTHER: @@ -309,11 +311,13 @@ class CssParser extends Parser { switch (mode) { case CSS_MODE_AT_IMPORT_EXPECT_URL: { modeData.url = value; - mode = CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS; + modePos = end; + mode = CSS_MODE_AT_IMPORT_EXPECT_LAYER; break; } - case CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS: + case CSS_MODE_AT_IMPORT_EXPECT_LAYER: case CSS_MODE_AT_IMPORT_EXPECT_MEDIA: + case CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS: throw new Error( `Unexpected ${input.slice( start, @@ -332,11 +336,57 @@ class CssParser extends Parser { } return end; }, + layer: (input, start, end, contentStart, contentEnd) => { + const value = cssUnescape(input.slice(contentStart, contentEnd)); + switch (mode) { + case CSS_MODE_AT_IMPORT_EXPECT_LAYER: { + modeData.layer = value.trim(); + modePos = end; + mode = CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS; + break; + } + case CSS_MODE_AT_IMPORT_EXPECT_URL: + case CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS: + case CSS_MODE_AT_IMPORT_EXPECT_MEDIA: + throw new Error( + `Unexpected ${input.slice( + start, + end + )} at ${start} during ${explainMode(mode)}` + ); + } + return end; + }, + supports: (input, start, end, contentStart, contentEnd) => { + const value = cssUnescape(input.slice(contentStart, contentEnd)); + switch (mode) { + case CSS_MODE_AT_IMPORT_EXPECT_LAYER: + case CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS: { + modeData.supports = value.trim(); + modePos = end; + mode = CSS_MODE_AT_IMPORT_EXPECT_MEDIA; + break; + } + case CSS_MODE_AT_IMPORT_EXPECT_MEDIA: + case CSS_MODE_AT_IMPORT_EXPECT_URL: + throw new Error( + `Unexpected ${input.slice( + start, + end + )} at ${start} during ${explainMode(mode)}` + ); + } + return end; + }, string: (input, start, end) => { switch (mode) { case CSS_MODE_AT_IMPORT_EXPECT_URL: { modeData.url = cssUnescape(input.slice(start + 1, end - 1)); - mode = CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS; + modePos = end; + mode = CSS_MODE_AT_IMPORT_EXPECT_LAYER; + break; + } + default: { break; } } @@ -358,7 +408,9 @@ class CssParser extends Parser { modeData = { start: start, url: undefined, - supports: undefined + layer: undefined, + supports: undefined, + media: undefined }; } if (name === "@keyframes") { @@ -394,12 +446,28 @@ class CssParser extends Parser { } return pos + 1; } + if (name === "@layer") { + let pos = end; + const [newPos] = eatText(input, pos, eatAtRuleNested); + pos = newPos; + if (pos === input.length) return pos; + if ( + input.charCodeAt(pos) !== CC_LEFT_CURLY && + input.charCodeAt(pos) !== CC_SEMICOLON + ) { + throw new Error( + `Unexpected ${input[pos]} at ${pos} during parsing of @layer (expected '{' or ';')` + ); + } + return pos + 1; + } return end; }, semicolon: (input, start, end) => { switch (mode) { case CSS_MODE_AT_IMPORT_EXPECT_URL: throw new Error(`Expected URL for @import at ${start}`); + case CSS_MODE_AT_IMPORT_EXPECT_LAYER: case CSS_MODE_AT_IMPORT_EXPECT_MEDIA: case CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS: { const { line: sl, column: sc } = locConverter.get(modeData.start); @@ -409,6 +477,7 @@ class CssParser extends Parser { const dep = new CssImportDependency( modeData.url, [modeData.start, end], + modeData.layer, modeData.supports, media ); @@ -488,6 +557,9 @@ class CssParser extends Parser { lastIdentifier = [start, end]; } break; + case CSS_MODE_AT_IMPORT_EXPECT_MEDIA: + modeData.mediaStart = start - 1; + break; } return end; }, diff --git a/lib/css/walkCssTokens.js b/lib/css/walkCssTokens.js index 26276b10a43..ae8ca8b31b7 100644 --- a/lib/css/walkCssTokens.js +++ b/lib/css/walkCssTokens.js @@ -9,6 +9,8 @@ * @typedef {Object} CssTokenCallbacks * @property {function(string, number): boolean} isSelector * @property {function(string, number, number, number, number): number=} url + * @property {function(string, number, number, number, number): number=} supports + * @property {function(string, number, number, number, number): number=} layer * @property {function(string, number, number): number=} string * @property {function(string, number, number): number=} leftParenthesis * @property {function(string, number, number): number=} rightParenthesis @@ -57,6 +59,8 @@ const CC_AT_SIGN = "@".charCodeAt(0); const CC_LOW_LINE = "_".charCodeAt(0); const CC_LOWER_A = "a".charCodeAt(0); +const CC_LOWER_L = "l".charCodeAt(0); +const CC_LOWER_S = "s".charCodeAt(0); const CC_LOWER_U = "u".charCodeAt(0); const CC_LOWER_E = "e".charCodeAt(0); const CC_LOWER_Z = "z".charCodeAt(0); @@ -361,6 +365,119 @@ const consumePotentialUrl = (input, pos, callbacks) => { } }; +/** @type {CharHandler} */ +const consumePotentialLayer = (input, pos, callbacks) => { + const start = pos; + pos = _consumeIdentifier(input, pos); + if (pos === start + 5 && input.slice(start, pos + 1) === "layer(") { + pos++; + let cc = input.charCodeAt(pos); + while (_isWhiteSpace(cc)) { + pos++; + if (pos === input.length) return pos; + cc = input.charCodeAt(pos); + } + const contentStart = pos; + let contentEnd; + for (;;) { + while (_isWhiteSpace(cc)) { + pos++; + if (pos === input.length) return pos; + cc = input.charCodeAt(pos); + } + if (cc === CC_RIGHT_PARENTHESIS) { + contentEnd = pos; + let previousPos = pos - 1; + let previousCc = input.charCodeAt(previousPos); + while (_isWhiteSpace(previousCc)) { + contentEnd -= 1; + previousPos -= 1; + previousCc = input.charCodeAt(previousPos); + } + pos++; + if (callbacks.layer !== undefined) { + return callbacks.layer(input, start, pos, contentStart, contentEnd); + } + return pos; + } else if (cc === CC_LEFT_PARENTHESIS) { + return pos; + } else { + pos++; + } + if (pos === input.length) return pos; + cc = input.charCodeAt(pos); + } + } else { + if (callbacks.identifier !== undefined) { + return callbacks.identifier(input, start, pos); + } + return pos; + } +}; + +/** @type {CharHandler} */ +const consumePotentialSupports = (input, pos, callbacks) => { + const start = pos; + let level = 0; + pos = _consumeIdentifier(input, pos); + if (pos === start + 8 && input.slice(start, pos + 1) === "supports(") { + pos++; + let cc = input.charCodeAt(pos); + while (_isWhiteSpace(cc)) { + pos++; + if (pos === input.length) return pos; + cc = input.charCodeAt(pos); + } + const contentStart = pos; + let contentEnd; + for (;;) { + while (_isWhiteSpace(cc)) { + pos++; + if (pos === input.length) return pos; + cc = input.charCodeAt(pos); + } + if (cc === CC_RIGHT_PARENTHESIS) { + if (level > 0) { + level -= 1; + pos++; + } else { + contentEnd = pos; + let previousPos = pos - 1; + let previousCc = input.charCodeAt(previousPos); + while (_isWhiteSpace(previousCc)) { + contentEnd -= 1; + previousPos -= 1; + previousCc = input.charCodeAt(previousPos); + } + pos++; + if (callbacks.supports !== undefined) { + return callbacks.supports( + input, + start, + pos, + contentStart, + contentEnd + ); + } + return pos; + } + } else if (cc === CC_LEFT_PARENTHESIS) { + level += 1; + pos++; + } else { + pos++; + } + if (pos === input.length) return pos; + cc = input.charCodeAt(pos); + } + } else { + if (callbacks.identifier !== undefined) { + return callbacks.identifier(input, start, pos); + } + return pos; + } +}; + /** @type {CharHandler} */ const consumePotentialPseudo = (input, pos, callbacks) => { const start = pos; @@ -572,6 +689,10 @@ const CHAR_MAP = Array.from({ length: 0x80 }, (_, cc) => { return consumeLessThan; case CC_AT_SIGN: return consumeAt; + case CC_LOWER_L: + return consumePotentialLayer; + case CC_LOWER_S: + return consumePotentialSupports; case CC_LOWER_U: case CC_UPPER_U: return consumePotentialUrl; diff --git a/lib/dependencies/CssImportDecoratorDependency.js b/lib/dependencies/CssImportDecoratorDependency.js new file mode 100644 index 00000000000..2ace0e50dea --- /dev/null +++ b/lib/dependencies/CssImportDecoratorDependency.js @@ -0,0 +1,112 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const Template = require("../Template"); +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class CssImportDecoratorDependency extends NullDependency { + /** + * @param {string[][]} decorations The decorations to wrap the source + */ + constructor(decorations) { + super(); + this.decorations = decorations; + } + + get type() { + return "css @import decorator"; + } +} + +/** + * @param {string | undefined} layer The layer query to parse + * @param {string | undefined} supports The supports query to parse + * @param {string | undefined} media The media query to parse + * @returns {string[][]} The decorations to wrap the source + */ +const getDecorator = (layer, supports, media) => { + const decorations = []; + + if (layer) { + decorations.push([`@layer ${layer} {`, `}`]); + } + + if (supports) { + decorations.push([`@supports(${supports}) {`, `}`]); + } + + if (media) { + decorations.push([`@media ${media} {`, `}`]); + } + + return decorations; +}; + +CssImportDecoratorDependency.Template = class CssImportDecoratorDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @param {Dependency} dependency the dependency for which the template should be applied + * @param {ReplaceSource} source the current replace source which can be modified + * @param {DependencyTemplateContext} templateContext the context object + * @returns {void} + */ + apply(dependency, source, templateContext) { + const dep = /** @type {CssImportDecoratorDependency} */ (dependency); + const { decorations } = dep; + const replacedSource = `${source.source()}`; + + // We use the replaced source and reset the replacements as a patch for to replace all the code + // @ts-expect-error + source._replacements = []; + + source.replace( + 0, + source.size(), + ` +${decorations + .map(([before, after], idx) => { + return Array(idx) + .fill() + .reduce(tpl => Template.indent(tpl), before); + }) + .join("\n")} +${Array(decorations.length) + .fill() + .reduce(tpl => Template.indent(tpl), replacedSource)} +${decorations + .map(([before, after], idx) => { + return Array(decorations.length - 1 - idx) + .fill() + .reduce(tpl => Template.indent(tpl), after); + }) + .join("\n")}` + ); + } +}; + +makeSerializable( + CssImportDecoratorDependency, + "webpack/lib/dependencies/CssImportDecoratorDependency" +); + +module.exports = CssImportDecoratorDependency; +module.exports.getDecorator = getDecorator; diff --git a/lib/dependencies/CssImportDependency.js b/lib/dependencies/CssImportDependency.js index 8f02d6e1fc3..97eb002413a 100644 --- a/lib/dependencies/CssImportDependency.js +++ b/lib/dependencies/CssImportDependency.js @@ -6,6 +6,7 @@ "use strict"; const makeSerializable = require("../util/makeSerializable"); +const CssImportDecoratorDependency = require("./CssImportDecoratorDependency"); const ModuleDependency = require("./ModuleDependency"); /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ @@ -17,6 +18,7 @@ const ModuleDependency = require("./ModuleDependency"); /** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ /** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../NormalModule")} NormalModule */ /** @typedef {import("../util/Hash")} Hash */ /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ @@ -24,12 +26,14 @@ class CssImportDependency extends ModuleDependency { /** * @param {string} request request * @param {[number, number]} range range of the argument + * @param {string | undefined} layer layer rule * @param {string | undefined} supports list of supports conditions * @param {string | undefined} media list of media conditions */ - constructor(request, range, supports, media) { + constructor(request, range, layer, supports, media) { super(request); this.range = range; + this.layer = layer; this.supports = supports; this.media = media; } @@ -64,6 +68,23 @@ CssImportDependency.Template = class CssImportDependencyTemplate extends ( const dep = /** @type {CssImportDependency} */ (dependency); source.replace(dep.range[0], dep.range[1] - 1, ""); + + if (!dep.layer && !dep.media && !dep.supports) { + return; + } + + const dependencyModule = /** @type {NormalModule} */ ( + templateContext.moduleGraph.getModule(dep) + ); + + const decorations = CssImportDecoratorDependency.getDecorator( + dep.layer, + dep.supports, + dep.media + ); + const decoratorDep = new CssImportDecoratorDependency(decorations); + + dependencyModule.addDependency(decoratorDep); } }; diff --git a/lib/util/internalSerializables.js b/lib/util/internalSerializables.js index 4fe124cdb3a..f1d58101287 100644 --- a/lib/util/internalSerializables.js +++ b/lib/util/internalSerializables.js @@ -67,6 +67,8 @@ module.exports = { require("../dependencies/ContextElementDependency"), "dependencies/CriticalDependencyWarning": () => require("../dependencies/CriticalDependencyWarning"), + "dependencies/CssImportDecoratorDependency": () => + require("../dependencies/CssImportDecoratorDependency"), "dependencies/CssImportDependency": () => require("../dependencies/CssImportDependency"), "dependencies/CssLocalIdentifierDependency": () => diff --git a/test/configCases/css/_imports/index.js b/test/configCases/css/_imports/index.js new file mode 100644 index 00000000000..609b5d39199 --- /dev/null +++ b/test/configCases/css/_imports/index.js @@ -0,0 +1,19 @@ +import * as style from "./style.css"; + +/** + * This test is not working due to missing support of @media, @supports and + * @layer in JSDOM (which relies on CSSOM). + **/ +it("should compile at import rules", done => { + expect(style).toEqual(nsObj({})); + import("./style2.css").then(x => { + expect(x).toEqual(nsObj({})); + const style = getComputedStyle(document.body); + expect(style.getPropertyValue("background")).toBe(" orange"); + expect(style.getPropertyValue("display")).toBe(" flex"); + expect(style.getPropertyValue("font-size")).toBe(" 32px"); + expect(style.getPropertyValue("color")).toBe(" orange"); + expect(style.getPropertyValue("padding")).toBe(" 20px 10px"); + done(); + }, done); +}); diff --git a/test/configCases/css/_imports/style-imported1.css b/test/configCases/css/_imports/style-imported1.css new file mode 100644 index 00000000000..054919226e5 --- /dev/null +++ b/test/configCases/css/_imports/style-imported1.css @@ -0,0 +1,3 @@ +body { + background: orange; +} diff --git a/test/configCases/css/_imports/style-imported2.css b/test/configCases/css/_imports/style-imported2.css new file mode 100644 index 00000000000..3ad32ef667b --- /dev/null +++ b/test/configCases/css/_imports/style-imported2.css @@ -0,0 +1,3 @@ +body { + display: flex; +} diff --git a/test/configCases/css/_imports/style-imported3.css b/test/configCases/css/_imports/style-imported3.css new file mode 100644 index 00000000000..72503cac070 --- /dev/null +++ b/test/configCases/css/_imports/style-imported3.css @@ -0,0 +1,3 @@ +body { + font-size: 32px; +} diff --git a/test/configCases/css/_imports/style.css b/test/configCases/css/_imports/style.css new file mode 100644 index 00000000000..f2866193a8d --- /dev/null +++ b/test/configCases/css/_imports/style.css @@ -0,0 +1,7 @@ +@import "style-imported1.css" screen; +@import "style-imported2.css" supports(display: flex); +@import "style-imported3.css" supports(display: block) screen; + +body { + border: 1px solid red; +} diff --git a/test/configCases/css/_imports/style2-imported.css b/test/configCases/css/_imports/style2-imported.css new file mode 100644 index 00000000000..d7d70f7ffae --- /dev/null +++ b/test/configCases/css/_imports/style2-imported.css @@ -0,0 +1,4 @@ +body { + color: orange; + padding: 20px 10px; +} diff --git a/test/configCases/css/_imports/style2.css b/test/configCases/css/_imports/style2.css new file mode 100644 index 00000000000..3deb47485c4 --- /dev/null +++ b/test/configCases/css/_imports/style2.css @@ -0,0 +1,19 @@ +@import "./style2-imported.css" layer(special); + +body { + color: green; +} + +@layer base, special; + +@layer special { + body { + text-decoration: underline; + } +} + +@layer base { + body { + color: green; + } +} diff --git a/test/configCases/css/_imports/test.config.js b/test/configCases/css/_imports/test.config.js new file mode 100644 index 00000000000..0590757288f --- /dev/null +++ b/test/configCases/css/_imports/test.config.js @@ -0,0 +1,8 @@ +module.exports = { + moduleScope(scope) { + const link = scope.window.document.createElement("link"); + link.rel = "stylesheet"; + link.href = "bundle0.css"; + scope.window.document.head.appendChild(link); + } +}; diff --git a/test/configCases/css/_imports/webpack.config.js b/test/configCases/css/_imports/webpack.config.js new file mode 100644 index 00000000000..cfb8e5c0346 --- /dev/null +++ b/test/configCases/css/_imports/webpack.config.js @@ -0,0 +1,8 @@ +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + mode: "development", + experiments: { + css: true + } +}; diff --git a/test/configCases/css/css-modules-in-node/index.js b/test/configCases/css/css-modules-in-node/index.js index ffebb43946a..86a9c068f4a 100644 --- a/test/configCases/css/css-modules-in-node/index.js +++ b/test/configCases/css/css-modules-in-node/index.js @@ -6,14 +6,26 @@ it("should allow to create css modules", done => { expect(x).toEqual({ global: undefined, class: prod ? "my-app-491-S" : "./style.module.css-class", - currentWmultiParams: prod ? "my-app-491-yK" : "./style.module.css-local12", - futureWmultiParams: prod ? "my-app-491-Y4" : "./style.module.css-local14", - hasWmultiParams: prod ? "my-app-491-PK" : "./style.module.css-local11", - matchesWmultiParams: prod ? "my-app-491-$Y" : "./style.module.css-local9", - mozAnyWmultiParams: prod ? "my-app-491-TT" : "./style.module.css-local15", - pastWmultiParams: prod ? "my-app-491-P_" : "./style.module.css-local13", - webkitAnyWmultiParams: prod ? "my-app-491-rT" : "./style.module.css-local16", - whereWmultiParams: prod ? "my-app-491-ie" : "./style.module.css-local10", + currentWmultiParams: prod + ? "my-app-491-yK" + : "./style.module.css-local12", + futureWmultiParams: prod + ? "my-app-491-Y4" + : "./style.module.css-local14", + hasWmultiParams: prod ? "my-app-491-PK" : "./style.module.css-local11", + matchesWmultiParams: prod + ? "my-app-491-$Y" + : "./style.module.css-local9", + mozAnyWmultiParams: prod + ? "my-app-491-TT" + : "./style.module.css-local15", + pastWmultiParams: prod ? "my-app-491-P_" : "./style.module.css-local13", + webkitAnyWmultiParams: prod + ? "my-app-491-rT" + : "./style.module.css-local16", + whereWmultiParams: prod + ? "my-app-491-ie" + : "./style.module.css-local10", local: prod ? "my-app-491-Zw my-app-491-yl my-app-491-J_ my-app-491-gc" : "./style.module.css-local1 ./style.module.css-local2 ./style.module.css-local3 ./style.module.css-local4", @@ -23,21 +35,15 @@ it("should allow to create css modules", done => { nested: prod ? "my-app-491-RX undefined my-app-491-X2" : "./style.module.css-nested1 undefined ./style.module.css-nested3", - notWmultiParams: prod - ? "my-app-491-Kw" - : "./style.module.css-local7", - isWmultiParams: prod - ? "my-app-491-rw" - : "./style.module.css-local8", + notWmultiParams: prod ? "my-app-491-Kw" : "./style.module.css-local7", + isWmultiParams: prod ? "my-app-491-rw" : "./style.module.css-local8", ident: prod ? "my-app-491-yR" : "./style.module.css-ident", keyframes: prod ? "my-app-491-y3" : "./style.module.css-localkeyframes", animation: prod ? "my-app-491-oQ" : "./style.module.css-animation", vars: prod ? "--my-app-491-y4 my-app-491-gR undefined my-app-491-xk" : "--./style.module.css-local-color ./style.module.css-vars undefined ./style.module.css-globalVars", - media: prod - ? "my-app-491-w7" - : "./style.module.css-wideScreenClass", + media: prod ? "my-app-491-w7" : "./style.module.css-wideScreenClass", mediaWithOperator: prod ? "my-app-491-J" : "./style.module.css-narrowScreenClass", @@ -65,6 +71,7 @@ it("should allow to create css modules", done => { VARS: prod ? "--my-app-491-DJ my-app-491-ms undefined my-app-491-cU" : "--./style.module.css-LOCAL-COLOR ./style.module.css-VARS undefined ./style.module.css-globalVarsUpperCase", + layer: prod ? "my-app-491-EY" : "./style.module.css-layer" }); } catch (e) { return done(e); diff --git a/test/configCases/css/css-modules/index.js b/test/configCases/css/css-modules/index.js index 1f81266de43..dccb477eb0a 100644 --- a/test/configCases/css/css-modules/index.js +++ b/test/configCases/css/css-modules/index.js @@ -9,14 +9,26 @@ it("should allow to create css modules", done => { expect(x).toEqual({ global: undefined, class: prod ? "my-app-491-S" : "./style.module.css-class", - currentWmultiParams: prod ? "my-app-491-yK" : "./style.module.css-local12", - futureWmultiParams: prod ? "my-app-491-Y4" : "./style.module.css-local14", - hasWmultiParams: prod ? "my-app-491-PK" : "./style.module.css-local11", - matchesWmultiParams: prod ? "my-app-491-$Y" : "./style.module.css-local9", - mozAnyWmultiParams: prod ? "my-app-491-TT" : "./style.module.css-local15", - pastWmultiParams: prod ? "my-app-491-P_" : "./style.module.css-local13", - webkitAnyWmultiParams: prod ? "my-app-491-rT" : "./style.module.css-local16", - whereWmultiParams: prod ? "my-app-491-ie" : "./style.module.css-local10", + currentWmultiParams: prod + ? "my-app-491-yK" + : "./style.module.css-local12", + futureWmultiParams: prod + ? "my-app-491-Y4" + : "./style.module.css-local14", + hasWmultiParams: prod ? "my-app-491-PK" : "./style.module.css-local11", + matchesWmultiParams: prod + ? "my-app-491-$Y" + : "./style.module.css-local9", + mozAnyWmultiParams: prod + ? "my-app-491-TT" + : "./style.module.css-local15", + pastWmultiParams: prod ? "my-app-491-P_" : "./style.module.css-local13", + webkitAnyWmultiParams: prod + ? "my-app-491-rT" + : "./style.module.css-local16", + whereWmultiParams: prod + ? "my-app-491-ie" + : "./style.module.css-local10", local: prod ? "my-app-491-Zw my-app-491-yl my-app-491-J_ my-app-491-gc" : "./style.module.css-local1 ./style.module.css-local2 ./style.module.css-local3 ./style.module.css-local4", @@ -26,21 +38,15 @@ it("should allow to create css modules", done => { nested: prod ? "my-app-491-RX undefined my-app-491-X2" : "./style.module.css-nested1 undefined ./style.module.css-nested3", - notWmultiParams: prod - ? "my-app-491-Kw" - : "./style.module.css-local7", - isWmultiParams: prod - ? "my-app-491-rw" - : "./style.module.css-local8", + notWmultiParams: prod ? "my-app-491-Kw" : "./style.module.css-local7", + isWmultiParams: prod ? "my-app-491-rw" : "./style.module.css-local8", ident: prod ? "my-app-491-yR" : "./style.module.css-ident", keyframes: prod ? "my-app-491-y3" : "./style.module.css-localkeyframes", animation: prod ? "my-app-491-oQ" : "./style.module.css-animation", vars: prod ? "--my-app-491-y4 my-app-491-gR undefined my-app-491-xk" : "--./style.module.css-local-color ./style.module.css-vars undefined ./style.module.css-globalVars", - media: prod - ? "my-app-491-w7" - : "./style.module.css-wideScreenClass", + media: prod ? "my-app-491-w7" : "./style.module.css-wideScreenClass", mediaWithOperator: prod ? "my-app-491-J" : "./style.module.css-narrowScreenClass", @@ -68,6 +74,7 @@ it("should allow to create css modules", done => { VARS: prod ? "--my-app-491-DJ my-app-491-ms undefined my-app-491-cU" : "--./style.module.css-LOCAL-COLOR ./style.module.css-VARS undefined ./style.module.css-globalVarsUpperCase", + layer: prod ? "my-app-491-EY" : "./style.module.css-layer" }); } catch (e) { return done(e); diff --git a/test/configCases/css/css-modules/style.module.css b/test/configCases/css/css-modules/style.module.css index da1dc236bf1..22288b4a33a 100644 --- a/test/configCases/css/css-modules/style.module.css +++ b/test/configCases/css/css-modules/style.module.css @@ -219,3 +219,9 @@ COLOR: VAR(--GLOBAR-COLOR); --GLOBAR-COLOR: red; } + +@layer default { + .layer { + color: green; + } +} diff --git a/test/configCases/css/css-modules/use-style.js b/test/configCases/css/css-modules/use-style.js index 9013436b2cb..26e7f2c1066 100644 --- a/test/configCases/css/css-modules/use-style.js +++ b/test/configCases/css/css-modules/use-style.js @@ -29,6 +29,8 @@ export default { supportsWithOperator: style.floatRightInNegativeSupports, mediaInSupports: style.displayFlexInMediaInSupports, supportsInMedia: style.displayFlexInSupportsInMedia, - displayFlexInSupportsInMediaUpperCase: style.displayFlexInSupportsInMediaUpperCase, + displayFlexInSupportsInMediaUpperCase: + style.displayFlexInSupportsInMediaUpperCase, VARS: `${style["LOCAL-COLOR"]} ${style.VARS} ${style["GLOBAL-COLOR"]} ${style.globalVarsUpperCase}`, + layer: style.layer }; diff --git a/test/walkCssTokens.unittest.js b/test/walkCssTokens.unittest.js index 75f0b04acd7..29eff995215 100644 --- a/test/walkCssTokens.unittest.js +++ b/test/walkCssTokens.unittest.js @@ -10,6 +10,14 @@ describe("walkCssTokens", () => { results.push(["url", input.slice(s, e), input.slice(cs, ce)]); return e; }, + layer: (input, s, e, cs, ce) => { + results.push(["layer", input.slice(s, e), input.slice(cs, ce)]); + return e; + }, + supports: (input, s, e, cs, ce) => { + results.push(["supports", input.slice(s, e), input.slice(cs, ce)]); + return e; + }, leftCurlyBracket: (input, s, e) => { results.push(["leftCurlyBracket", input.slice(s, e)]); return e; @@ -300,4 +308,114 @@ describe("walkCssTokens", () => { ] `) ); + + test( + "parse at import rules", + `@import url( "style.css" ) layer( my-layer ) supports( display: flex ) screen and (min-width: 1024px); +@import url("style2.css") supports(not (display: flex) and selector(A > B)); +@import url("style3.css") supports((display: grid) and (not (display: inline-grid))); +@import url("style4.css") supports((display: grid) or (display: inline-grid));`, + e => + e.toMatchInlineSnapshot(` + Array [ + Array [ + "atKeyword", + "@import", + ], + Array [ + "url", + "url( \\"style.css\\" )", + "style.css", + ], + Array [ + "layer", + "layer( my-layer )", + "my-layer", + ], + Array [ + "supports", + "supports( display: flex )", + "display: flex", + ], + Array [ + "identifier", + "screen", + ], + Array [ + "identifier", + "and", + ], + Array [ + "leftParenthesis", + "(", + ], + Array [ + "identifier", + "min-width", + ], + Array [ + "rightParenthesis", + ")", + ], + Array [ + "semicolon", + ";", + ], + Array [ + "atKeyword", + "@import", + ], + Array [ + "url", + "url(\\"style2.css\\")", + "style2.css", + ], + Array [ + "supports", + "supports(not (display: flex) and selector(A > B))", + "not (display: flex) and selector(A > B)", + ], + Array [ + "semicolon", + ";", + ], + Array [ + "atKeyword", + "@import", + ], + Array [ + "url", + "url(\\"style3.css\\")", + "style3.css", + ], + Array [ + "supports", + "supports((display: grid) and (not (display: inline-grid)))", + "(display: grid) and (not (display: inline-grid))", + ], + Array [ + "semicolon", + ";", + ], + Array [ + "atKeyword", + "@import", + ], + Array [ + "url", + "url(\\"style4.css\\")", + "style4.css", + ], + Array [ + "supports", + "supports((display: grid) or (display: inline-grid))", + "(display: grid) or (display: inline-grid)", + ], + Array [ + "semicolon", + ";", + ], + ] + `) + ); });