diff --git a/README.md b/README.md index 1c0905b0..bf483fab 100644 --- a/README.md +++ b/README.md @@ -911,6 +911,150 @@ module.exports = { }; ``` +### Multiple Themes + +**webpack.config.js** + +```js +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); + +module.exports = { + entry: "./src/index.js", + module: { + rules: [ + { + test: /\.s[ac]ss$/i, + oneOf: [ + { + resourceQuery: "?dark", + use: [ + Self.loader, + "css-loader", + { + loader: "sass-loader", + options: { + additionalData: `@use 'dark-theme/vars' as vars;`, + }, + }, + ], + }, + { + use: [ + Self.loader, + "css-loader", + { + loader: "sass-loader", + options: { + additionalData: `@use 'light-theme/vars' as vars;`, + }, + }, + ], + }, + ], + }, + ], + }, + plugins: [ + new Self({ + filename: "[name].css", + attributes: { + id: "theme", + }, + }), + ], +}; +``` + +**src/index.js** + +```js +import "./style.scss"; + +let theme = "light"; +const themes = {}; + +themes[theme] = document.querySelector("#theme"); + +async function loadTheme(newTheme) { + // eslint-disable-next-line no-console + console.log(`CHANGE THEME - ${newTheme}`); + + const themeElement = document.querySelector("#theme"); + + if (themeElement) { + themeElement.remove(); + } + + if (themes[newTheme]) { + // eslint-disable-next-line no-console + console.log(`THEME ALREADY LOADED - ${newTheme}`); + + document.head.appendChild(themes[newTheme]); + + return; + } + + if (newTheme === "dark") { + // eslint-disable-next-line no-console + console.log(`LOADING THEME - ${newTheme}`); + + import(/* webpackChunkName: "dark" */ "./style.scss?dark").then(() => { + themes[newTheme] = document.querySelector("#theme"); + + // eslint-disable-next-line no-console + console.log(`LOADED - ${newTheme}`); + }); + } +} + +document.onclick = () => { + if (theme === "light") { + theme = "dark"; + } else { + theme = "light"; + } + + loadTheme(theme); +}; +``` + +**src/dark-theme/\_vars.scss** + +```scss +$background: black; +``` + +**src/light-theme/\_vars.scss** + +```scss +$background: white; +``` + +**src/styles.scss** + +```scss +body { + background-color: vars.$background; +} +``` + +**public/index.html** + +```html + + + + + + Document + + + + + + +``` + ### Media Query Plugin If you'd like to extract the media queries from the extracted CSS (so mobile users don't need to load desktop or tablet specific CSS anymore) you should use one of the following plugins: diff --git a/package-lock.json b/package-lock.json index 25ab70db..6a8cb0cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,8 @@ "memfs": "^3.0.2", "npm-run-all": "^4.1.5", "prettier": "^2.3.2", + "sass": "^1.39.0", + "sass-loader": "^12.1.0", "standard-version": "^9.3.0", "webpack": "^5.48.0", "webpack-cli": "^4.7.2", @@ -11633,6 +11635,15 @@ "node": ">=6" } }, + "node_modules/klona": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", + "integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -14776,6 +14787,55 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/sass": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.39.0.tgz", + "integrity": "sha512-F4o+RhJkNOIG0b6QudYU8c78ZADKZjKDk5cyrf8XTKWfrgbtyVVXImFstJrc+1pkQDCggyidIOytq6gS4gCCZg==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/sass-loader": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.1.0.tgz", + "integrity": "sha512-FVJZ9kxVRYNZTIe2xhw93n3xJNYZADr+q69/s98l9nTCrWASo+DR2Ot0s5xTKQDDEosUkatsGeHxcH4QBp5bSg==", + "dev": true, + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0", + "sass": "^1.3.0", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, "node_modules/saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -26188,6 +26248,12 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, + "klona": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", + "integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==", + "dev": true + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -28557,6 +28623,25 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "sass": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.39.0.tgz", + "integrity": "sha512-F4o+RhJkNOIG0b6QudYU8c78ZADKZjKDk5cyrf8XTKWfrgbtyVVXImFstJrc+1pkQDCggyidIOytq6gS4gCCZg==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0" + } + }, + "sass-loader": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.1.0.tgz", + "integrity": "sha512-FVJZ9kxVRYNZTIe2xhw93n3xJNYZADr+q69/s98l9nTCrWASo+DR2Ot0s5xTKQDDEosUkatsGeHxcH4QBp5bSg==", + "dev": true, + "requires": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + } + }, "saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", diff --git a/package.json b/package.json index 947fc76a..a6715cb9 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,8 @@ "memfs": "^3.0.2", "npm-run-all": "^4.1.5", "prettier": "^2.3.2", + "sass": "^1.39.0", + "sass-loader": "^12.1.0", "standard-version": "^9.3.0", "webpack": "^5.48.0", "webpack-cli": "^4.7.2", diff --git a/test/cases/multiple-themes-async-loading-with-default-light/expected/dark.css b/test/cases/multiple-themes-async-loading-with-default-light/expected/dark.css new file mode 100644 index 00000000..c4d554e0 --- /dev/null +++ b/test/cases/multiple-themes-async-loading-with-default-light/expected/dark.css @@ -0,0 +1,3 @@ +body { + background-color: black; +} diff --git a/test/cases/multiple-themes-async-loading-with-default-light/expected/main.css b/test/cases/multiple-themes-async-loading-with-default-light/expected/main.css new file mode 100644 index 00000000..cdf802a3 --- /dev/null +++ b/test/cases/multiple-themes-async-loading-with-default-light/expected/main.css @@ -0,0 +1,3 @@ +body { + background-color: white; +} diff --git a/test/cases/multiple-themes-async-loading-with-default-light/public/index.html b/test/cases/multiple-themes-async-loading-with-default-light/public/index.html new file mode 100644 index 00000000..061216f2 --- /dev/null +++ b/test/cases/multiple-themes-async-loading-with-default-light/public/index.html @@ -0,0 +1,12 @@ + + + + + + Document + + + + + + \ No newline at end of file diff --git a/test/cases/multiple-themes-async-loading-with-default-light/src/dark-theme/_vars.scss b/test/cases/multiple-themes-async-loading-with-default-light/src/dark-theme/_vars.scss new file mode 100644 index 00000000..2abe50c6 --- /dev/null +++ b/test/cases/multiple-themes-async-loading-with-default-light/src/dark-theme/_vars.scss @@ -0,0 +1 @@ +$background: black; diff --git a/test/cases/multiple-themes-async-loading-with-default-light/src/index.js b/test/cases/multiple-themes-async-loading-with-default-light/src/index.js new file mode 100644 index 00000000..cce9cc7c --- /dev/null +++ b/test/cases/multiple-themes-async-loading-with-default-light/src/index.js @@ -0,0 +1,50 @@ +/* eslint-env browser */ +import "./style.scss"; + +let theme = "light"; +const themes = {}; + +themes[theme] = document.querySelector("#theme"); + +async function loadTheme(newTheme) { + // eslint-disable-next-line no-console + console.log(`CHANGE THEME - ${newTheme}`); + + const themeElement = document.querySelector("#theme"); + + if (themeElement) { + themeElement.remove(); + } + + if (themes[newTheme]) { + // eslint-disable-next-line no-console + console.log(`THEME ALREADY LOADED - ${newTheme}`); + + document.head.appendChild(themes[newTheme]); + + return; + } + + if (newTheme === "dark") { + // eslint-disable-next-line no-console + console.log(`LOADING THEME - ${newTheme}`); + + // eslint-disable-next-line import/no-unresolved + import(/* webpackChunkName: "dark" */ "./style.scss?dark").then(() => { + themes[newTheme] = document.querySelector("#theme"); + + // eslint-disable-next-line no-console + console.log(`LOADED - ${newTheme}`); + }); + } +} + +document.onclick = () => { + if (theme === "light") { + theme = "dark"; + } else { + theme = "light"; + } + + loadTheme(theme); +}; diff --git a/test/cases/multiple-themes-async-loading-with-default-light/src/light-theme/_vars.scss b/test/cases/multiple-themes-async-loading-with-default-light/src/light-theme/_vars.scss new file mode 100644 index 00000000..6a1fb84b --- /dev/null +++ b/test/cases/multiple-themes-async-loading-with-default-light/src/light-theme/_vars.scss @@ -0,0 +1 @@ +$background: white; diff --git a/test/cases/multiple-themes-async-loading-with-default-light/src/style.scss b/test/cases/multiple-themes-async-loading-with-default-light/src/style.scss new file mode 100644 index 00000000..fb281175 --- /dev/null +++ b/test/cases/multiple-themes-async-loading-with-default-light/src/style.scss @@ -0,0 +1,3 @@ +body { + background-color: vars.$background; +} diff --git a/test/cases/multiple-themes-async-loading-with-default-light/webpack.config.js b/test/cases/multiple-themes-async-loading-with-default-light/webpack.config.js new file mode 100644 index 00000000..58c979cd --- /dev/null +++ b/test/cases/multiple-themes-async-loading-with-default-light/webpack.config.js @@ -0,0 +1,47 @@ +import Self from "../../../src"; + +module.exports = { + entry: "./src/index.js", + module: { + rules: [ + { + test: /\.s[ac]ss$/i, + oneOf: [ + { + resourceQuery: "?dark", + use: [ + Self.loader, + "css-loader", + { + loader: "sass-loader", + options: { + additionalData: `@use 'dark-theme/vars' as vars;`, + }, + }, + ], + }, + { + use: [ + Self.loader, + "css-loader", + { + loader: "sass-loader", + options: { + additionalData: `@use 'light-theme/vars' as vars;`, + }, + }, + ], + }, + ], + }, + ], + }, + plugins: [ + new Self({ + filename: "[name].css", + attributes: { + id: "theme", + }, + }), + ], +}; diff --git a/test/cases/multiple-themes-async-loading/expected/dark.css b/test/cases/multiple-themes-async-loading/expected/dark.css new file mode 100644 index 00000000..c4d554e0 --- /dev/null +++ b/test/cases/multiple-themes-async-loading/expected/dark.css @@ -0,0 +1,3 @@ +body { + background-color: black; +} diff --git a/test/cases/multiple-themes-async-loading/expected/light.css b/test/cases/multiple-themes-async-loading/expected/light.css new file mode 100644 index 00000000..cdf802a3 --- /dev/null +++ b/test/cases/multiple-themes-async-loading/expected/light.css @@ -0,0 +1,3 @@ +body { + background-color: white; +} diff --git a/test/cases/multiple-themes-async-loading/public/index.html b/test/cases/multiple-themes-async-loading/public/index.html new file mode 100644 index 00000000..c51cce96 --- /dev/null +++ b/test/cases/multiple-themes-async-loading/public/index.html @@ -0,0 +1,11 @@ + + + + + + Document + + + + + \ No newline at end of file diff --git a/test/cases/multiple-themes-async-loading/src/dark-theme/_vars.scss b/test/cases/multiple-themes-async-loading/src/dark-theme/_vars.scss new file mode 100644 index 00000000..2abe50c6 --- /dev/null +++ b/test/cases/multiple-themes-async-loading/src/dark-theme/_vars.scss @@ -0,0 +1 @@ +$background: black; diff --git a/test/cases/multiple-themes-async-loading/src/index.js b/test/cases/multiple-themes-async-loading/src/index.js new file mode 100644 index 00000000..8c016077 --- /dev/null +++ b/test/cases/multiple-themes-async-loading/src/index.js @@ -0,0 +1,58 @@ +/* eslint-env browser */ + +let theme = "light"; + +const themes = {}; + +async function loadTheme(newTheme) { + // eslint-disable-next-line no-console + console.log(`CHANGE THEME - ${newTheme}`); + + const themeElement = document.querySelector("#theme"); + + if (themeElement) { + themeElement.remove(); + } + + if (themes[newTheme]) { + // eslint-disable-next-line no-console + // eslint-disable-next-line no-console + console.log(`THEME ALREADY LOADED - ${newTheme}`); + + document.head.appendChild(themes[newTheme]); + + return; + } + + // eslint-disable-next-line no-console + console.log(`LOADING THEME - ${newTheme}`); + + if (newTheme === "light") { + import(/* webpackChunkName: "light" */ "./style.scss").then(() => { + themes[newTheme] = document.querySelector("#theme"); + + // eslint-disable-next-line no-console + console.log(`LOADED - ${newTheme}`); + }); + } else { + // eslint-disable-next-line import/no-unresolved + import(/* webpackChunkName: "dark" */ "./style.scss?dark").then(() => { + themes[newTheme] = document.querySelector("#theme"); + + // eslint-disable-next-line no-console + console.log(`LOADED - ${newTheme}`); + }); + } +} + +document.onclick = () => { + if (theme === "light") { + theme = "dark"; + } else { + theme = "light"; + } + + loadTheme(theme); +}; + +loadTheme(theme); diff --git a/test/cases/multiple-themes-async-loading/src/light-theme/_vars.scss b/test/cases/multiple-themes-async-loading/src/light-theme/_vars.scss new file mode 100644 index 00000000..6a1fb84b --- /dev/null +++ b/test/cases/multiple-themes-async-loading/src/light-theme/_vars.scss @@ -0,0 +1 @@ +$background: white; diff --git a/test/cases/multiple-themes-async-loading/src/style.scss b/test/cases/multiple-themes-async-loading/src/style.scss new file mode 100644 index 00000000..fb281175 --- /dev/null +++ b/test/cases/multiple-themes-async-loading/src/style.scss @@ -0,0 +1,3 @@ +body { + background-color: vars.$background; +} diff --git a/test/cases/multiple-themes-async-loading/webpack.config.js b/test/cases/multiple-themes-async-loading/webpack.config.js new file mode 100644 index 00000000..58c979cd --- /dev/null +++ b/test/cases/multiple-themes-async-loading/webpack.config.js @@ -0,0 +1,47 @@ +import Self from "../../../src"; + +module.exports = { + entry: "./src/index.js", + module: { + rules: [ + { + test: /\.s[ac]ss$/i, + oneOf: [ + { + resourceQuery: "?dark", + use: [ + Self.loader, + "css-loader", + { + loader: "sass-loader", + options: { + additionalData: `@use 'dark-theme/vars' as vars;`, + }, + }, + ], + }, + { + use: [ + Self.loader, + "css-loader", + { + loader: "sass-loader", + options: { + additionalData: `@use 'light-theme/vars' as vars;`, + }, + }, + ], + }, + ], + }, + ], + }, + plugins: [ + new Self({ + filename: "[name].css", + attributes: { + id: "theme", + }, + }), + ], +};