Skip to content

Commit

Permalink
fix(dev): lazily generate CSS bundle (#6535)
Browse files Browse the repository at this point in the history
  • Loading branch information
markdalgleish authored and jacob-ebey committed Jun 8, 2023
1 parent 72d7377 commit a1855e4
Show file tree
Hide file tree
Showing 16 changed files with 187 additions and 157 deletions.
6 changes: 6 additions & 0 deletions .changeset/lazy-css-bundle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@remix-run/css-bundle": patch
"@remix-run/dev": patch
---

Lazily generate CSS bundle when import of `@remix-run/css-bundle` is detected
13 changes: 0 additions & 13 deletions packages/remix-css-bundle/browser.ts

This file was deleted.

9 changes: 9 additions & 0 deletions packages/remix-css-bundle/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
declare const __INJECT_CSS_BUNDLE_HREF__: string | undefined;

// Injected by `cssBundlePlugin`
let cssBundleHref: string | undefined =
typeof __INJECT_CSS_BUNDLE_HREF__ === "string"
? __INJECT_CSS_BUNDLE_HREF__
: undefined;

export { cssBundleHref };
13 changes: 3 additions & 10 deletions packages/remix-css-bundle/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,7 @@
"directory": "packages/remix-css-bundle"
},
"license": "MIT",
"main": "./dist/server.js",
"module": "./dist/esm/server.js",
"typings": "./dist/server.d.ts",
"browser": {
"./dist/server.js": "./dist/browser.js",
"./dist/esm/server.js": "./dist/esm/browser.js"
},
"dependencies": {
"@remix-run/dev": "1.17.0"
}
"main": "./dist/index.js",
"module": "./dist/esm/index.js",
"typings": "./dist/index.d.ts"
}
4 changes: 2 additions & 2 deletions packages/remix-css-bundle/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = function rollup() {
external(id) {
return isBareModuleId(id);
},
input: [`${sourceDir}/browser.ts`, `${sourceDir}/server.ts`],
input: `${sourceDir}/index.ts`,
output: {
banner: createBanner(packageName, version),
dir: outputDist,
Expand Down Expand Up @@ -50,7 +50,7 @@ module.exports = function rollup() {
external(id) {
return isBareModuleId(id);
},
input: [`${sourceDir}/browser.ts`, `${sourceDir}/server.ts`],
input: `${sourceDir}/index.ts`,
output: {
banner: createBanner(packageName, version),
dir: `${outputDist}/esm`,
Expand Down
3 changes: 0 additions & 3 deletions packages/remix-css-bundle/server.ts

This file was deleted.

76 changes: 40 additions & 36 deletions packages/remix-dev/compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import * as Server from "./server";
import * as Channel from "../channel";
import type { Manifest } from "../manifest";
import { create as createManifest, write as writeManifest } from "./manifest";
import type { LazyValue } from "./lazyValue";
import { createLazyValue } from "./lazyValue";
import { err, ok } from "../result";
import { Cancel } from "./cancel";

type Compiler = {
compile: (options?: {
Expand All @@ -18,24 +21,24 @@ type Compiler = {
};

export let create = async (ctx: Context): Promise<Compiler> => {
// channels _should_ be scoped to a build, not a compiler
// these variables _should_ be scoped to a build, not a compiler
// but esbuild doesn't have an API for passing build-specific arguments for rebuilds
// so instead use a mutable reference (`channels`) that is compiler-scoped
// so instead use a mutable reference (`refs`) that is compiler-scoped
// and gets reset on each build
let channels = {
cssBundleHref: undefined as unknown as Channel.Type<string | undefined>,
manifest: undefined as unknown as Channel.Type<Manifest>,
let refs = {
lazyCssBundleHref: undefined as unknown as LazyValue<string | undefined>,
manifestChannel: undefined as unknown as Channel.Type<Manifest>,
};

let subcompiler = {
css: await CSS.createCompiler(ctx),
js: await JS.createCompiler(ctx, channels),
server: await Server.createCompiler(ctx, channels),
js: await JS.createCompiler(ctx, refs),
server: await Server.createCompiler(ctx, refs),
};
let cancel = async () => {
// resolve channels with error so that downstream tasks don't hang waiting for results from upstream tasks
channels.cssBundleHref.err();
channels.manifest.err();
refs.lazyCssBundleHref.cancel();
refs.manifestChannel.err();

// optimization: cancel tasks
await Promise.all([
Expand All @@ -57,40 +60,42 @@ export let create = async (ctx: Context): Promise<Compiler> => {
return err(thrown);
};

// reset channels
channels.cssBundleHref = Channel.create();
channels.manifest = Channel.create();

// kickoff compilations in parallel
let tasks = {
css: subcompiler.css.compile().then(ok, errCancel),
js: subcompiler.js.compile().then(ok, errCancel),
server: subcompiler.server.compile().then(ok, errCancel),
};

// keep track of manually written artifacts
let writes: {
cssBundle?: Promise<void>;
manifest?: Promise<void>;
server?: Promise<void>;
} = {};

// css compilation
let css = await tasks.css;
if (!css.ok) throw error ?? css.error;
// reset refs for this compilation
refs.manifestChannel = Channel.create();
refs.lazyCssBundleHref = createLazyValue({
async get() {
let { bundleOutputFile, outputFiles } = await subcompiler.css.compile();

if (bundleOutputFile) {
writes.cssBundle = CSS.writeBundle(ctx, outputFiles);
}

// css bundle
let cssBundleHref =
css.value.bundle &&
ctx.config.publicPath +
path.relative(
ctx.config.assetsBuildDirectory,
path.resolve(css.value.bundle.path)
return (
bundleOutputFile &&
ctx.config.publicPath +
path.relative(
ctx.config.assetsBuildDirectory,
path.resolve(bundleOutputFile.path)
)
);
channels.cssBundleHref.ok(cssBundleHref);
if (css.value.bundle) {
writes.cssBundle = CSS.writeBundle(ctx, css.value.outputFiles);
}
},
onCancel: ({ reject }) => {
reject(new Cancel("css-bundle"));
},
});

// kickoff compilations in parallel
let tasks = {
js: subcompiler.js.compile().then(ok, errCancel),
server: subcompiler.server.compile().then(ok, errCancel),
};

// js compilation (implicitly writes artifacts/js)
let js = await tasks.js;
Expand All @@ -100,11 +105,10 @@ export let create = async (ctx: Context): Promise<Compiler> => {
// artifacts/manifest
let manifest = await createManifest({
config: ctx.config,
cssBundleHref,
metafile,
hmr,
});
channels.manifest.ok(manifest);
refs.manifestChannel.ok(manifest);
options.onManifest?.(manifest);
writes.manifest = writeManifest(ctx.config, manifest);

Expand Down
4 changes: 2 additions & 2 deletions packages/remix-dev/compiler/css/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@ export let create = async (ctx: Context) => {
});
let compile = async () => {
let { outputFiles } = await compiler.rebuild();
let bundle = outputFiles.find((outputFile) =>
let bundleOutputFile = outputFiles.find((outputFile) =>
isBundle(ctx, outputFile, ".css")
);
return { bundle, outputFiles };
return { bundleOutputFile, outputFiles };
};
return {
compile,
Expand Down
12 changes: 6 additions & 6 deletions packages/remix-dev/compiler/js/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { deprecatedRemixPackagePlugin } from "../plugins/deprecatedRemixPackage"
import { emptyModulesPlugin } from "../plugins/emptyModules";
import { mdxPlugin } from "../plugins/mdx";
import { externalPlugin } from "../plugins/external";
import { cssBundleUpdatePlugin } from "./plugins/cssBundleUpdate";
import { cssBundlePlugin } from "../plugins/cssBundlePlugin";
import { cssModulesPlugin } from "../plugins/cssModuleImports";
import {
cssSideEffectImportsPlugin,
Expand All @@ -25,7 +25,7 @@ import invariant from "../../invariant";
import { hmrPlugin } from "./plugins/hmr";
import { createMatchPath } from "../utils/tsconfig";
import { detectPackageManager } from "../../cli/detectPackageManager";
import type * as Channel from "../../channel";
import type { LazyValue } from "../lazyValue";
import type { Context } from "../context";

type Compiler = {
Expand Down Expand Up @@ -74,7 +74,7 @@ const getExternals = (remixConfig: RemixConfig): string[] => {

const createEsbuildConfig = (
ctx: Context,
channels: { cssBundleHref: Channel.Type<string | undefined> }
refs: { lazyCssBundleHref: LazyValue<string | undefined> }
): esbuild.BuildOptions => {
let entryPoints: Record<string, string> = {
"entry.client": ctx.config.entryClientFilePath,
Expand Down Expand Up @@ -120,6 +120,7 @@ const createEsbuildConfig = (
let plugins: esbuild.Plugin[] = [
browserRouteModulesPlugin(ctx, /\?browser$/),
deprecatedRemixPackagePlugin(ctx),
cssBundlePlugin(refs),
cssModulesPlugin(ctx, { outputCss: false }),
vanillaExtractPlugin(ctx, { outputCss: false }),
cssSideEffectImportsPlugin(ctx, {
Expand Down Expand Up @@ -181,7 +182,6 @@ const createEsbuildConfig = (

if (ctx.options.mode === "development" && ctx.config.future.unstable_dev) {
plugins.push(hmrPlugin(ctx));
plugins.push(cssBundleUpdatePlugin(channels));
}

return {
Expand Down Expand Up @@ -223,10 +223,10 @@ const createEsbuildConfig = (

export const create = async (
ctx: Context,
channels: { cssBundleHref: Channel.Type<string | undefined> }
refs: { lazyCssBundleHref: LazyValue<string | undefined> }
): Promise<Compiler> => {
let compiler = await esbuild.context({
...createEsbuildConfig(ctx, channels),
...createEsbuildConfig(ctx, refs),
metafile: true,
});

Expand Down
74 changes: 0 additions & 74 deletions packages/remix-dev/compiler/js/plugins/cssBundleUpdate.ts

This file was deleted.

45 changes: 45 additions & 0 deletions packages/remix-dev/compiler/lazyValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as Channel from "../channel";

export type LazyValue<T> = {
get: () => Promise<T>;
cancel: () => void;
};

export const createLazyValue = <T>(args: {
get: () => Promise<T>;
onCancel?: (args: {
resolve: (value: T) => void;
reject: (err?: any) => void;
}) => void;
}): LazyValue<T> => {
let channel: Channel.Type<T> | undefined;

return {
async get() {
// Create channel and request lazy value on first `get` call
if (!channel) {
channel = Channel.create();
try {
channel.ok(await args.get());
} catch (err) {
channel.err(err);
}
}

// Share the same result with all callers
let result = await channel.result;

if (!result.ok) {
throw result.error;
}

return result.value;
},
cancel() {
args.onCancel?.({
resolve: (value) => channel?.ok(value),
reject: (error) => channel?.err(error),
});
},
};
};
Loading

0 comments on commit a1855e4

Please sign in to comment.