diff --git a/CHANGELOG.md b/CHANGELOG.md index 3085788e..2455c276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change log +## 3.12.0 (2024-05-19) + +- feat: add support for the `css-loader` option `exportType` as [css-style-sheet](https://github.com/webpack-contrib/css-loader?#exporttype) +- test: add tests for import of CSS stylesheet +- docs: update readme + ## 3.11.0 (2024-04-23) - feat: add `entryFilter` option to include or exclude entry files when the `entry` option is the path diff --git a/README.md b/README.md index a8caee0a..dc0486f8 100644 --- a/README.md +++ b/README.md @@ -529,6 +529,7 @@ See [boilerplate](https://github.com/webdiscus/webpack-html-scss-boilerplate) - [How to resolve source assets in an attribute containing JSON value](#recipe-resolve-attr-json) - [How to load CSS file dynamically](#recipe-dynamic-load-css) (lazy loading CSS) - [How to import CSS class names in JS](#recipe-css-modules) (CSS modules) + - [How to import CSS stylesheet in JS](#recipe-css-style-sheet) ([CSSStyleSheet](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet)) - [How to load JS and CSS from `node_modules` in template](#recipe-load-js-css-from-node-modules) - [How to import CSS or SCSS from `node_modules` in SCSS](#recipe-import-style-from-node-modules) - [How to process a PHP template](#recipe-preprocessor-php) @@ -5450,9 +5451,98 @@ The imported `styles` object contains generated class names like followings: } ``` - Read more information about [CSS Modules](https://github.com/css-modules/css-modules). +--- + +#### [↑ back to contents](#contents) + + + +## How to import CSS stylesheet in JS + +Using the `css-loader` option [exportType](https://github.com/webpack-contrib/css-loader?#exporttype) as `css-style-sheet` +you can import the CSS stylesheets as the instance of the [CSSStyleSheet](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet) object. + +Import a CSS module script and apply it to a document or a shadow root like this: + +```js +import sheet from './style.scss?sheet'; + +document.adoptedStyleSheets = [sheet]; +shadowRoot.adoptedStyleSheets = [sheet]; +``` + +You can use the `?sheet` URL query to import a style file as stylesheets. +The query must be configured in the webpack config: + +```js +module.exports = { + plugins: [ + new HtmlBundlerPlugin({ + entry: { + index: './src/index.html', + }, + js: { + filename: '[name].[contenthash:8].js', + }, + css: { + filename: '[name].[contenthash:8].css', + }, + }), + ], + module: { + rules: [ + { + test: /\.(s?css)$/, + oneOf: [ + // Import CSS/SCSS source file as a CSSStyleSheet object + { + resourceQuery: /sheet/, // <= the query, e.g. style.scss?sheet + use: [ + { + loader: 'css-loader', + options: { + exportType: 'css-style-sheet', // <= define this option + }, + }, + { + loader: 'sass-loader', + }, + ], + }, + // Import CSS/SCSS source file as a CSS string + { + use: [ + 'css-loader', + 'sass-loader', + ], + } + ], + } + ], + }, +}; +``` + +Using the universal configuration above you can apply CSS stylesheets in JS and extract CSS into separate file or inject CSS into HTML: + +```js +import sheet from './style.scss?sheet'; // import as CSSStyleSheet object +import './style2.scss?inline'; // the extracted CSS will be injected into HTML +import './style3.scss'; // the extracted CSS will be saved into separate output file + +// apply stylesheet to document and shadow root +document.adoptedStyleSheets = [sheet]; +shadowRoot.adoptedStyleSheets = [sheet]; +``` + +This is useful for [custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) and shadow DOM. + +More information: + +- [Using CSS Module Scripts to import stylesheets](https://web.dev/css-module-scripts/) +- [Constructable Stylesheets: seamless reusable styles](https://developers.google.com/web/updates/2019/02/constructable-stylesheets) --- @@ -6067,7 +6157,7 @@ dist/js/app-5fa74877.1aceb2db.js Using the bundler plugin, all your style source files should be specified directly in the template. You can import style files in JavaScript, like it works using the `mini-css-extract-plugin` and `html-webpack-plugin`, -but it is a **dirty hack**, **bad practice**, processing is **slow**, avoid it if possible. +but it is a **bad practice** and processing is **slower**. You can separate the styles into multiple bundles yourself. diff --git a/package-lock.json b/package-lock.json index 209abdeb..1ca29439 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "html-bundler-webpack-plugin", - "version": "3.10.0", + "version": "3.11.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "html-bundler-webpack-plugin", - "version": "3.10.0", + "version": "3.11.0", "license": "ISC", "dependencies": { "@types/html-minifier-terser": "^7.0.2", @@ -40,6 +40,7 @@ "jest": "^29.7.0", "liquidjs": "^10.10.0", "markdown-it": "^14.0.0", + "mini-css-extract-plugin": "^2.9.0", "mustache": "^4.2.0", "normalize.css": "^8.0.1", "nunjucks": "^3.2.4", @@ -9907,6 +9908,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -16668,6 +16689,7 @@ "jest": "^29.7.0", "liquidjs": "^10.10.0", "markdown-it": "^14.0.0", + "mini-css-extract-plugin": "^2.9.0", "mustache": "^4.2.0", "normalize.css": "^8.0.1", "nunjucks": "^3.2.4", @@ -23921,6 +23943,16 @@ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true }, + "mini-css-extract-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "dev": true, + "requires": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + } + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -31954,6 +31986,16 @@ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true }, + "mini-css-extract-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "dev": true, + "requires": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + } + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", diff --git a/package.json b/package.json index bc8e3c06..54bf1cd8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-bundler-webpack-plugin", - "version": "3.11.0", + "version": "3.12.0", "description": "HTML bundler plugin for webpack handles a template as an entry point, extracts CSS and JS from their sources referenced in HTML, supports template engines like Eta, EJS, Handlebars, Nunjucks.", "keywords": [ "html", @@ -20,10 +20,12 @@ "twig", "twigjs", "integrity", - "style", + "js", "javascript", "css", - "scss" + "scss", + "style", + "stylesheet" ], "license": "ISC", "author": "webdiscus (https://github.com/webdiscus)", diff --git a/src/Plugin/AssetCompiler.js b/src/Plugin/AssetCompiler.js index 216af344..59c871d9 100644 --- a/src/Plugin/AssetCompiler.js +++ b/src/Plugin/AssetCompiler.js @@ -580,9 +580,10 @@ class AssetCompiler { // the filename with an extension is available only after resolve meta.isStyle = Option.isStyle(file); + meta.isCSSStyleSheet = this.isCSSStyleSheet(createData); // skip: module loaded via importModule, css url, data-URL - if (meta.isLoaderImport || meta.isDependencyUrl || request.startsWith('data:')) return; + if (meta.isLoaderImport || meta.isCSSStyleSheet || meta.isDependencyUrl || request.startsWith('data:')) return; if (issuer) { const isIssuerStyle = Option.isStyle(issuer); @@ -642,6 +643,21 @@ class AssetCompiler { meta.isScript = Collection.hasScript(request); } + /** + * Whether the module is imported CSSStyleSheet in JS. + * + * @param {{}} module + * @return {boolean} + */ + isCSSStyleSheet(module) { + return ( + Array.isArray(module.loaders) && + module?.loaders.some( + (loader) => loader.loader.includes('css-loader') && loader.options?.exportType === 'css-style-sheet' + ) + ); + } + /** * Returns unique style loaders only. * @@ -812,12 +828,13 @@ class AssetCompiler { for (const module of chunkModules) { const { buildInfo, resource, resourceResolveData } = module; - const { isScript, isImportedStyle } = resourceResolveData?._bundlerPluginMeta || {}; + const { isScript, isImportedStyle, isCSSStyleSheet } = resourceResolveData?._bundlerPluginMeta || {}; let moduleType = module.type; if ( isScript || isImportedStyle || + isCSSStyleSheet || !resource || !resourceResolveData?.context || AssetInline.isDataUrl(resource) @@ -959,7 +976,10 @@ class AssetCompiler { // extract CSS const cssOptions = Option.getStyleOptions(sourceFile); - if (cssOptions == null) return; + if (cssOptions == null) { + // ignore file if css option is disabled + return; + } const inline = Collection.isInlineStyle(resource); const { name } = path.parse(sourceFile); diff --git a/test/cases/js-import-css-modules-type-css-style-sheet-mix/expected/index.html b/test/cases/js-import-css-modules-type-css-style-sheet-mix/expected/index.html new file mode 100644 index 00000000..319b1610 --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet-mix/expected/index.html @@ -0,0 +1,20 @@ + + + + Test + + + + + + +

Hello World!

+
Red
+
Green
+
Blue
+ + \ No newline at end of file diff --git a/test/cases/js-import-css-modules-type-css-style-sheet-mix/expected/main.cc0cc899.js b/test/cases/js-import-css-modules-type-css-style-sheet-mix/expected/main.cc0cc899.js new file mode 100644 index 00000000..b220becd --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet-mix/expected/main.cc0cc899.js @@ -0,0 +1 @@ +(()=>{"use strict";var t,e={77:(t,e,n)=>{n.d(e,{Z:()=>l});var r=n(558),o=n.n(r),c=n(361),a=n.n(c)()(o());a.push([t.id,".lime-green {\n border: 5px solid limegreen;\n color: limegreen;\n}\n",""]);var i=new CSSStyleSheet;i.replaceSync(a.toString());const l=i},361:t=>{t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var n="",r=void 0!==e[5];return e[4]&&(n+="@supports (".concat(e[4],") {")),e[2]&&(n+="@media ".concat(e[2]," {")),r&&(n+="@layer".concat(e[5].length>0?" ".concat(e[5]):""," {")),n+=t(e),r&&(n+="}"),e[2]&&(n+="}"),e[4]&&(n+="}"),n})).join("")},e.i=function(t,n,r,o,c){"string"==typeof t&&(t=[[null,t,void 0]]);var a={};if(r)for(var i=0;i0?" ".concat(u[5]):""," {").concat(u[1],"}")),u[5]=c),n&&(u[2]?(u[1]="@media ".concat(u[2]," {").concat(u[1],"}"),u[2]=n):u[2]=n),o&&(u[4]?(u[1]="@supports (".concat(u[4],") {").concat(u[1],"}"),u[4]=o):u[4]="".concat(o)),e.push(u))}},e}},558:t=>{t.exports=function(t){return t[1]}}},n={};function r(t){var o=n[t];if(void 0!==o)return o.exports;var c=n[t]={id:t,exports:{}};return e[t](c,c.exports,r),c.exports}r.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return r.d(e,{a:e}),e},r.d=(t,e)=>{for(var n in e)r.o(e,n)&&!r.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},r.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),t=r(77),console.log("sheet: ",t.Z),document.adoptedStyleSheets=[t.Z]})(); \ No newline at end of file diff --git a/test/cases/js-import-css-modules-type-css-style-sheet-mix/expected/main.dc4ea4af.css b/test/cases/js-import-css-modules-type-css-style-sheet-mix/expected/main.dc4ea4af.css new file mode 100644 index 00000000..e5bce632 --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet-mix/expected/main.dc4ea4af.css @@ -0,0 +1,4 @@ +.royal-blue { + border: 5px solid royalblue; + color: royalblue; +} diff --git a/test/cases/js-import-css-modules-type-css-style-sheet-mix/expected/style-a.22451d9b.css b/test/cases/js-import-css-modules-type-css-style-sheet-mix/expected/style-a.22451d9b.css new file mode 100644 index 00000000..11bead78 --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet-mix/expected/style-a.22451d9b.css @@ -0,0 +1,4 @@ +h1 { + color: coral; + font-size: 20px; +} diff --git a/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/index.html b/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/index.html new file mode 100644 index 00000000..421c42b7 --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/index.html @@ -0,0 +1,15 @@ + + + + Test + + + + + +

Hello World!

+
Red
+
Green
+
Blue
+ + \ No newline at end of file diff --git a/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/main.js b/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/main.js new file mode 100644 index 00000000..5fc45fe3 --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/main.js @@ -0,0 +1,7 @@ +import sheet from './style-c.css?sheet'; // `css-loader` option `exportType: 'css-style-sheet'` +import './style-d.css'; // `css-loader` option `exportType: 'array'` + +console.log('sheet: ', sheet); + +document.adoptedStyleSheets = [sheet]; +//shadowRoot.adoptedStyleSheets = [sheet]; // error in browser: shadowRoot is not defined diff --git a/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/style-a.css b/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/style-a.css new file mode 100644 index 00000000..11bead78 --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/style-a.css @@ -0,0 +1,4 @@ +h1 { + color: coral; + font-size: 20px; +} diff --git a/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/style-b.css b/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/style-b.css new file mode 100644 index 00000000..89cb7e28 --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/style-b.css @@ -0,0 +1,4 @@ +.red { + border: 5px solid red; + color: indianred; +} diff --git a/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/style-c.css b/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/style-c.css new file mode 100644 index 00000000..6b8631dc --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/style-c.css @@ -0,0 +1,4 @@ +.lime-green { + border: 5px solid limegreen; + color: limegreen; +} diff --git a/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/style-d.css b/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/style-d.css new file mode 100644 index 00000000..e5bce632 --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet-mix/src/style-d.css @@ -0,0 +1,4 @@ +.royal-blue { + border: 5px solid royalblue; + color: royalblue; +} diff --git a/test/cases/js-import-css-modules-type-css-style-sheet-mix/webpack.config.js b/test/cases/js-import-css-modules-type-css-style-sheet-mix/webpack.config.js new file mode 100644 index 00000000..47037693 --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet-mix/webpack.config.js @@ -0,0 +1,59 @@ +const path = require('path'); +const HtmlBundlerPlugin = require('@test/html-bundler-webpack-plugin'); + +module.exports = { + mode: 'production', + + output: { + path: path.join(__dirname, 'dist/'), + }, + + plugins: [ + new HtmlBundlerPlugin({ + entry: { + index: './src/index.html', + }, + js: { + filename: '[name].[contenthash:8].js', + }, + css: { + filename: '[name].[contenthash:8].css', + }, + }), + ], + + module: { + rules: [ + { + test: /\.(css)$/, + oneOf: [ + // extract CSS as the CSSStyleSheet object to apply it to the browser document at runtime in JS + { + resourceQuery: /sheet/, // style.css?sheet + use: [ + { + loader: 'css-loader', + options: { + esModule: true, // must be enabled for using CSS module and `css-style-sheet` + // https://github.com/webpack-contrib/css-loader?#exporttype + exportType: 'css-style-sheet', + }, + }, + ], + }, + // extract CSS as string to inline into HTML or save into separate file + { + use: [ + { + loader: 'css-loader', + options: { + exportType: 'array', // default type + }, + }, + ], + }, + ], + }, + ], + }, +}; diff --git a/test/cases/js-import-css-modules-type-css-style-sheet/expected/index.html b/test/cases/js-import-css-modules-type-css-style-sheet/expected/index.html new file mode 100644 index 00000000..c79194aa --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet/expected/index.html @@ -0,0 +1,13 @@ + + + + Test + + + +

Hello World!

+
Red
+
Green
+
Blue
+ + \ No newline at end of file diff --git a/test/cases/js-import-css-modules-type-css-style-sheet/expected/js/main.5283af7c.js b/test/cases/js-import-css-modules-type-css-style-sheet/expected/js/main.5283af7c.js new file mode 100644 index 00000000..00873b95 --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet/expected/js/main.5283af7c.js @@ -0,0 +1 @@ +(()=>{"use strict";var n,o={367:(n,o,r)=>{r.d(o,{Z:()=>l});var e=r(558),t=r.n(e),c=r(361),a=r.n(c)()(t());a.push([n.id,"h1 {\n color: coral;\n font-size: 20px;\n}\n\n.red {\n border: 5px solid red;\n color: indianred;\n}\n\n.lime-green {\n border: 5px solid limegreen;\n color: limegreen;\n}\n\n.royal-blue {\n border: 5px solid royalblue;\n color: royalblue;\n}",""]);var i=new CSSStyleSheet;i.replaceSync(a.toString());const l=i},361:n=>{n.exports=function(n){var o=[];return o.toString=function(){return this.map((function(o){var r="",e=void 0!==o[5];return o[4]&&(r+="@supports (".concat(o[4],") {")),o[2]&&(r+="@media ".concat(o[2]," {")),e&&(r+="@layer".concat(o[5].length>0?" ".concat(o[5]):""," {")),r+=n(o),e&&(r+="}"),o[2]&&(r+="}"),o[4]&&(r+="}"),r})).join("")},o.i=function(n,r,e,t,c){"string"==typeof n&&(n=[[null,n,void 0]]);var a={};if(e)for(var i=0;i0?" ".concat(s[5]):""," {").concat(s[1],"}")),s[5]=c),r&&(s[2]?(s[1]="@media ".concat(s[2]," {").concat(s[1],"}"),s[2]=r):s[2]=r),t&&(s[4]?(s[1]="@supports (".concat(s[4],") {").concat(s[1],"}"),s[4]=t):s[4]="".concat(t)),o.push(s))}},o}},558:n=>{n.exports=function(n){return n[1]}}},r={};function e(n){var t=r[n];if(void 0!==t)return t.exports;var c=r[n]={id:n,exports:{}};return o[n](c,c.exports,e),c.exports}e.n=n=>{var o=n&&n.__esModule?()=>n.default:()=>n;return e.d(o,{a:o}),o},e.d=(n,o)=>{for(var r in o)e.o(o,r)&&!e.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:o[r]})},e.o=(n,o)=>Object.prototype.hasOwnProperty.call(n,o),n=e(367),console.log("sheet: ",n.Z),document.adoptedStyleSheets=[n.Z]})(); \ No newline at end of file diff --git a/test/cases/js-import-css-modules-type-css-style-sheet/src/index.html b/test/cases/js-import-css-modules-type-css-style-sheet/src/index.html new file mode 100644 index 00000000..f2c0c5cc --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet/src/index.html @@ -0,0 +1,13 @@ + + + + Test + + + +

Hello World!

+
Red
+
Green
+
Blue
+ + \ No newline at end of file diff --git a/test/cases/js-import-css-modules-type-css-style-sheet/src/main.js b/test/cases/js-import-css-modules-type-css-style-sheet/src/main.js new file mode 100644 index 00000000..877436f8 --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet/src/main.js @@ -0,0 +1,7 @@ +import sheet from './style.css'; // using the `css-loader` option `exportType: 'css-style-sheet'` +//import sheet from './style.css' assert { type: 'css' }; // using the `css-loader` option `exportType: 'css-style-sheet'` + +console.log('sheet: ', sheet); + +document.adoptedStyleSheets = [sheet]; +//shadowRoot.adoptedStyleSheets = [sheet]; // error in browser: shadowRoot is not defined \ No newline at end of file diff --git a/test/cases/js-import-css-modules-type-css-style-sheet/src/style.css b/test/cases/js-import-css-modules-type-css-style-sheet/src/style.css new file mode 100644 index 00000000..961ac7da --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet/src/style.css @@ -0,0 +1,19 @@ +h1 { + color: coral; + font-size: 20px; +} + +.red { + border: 5px solid red; + color: indianred; +} + +.lime-green { + border: 5px solid limegreen; + color: limegreen; +} + +.royal-blue { + border: 5px solid royalblue; + color: royalblue; +} \ No newline at end of file diff --git a/test/cases/js-import-css-modules-type-css-style-sheet/webpack.config.js b/test/cases/js-import-css-modules-type-css-style-sheet/webpack.config.js new file mode 100644 index 00000000..347c902c --- /dev/null +++ b/test/cases/js-import-css-modules-type-css-style-sheet/webpack.config.js @@ -0,0 +1,47 @@ +const path = require('path'); +const HtmlBundlerPlugin = require('@test/html-bundler-webpack-plugin'); + +module.exports = { + mode: 'production', + + output: { + path: path.join(__dirname, 'dist/'), + }, + + plugins: [ + new HtmlBundlerPlugin({ + entry: { + index: './src/index.html', + }, + js: { + filename: 'js/[name].[contenthash:8].js', + }, + css: { + //enabled: false, // must be false if used the `exportType: 'css-style-sheet'` + }, + }), + ], + + module: { + rules: [ + { + test: /\.(css)$/, + use: [ + { + loader: 'css-loader', + options: { + esModule: true, // must be enabled for using CSS module and `css-style-sheet` + // https://github.com/webpack-contrib/css-loader?#exporttype + exportType: 'css-style-sheet', + // see https://github.com/webpack-contrib/css-loader#modules + // modules: { + // localIdentName: '[name]__[local]--[hash:base64:5]', + // exportLocalsConvention: 'camelCase', + // }, + }, + }, + ], + }, + ], + }, +}; \ No newline at end of file diff --git a/test/integration.test.js b/test/integration.test.js index 77c6fe5c..d6204874 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -301,6 +301,8 @@ describe('import styles in JavaScript', () => { test('import CSS Modules, CJS', () => compareFiles('js-import-css-modules-cjs')); test('import CSS Modules, ESM', () => compareFiles('js-import-css-modules-esm')); + test('import CSS, css-style-sheet', () => compareFiles('js-import-css-modules-type-css-style-sheet')); + test('import CSS, css-style-sheet mix types', () => compareFiles('js-import-css-modules-type-css-style-sheet-mix')); // dynamic import of the style in the dynamic imported js test('dynamic import css in js', () => compareFiles('js-import-css-dynamic-import'));