Feature: add support for `exportType: "css-style-sheet"` for css-loader
webdiscus opened this issue · 14 comments
Discussed in #92
Originally posted by maltevesper May 18, 2024
I am trying to use the css-style-sheet
option (load css module as a CSSStyleSheet for use in a CustomElement), however this breaks my build. Apparently webpack tries to compile the javascript generated for the CSS module and is unable to find CSSStyleSheet.
Unfortunately I am not a webdeveloper, thus not to familiar with node.js/webpack. What is the error in my configuration, and how could I start debugging this/Fix it?
stacktrace
ERROR in ./src/test.module.css
Module build failed (from ./node_modules/html-bundler-webpack-plugin/src/Loader/cssLoader.js):
HookWebpackError: CSSStyleSheet is not defined
at tryRunOrWebpackError (/home/malte/projects/logview/node_modules/webpack/lib/HookWebpackError.js:88:9)
at __webpack_require_module__ (/home/malte/projects/logview/node_modules/webpack/lib/Compilation.js:5241:12)
at __webpack_require__ (/home/malte/projects/logview/node_modules/webpack/lib/Compilation.js:5198:18)
at /home/malte/projects/logview/node_modules/webpack/lib/Compilation.js:5270:20
at symbolIterator (/home/malte/projects/logview/node_modules/neo-async/async.js:3485:9)
at done (/home/malte/projects/logview/node_modules/neo-async/async.js:3527:9)
at Hook.eval [as callAsync] (eval at create (/home/malte/projects/logview/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:15:1)
at /home/malte/projects/logview/node_modules/webpack/lib/Compilation.js:5176:43
at symbolIterator (/home/malte/projects/logview/node_modules/neo-async/async.js:3482:9)
at timesSync (/home/malte/projects/logview/node_modules/neo-async/async.js:2297:7)
-- inner error --
ReferenceError: CSSStyleSheet is not defined
at Module.<anonymous> (/home/malte/projects/logview/node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[2].use[0]!/home/malte/projects/logview/src/test.module.css?HTMLBundlerCSSLoader:19:36)
at /home/malte/projects/logview/node_modules/webpack/lib/javascript/JavascriptModulesPlugin.js:452:10
at Hook.eval [as call] (eval at create (/home/malte/projects/logview/node_modules/tapable/lib/HookCodeFactory.js:19:10), <anonymous>:7:1)
at /home/malte/projects/logview/node_modules/webpack/lib/Compilation.js:5243:39
at tryRunOrWebpackError (/home/malte/projects/logview/node_modules/webpack/lib/HookWebpackError.js:83:7)
at __webpack_require_module__ (/home/malte/projects/logview/node_modules/webpack/lib/Compilation.js:5241:12)
at __webpack_require__ (/home/malte/projects/logview/node_modules/webpack/lib/Compilation.js:5198:18)
at /home/malte/projects/logview/node_modules/webpack/lib/Compilation.js:5270:20
at symbolIterator (/home/malte/projects/logview/node_modules/neo-async/async.js:3485:9)
at done (/home/malte/projects/logview/node_modules/neo-async/async.js:3527:9)
Generated code for /home/malte/projects/logview/node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[2].use[0]!/home/malte/projects/logview/src/test.module.css?HTMLBundlerCSSLoader
1 | __webpack_require__.r(__webpack_exports__);
2 | /* harmony export */ __webpack_require__.d(__webpack_exports__, {
3 | /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
4 | /* harmony export */ });
5 | /* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../node_modules/css-loader/dist/runtime/noSourceMaps.js */ "/home/malte/projects/logview/node_modules/css-loader/dist/runtime/noSourceMaps.js");
6 | /* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
7 | /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../node_modules/css-loader/dist/runtime/api.js */ "/home/malte/projects/logview/node_modules/css-loader/dist/runtime/api.js");
8 | /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
9 | // Imports
10 |
11 |
12 | var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));
13 | // Module
14 | ___CSS_LOADER_EXPORT___.push([module.id, `body {
15 | background-color: pink;
16 | }
17 | `, ""]);
18 | // Exports
19 | var ___CSS_LOADER_STYLE_SHEET___ = new CSSStyleSheet();
20 | ___CSS_LOADER_STYLE_SHEET___.replaceSync(___CSS_LOADER_EXPORT___.toString());
21 | /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_STYLE_SHEET___);
22 |
@ ./src/test.js 1:0-47
... /* follow up error */
webpack.config.js
const path = require("path");
const HtmlBundlerPlugin = require("html-bundler-webpack-plugin");
module.exports = {
mode: "development", // TODO: how should we switch this between development and production?
devtool: false, // TODO: what is the correct setting here, to either do no transform or fast builds?
entry: {
test: "./src/test.html",
},
plugins: [
new HtmlBundlerPlugin({
css:{inline:true},
js:{inline:true},
}),
],
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/,
},
{
test: /\.css$/,
use: [
{
loader: "css-loader",
options: {
exportType: "css-style-sheet",
modules: {
auto: true,
localIdentName: "[name]__[local]--[hash:base64:5]",
exportLocalsConvention: "camelCase",
},
},
},
],
},
{
test: /\.(png|jpe?g|svg|webp|woff2?)$/i,
type: "asset/inline",
},
],
},
resolve: {
extensions: [ ".tsx", ".ts", ".js" ],
extensionAlias: {
".js": [".ts", ".js"],
},
},
output: {
//filename: "[name].js",
path: path.resolve(__dirname, "dist"),
clean: true,
},
};
test.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
</head>
<body>
<script src="test.js" async defer></script>
</body>
</html>
test.js
import dropdownstyles from "./test.module.css";
test.module.css
body {
background-color: pink;
}
Hello @maltevesper,
thanks for the issue report.
Currently the plugin supports the default exportType: 'array',
only. You can import styles in JS as the module
, but not as the CSSStyleSheet
.
I will add support for exportType: 'css-style-sheet'
in the next release.
If used exportType: 'css-style-sheet'
then other loaders should not be used (defaults, the Bundler Plugin uses own CSS loader).
The imported CSSStyleSheet will be available in your JS only and no CSS file fill be generated or inlined in HTML.
In this case you must
self add the imported CSSStyleSheet
into document
:
import sheet from './style.css'; // <= import CSSStyleSheet using the `css-loader` option `exportType: 'css-style-sheet'`
document.adoptedStyleSheets = [sheet]; // <= apply the CSSStyleSheet to the document
Workaround
If is used the exportType: 'css-style-sheet'
, then simple disable Bundler CSS loader
in the plugin option:
new HtmlBundlerPlugin({
// NOTE: define entry option here
entry: {
test: "./src/test.html",
},
css:{
enabled: false, // <= this fixes the ERROR: CSSStyleSheet is not defined
},
js:{
inline: true
},
}),
Solution in the next version
The plugin will detect whether in css-loader
used the exportType: 'css-style-sheet'
and will not use own css loader in this case.
I am not next to my PC but I assume disabling the loader as described in the workaround will stop the bundler from inlining any css?
In my project there is a customelement.module.css
(for which I need the workaround) and then there is a `` main.css` which I assume still needs the bundler css loader? I hope you can cover this case in the next version.
Out of curiosity, your service is exceptional (less than 24h response time, workaround and proposed Bugfix), when do you reckon the next version will be released (a week, a month, ...). Since this is a side project for me I might either wait or rework my code to work with arrays in the meantime.
Thank you for the excellent support ❤️
The generated CSS will be "inlined" into DOM in your JS file:
import sheet from './style.css'; // <= import sheet as the CSSStyleSheet object
document.adoptedStyleSheets = [sheet]; // <= apply the CSSStyleSheet to the document (inline CSS)
The plugin option css: { inline: true }
works if used normal (default) css-loader
mode. In this case the generated CSS will be extracted as a sting
and the plugin can inject the CSS string
into HTML at compilation on server-side.
But using the option exportType: 'css-style-sheet'
(css-loader), will be generated an instance
of CSSStyleSheet
. This is an object
, not a string
. Therefore this object can't be inlined into HTML on server-side, otherwise the CSSStyleSheet
object will be "inlined" on cleint-side at runtime in your browser using the code line:
document.adoptedStyleSheets = [sheet];
You should not wait until next release. The "workaround" is "production ready". The solution in next version do nothing. This will auto "disable" using inner CSS loader of the Bundler plugin if used the 'css-style-sheet'
option.
I think, in one week will be next release with support the 'css-style-sheet'
option.
In the next release can be used complex CSS loader configuration, where can be combine 'css-style-sheet'
for special use case and the normal mode, where you can import CSS in default mode with inlining.
I must create many test cases to cover such complex usages.
please update the plugin to the version 3.12.0
.
New version supports the css-style-sheet
option w/o disabling the css: {enabled: false}
.
Using new version you can import CSS StyleSheet in JS and inline other CSS files into HTML.
See please the How to import CSS stylesheet in JS and the test case as an usage example.
@webdiscus :
Neat, it eradicated the builderrors immediately (without any changes, although it then failed to inline the vanilla css, so I followed the recipie). I had to adjust the recipie slightly, since my typescript configuration was not happy about the query to separate css module sheets and normal css (I swapped resourceQUery for test, not sure if this is the best way).
{
test: /\.css$/,
oneOf: [
// Import CSS/SCSS source file as a CSSStyleSheet object
{
//resourceQuery: /sheet/, // <= the query, e.g. style.scss?sheet
test: /\.module\.css$/,
use: [
{
loader: "css-loader",
options: {
exportType: "css-style-sheet", // <= define this option
},
},
],
},
// Import CSS/SCSS source file as a CSS string
{
use: [
"css-loader",
],
}
],
},
Currently, it seems like the css file is not correctly inlined on the typescript path:
/*!*******************************!*\
!*** ./src/DropDownButton.ts ***!
\*******************************/
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _DropDownButton_module_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./DropDownButton.module.css */ "./src/DropDownButton.module.css");
on the other hand there is a bit where the actual css is inlined further down in my file, so it is not clear why the generated code still wants to access the file.
___CSS_LOADER_EXPORT___.push([module.id, `[class^="icon-"]:before,
[class*=" icon-"]:before {
font-family: "CaskaydiaMono NF";
font-style: normal;
font-weight: normal;
...
I am still toying with this, but wanted to give you a bit of early feedback, if I can't figure it out I will put together a minimal example.
While working on it I realized I need: esModule: true,
with exportType: "css-style-sheet",
to get access to the mangled class names, maybe the example should be adjusted.
See the warning here: https://github.com/webpack-contrib/css-loader?tab=readme-ov-file#css-style-sheet (although the examples there do not include that option, I ended up needing it)
no, no, your changes in. webpack will not works:
{
test: /\.css$/,
oneOf: [
// Import CSS/SCSS source file as a CSSStyleSheet object
{
/* tslint:disable-next-line */ // <= use this to ignore TypeScript error
resourceQuery: /sheet/, // <= the query, e.g. style.scss?sheet
// test: /\.module\.css$/, // <= this is WRONG place for test, it must be outer `oneOf`
use: [
{
loader: "css-loader",
options: {
exportType: "css-style-sheet", // <= define this option
},
},
],
},
// Import CSS/SCSS source file as a CSS string
{
use: [
"css-loader",
],
}
],
},
please, create small repo with your use case. Then I can help you to run your project.
to get access to the mangled class names in JS you don't need define any exportType
, please remove this option!
The exportType: "css-style-sheet"
option is required only
is you will import CSS stylesheet object in JS and apply it to shadow DOM. Do you really need it?
If you will use both the CSS stylesheet as object
and inlined CSS into HTML, then use the rule from my recipe (w/o modification).
P.S. please, create a repo so I can understand what you need.
I will create the repo later today ~12h, for what its worth, I am using a CustomElement with shadow dom so felt that css-style-sheet
fits the usage model of shadowdom.adoptedStyleSheets.push()
best. When I tried this, I noticed that the styles were not applied due to mangled names. I first worked out how to get at the mangled names to later realize that you are right, due to the shadow dom isolation I do not need to worry about mangled names, and can turn name mangling off by setting modules { localIdentName: "[local]"}
in the css-loader options.
Either way, without an example repo this is all very fuzzy. Will set it up after work.
So, while being a bit late with it I set up a repo: https://github.com/maltevesper/testWebpack/tree/main/src
Now here is the odd thing: I can't reproduce the issue I stated above, neither in the MWE nor in my main project. While I feel a bit silly (even though I have a screenshot), I am also happy that it worked out for me.
Given that everything works as expected even with my "wrong" configuration (I am not sure I understand what is wrong with it #93 (comment)).
Given that everything works for me, I think it is time to close this issue. Thank you for your help.
thanks for the repo. Now I understand your use cases.
Note: you can import CSS stylesheet without assert
:
import dropdownstyles from "./test.module.css"; // <= works too
The assert { type: "css" }
does nothing, this is just only as a marker for the matching the oneOf
rule.
Because you use the test: /\.module\.css$/,
matching option, the asset
by import is not needed in this use case.