Skip to content

Commit

Permalink
feat: add support for css-loader option exportType as css-style-sheet
Browse files Browse the repository at this point in the history
  • Loading branch information
webdiscus committed May 19, 2024
1 parent 33330bc commit acf88db
Show file tree
Hide file tree
Showing 23 changed files with 398 additions and 10 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
94 changes: 92 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
<a id="recipe-css-style-sheet" name="recipe-css-style-sheet"></a>
## 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)
---
Expand Down Expand Up @@ -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.
Expand Down
46 changes: 44 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -20,10 +20,12 @@
"twig",
"twigjs",
"integrity",
"style",
"js",
"javascript",
"css",
"scss"
"scss",
"style",
"stylesheet"
],
"license": "ISC",
"author": "webdiscus (https://github.com/webdiscus)",
Expand Down
26 changes: 23 additions & 3 deletions src/Plugin/AssetCompiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<link href="style-a.22451d9b.css" rel="stylesheet" />
<style>.red {
border: 5px solid red;
color: indianred;
}
</style>
<link href="main.dc4ea4af.css" rel="stylesheet">
<script src="main.cc0cc899.js" async defer="defer"></script>
</head>
<body>
<h1>Hello World!</h1>
<div class="red">Red</div>
<div class="lime-green">Green</div>
<div class="royal-blue">Blue</div>
</body>
</html>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.royal-blue {
border: 5px solid royalblue;
color: royalblue;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
h1 {
color: coral;
font-size: 20px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<link href="./style-a.css" rel="stylesheet" />
<link href="./style-b.css?inline" rel="stylesheet" />
<script src="./main.js" async defer="defer"></script>
</head>
<body>
<h1>Hello World!</h1>
<div class="red">Red</div>
<div class="lime-green">Green</div>
<div class="royal-blue">Blue</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
h1 {
color: coral;
font-size: 20px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.red {
border: 5px solid red;
color: indianred;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.lime-green {
border: 5px solid limegreen;
color: limegreen;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.royal-blue {
border: 5px solid royalblue;
color: royalblue;
}
Loading

0 comments on commit acf88db

Please sign in to comment.