From ff98ef44655f4c0c7dbf74c0537b289e0979ef36 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 10 Apr 2022 14:49:19 +0530 Subject: [PATCH 1/6] Support theming in dev server --- .../rollup-plugin-build-themes.js | 198 ++++++++++++------ 1 file changed, 137 insertions(+), 61 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 74fe4daf..29fd91e1 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -13,17 +13,45 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ +const path = require('path'); async function readCSSSource(location) { const fs = require("fs").promises; const path = require("path"); - const resolvedLocation = path.resolve(__dirname, "../../", `${location}/theme.css`); + const resolvedLocation = path.resolve(__dirname, "../../", `${location}/theme.css`); const data = await fs.readFile(resolvedLocation); return data; } -async function appendVariablesToCSS(variables, cssSource) { - return cssSource + `:root{\n${Object.entries(variables).reduce((acc, [key, value]) => acc + `--${key}: ${value};\n`, "")} }\n\n`; +function getRootSectionWithVariables(variables) { + return `:root{\n${Object.entries(variables).reduce((acc, [key, value]) => acc + `--${key}: ${value};\n`, "")} }\n\n`; +} + +function appendVariablesToCSS(variables, cssSource) { + return cssSource + getRootSectionWithVariables(variables); +} + +function findLocationFromThemeName(name, locations) { + const themeLocation = locations.find(location => { + const manifest = require(`${location}/manifest.json`); + if (manifest.name === name) { + return true; + } + }); + if (!themeLocation) { + throw new Error(`Cannot find location from theme name "${name}"`); + } + return themeLocation; +} + +function findManifestFromThemeName(name, locations) { + for (const location of locations) { + const manifest = require(`${location}/manifest.json`); + if (manifest.name === name) { + return manifest; + } + } + throw new Error(`Cannot find manifest from theme name "${name}"`); } function parseBundle(bundle) { @@ -68,11 +96,20 @@ function parseBundle(bundle) { module.exports = function buildThemes(options) { let manifest, variants, defaultDark, defaultLight; + let isDevelopment = false; + const virtualModuleId = '@theme/' + const resolvedVirtualModuleId = '\0' + virtualModuleId; return { name: "build-themes", enforce: "pre", + configResolved(config) { + if (config.command === "serve") { + isDevelopment = true; + } + }, + async buildStart() { const { manifestLocations } = options; for (const location of manifestLocations) { @@ -106,69 +143,108 @@ module.exports = function buildThemes(options) { } }, + resolveId(id) { + if (id.startsWith(virtualModuleId)) { + return isDevelopment? '\0' + id: false; + } + }, + async load(id) { - const result = id.match(/(.+)\/theme.css\?variant=(.+)/); - if (result) { - const [, location, variant] = result; - const cssSource = await readCSSSource(location); - const config = variants[variant]; - return await appendVariablesToCSS(config.variables, cssSource); - } - return null; - }, - - transformIndexHtml(_, ctx) { - let darkThemeLocation, lightThemeLocation; - for (const [, bundle] of Object.entries(ctx.bundle)) { - if (bundle.name === defaultDark) { - darkThemeLocation = bundle.fileName; - } - if (bundle.name === defaultLight) { - lightThemeLocation = bundle.fileName; + if (isDevelopment) { + if (id.startsWith(resolvedVirtualModuleId)) { + let [theme, variant, file] = id.substr(resolvedVirtualModuleId.length).split("/"); + if (theme === "default") { + theme = "Element"; + } + if (!variant || variant === "default") { + variant = "light"; + } + if (!file) { + file = "index.js"; + } + switch (file) { + case "index.js": { + const location = findLocationFromThemeName(theme, options.manifestLocations); + return `import "${path.resolve(`${location}/theme.css`)}";` + + `import "@theme/${theme}/${variant}/variables.css"`; + } + case "variables.css": { + const manifest = findManifestFromThemeName(theme, options.manifestLocations); + const variables = manifest.values.variants[variant].variables; + const css = getRootSectionWithVariables(variables); + return css; + } + } } } - return [ - { - tag: "link", - attrs: { - rel: "stylesheet", - type: "text/css", - media: "(prefers-color-scheme: dark)", - href: `./${darkThemeLocation}`, - } - }, - { - tag: "link", - attrs: { - rel: "stylesheet", - type: "text/css", - media: "(prefers-color-scheme: light)", - href: `./${lightThemeLocation}`, - } - }, - ]; - }, + else { + const result = id.match(/(.+)\/theme.css\?variant=(.+)/); + if (result) { + const [, location, variant] = result; + const cssSource = await readCSSSource(location); + const config = variants[variant]; + return await appendVariablesToCSS(config.variables, cssSource); + } + return null; + } +}, - generateBundle(_, bundle) { - const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); - for (const [location, chunkArray] of chunkMap) { - const manifest = require(`${location}/manifest.json`); - const compiledVariables = options.compiledVariables.get(location); - const derivedVariables = compiledVariables["derived-variables"]; - const icon = compiledVariables["icon"]; - manifest.source = { - "built-asset": chunkArray.map(chunk => assetMap.get(chunk.fileName).fileName), - "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, - "derived-variables": derivedVariables, - "icon": icon - }; - const name = `theme-${manifest.name}.json`; - this.emitFile({ - type: "asset", - name, - source: JSON.stringify(manifest), - }); + transformIndexHtml(_, ctx) { + if (isDevelopment) { + // Don't add default stylesheets to index.html on dev + return; + } + let darkThemeLocation, lightThemeLocation; + for (const [, bundle] of Object.entries(ctx.bundle)) { + if (bundle.name === defaultDark) { + darkThemeLocation = bundle.fileName; + } + if (bundle.name === defaultLight) { + lightThemeLocation = bundle.fileName; } } + return [ + { + tag: "link", + attrs: { + rel: "stylesheet", + type: "text/css", + media: "(prefers-color-scheme: dark)", + href: `./${darkThemeLocation}`, + } + }, + { + tag: "link", + attrs: { + rel: "stylesheet", + type: "text/css", + media: "(prefers-color-scheme: light)", + href: `./${lightThemeLocation}`, + } + }, + ]; +}, + +generateBundle(_, bundle) { + const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); + for (const [location, chunkArray] of chunkMap) { + const manifest = require(`${location}/manifest.json`); + const compiledVariables = options.compiledVariables.get(location); + const derivedVariables = compiledVariables["derived-variables"]; + const icon = compiledVariables["icon"]; + manifest.source = { + "built-asset": chunkArray.map(chunk => assetMap.get(chunk.fileName).fileName), + "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, + "derived-variables": derivedVariables, + "icon": icon + }; + const name = `theme-${manifest.name}.json`; + this.emitFile({ + type: "asset", + name, + source: JSON.stringify(manifest), + }); + } +} } } From 0a95eb09405816c0cbd73883eb8b06e8c5d48e9f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 10 Apr 2022 14:52:26 +0530 Subject: [PATCH 2/6] Fix formatting --- .../rollup-plugin-build-themes.js | 110 +++++++++--------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 29fd91e1..eb063c72 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -187,64 +187,64 @@ module.exports = function buildThemes(options) { } return null; } -}, + }, - transformIndexHtml(_, ctx) { - if (isDevelopment) { - // Don't add default stylesheets to index.html on dev - return; - } - let darkThemeLocation, lightThemeLocation; - for (const [, bundle] of Object.entries(ctx.bundle)) { - if (bundle.name === defaultDark) { - darkThemeLocation = bundle.fileName; - } - if (bundle.name === defaultLight) { - lightThemeLocation = bundle.fileName; - } - } - return [ - { - tag: "link", - attrs: { - rel: "stylesheet", - type: "text/css", - media: "(prefers-color-scheme: dark)", - href: `./${darkThemeLocation}`, + transformIndexHtml(_, ctx) { + if (isDevelopment) { + // Don't add default stylesheets to index.html on dev + return; + } + let darkThemeLocation, lightThemeLocation; + for (const [, bundle] of Object.entries(ctx.bundle)) { + if (bundle.name === defaultDark) { + darkThemeLocation = bundle.fileName; } - }, - { - tag: "link", - attrs: { - rel: "stylesheet", - type: "text/css", - media: "(prefers-color-scheme: light)", - href: `./${lightThemeLocation}`, + if (bundle.name === defaultLight) { + lightThemeLocation = bundle.fileName; } - }, - ]; -}, + } + return [ + { + tag: "link", + attrs: { + rel: "stylesheet", + type: "text/css", + media: "(prefers-color-scheme: dark)", + href: `./${darkThemeLocation}`, + } + }, + { + tag: "link", + attrs: { + rel: "stylesheet", + type: "text/css", + media: "(prefers-color-scheme: light)", + href: `./${lightThemeLocation}`, + } + }, + ]; + }, -generateBundle(_, bundle) { - const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); - for (const [location, chunkArray] of chunkMap) { - const manifest = require(`${location}/manifest.json`); - const compiledVariables = options.compiledVariables.get(location); - const derivedVariables = compiledVariables["derived-variables"]; - const icon = compiledVariables["icon"]; - manifest.source = { - "built-asset": chunkArray.map(chunk => assetMap.get(chunk.fileName).fileName), - "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, - "derived-variables": derivedVariables, - "icon": icon - }; - const name = `theme-${manifest.name}.json`; - this.emitFile({ - type: "asset", - name, - source: JSON.stringify(manifest), - }); - } -} + generateBundle(_, bundle) { + const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); + for (const [location, chunkArray] of chunkMap) { + const manifest = require(`${location}/manifest.json`); + const compiledVariables = options.compiledVariables.get(location); + const derivedVariables = compiledVariables["derived-variables"]; + const icon = compiledVariables["icon"]; + manifest.source = { + "built-asset": chunkArray.map(chunk => assetMap.get(chunk.fileName).fileName), + "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, + "derived-variables": derivedVariables, + "icon": icon + }; + const name = `theme-${manifest.name}.json`; + this.emitFile({ + type: "asset", + name, + source: JSON.stringify(manifest), + }); + } + }, } } From 743bd0db1c6496e359561630230d62fd64305046 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 12 Apr 2022 20:39:04 +0530 Subject: [PATCH 3/6] Support dark mode and remove dev script tag --- .../rollup-plugin-build-themes.js | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index eb063c72..14d306e8 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -111,6 +111,7 @@ module.exports = function buildThemes(options) { }, async buildStart() { + if (isDevelopment) { return; } const { manifestLocations } = options; for (const location of manifestLocations) { manifest = require(`${location}/manifest.json`); @@ -130,7 +131,7 @@ module.exports = function buildThemes(options) { // emit the css as built theme bundle this.emitFile({ type: "chunk", - id: `${location}/theme.css?variant=${variant}`, + id: `${location}/theme.css?variant=${variant}${details.dark? "&dark=true": ""}`, fileName, }); } @@ -145,7 +146,7 @@ module.exports = function buildThemes(options) { resolveId(id) { if (id.startsWith(virtualModuleId)) { - return isDevelopment? '\0' + id: false; + return '\0' + id; } }, @@ -165,7 +166,9 @@ module.exports = function buildThemes(options) { switch (file) { case "index.js": { const location = findLocationFromThemeName(theme, options.manifestLocations); - return `import "${path.resolve(`${location}/theme.css`)}";` + + const manifest = findManifestFromThemeName(theme, options.manifestLocations); + const isDark = manifest.values.variants[variant].dark; + return `import "${path.resolve(`${location}/theme.css`)}${isDark? "?dark=true": ""}";` + `import "@theme/${theme}/${variant}/variables.css"`; } case "variables.css": { @@ -178,7 +181,7 @@ module.exports = function buildThemes(options) { } } else { - const result = id.match(/(.+)\/theme.css\?variant=(.+)/); + const result = id.match(/(.+)\/theme.css\?variant=([^&]+)/); if (result) { const [, location, variant] = result; const cssSource = await readCSSSource(location); @@ -189,6 +192,18 @@ module.exports = function buildThemes(options) { } }, + transform(code, id) { + if (isDevelopment) { + return; + } + // Removes develop-only script tag; this cannot be done in transformIndexHtml hook. + const devScriptTag = /