Skip to content

Commit

Permalink
chore(dev-utils): Combine all scss files into `react-md/dist/_everyth…
Browse files Browse the repository at this point in the history
…ing.scss`

I really only created this file because after switching from `node-sass`
to `sass`, my `sassdoc` generator ended up taking 45s+ to compile each
example instead of 5-10s. After debugging a bit, I found out the
bottleneck is the file resolution IO so I decided to just combine the
files myself into a single `react-md/dist/_everything.scss` file which
cut down the **total** `sassdoc` generation time to ~55s.

It will be interesting to see if this single file import will improve
build performance for real world apps, but it'll also require users to
be fully migrated to `v3`.
  • Loading branch information
mlaursen committed Aug 13, 2021
1 parent e0ef881 commit c7177e6
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 2 deletions.
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -14,6 +14,7 @@
"dev": "npm-run-all -p watch start",
"setup": "npm-run-all build-dev-utils force-install build \"sandbox --empty\"",
"force-install": "yarn --force",
"combine-styles": "dev-utils combine-styles",
"styles": "dev-utils styles",
"variables": "dev-utils variables",
"run-indexer": "dev-utils doc-index",
Expand All @@ -26,7 +27,7 @@
"build-cjs": "tsc -b tsconfig.cjs.json",
"build-var": "tsc -b tsconfig.var.json",
"build-umd": "yarn workspace react-md umd --silent",
"build": "npm-run-all styles build-ejs build-cjs build-var",
"build": "npm-run-all styles combine-styles build-ejs build-cjs build-var",
"typecheck": "tsc -p tsconfig.check.json",
"lint-scripts": "eslint \"packages/*/src/**/*.{ts,tsx,js,jsx}\"",
"lint-styles": "sass-lint -c .sass-lint.yml -v",
Expand Down
8 changes: 7 additions & 1 deletion packages/dev-utils/src/cli.ts
Expand Up @@ -16,7 +16,7 @@ import { shared } from "./shared";
import { themes } from "./themes";
import { typedoc } from "./typedoc";
import { umd } from "./umd";
import { copyStyles } from "./utils";
import { combineAllFiles, copyStyles } from "./utils";
import { variables } from "./variables";
import { watch } from "./watch";

Expand Down Expand Up @@ -56,6 +56,12 @@ createCommand("styles")
)
.action(() => copyStyles());

createCommand("combine-styles")
.description(
"Combines all the .scss files into a single file in the react-md package."
)
.action(() => combineAllFiles());

createCommand("sassdoc")
.description(
"Creates the sassdoc for the documentation site in all scoped packages."
Expand Down
3 changes: 3 additions & 0 deletions packages/dev-utils/src/constants.ts
Expand Up @@ -24,6 +24,9 @@ export const dist = "dist";
export const nonWebpackDist = join(dist, "scss");
export const tempStylesDir = "tempStyles";

export const reactMdDist = join(packagesRoot, "react-md", dist);
export const everythingScss = join(reactMdDist, "_everything.scss");

// common files
export const stylesScss = "styles.scss";
export const scssVariables = "scssVariables.ts";
Expand Down
194 changes: 194 additions & 0 deletions packages/dev-utils/src/utils/styles/combineAllFiles.ts
@@ -0,0 +1,194 @@
import { existsSync, readFileSync, writeFileSync } from "fs";
import { ensureDirSync } from "fs-extra";
import { difference } from "lodash";
import log from "loglevel";
import { join, sep } from "path";

import {
everythingScss,
packagesRoot,
reactMdDist,
src,
} from "../../constants";
import { format } from "../format";
import { getPackages } from "../packages";

const getFileOrder = (packageName: string): readonly string[] => {
switch (packageName) {
case "theme":
return [
"color-palette",
"color-a11y",
"variables",
"helpers",
"functions",
"mixins",
];
case "icon":
return ["variables", "functions", "mixins", "material-icons"];
case "media":
case "transition":
return ["variables", "mixins"];
case "form":
return [
join("label", "variables"),
join("text-field", "variables"),
join("select", "variables"),
join("toggle", "variables"),
join("slider", "variables"),
"variables",
"functions",
join("slider", "functions"),
join("file-input", "mixins"),
join("label", "mixins"),
join("toggle", "mixins"),
join("slider", "mixins"),
join("text-field", "mixins"),
join("select", "mixins"),
"mixins",
];
default:
return ["variables", "functions", "mixins"];
}
};

const IMPORT_STATEMENT_REGEXP = /^.+('|")(.+)('|");(.*)$/;

const PACKAGE_ORDER = [
"utils",
"theme",
"transition",
"typography",
"elevation",
"divider",
"media",
"icon",
"states",
"overlay",
"tooltip",
"avatar",
"button",
"alert",
"app-bar",
"badge",
"card",
"chip",
"link",
"list",
"expansion-panel",
"dialog",
"sheet",
"menu",
"progress",
"tree",
"table",
"tabs",
"form",
"layout",
];

function assertAllPackages(): void {
const packages = getPackages("scss");
const diff = difference(packages, PACKAGE_ORDER);
if (diff.length) {
process.exit(1);
}
}

const imported: Record<string, boolean> = {};

export function combineAllFiles(): void {
assertAllPackages();
const uses = new Set<string>();
const files: string[] = [];
PACKAGE_ORDER.forEach((packageName) => {
getFileOrder(packageName).forEach((fileName) => {
const packageRoot = join(packagesRoot, packageName, src);
if (fileName.includes(sep)) {
fileName = fileName.replace(sep, `${sep}_`);
} else {
fileName = `_${fileName}`;
}
const filePath = join(packageRoot, `${fileName}.scss`);

imported[filePath] = true;
if (!existsSync(filePath)) {
log.error(`${filePath} does not exist`);
process.exit(1);
}

const contents = readFileSync(filePath, "utf8");

const fileUses = contents.match(/^@use "(.+)";/gm);
const imports = contents.match(/^@import.+$/gm);
files.push(
contents
// remove import and use statements
.replace(/^@(use|import).+$/gm, "")
// remove all comments
.replace(/^\s*\/\/.+$/gm, "")
);

if (fileUses) {
fileUses.forEach((use) => {
uses.add(use);
});
}

if (imports) {
imports.forEach((imp) => {
let importName = imp.replace(IMPORT_STATEMENT_REGEXP, "$2.scss");
if (importName.startsWith("./")) {
importName = join(packageRoot, importName);
} else if (importName.startsWith("../")) {
const [folder] = fileName.split(sep);
importName = join(packageRoot, folder, importName);
}

importName = importName
.replace(/\/([a-z0-9-]+\.scss)/, "/_$1")
.replace(
/~@react-md\/([a-z-]+)\/dist/,
join(packagesRoot, "$1", src)
);

if (!imported[importName]) {
log.error(`imp: "${imp}"`);
log.error(`${importName} needs to be imported before ${filePath}`);
const sorted = Object.keys(imported)
.map((name) => name.replace(packagesRoot, ""))
.sort();
log.error(JSON.stringify(sorted, null, 2));
process.exit(1);
}
});
}
});
});

const contents = `${Array.from(uses).join("\n")}
${files.join("\n")}
`;
const formatted = format(contents, "scss")
// remove extra spaces between variables after comments were removed
.replace(/(\r?\n)+\$/g, "\n$");

ensureDirSync(reactMdDist);
writeFileSync(everythingScss, formatted);
}

let cachedEverythingScss = "";

/**
* Lazy loads the `packages/react-md/dist/_everything.scss` file contents which
* should be used in all the `renderSync` `data` calls to increase build
* performance.
*/
export function getEverythingScss(): string {
if (!cachedEverythingScss) {
cachedEverythingScss = readFileSync(everythingScss, "utf8");
}

return cachedEverythingScss;
}
1 change: 1 addition & 0 deletions packages/dev-utils/src/utils/styles/index.ts
@@ -1,3 +1,4 @@
export * from "./combineAllFiles";
export * from "./createScssVariables";
export * from "./getSassdoc";
export * from "./helpers";
Expand Down

0 comments on commit c7177e6

Please sign in to comment.