From 07d06c530960e82b19b9dc41a3f07a0165b1e18a Mon Sep 17 00:00:00 2001 From: "[ Cassondra ]" Date: Fri, 26 Jul 2024 10:45:33 -0400 Subject: [PATCH] chore: revert site builder --- .changeset/lazy-walls-pull.md | 5 - .changeset/violet-pugs-return.md | 5 - .github/actions/file-diff/index.js | 383 ++++++++++++++----------- .github/actions/file-diff/utilities.js | 2 +- postcss.config.js | 7 +- tasks/component-builder.js | 35 ++- 6 files changed, 243 insertions(+), 194 deletions(-) delete mode 100644 .changeset/lazy-walls-pull.md delete mode 100644 .changeset/violet-pugs-return.md diff --git a/.changeset/lazy-walls-pull.md b/.changeset/lazy-walls-pull.md deleted file mode 100644 index 9894d89f961..00000000000 --- a/.changeset/lazy-walls-pull.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@spectrum-css/tokens": patch ---- - -Component assets are running through prettier; whitespace is standardized diff --git a/.changeset/violet-pugs-return.md b/.changeset/violet-pugs-return.md deleted file mode 100644 index 83201d6841d..00000000000 --- a/.changeset/violet-pugs-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@spectrum-tools/documentation": patch ---- - -Load minified assets for the documentation site. diff --git a/.github/actions/file-diff/index.js b/.github/actions/file-diff/index.js index 5e3f01d2e92..76fa7e94da0 100644 --- a/.github/actions/file-diff/index.js +++ b/.github/actions/file-diff/index.js @@ -12,7 +12,7 @@ */ const { existsSync } = require("fs"); -const { join, sep, dirname } = require("path"); +const { join, sep, dirname, relative } = require("path"); const core = require("@actions/core"); @@ -36,17 +36,19 @@ async function run() { // --------------- End user input --------------- // --------------- Evaluate compiled assets --------------- - /** @type Map */ + /** @type {Awaited>} */ const headOutput = await fetchFilesAndSizes(headPath, fileGlobPattern, { core, }); + /** * If a diff path is provided, get the diff files and their sizes - * @type Map + * @type {Awaited>} **/ const baseOutput = await fetchFilesAndSizes(basePath, fileGlobPattern, { core, }); + /** * Indicates that there are files we're comparing against * and not just reporting on the overall size of the compiled assets @@ -55,123 +57,160 @@ async function run() { const hasBase = baseOutput.size > 0; // --------------- End evaluation --------------- - /** Split the data by component package */ - const { filePath, PACKAGES } = splitDataByPackage(headOutput, headPath, baseOutput); + /** + * Split the data by component package + * @type {ReturnType} + */ + const { filePath, PACKAGES } = splitDataByPackage(headOutput, baseOutput); + + /** + * Create a detailed table of the changes in the compiled assets + * with a full view of all files present in the head and base branches; + * this will be used to generate the markdown for the comment. + * Sections represent the components in the library. + * @type {ReturnType} + */ const sections = makeTable(PACKAGES, filePath, headPath); + /** + * Calculate the total size of the minified files where applicable, use the regular size + * if the minified file doesn't exist + * @param {Map} contentMap - The map of file names and their sizes + * @returns {number} - The total size of the minified files where applicable + */ + const calculateMinifiedTotal = (contentMap) => [...contentMap.entries()] + .reduce((acc, [filename, size]) => { + // We don't include anything other than css files in the total size + if (!filename.endsWith(".css") || filename.endsWith(".min.css")) return acc; + + // If filename ends with *.css but not *.min.css, add the size of the minified file + if (/\.css$/.test(filename) && !/\.min\.css$/.test(filename)) { + const minified = filename.replace(/\.css$/, ".min.css"); + // Check if the minified file exists in the headOutput + if (headOutput.has(minified)) { + const minSize = headOutput.get(minified); + if (minSize) return acc + minSize; + } else { + // If the minified file doesn't exist, add the size of the css file + return acc + size; + } + } + return acc + size; + }, 0); + /** Calculate the total size of the pull request's assets */ - const overallHeadSize = [...headOutput.values()].reduce( - (acc, size) => acc + size, - 0 - ); + const overallHeadSize = calculateMinifiedTotal(headOutput); - /** Calculate the overall size of the base branch's assets */ - const overallBaseSize = hasBase - ? [...baseOutput.values()].reduce((acc, size) => acc + size, 0) - : undefined; + /** + * Calculate the overall size of the base branch's assets + * if there is a base branch + * @type number + */ + const overallBaseSize = hasBase ? calculateMinifiedTotal(baseOutput) : 0; + /** + * If there is a base branch, check if there is a change in the overall size, + * otherwise, check if the overall size of the head branch is greater than 0 + * @type boolean + */ const hasChange = overallHeadSize !== overallBaseSize; - /** If no diff map data provided, we're going to report on the overall size */ - /** - * If the updated assets are the same as the original, - * report no change - * @todo could likely use this to report the overall change; is that helpful info? + * Report the changes in the compiled assets in a markdown format + * @type string[] **/ const markdown = []; + + /** + * This summary will be added at the top of the comment + * to give a high-level overview of the changes + * @type string[] + */ const summary = [ "### Summary", `**Total size**: ${bytesToSize(overallHeadSize)}*`, ]; + // If no components registered any changes, add a message to the summary if (sections.length === 0) { summary.push(...["", " 🎉 No changes detected in any packages"]); - } - else { - /** - * Calculate the change in size - * PR - base / base = change - */ - let changeSummary = ""; + } else { + /** Calculate the change in size [(head - base) / base = change] */ if (baseOutput.size > 0 && hasBase && hasChange) { - changeSummary = `**Total change (Δ)**: ${printChange(overallBaseSize, overallHeadSize)} (${printPercentChange(overallHeadSize, overallBaseSize)})`; - } - else if (baseOutput.size > 0 && hasBase && !hasChange) { - changeSummary = "No change in file sizes"; - } - - if (changeSummary !== "") { - summary.push( - changeSummary, - "", - ); - } + summary.push(`**Total change (Δ)**: ${printChange(overallHeadSize, overallBaseSize)} (${printPercentChange(overallHeadSize, overallBaseSize)})`); + } else summary.push(""); + /** Next iterate over the components and report on the changes */ sections.map(({ name, hasChange, mainFile, fileMap }) => { if (!hasChange) return; - const md = ["", `#### ${name}`, ""]; - md.push( + const tableHead = ["Filename", "Head", "Minified", "Gzipped", ...(hasBase ? ["Compared to base"] : [])]; + + /** + * Iterate over the files in the component and create a markdown table + * @param {Array} table - The markdown table accumulator + * @param {[readableFilename, { headByteSize = 0, baseByteSize = 0 }]} - The deconstructed filemap entry + */ + const tableRows = ( + table, // accumulator + [readableFilename, { headByteSize = 0, baseByteSize = 0 }] // deconstructed filemap entry; i.e., Map = [key, { ...values }] + ) => { + // @todo readable filename can be linked to html diff of the file? + // https://github.com/adobe/spectrum-css/pull/2093/files#diff-6badd53e481452b5af234953767029ef2e364427dd84cdeed25f5778b6fca2e6 + + // table is an array containing the printable data for the markdown table + if (readableFilename.endsWith(".map")) return table; + + // If the file is a minified file, don't include it separately in the table + if (/\.min\.css/.test(readableFilename)) return table; + + const removedOnBranch = isRemoved(headByteSize, baseByteSize); + + // @todo should there be any normalization before comparing the file names? + const isMainFile = readableFilename === mainFile; + + const gzipName = readableFilename.replace(/\.([a-z]+)$/, ".min.$1.gz"); + const minName = readableFilename.replace(/\.([a-z]+)$/, ".min.$1"); + + const size = removedOnBranch ? "🚨 deleted/moved" : bytesToSize(headByteSize); + const gzipSize = removedOnBranch ? " - " : gzipName ? bytesToSize(fileMap.get(gzipName)?.headByteSize ?? 0) : "-"; + const minSize = removedOnBranch ? " - " : minName ? bytesToSize(fileMap.get(minName)?.headByteSize ?? 0) : "-"; + + const change = !removedOnBranch ? `${isNew(headByteSize, baseByteSize) ? "🆕 " : ""}${printChange(headByteSize, baseByteSize)}` : `⬇ ${bytesToSize(baseByteSize)}`; + const diff = difference(baseByteSize, headByteSize) !== 0 ? ` (${printPercentChange(headByteSize , baseByteSize)})` : ""; + const delta = `${change}${removedOnBranch ? "" : diff}`; + + const newFileRow = [ + // Bold the main file to help it stand out + isMainFile ? `**${readableFilename}**` : readableFilename, + // If the file was removed, note it's absense with a dash; otherwise, note it's size + size, + minSize, + gzipSize, + ...(hasBase ? [delta] : []), + ]; + + table.push(newFileRow); + + return table; + }; + + markdown.push( + "", + `#### ${name}`, + "", ...[ - ["Filename", "Head", "Minified", "Gzipped", ...(hasBase ? ["Compared to base"] : [])], - [" - ", " - ", "-", "-", ...(hasBase ? [" - "] : [])], + tableHead, + tableHead.map(() => "-"), ].map((row) => `| ${row.join(" | ")} |`), - ...[...fileMap.entries()] - .reduce( - ( - table, // accumulator - [readableFilename, { headByteSize = 0, baseByteSize = 0 }] // deconstructed filemap entry; i.e., Map = [key, { ...values }] - ) => { - // table is an array containing the printable data for the markdown table - if (readableFilename.endsWith(".map")) return table; - - // If the file is a minified file, don't include it separately in the table - if (/\.min\.css/.test(readableFilename)) return table; - - const removedOnBranch = isRemoved(headByteSize, baseByteSize); - // @todo should there be any normalization before comparing the file names? - const isMainFile = readableFilename === mainFile; - - const gzipName = readableFilename.replace(/\.([a-z]+)$/, ".min.$1.gz"); - const minName = readableFilename.replace(/\.([a-z]+)$/, ".min.$1"); - - const size = removedOnBranch ? "🚨 deleted/moved" : bytesToSize(headByteSize); - const change = !removedOnBranch ? printChange(headByteSize, baseByteSize) : `⬇ ${bytesToSize(baseByteSize)}`; - const diff = difference(baseByteSize, headByteSize) !== 0 ? ` (${printPercentChange(headByteSize , baseByteSize)})` : ""; - const delta = `${change}${removedOnBranch ? "" : diff}`; - const gzipSize = removedOnBranch ? " - " : gzipName ? bytesToSize(fileMap.get(gzipName)?.headByteSize ?? 0) : "-"; - const minSize = removedOnBranch ? " - " : minName ? bytesToSize(fileMap.get(minName)?.headByteSize ?? 0) : "-"; - - const delta = removedOnBranch ? "🚨 deleted, moved, or renamed" : isNew(headByteSize, baseByteSize) ? "🎉 **new**" : `${printChange(headByteSize, baseByteSize)}${difference(baseByteSize, headByteSize) !== 0 ? ` (${printPercentChange(headByteSize , baseByteSize)})` : ""}`; - - // @todo readable filename can be linked to html diff of the file? - // https://github.com/adobe/spectrum-css/pull/2093/files#diff-6badd53e481452b5af234953767029ef2e364427dd84cdeed25f5778b6fca2e6 - - return [ - ...table, - [ - // Bold the main file to help it stand out - isMainFile ? `**${readableFilename}**` : readableFilename, - // If the file was removed, note it's absense with a dash; otherwise, note it's size - size, - minSize, - gzipSize, - ...(hasBase ? [delta] : []), - ] - ]; - }, - [] - ) + .reduce(tableRows, []) .map((row) => `| ${row.join(" | ")} |`), ); - - markdown.push(...md); }); // If there is more than 1 component updated, add a details/summary section to the markdown at the start of the array - if (markdown.length > 5) { + if (markdown.length > 1) { markdown.unshift( "", "
", @@ -235,12 +274,10 @@ async function run() { hasBase && headMainSize !== baseMainSize ? "true" : "false" ); } - } - else { + } else { core.setOutput("total-size", 0); } - } - catch (error) { + } catch (error) { core.error(error.stack); core.setFailed(error.message); } @@ -281,48 +318,45 @@ const printPercentChange = function (v1, v0) { }; /** - * - * @param {Map>} PACKAGES + * @typedef {string} PackageName - The name of the component package + * @typedef {string} FileName - The name of the file in the component package + * @typedef {{ headByteSize: number, baseByteSize: number }} FileSpecs - The size of the file in the head and base branches + * @typedef {Map} FileDetails - A map of file sizes from the head and base branches keyed by filename (full path, not shorthand) + * @typedef {{ name: PackageName, filePath: string, hasChange: boolean, mainFile: string, fileMap: FileDetails}} PackageDetails - The details of the component package including the main file and the file map as well as other short-hand properties for reporting + */ + +/** + * From the data indexed by filename, create a detailed table of the changes in the compiled assets + * with a full view of all files present in the head and base branches. + * @param {Map} PACKAGES * @param {string} filePath - The path to the component's dist folder from the root of the repo - * @param {string} path - The path from the github workspace to the root of the repo - * @returns {Array<{ name: string, filePath: string, headMainSize: number, baseMainSize: number, hasChange: boolean, fileMap: Map}>} + * @param {string} rootPath - The path from the github workspace to the root of the repo + * @returns {PackageDetails[]} */ -const makeTable = function (PACKAGES, filePath, path) { +const makeTable = function (PACKAGES, filePath, rootPath) { const sections = []; /** Next convert that component data into a detailed object for reporting */ PACKAGES.forEach((fileMap, packageName) => { // Read in the main asset file from the package.json - const packagePath = join(path, filePath, packageName, "package.json"); + const packagePath = join(rootPath, filePath, packageName, "package.json"); - let mainFile = "index.min.css"; + // Default to the index.css file if no main file is provided in the package.json + let mainFile = "index.css"; + // If the package.json exists, read in the main file if (existsSync(packagePath)) { const { main } = require(packagePath) ?? {}; - if (main) { - mainFile = main.replace(/^.*\/dist\//, ""); - - // Check if a minified output of this file exists - if (existsSync(join(dirname(packagePath), main.replace(/\.css$/, ".min.css")))) { - mainFile = mainFile.replace(/\.css$/, ".min.css"); - } + // If the main file is a string, use it as the main file + if (typeof main === "string") { + // Strip out the path to the dist folder from the main file + mainFile = main.replace(new RegExp("^.*\/?dist\/"), ""); } } - let mainFileOnly = [...fileMap.keys()].filter((file) => file === mainFile); - - // If no main file is found, look for the first file matching the filename only - if (mainFileOnly.length === 0) { - mainFileOnly = [...fileMap.keys()].filter((file) => file.endsWith(mainFile)); - } - - const headMainSize = mainFileOnly.reduce( - (acc, filename) => { - const { headByteSize = 0 } = fileMap.get(filename); - return acc + headByteSize; - }, - 0 - ); - + /** + * Check if any of the files in the component have changed + * @type boolean + */ const hasChange = fileMap.size > 0 && [...fileMap.values()].some(({ headByteSize, baseByteSize }) => headByteSize !== baseByteSize); /** @@ -334,7 +368,7 @@ const makeTable = function (PACKAGES, filePath, path) { name: packageName, filePath, hasChange, - mainFile: mainFileOnly?.[0], + mainFile, fileMap }); }); @@ -344,69 +378,92 @@ const makeTable = function (PACKAGES, filePath, path) { /** * Split out the data indexed by filename into groups by component - * @param {Map} dataMap - * @param {string} path - * @param {Map} baseMap - * @returns {{ filePath: string, PACKAGES: Map>}} + * @param {Map} dataMap - A map of file names relative to the root of the repo and their sizes + * @param {string} rootPath - The path to the component's dist folder from the root of the repo + * @param {Map=[new Map()]} baseMap - The map of file sizes from the base branch indexed by filename (optional) + * @returns {{ filePath: string, PACKAGES: Map}} */ -const splitDataByPackage = function (dataMap, path, baseMap = new Map()) { +const splitDataByPackage = function (dataMap, baseMap = new Map()) { + /** + * Path to the component's dist folder relative to the root of the repo + * @type {string|undefined} + */ + let filePath; + const PACKAGES = new Map(); - let filePath; - [...dataMap.entries()].forEach(([file, headByteSize]) => { - // Determine the name of the component - const parts = file.split(sep); - const componentIdx = parts.findIndex((part) => part === "dist") - 1; - const packageName = parts[componentIdx]; - - if (!filePath) { - filePath = `${file.replace(path, "")}/${parts.slice(componentIdx + 1, -1).join(sep)}`; + /** + * Determine the name of the component + * @param {string} file - The full path to the file + * @param {{ part: string|undefined, offset: number|undefined, length: number|undefined }} options - The part of the path to split on and the offset to start from + * @returns {string} + */ + const getPathPart = (file, { part, offset, length, reverse = false } = {}) => { + // If the file is not a string, return it as is + if (!file || typeof file !== "string") return file; + + // Split the file path into parts + const parts = file.split("/"); + + // Default our index to 0 + let idx = 0; + // If a part is provided, find the position of that part + if (typeof part !== "undefined") { + idx = parts.findIndex((p) => p === part); + // index is -1 if the part is not found, return the file as is + if (idx === -1) return file; } - const readableFilename = file.replace(/^.*\/dist\//, ""); + // If an offset is provided, add it to the index + if (typeof offset !== "undefined") idx += offset; - const fileMap = PACKAGES.has(packageName) - ? PACKAGES.get(packageName) - : new Map(); + // If a length is provided, return the parts from the index to the index + length + if (typeof length !== "undefined") { + // If the length is negative, return the parts from the index + length to the index + // this captures the previous n parts before the index + if (length < 0) { + return parts.slice(idx + length, idx).join(sep); + } - if (!fileMap.has(readableFilename)) { - fileMap.set(readableFilename, { - headByteSize, - baseByteSize: baseMap.get(file), - }); + return parts.slice(idx, idx + length).join(sep); } - /** Update the component's table data */ - PACKAGES.set(packageName, fileMap); - }); + // Otherwise, return the parts from the index to the end + if (!reverse) return parts.slice(idx).join(sep); + return parts.slice(0, idx).join(sep); + }; - // Look for any base files not present in the head - [...baseMap.entries()].forEach(([file, baseByteSize]) => { - // Determine the name of the component - const parts = file.split(sep); - const componentIdx = parts.findIndex((part) => part === "dist") - 1; - const packageName = parts[componentIdx]; + const pullDataIntoPackages = (filepath, size, isHead = true) => { + const packageName = getPathPart(filepath, { part: "dist", offset: -1, length: 1 }); - if (!filePath) { - filePath = `${file.replace(path, "")}/${parts.slice(componentIdx + 1, -1).join(sep)}`; - } + // Capture the path to the component's dist folder, this doesn't include the root path from outside the repo + if (!filePath) filePath = getPathPart(filepath, { part: "dist", reverse: true }); - const readableFilename = file.replace(/^.*\/dist\//, ""); + // Capture the filename without the path to the dist folder + const readableFilename = getPathPart(filepath, { part: "dist", offset: 1 }); + // If fileMap data already exists for the package, use it; otherwise, create a new map const fileMap = PACKAGES.has(packageName) ? PACKAGES.get(packageName) : new Map(); + // If the fileMap doesn't have the file, add it if (!fileMap.has(readableFilename)) { fileMap.set(readableFilename, { - headByteSize: dataMap.get(file), - baseByteSize, + headByteSize: isHead ? size : dataMap.get(filepath), + baseByteSize: isHead ? baseMap.get(filepath) : size, }); } /** Update the component's table data */ PACKAGES.set(packageName, fileMap); - }); + }; + + // This sets up the core data structure for the package files + [...dataMap.entries()].forEach(([file, headByteSize]) => pullDataIntoPackages(file, headByteSize, true)); + + // Look for any base files not present in the head to ensure we capture when files are deleted + [...baseMap.entries()].forEach(([file, baseByteSize]) => pullDataIntoPackages(file, baseByteSize, false)); return { filePath, PACKAGES }; }; diff --git a/.github/actions/file-diff/utilities.js b/.github/actions/file-diff/utilities.js index e7b4e4bed91..42dd663b007 100644 --- a/.github/actions/file-diff/utilities.js +++ b/.github/actions/file-diff/utilities.js @@ -148,7 +148,7 @@ exports.addComment = async function ({ search, content, token }) { * filesystem and return a Map of the files and their sizes. * @param {string} rootPath * @param {string[]} patterns - * @returns {Promise>} + * @returns {Promise>} - Returns the relative path and size of the files */ exports.fetchFilesAndSizes = async function (rootPath, patterns = [], { core }) { if (!existsSync(rootPath)) return new Map(); diff --git a/postcss.config.js b/postcss.config.js index badd18d704b..b3a957016d0 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -26,10 +26,9 @@ module.exports = ({ resolveImports = true, shouldCombine = false, lint = true, - shouldMinify = false, verbose = true, additionalPlugins = {}, - minify = true, + minify = false, env = process.env.NODE_ENV ?? "development", ...options } = {}) => { @@ -60,6 +59,10 @@ module.exports = ({ splitinatorOptions.referencesOnly = true; } + if (!minify && outputFilename?.includes(".min.")) { + minify = true; + } + return { ...options, plugins: { diff --git a/tasks/component-builder.js b/tasks/component-builder.js index 28718fab36a..2e47843fa8d 100644 --- a/tasks/component-builder.js +++ b/tasks/component-builder.js @@ -78,7 +78,7 @@ async function processCSS( } // If the output file is a minified file, force the minify flag to true - if (path.basename(output, ".css").endsWith(".min")) minify = true; + if (output && path.basename(output, ".css").endsWith(".min")) minify = true; const ctx = { cwd, @@ -133,18 +133,7 @@ async function processCSS( if (minify) { promises.push( - gzip(formatted) - .then(zipped => fsp.writeFile(`${output}.gz`, zipped) - .then(() => { - const stats = fs.statSync(`${output}.gz`); - return `${"✓".green} ${relativePrint(`${output}.gz`, { cwd }).padEnd(20, " ").yellow} ${bytesToSize(stats.size).gray}`; - }).catch((err) => { - if (!err) return; - console.log(`${"✗".red} ${relativePrint(`${output}.gz`, { cwd }).yellow} not written`); - return Promise.reject(err); - }) - ) - .catch((err) => Promise.resolve(err)) + gzip(formatted).then(zipped => writeAndReport(zipped, `${output}.gz`, { cwd })) ); } @@ -181,7 +170,11 @@ async function build({ cwd = process.cwd(), clean = false, minify = false, compo const indexOutputPath = path.join(cwd, "dist", "index.css"); - // These methods are used to build and minify the CSS content; settings are unique to the core component file + /** + * Abstraction to run the build twice, once vanilla and once minified + * @param {string} fileRoot - The root name of the file to be built (e.g. index, index-base), not including the extension + * @param {object} postCSSOptions - The options to be passed to the postcss processor + */ const buildAndMinify = (fileRoot, postCSSOptions) => { return Promise.all([ processCSS(content, path.join(cwd, "index.css"), path.join(cwd, "dist", `${fileRoot}.css`), { @@ -238,7 +231,13 @@ async function buildThemes({ cwd = process.cwd(), minify = false, clean = false const imports = contentData.map(({ input }) => input); const importMap = imports.map((i) => `@import "${i}";`).join("\n"); - // These methods are used to build and minify the CSS content; settings are unique to the theme files + + /** + * Abstraction to run the build twice, once vanilla and once minified + * @param {string} content - The content of the file to be built + * @param {string} fileRoot - The root name of the file to be built (e.g. index, index-base), not including the extension + * @param {object} postCSSOptions - The options to be passed to the postcss processor + */ const buildAndMinify = (content, fileRoot, postCSSOptions) => { return Promise.all([ processCSS(content, path.join(cwd, (fileRoot !== "index-theme" ? fileRoot : "index") + ".css"), path.join(cwd, "dist", `${fileRoot}.css`), { @@ -260,11 +259,10 @@ async function buildThemes({ cwd = process.cwd(), minify = false, clean = false new Error(`No content found for ${relativePrint(input, { cwd })}`), ); - const theme = path.basename(input, ".css"); + const theme = input.replace(/\.css$/, ""); return buildAndMinify( content, - // remove all file extensions from the theme name theme, { lint: false, @@ -309,6 +307,7 @@ async function main({ componentName = process.env.NX_TASK_TARGET_PROJECT, cwd, clean, + minify = false, } = {}) { if (!cwd && componentName) { cwd = path.join(dirs.components, componentName); @@ -324,10 +323,10 @@ async function main({ clean = process.env.NODE_ENV === "production"; } - let minify = false; if (process.env.NODE_ENV === "production") { minify = true; } + const key = `[build] ${`@spectrum-css/${componentName}`.cyan}`; console.time(key);