Skip to content

Commit acf88db

Browse files
committed
feat: add support for css-loader option exportType as css-style-sheet
1 parent 33330bc commit acf88db

23 files changed

Lines changed: 398 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Change log
22

3+
## 3.12.0 (2024-05-19)
4+
5+
- feat: add support for the `css-loader` option `exportType` as [css-style-sheet](https://github.com/webpack-contrib/css-loader?#exporttype)
6+
- test: add tests for import of CSS stylesheet
7+
- docs: update readme
8+
39
## 3.11.0 (2024-04-23)
410

511
- feat: add `entryFilter` option to include or exclude entry files when the `entry` option is the path

README.md

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,7 @@ See [boilerplate](https://github.com/webdiscus/webpack-html-scss-boilerplate)
529529
- [How to resolve source assets in an attribute containing JSON value](#recipe-resolve-attr-json)
530530
- [How to load CSS file dynamically](#recipe-dynamic-load-css) (lazy loading CSS)
531531
- [How to import CSS class names in JS](#recipe-css-modules) (CSS modules)
532+
- [How to import CSS stylesheet in JS](#recipe-css-style-sheet) ([CSSStyleSheet](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet))
532533
- [How to load JS and CSS from `node_modules` in template](#recipe-load-js-css-from-node-modules)
533534
- [How to import CSS or SCSS from `node_modules` in SCSS](#recipe-import-style-from-node-modules)
534535
- [How to process a PHP template](#recipe-preprocessor-php)
@@ -5450,9 +5451,98 @@ The imported `styles` object contains generated class names like followings:
54505451
}
54515452
```
54525453
5453-
54545454
Read more information about [CSS Modules](https://github.com/css-modules/css-modules).
54555455
5456+
---
5457+
5458+
#### [↑ back to contents](#contents)
5459+
5460+
<a id="recipe-css-style-sheet" name="recipe-css-style-sheet"></a>
5461+
5462+
## How to import CSS stylesheet in JS
5463+
5464+
Using the `css-loader` option [exportType](https://github.com/webpack-contrib/css-loader?#exporttype) as `css-style-sheet`
5465+
you can import the CSS stylesheets as the instance of the [CSSStyleSheet](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet) object.
5466+
5467+
Import a CSS module script and apply it to a document or a shadow root like this:
5468+
5469+
```js
5470+
import sheet from './style.scss?sheet';
5471+
5472+
document.adoptedStyleSheets = [sheet];
5473+
shadowRoot.adoptedStyleSheets = [sheet];
5474+
```
5475+
5476+
You can use the `?sheet` URL query to import a style file as stylesheets.
5477+
The query must be configured in the webpack config:
5478+
5479+
```js
5480+
module.exports = {
5481+
plugins: [
5482+
new HtmlBundlerPlugin({
5483+
entry: {
5484+
index: './src/index.html',
5485+
},
5486+
js: {
5487+
filename: '[name].[contenthash:8].js',
5488+
},
5489+
css: {
5490+
filename: '[name].[contenthash:8].css',
5491+
},
5492+
}),
5493+
],
5494+
module: {
5495+
rules: [
5496+
{
5497+
test: /\.(s?css)$/,
5498+
oneOf: [
5499+
// Import CSS/SCSS source file as a CSSStyleSheet object
5500+
{
5501+
resourceQuery: /sheet/, // <= the query, e.g. style.scss?sheet
5502+
use: [
5503+
{
5504+
loader: 'css-loader',
5505+
options: {
5506+
exportType: 'css-style-sheet', // <= define this option
5507+
},
5508+
},
5509+
{
5510+
loader: 'sass-loader',
5511+
},
5512+
],
5513+
},
5514+
// Import CSS/SCSS source file as a CSS string
5515+
{
5516+
use: [
5517+
'css-loader',
5518+
'sass-loader',
5519+
],
5520+
}
5521+
],
5522+
}
5523+
],
5524+
},
5525+
};
5526+
```
5527+
5528+
Using the universal configuration above you can apply CSS stylesheets in JS and extract CSS into separate file or inject CSS into HTML:
5529+
5530+
```js
5531+
import sheet from './style.scss?sheet'; // import as CSSStyleSheet object
5532+
import './style2.scss?inline'; // the extracted CSS will be injected into HTML
5533+
import './style3.scss'; // the extracted CSS will be saved into separate output file
5534+
5535+
// apply stylesheet to document and shadow root
5536+
document.adoptedStyleSheets = [sheet];
5537+
shadowRoot.adoptedStyleSheets = [sheet];
5538+
```
5539+
5540+
This is useful for [custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) and shadow DOM.
5541+
5542+
More information:
5543+
5544+
- [Using CSS Module Scripts to import stylesheets](https://web.dev/css-module-scripts/)
5545+
- [Constructable Stylesheets: seamless reusable styles](https://developers.google.com/web/updates/2019/02/constructable-stylesheets)
54565546
54575547
---
54585548
@@ -6067,7 +6157,7 @@ dist/js/app-5fa74877.1aceb2db.js
60676157
60686158
Using the bundler plugin, all your style source files should be specified directly in the template.
60696159
You can import style files in JavaScript, like it works using the `mini-css-extract-plugin` and `html-webpack-plugin`,
6070-
but it is a **dirty hack**, **bad practice**, processing is **slow**, avoid it if possible.
6160+
but it is a **bad practice** and processing is **slower**.
60716161
60726162
You can separate the styles into multiple bundles yourself.
60736163

package-lock.json

Lines changed: 44 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "html-bundler-webpack-plugin",
3-
"version": "3.11.0",
3+
"version": "3.12.0",
44
"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.",
55
"keywords": [
66
"html",
@@ -20,10 +20,12 @@
2020
"twig",
2121
"twigjs",
2222
"integrity",
23-
"style",
23+
"js",
2424
"javascript",
2525
"css",
26-
"scss"
26+
"scss",
27+
"style",
28+
"stylesheet"
2729
],
2830
"license": "ISC",
2931
"author": "webdiscus (https://github.com/webdiscus)",

src/Plugin/AssetCompiler.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -580,9 +580,10 @@ class AssetCompiler {
580580

581581
// the filename with an extension is available only after resolve
582582
meta.isStyle = Option.isStyle(file);
583+
meta.isCSSStyleSheet = this.isCSSStyleSheet(createData);
583584

584585
// skip: module loaded via importModule, css url, data-URL
585-
if (meta.isLoaderImport || meta.isDependencyUrl || request.startsWith('data:')) return;
586+
if (meta.isLoaderImport || meta.isCSSStyleSheet || meta.isDependencyUrl || request.startsWith('data:')) return;
586587

587588
if (issuer) {
588589
const isIssuerStyle = Option.isStyle(issuer);
@@ -642,6 +643,21 @@ class AssetCompiler {
642643
meta.isScript = Collection.hasScript(request);
643644
}
644645

646+
/**
647+
* Whether the module is imported CSSStyleSheet in JS.
648+
*
649+
* @param {{}} module
650+
* @return {boolean}
651+
*/
652+
isCSSStyleSheet(module) {
653+
return (
654+
Array.isArray(module.loaders) &&
655+
module?.loaders.some(
656+
(loader) => loader.loader.includes('css-loader') && loader.options?.exportType === 'css-style-sheet'
657+
)
658+
);
659+
}
660+
645661
/**
646662
* Returns unique style loaders only.
647663
*
@@ -812,12 +828,13 @@ class AssetCompiler {
812828

813829
for (const module of chunkModules) {
814830
const { buildInfo, resource, resourceResolveData } = module;
815-
const { isScript, isImportedStyle } = resourceResolveData?._bundlerPluginMeta || {};
831+
const { isScript, isImportedStyle, isCSSStyleSheet } = resourceResolveData?._bundlerPluginMeta || {};
816832
let moduleType = module.type;
817833

818834
if (
819835
isScript ||
820836
isImportedStyle ||
837+
isCSSStyleSheet ||
821838
!resource ||
822839
!resourceResolveData?.context ||
823840
AssetInline.isDataUrl(resource)
@@ -959,7 +976,10 @@ class AssetCompiler {
959976

960977
// extract CSS
961978
const cssOptions = Option.getStyleOptions(sourceFile);
962-
if (cssOptions == null) return;
979+
if (cssOptions == null) {
980+
// ignore file if css option is disabled
981+
return;
982+
}
963983

964984
const inline = Collection.isInlineStyle(resource);
965985
const { name } = path.parse(sourceFile);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Test</title>
5+
<link href="style-a.22451d9b.css" rel="stylesheet" />
6+
<style>.red {
7+
border: 5px solid red;
8+
color: indianred;
9+
}
10+
</style>
11+
<link href="main.dc4ea4af.css" rel="stylesheet">
12+
<script src="main.cc0cc899.js" async defer="defer"></script>
13+
</head>
14+
<body>
15+
<h1>Hello World!</h1>
16+
<div class="red">Red</div>
17+
<div class="lime-green">Green</div>
18+
<div class="royal-blue">Blue</div>
19+
</body>
20+
</html>

test/cases/js-import-css-modules-type-css-style-sheet-mix/expected/main.cc0cc899.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.royal-blue {
2+
border: 5px solid royalblue;
3+
color: royalblue;
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
h1 {
2+
color: coral;
3+
font-size: 20px;
4+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Test</title>
5+
<link href="./style-a.css" rel="stylesheet" />
6+
<link href="./style-b.css?inline" rel="stylesheet" />
7+
<script src="./main.js" async defer="defer"></script>
8+
</head>
9+
<body>
10+
<h1>Hello World!</h1>
11+
<div class="red">Red</div>
12+
<div class="lime-green">Green</div>
13+
<div class="royal-blue">Blue</div>
14+
</body>
15+
</html>

0 commit comments

Comments
 (0)