diff --git a/README.md b/README.md index 589b1e4a..07352328 100644 --- a/README.md +++ b/README.md @@ -527,7 +527,7 @@ See [boilerplate](https://github.com/webdiscus/webpack-html-scss-boilerplate) - [How to inline JS in HTML](#recipe-inline-js) - [How to inline SVG, PNG images in HTML](#recipe-inline-image) - [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) + - [How to load CSS file dynamically](#recipe-dynamic-load-css) (lazy loading CSS) - [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) @@ -2417,7 +2417,7 @@ Possible values: - `false` - disable minification - `true` - enable minification with default options -- `auto` - in `development` mode disable minification, in `production` mode enable minification with default options, +- `'auto'` - in `development` mode disable minification, in `production` mode enable minification with default options, use [minifyOptions](#option-minify-options) to customize options - `{}` - enable minification with custom options, this object are merged with `default options`\ see [options reference](https://github.com/terser/html-minifier-terser#options-quick-reference) @@ -2428,7 +2428,7 @@ Possible values: Type: `Object` Default: `null` -When the [minify](#option-minify) option is set to `auto`, you can configure minification options using the `minifyOptions`. +When the [minify](#option-minify) option is set to `'auto'` or `true`, you can configure minification options using the `minifyOptions`. #### [↑ back to contents](#contents) @@ -5209,26 +5209,47 @@ The custom attribute will contains in the generated HTML the resolved output ass ## How to load CSS file dynamically For dynamic file loading, we need the output filename of extracted CSS from a source style file. -To get the CSS output filename in JavaScript, you can use the `url` query in `require()` function: +To get the CSS output filename in JavaScript, you can use the `url` query: ```js -const cssFile = require('./style.scss?url'); +import cssUrl from './style.scss?url'; +// - OR - +const cssUrl = require('./style.scss?url'); ``` Where the `./style.scss` is the source SCSS file relative to the JavaScript file. -To load a CSS file dynamically, you can use the function: +To load a CSS file dynamically, you can use following function: ```js -function loadCSS(file) { +import cssUrl from './style.scss?url'; + +function loadCSS(url) { const style = document.createElement('link'); - style.href = file; + style.href = url; style.rel = 'stylesheet'; document.head.appendChild(style); } -const cssFile = require('./style.scss?url'); -loadCSS(cssFile); +loadCSS(cssUrl); ``` -The CSS will be extracted into separate file and the `cssFile` variable will contains the CSS output filename. +The CSS will be extracted into separate file and the `cssUrl` variable will contains the CSS output filename. + +Since 2023, many browsers support the modern way to add the stylesheets into DOM without creating the `link` tag. + +```js +import cssUrl from './style.scss?url'; + +async function loadCSS(url) { + const response = await fetch(url); + const css = await response.text(); + const sheet = new CSSStyleSheet(); + sheet.replaceSync(css); + document.adoptedStyleSheets = [sheet]; +} + +loadCSS(cssUrl); +``` + +See the [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets#browser_compatibility). #### [↑ back to contents](#contents) diff --git a/test/cases/entry-multiple-chunks-same-filename/webpack.config.js b/test/cases/entry-multiple-chunks-same-filename/webpack.config.js index 555e2103..df53a860 100644 --- a/test/cases/entry-multiple-chunks-same-filename/webpack.config.js +++ b/test/cases/entry-multiple-chunks-same-filename/webpack.config.js @@ -11,10 +11,10 @@ module.exports = { entry: { // Test case multiple chunks with same filename: // - the `main.css` file is defined here, in entry point - // - the same `main.css` file is defined in `index.pug` + // - the same `main.css` file is defined in `index.html` // Note: this use case has no sense and should not be used! - // Specify all scripts and styles directly in Pug. + // Specify all scripts and styles directly in HTML. index: './src/index.html', diff --git a/test/cases/js-import-css-lazy-url-fetch/expected/favicon.edda23bf.png b/test/cases/js-import-css-lazy-url-fetch/expected/favicon.edda23bf.png new file mode 100644 index 00000000..0eb28164 Binary files /dev/null and b/test/cases/js-import-css-lazy-url-fetch/expected/favicon.edda23bf.png differ diff --git a/test/cases/js-import-css-lazy-url-fetch/expected/index.html b/test/cases/js-import-css-lazy-url-fetch/expected/index.html new file mode 100644 index 00000000..ceff3759 --- /dev/null +++ b/test/cases/js-import-css-lazy-url-fetch/expected/index.html @@ -0,0 +1,13 @@ + + +
+Lazy load CSS
+ + \ No newline at end of file diff --git a/test/cases/js-import-css-lazy-url-fetch/expected/main.js b/test/cases/js-import-css-lazy-url-fetch/expected/main.js new file mode 100644 index 00000000..62bfcbc6 --- /dev/null +++ b/test/cases/js-import-css-lazy-url-fetch/expected/main.js @@ -0,0 +1 @@ +(()=>{"use strict";var t={};t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),(()=>{var e;t.g.importScripts&&(e=t.g.location+"");var r=t.g.document;if(!e&&r&&(r.currentScript&&(e=r.currentScript.src),!e)){var c=r.getElementsByTagName("script");if(c.length)for(var n=c.length-1;n>-1&&!e;)e=c[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),t.p=e})();const e=t.p+"style-dynamic.1f7039c8.css";!async function(t){const r=await fetch(t),c=await r.text(),n=new CSSStyleSheet;n.replaceSync(c),document.adoptedStyleSheets=[n],console.log("lazyLoad3: ",{cssUrl:e})}(e)})(); \ No newline at end of file diff --git a/test/cases/js-import-css-lazy-url-fetch/expected/style-dynamic.1f7039c8.css b/test/cases/js-import-css-lazy-url-fetch/expected/style-dynamic.1f7039c8.css new file mode 100644 index 00000000..72aa6df3 --- /dev/null +++ b/test/cases/js-import-css-lazy-url-fetch/expected/style-dynamic.1f7039c8.css @@ -0,0 +1 @@ +/* extracted by HTMLBundler CSSLoader */.lazy{color:green} \ No newline at end of file diff --git a/test/cases/js-import-css-lazy-url-fetch/expected/style.7f152e84.css b/test/cases/js-import-css-lazy-url-fetch/expected/style.7f152e84.css new file mode 100644 index 00000000..a881aa29 --- /dev/null +++ b/test/cases/js-import-css-lazy-url-fetch/expected/style.7f152e84.css @@ -0,0 +1 @@ +h1{color:red} \ No newline at end of file diff --git a/test/cases/js-import-css-lazy-url-fetch/src/index.html b/test/cases/js-import-css-lazy-url-fetch/src/index.html new file mode 100644 index 00000000..5baf5750 --- /dev/null +++ b/test/cases/js-import-css-lazy-url-fetch/src/index.html @@ -0,0 +1,13 @@ + + + +Lazy load CSS
+ + \ No newline at end of file diff --git a/test/cases/js-import-css-lazy-url-fetch/src/main.js b/test/cases/js-import-css-lazy-url-fetch/src/main.js new file mode 100644 index 00000000..a765f094 --- /dev/null +++ b/test/cases/js-import-css-lazy-url-fetch/src/main.js @@ -0,0 +1,40 @@ +import cssUrl from './style-dynamic.scss?url'; + +// universal way +function lazyLoad(url) { + const style = document.createElement('link'); + style.rel = 'stylesheet'; + style.href = url; + document.head.appendChild(style); + console.log('lazyLoad: ', { cssUrl }); +} + +// Using fetch and document.adoptedStyleSheets +// Browser compatibility (since early 2023): +// https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets#browser_compatibility + +// modern way, using promise +function lazyLoad2(url) { + fetch(url).then((response) => { + response.text().then((css) => { + const sheet = new CSSStyleSheet(); + sheet.replaceSync(css); + document.adoptedStyleSheets = [sheet]; + console.log('lazyLoad2: ', { cssUrl }); + }); + }); +} + +// modern way, using async await +async function lazyLoad3(url) { + const response = await fetch(url); + const css = await response.text(); + const sheet = new CSSStyleSheet(); + sheet.replaceSync(css); + document.adoptedStyleSheets = [sheet]; + console.log('lazyLoad3: ', { cssUrl }); +} + +//lazyLoad(cssUrl); +//lazyLoad2(cssUrl); +lazyLoad3(cssUrl); diff --git a/test/cases/js-import-css-lazy-url-fetch/src/style-dynamic.scss b/test/cases/js-import-css-lazy-url-fetch/src/style-dynamic.scss new file mode 100644 index 00000000..ba7a214c --- /dev/null +++ b/test/cases/js-import-css-lazy-url-fetch/src/style-dynamic.scss @@ -0,0 +1,4 @@ +$color: green; +.lazy { + color: $color; +} diff --git a/test/cases/js-import-css-lazy-url-fetch/src/style.scss b/test/cases/js-import-css-lazy-url-fetch/src/style.scss new file mode 100644 index 00000000..1cdbcbd6 --- /dev/null +++ b/test/cases/js-import-css-lazy-url-fetch/src/style.scss @@ -0,0 +1,4 @@ +$color: red; +h1 { + color: $color; +} diff --git a/test/cases/js-import-css-lazy-url-fetch/webpack.config.js b/test/cases/js-import-css-lazy-url-fetch/webpack.config.js new file mode 100644 index 00000000..56edf717 --- /dev/null +++ b/test/cases/js-import-css-lazy-url-fetch/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/'), + }, + + resolve: { + alias: { + '@images': path.join(__dirname, '../../fixtures/images'), + }, + }, + + plugins: [ + new HtmlBundlerPlugin({ + entry: { + index: './src/index.html', + }, + css: { + filename: '[name].[contenthash:8].css', + }, + // test disable - OK + preprocessor: false, + // test output of lazy loaded CSS file + verbose: true, + }), + ], + + module: { + rules: [ + { + test: /\.s?css$/, + use: ['css-loader', 'sass-loader'], + }, + { + test: /\.(png|jpe?g|ico|svg)$/, + type: 'asset/resource', + generator: { + filename: '[name].[hash:8][ext]', + }, + }, + ], + }, +}; diff --git a/test/integration.test.js b/test/integration.test.js index 5b7c3bd6..e841ca54 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -335,8 +335,8 @@ describe('import styles in JavaScript', () => { }); describe('import lazy styles in JavaScript', () => { - // TODO: yet experimental, undocumented test('lazy url in js', () => compareFiles('js-import-css-lazy-url')); + test('lazy load CSS in js using fetch', () => compareFiles('js-import-css-lazy-url-fetch')); }); describe('CSS source map', () => {