Merge pull request #716 from vector-im/vite-plugin-dev

Theming - Support theming in dev server
This commit is contained in:
R Midhun Suresh 2022-04-13 14:20:04 +05:30 committed by GitHub
commit 366e75b242
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const path = require('path');
async function readCSSSource(location) { async function readCSSSource(location) {
const fs = require("fs").promises; const fs = require("fs").promises;
@ -22,8 +23,12 @@ async function readCSSSource(location) {
return data; return data;
} }
async function appendVariablesToCSS(variables, cssSource) { function getRootSectionWithVariables(variables) {
return cssSource + `:root{\n${Object.entries(variables).reduce((acc, [key, value]) => acc + `--${key}: ${value};\n`, "")} }\n\n`; 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 parseBundle(bundle) { function parseBundle(bundle) {
@ -68,21 +73,30 @@ function parseBundle(bundle) {
module.exports = function buildThemes(options) { module.exports = function buildThemes(options) {
let manifest, variants, defaultDark, defaultLight; let manifest, variants, defaultDark, defaultLight;
let isDevelopment = false;
const virtualModuleId = '@theme/'
const resolvedVirtualModuleId = '\0' + virtualModuleId;
return { return {
name: "build-themes", name: "build-themes",
enforce: "pre", enforce: "pre",
configResolved(config) {
if (config.command === "serve") {
isDevelopment = true;
}
},
async buildStart() { async buildStart() {
const { manifestLocations } = options; if (isDevelopment) { return; }
for (const location of manifestLocations) { const { themeConfig } = options;
for (const [name, location] of Object.entries(themeConfig.themes)) {
manifest = require(`${location}/manifest.json`); manifest = require(`${location}/manifest.json`);
variants = manifest.values.variants; variants = manifest.values.variants;
const themeName = manifest.name;
for (const [variant, details] of Object.entries(variants)) { for (const [variant, details] of Object.entries(variants)) {
const fileName = `theme-${themeName}-${variant}.css`; const fileName = `theme-${name}-${variant}.css`;
if (details.default) { if (name === themeConfig.default && details.default) {
// This is one of the default variants for this theme. // This is the default theme, stash the file name for later
if (details.dark) { if (details.dark) {
defaultDark = fileName; defaultDark = fileName;
} }
@ -93,7 +107,7 @@ module.exports = function buildThemes(options) {
// emit the css as built theme bundle // emit the css as built theme bundle
this.emitFile({ this.emitFile({
type: "chunk", type: "chunk",
id: `${location}/theme.css?variant=${variant}`, id: `${location}/theme.css?variant=${variant}${details.dark? "&dark=true": ""}`,
fileName, fileName,
}); });
} }
@ -101,23 +115,89 @@ module.exports = function buildThemes(options) {
this.emitFile({ this.emitFile({
type: "chunk", type: "chunk",
id: `${location}/theme.css?type=runtime`, id: `${location}/theme.css?type=runtime`,
fileName: `theme-${themeName}-runtime.css`, fileName: `theme-${name}-runtime.css`,
}); });
} }
}, },
resolveId(id) {
if (id.startsWith(virtualModuleId)) {
return '\0' + id;
}
},
async load(id) { async load(id) {
const result = id.match(/(.+)\/theme.css\?variant=(.+)/); if (isDevelopment) {
/**
* To load the theme during dev, we need to take a different approach because emitFile is not supported in dev.
* We solve this by resolving virtual file "@theme/name/variant" into the necessary css import.
* This virtual file import is removed when hydrogen is built (see transform hook).
*/
if (id.startsWith(resolvedVirtualModuleId)) {
let [theme, variant, file] = id.substr(resolvedVirtualModuleId.length).split("/");
if (theme === "default") {
theme = options.themeConfig.default;
}
const location = options.themeConfig.themes[theme];
const manifest = require(`${location}/manifest.json`);
const variants = manifest.values.variants;
if (!variant || variant === "default") {
// choose the first default variant for now
// this will need to support light/dark variants as well
variant = Object.keys(variants).find(variantName => variants[variantName].default);
}
if (!file) {
file = "index.js";
}
switch (file) {
case "index.js": {
const isDark = variants[variant].dark;
return `import "${path.resolve(`${location}/theme.css`)}${isDark? "?dark=true": ""}";` +
`import "@theme/${theme}/${variant}/variables.css"`;
}
case "variables.css": {
const variables = variants[variant].variables;
const css = getRootSectionWithVariables(variables);
return css;
}
}
}
}
else {
const result = id.match(/(.+)\/theme.css\?variant=([^&]+)/);
if (result) { if (result) {
const [, location, variant] = result; const [, location, variant] = result;
const cssSource = await readCSSSource(location); const cssSource = await readCSSSource(location);
const config = variants[variant]; const config = variants[variant];
return await appendVariablesToCSS(config.variables, cssSource); return appendVariablesToCSS(config.variables, cssSource);
} }
return null; return null;
}
},
transform(code, id) {
if (isDevelopment) {
return;
}
/**
* Removes develop-only script tag; this cannot be done in transformIndexHtml hook because
* by the time that hook runs, the import is added to the bundled js file which would
* result in a runtime error.
*/
const devScriptTag =
/<script type="module"> import "@theme\/.+"; <\/script>/;
if (id.endsWith("index.html")) {
const htmlWithoutDevScript = code.replace(devScriptTag, "");
return htmlWithoutDevScript;
}
}, },
transformIndexHtml(_, ctx) { transformIndexHtml(_, ctx) {
if (isDevelopment) {
// Don't add default stylesheets to index.html on dev
return;
}
let darkThemeLocation, lightThemeLocation; let darkThemeLocation, lightThemeLocation;
for (const [, bundle] of Object.entries(ctx.bundle)) { for (const [, bundle] of Object.entries(ctx.bundle)) {
if (bundle.name === defaultDark) { if (bundle.name === defaultDark) {
@ -147,7 +227,7 @@ module.exports = function buildThemes(options) {
} }
}, },
]; ];
}, },
generateBundle(_, bundle) { generateBundle(_, bundle) {
const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle);
@ -169,6 +249,6 @@ module.exports = function buildThemes(options) {
source: JSON.stringify(manifest), source: JSON.stringify(manifest),
}); });
} }
} },
} }
} }