Skip to content

Commit

Permalink
perf(dev): cache regular stylesheet compilation (#6638)
Browse files Browse the repository at this point in the history
Co-authored-by: Pedro Cattori <[email protected]>
  • Loading branch information
markdalgleish and pcattori authored Jun 21, 2023
1 parent 286545e commit 1eb1832
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 94 deletions.
5 changes: 5 additions & 0 deletions .changeset/cache-regular-stylesheets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@remix-run/dev": patch
---

Add caching to regular stylesheet compilation
232 changes: 138 additions & 94 deletions packages/remix-dev/compiler/plugins/cssImports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import esbuild from "esbuild";

import invariant from "../../invariant";
import type { Context } from "../context";
import { getCachedPostcssProcessor } from "../utils/postcss";
import {
getPostcssProcessor,
populateDependenciesFromMessages,
} from "../utils/postcss";
import { absoluteCssUrlsPlugin } from "./absoluteCssUrlsPlugin";

const isExtendedLengthPath = /^\\\\\?\\/;
Expand Down Expand Up @@ -42,109 +45,150 @@ export function cssFilePlugin(ctx: Context): esbuild.Plugin {
} = build.initialOptions;

// eslint-disable-next-line prefer-let/prefer-let -- Avoid needing to repeatedly check for null since const can't be reassigned
const postcssProcessor = await getCachedPostcssProcessor(ctx);
const postcssProcessor = await getPostcssProcessor(ctx);

build.onLoad({ filter: /\.css$/ }, async (args) => {
let { metafile, outputFiles, warnings, errors } = await esbuild.build({
absWorkingDir,
assetNames,
chunkNames,
conditions,
define,
external,
format,
mainFields,
nodePaths,
platform,
publicPath,
sourceRoot,
target,
treeShaking,
tsconfig,
minify: ctx.options.mode === "production",
bundle: true,
minifySyntax: true,
metafile: true,
write: false,
sourcemap: Boolean(ctx.options.sourcemap && postcssProcessor), // We only need source maps if we're processing the CSS with PostCSS
splitting: false,
outdir: ctx.config.assetsBuildDirectory,
entryNames: assetNames,
entryPoints: [args.path],
loader: {
...loader,
".css": "css",
},
plugins: [
absoluteCssUrlsPlugin(),
...(postcssProcessor
? [
{
name: "postcss-plugin",
async setup(build) {
build.onLoad(
{ filter: /\.css$/, namespace: "file" },
async (args) => ({
contents: await postcssProcessor({ path: args.path }),
loader: "css",
})
);
},
} satisfies esbuild.Plugin,
]
: []),
],
});
let cacheKey = `css-file:${args.path}`;
let {
cacheValue: { contents, watchFiles, warnings },
} = await ctx.fileWatchCache.getOrSet(cacheKey, async () => {
let fileDependencies = new Set([args.path]);
let globDependencies = new Set<string>();

let { metafile, outputFiles, warnings, errors } = await esbuild.build(
{
absWorkingDir,
assetNames,
chunkNames,
conditions,
define,
external,
format,
mainFields,
nodePaths,
platform,
publicPath,
sourceRoot,
target,
treeShaking,
tsconfig,
minify: ctx.options.mode === "production",
bundle: true,
minifySyntax: true,
metafile: true,
write: false,
sourcemap: Boolean(ctx.options.sourcemap && postcssProcessor), // We only need source maps if we're processing the CSS with PostCSS
splitting: false,
outdir: ctx.config.assetsBuildDirectory,
entryNames: assetNames,
entryPoints: [args.path],
loader: {
...loader,
".css": "css",
},
plugins: [
absoluteCssUrlsPlugin(),
...(postcssProcessor
? [
{
name: "postcss-plugin",
async setup(build) {
build.onLoad(
{ filter: /\.css$/, namespace: "file" },
async (args) => {
let contents = await fse.readFile(
args.path,
"utf-8"
);

let { css, messages } =
await postcssProcessor.process(contents, {
from: args.path,
to: args.path,
map: ctx.options.sourcemap,
});

if (errors && errors.length) {
return { errors };
}

invariant(metafile, "metafile is missing");
let { outputs } = metafile;
let entry = Object.keys(outputs).find((out) => outputs[out].entryPoint);
invariant(entry, "entry point not found");

let normalizedEntry = path.resolve(
ctx.config.rootDirectory,
normalizePathSlashes(entry)
);
let entryFile = outputFiles.find((file) => {
return (
path.resolve(
ctx.config.rootDirectory,
normalizePathSlashes(file.path)
) === normalizedEntry
populateDependenciesFromMessages({
messages,
fileDependencies,
globDependencies,
});

return {
contents: css,
loader: "css",
};
}
);
},
} satisfies esbuild.Plugin,
]
: []),
],
}
);
});

invariant(entryFile, "entry file not found");
if (errors && errors.length) {
throw { errors };
}

let outputFilesWithoutEntry = outputFiles.filter(
(file) => file !== entryFile
);
invariant(metafile, "metafile is missing");
let { outputs } = metafile;
let entry = Object.keys(outputs).find(
(out) => outputs[out].entryPoint
);
invariant(entry, "entry point not found");

let normalizedEntry = path.resolve(
ctx.config.rootDirectory,
normalizePathSlashes(entry)
);
let entryFile = outputFiles.find((file) => {
return (
path.resolve(
ctx.config.rootDirectory,
normalizePathSlashes(file.path)
) === normalizedEntry
);
});

// write all assets
await Promise.all(
outputFilesWithoutEntry.map(({ path: filepath, contents }) =>
fse.outputFile(filepath, contents)
)
);
invariant(entryFile, "entry file not found");

let outputFilesWithoutEntry = outputFiles.filter(
(file) => file !== entryFile
);

// add all css assets to dependencies
for (let { inputs } of Object.values(outputs)) {
for (let input of Object.keys(inputs)) {
let resolvedInput = path.resolve(input);
fileDependencies.add(resolvedInput);
}
}

// write all assets
await Promise.all(
outputFilesWithoutEntry.map(({ path: filepath, contents }) =>
fse.outputFile(filepath, contents)
)
);

return {
cacheValue: {
contents: entryFile.contents,
// add all dependencies to watchFiles
watchFiles: Array.from(fileDependencies),
warnings,
},
fileDependencies,
globDependencies,
};
});

return {
contents: entryFile.contents,
contents,
loader: "file",
// add all css assets to watchFiles
watchFiles: Object.values(outputs).reduce<string[]>(
(arr, { inputs }) => {
let resolvedInputs = Object.keys(inputs).map((input) => {
return path.resolve(input);
});
arr.push(...resolvedInputs);
return arr;
},
[]
),
watchFiles,
warnings,
};
});
Expand Down

0 comments on commit 1eb1832

Please sign in to comment.