Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dev): cross-module loader change detection #6299

Merged
merged 9 commits into from
May 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/afraid-coats-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@remix-run/dev": patch
"@remix-run/react": patch
---

cross-module loader change detection for hdr
47 changes: 44 additions & 3 deletions integration/hmr-log-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ let fixture = (options: {
...cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : [],
];

// dummy loader to make sure that HDR is granular
export const loader = () => {
return null;
};

export default function Root() {
return (
<html lang="en" className="h-full">
Expand All @@ -141,6 +146,7 @@ let fixture = (options: {
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/mdx">MDX</Link></li>
</ul>
</nav>
</header>
Expand Down Expand Up @@ -179,6 +185,18 @@ let fixture = (options: {
)
}
`,
"app/routes/mdx.mdx": `import { useLoaderData } from '@remix-run/react'
export const loader = () => "crazy"
export const Component = () => {
const data = useLoaderData()
return <h1 id={data}>{data}</h1>
}

# heyo
whatsup

<Component/>
`,

"app/components/counter.tsx": js`
import * as React from "react";
Expand Down Expand Up @@ -272,6 +290,8 @@ test("HMR", async ({ page }) => {
let originalCounter = fs.readFileSync(counterPath, "utf8");
let cssModulePath = path.join(projectDir, "app", "styles.module.css");
let originalCssModule = fs.readFileSync(cssModulePath, "utf8");
let mdxPath = path.join(projectDir, "app", "routes", "mdx.mdx");
let originalMdx = fs.readFileSync(mdxPath, "utf8");

// make content and style changed to index route
let newCssModule = `
Expand Down Expand Up @@ -419,9 +439,30 @@ test("HMR", async ({ page }) => {
`#about-counter:has-text("inc 0")`
);

// This should not have triggered any revalidation but our detection is
// failing for x-module changes for route module imports
// expect(dataRequests).toBe(2);
expect(dataRequests).toBe(2);

// mdx
await page.click(`a[href="/mdx"]`);
await page.waitForSelector(`#crazy`);
let mdx = `import { useLoaderData } from '@remix-run/react'
export const loader = () => "hot"
export const Component = () => {
const data = useLoaderData()
return <h1 id={data}>{data}</h1>
}

# heyo
whatsup

<Component/>
`;
fs.writeFileSync(mdxPath, mdx);
await page.waitForSelector(`#hot`);
expect(dataRequests).toBe(4);

fs.writeFileSync(mdxPath, originalMdx);
await page.waitForSelector(`#crazy`);
expect(dataRequests).toBe(5);
} catch (e) {
console.log("stdout begin -----------------------");
console.log(devStdout());
Expand Down
48 changes: 44 additions & 4 deletions integration/hmr-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ let fixture = (options: {
...cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : [],
];

// dummy loader to make sure that HDR is granular
export const loader = () => {
return null;
};

export default function Root() {
return (
<html lang="en" className="h-full">
Expand All @@ -141,6 +146,7 @@ let fixture = (options: {
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/mdx">MDX</Link></li>
</ul>
</nav>
</header>
Expand Down Expand Up @@ -179,7 +185,18 @@ let fixture = (options: {
)
}
`,

"app/routes/mdx.mdx": `import { useLoaderData } from '@remix-run/react'
export const loader = () => "crazy"
export const Component = () => {
const data = useLoaderData()
return <h1 id={data}>{data}</h1>
}

# heyo
whatsup

<Component/>
`,
"app/components/counter.tsx": js`
import * as React from "react";
export default function Counter({ id }) {
Expand Down Expand Up @@ -272,6 +289,8 @@ test("HMR", async ({ page }) => {
let originalCounter = fs.readFileSync(counterPath, "utf8");
let cssModulePath = path.join(projectDir, "app", "styles.module.css");
let originalCssModule = fs.readFileSync(cssModulePath, "utf8");
let mdxPath = path.join(projectDir, "app", "routes", "mdx.mdx");
let originalMdx = fs.readFileSync(mdxPath, "utf8");

// make content and style changed to index route
let newCssModule = `
Expand Down Expand Up @@ -419,9 +438,30 @@ test("HMR", async ({ page }) => {
`#about-counter:has-text("inc 0")`
);

// This should not have triggered any revalidation but our detection is
// failing for x-module changes for route module imports
// expect(dataRequests).toBe(2);
expect(dataRequests).toBe(2);

// mdx
await page.click(`a[href="/mdx"]`);
await page.waitForSelector(`#crazy`);
let mdx = `import { useLoaderData } from '@remix-run/react'
export const loader = () => "hot"
export const Component = () => {
const data = useLoaderData()
return <h1 id={data}>{data}</h1>
}

# heyo
whatsup

<Component/>
`;
fs.writeFileSync(mdxPath, mdx);
await page.waitForSelector(`#hot`);
expect(dataRequests).toBe(4);

fs.writeFileSync(mdxPath, originalMdx);
await page.waitForSelector(`#crazy`);
expect(dataRequests).toBe(5);
} catch (e) {
console.log("stdout begin -----------------------");
console.log(devStdout());
Expand Down
2 changes: 1 addition & 1 deletion packages/remix-dev/__tests__/cli-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ describe("remix CLI", () => {
--http-host HTTP(S) host for the dev server. Default: localhost
--http-port HTTP(S) port for the dev server. Default: any open port
--no-restart Do not restart the app server when rebuilds occur.
--websocket-port Websocket port for the dev server. Default: any open port
--websocket-port WebSocket port for the dev server. Default: any open port
\`init\` Options:
--no-delete Skip deleting the \`remix.init\` script
\`routes\` Options:
Expand Down
16 changes: 8 additions & 8 deletions packages/remix-dev/cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export async function build(
host: dev.httpHost,
port: dev.httpPort,
};
options.devWebsocketPort = dev.websocketPort;
options.devWebSocketPort = dev.webSocketPort;
}

fse.emptyDirSync(config.assetsBuildDirectory);
Expand Down Expand Up @@ -475,7 +475,7 @@ type DevBuildFlags = {
httpScheme: string;
httpHost: string;
httpPort: number;
websocketPort: number;
webSocketPort: number;
};
let resolveDevBuild = async (
config: RemixConfig,
Expand All @@ -500,16 +500,16 @@ let resolveDevBuild = async (
(dev === true ? undefined : dev.httpPort) ??
(await findPort());
// prettier-ignore
let websocketPort =
flags.websocketPort ??
(dev === true ? undefined : dev.websocketPort) ??
let webSocketPort =
flags.webSocketPort ??
(dev === true ? undefined : dev.webSocketPort) ??
(await findPort());

return {
httpScheme,
httpHost,
httpPort,
websocketPort,
webSocketPort,
};
};

Expand All @@ -524,7 +524,7 @@ let resolveDevServe = async (
let dev = config.future.unstable_dev;
if (dev === false) throw Error("Cannot resolve dev options");

let { httpScheme, httpHost, httpPort, websocketPort } = await resolveDevBuild(
let { httpScheme, httpHost, httpPort, webSocketPort } = await resolveDevBuild(
config,
flags
);
Expand Down Expand Up @@ -561,7 +561,7 @@ let resolveDevServe = async (
httpScheme,
httpHost,
httpPort,
websocketPort,
webSocketPort,
restart,
};
};
4 changes: 2 additions & 2 deletions packages/remix-dev/cli/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ ${colors.logoBlue("R")} ${colors.logoGreen("E")} ${colors.logoYellow(
--http-host HTTP(S) host for the dev server. Default: localhost
--http-port HTTP(S) port for the dev server. Default: any open port
--no-restart Do not restart the app server when rebuilds occur.
--websocket-port Websocket port for the dev server. Default: any open port
--websocket-port WebSocket port for the dev server. Default: any open port
\`init\` Options:
--no-delete Skip deleting the \`remix.init\` script
\`routes\` Options:
Expand Down Expand Up @@ -226,7 +226,7 @@ export async function run(argv: string[] = process.argv.slice(2)) {
delete flags["http-port"];
}
if (flags["websocket-port"]) {
flags.websocketPort = flags["websocket-port"];
flags.webSocketPort = flags["websocket-port"];
delete flags["websocket-port"];
}

Expand Down
1 change: 0 additions & 1 deletion packages/remix-dev/compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ export let create = async (ctx: Context): Promise<Compiler> => {
}

// js compilation (implicitly writes artifacts/js)
// TODO: js task should not return metafile, but rather js assets
let js = await tasks.js;
if (!js.ok) throw error ?? js.error;
let { metafile, hmr } = js.value;
Expand Down
13 changes: 2 additions & 11 deletions packages/remix-dev/compiler/js/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ const getExternals = (remixConfig: RemixConfig): string[] => {

const createEsbuildConfig = (
ctx: Context,
onLoader: (filename: string, code: string) => void,
channels: { cssBundleHref: Channel.Type<string | undefined> }
): esbuild.BuildOptions => {
let entryPoints: Record<string, string> = {
Expand Down Expand Up @@ -142,7 +141,7 @@ const createEsbuildConfig = (
absoluteCssUrlsPlugin(),
externalPlugin(/^https?:\/\//, { sideEffects: false }),
ctx.config.future.unstable_dev
? browserRouteModulesPlugin_v2(ctx, routeModulePaths, onLoader)
? browserRouteModulesPlugin_v2(ctx, routeModulePaths)
: browserRouteModulesPlugin(ctx, /\?browser$/),
mdxPlugin(ctx),
emptyModulesPlugin(ctx, /\.server(\.[jt]sx?)?$/),
Expand Down Expand Up @@ -237,19 +236,12 @@ export const create = async (
ctx: Context,
channels: { cssBundleHref: Channel.Type<string | undefined> }
): Promise<Compiler> => {
let hmrRoutes: Record<string, { loaderHash: string }> = {};
let onLoader = (filename: string, code: string) => {
let key = path.relative(ctx.config.rootDirectory, filename);
hmrRoutes[key] = { loaderHash: code };
};

let compiler = await esbuild.context({
...createEsbuildConfig(ctx, onLoader, channels),
...createEsbuildConfig(ctx, channels),
metafile: true,
});

let compile = async () => {
hmrRoutes = {};
let { metafile } = await compiler.rebuild();

let hmr: Manifest["hmr"] | undefined = undefined;
Expand All @@ -266,7 +258,6 @@ export const create = async (
);
hmr = {
runtime: hmrRuntime,
routes: hmrRoutes,
timestamp: Date.now(),
};
}
Expand Down
27 changes: 4 additions & 23 deletions packages/remix-dev/compiler/js/plugins/routes_unstable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { processMDX } from "../../plugins/mdx";

const serverOnlyExports = new Set(["action", "loader"]);

let removeServerExports = (onLoader: (loader: string) => void) =>
let removeServerExports = () =>
Transform.create(({ types: t }) => {
return {
visitor: {
Expand All @@ -38,20 +38,12 @@ let removeServerExports = (onLoader: (loader: string) => void) =>
if (t.isFunctionDeclaration(node.declaration)) {
let name = node.declaration.id?.name;
if (!name) return;
if (name === "loader") {
let { code } = generate(node);
onLoader(code);
}
if (serverOnlyExports.has(name)) return path.remove();
}
if (t.isVariableDeclaration(node.declaration)) {
let declarations = node.declaration.declarations.filter((d) => {
let name = t.isIdentifier(d.id) ? d.id.name : undefined;
if (!name) return false;
if (name === "loader") {
let { code } = generate(node);
onLoader(code);
}
return !serverOnlyExports.has(name);
});
if (declarations.length === 0) return path.remove();
Expand All @@ -72,8 +64,7 @@ let removeServerExports = (onLoader: (loader: string) => void) =>
*/
export function browserRouteModulesPlugin(
{ config, options }: Context,
routeModulePaths: Map<string, string>,
onLoader: (filename: string, code: string) => void
routeModulePaths: Map<string, string>
): esbuild.Plugin {
return {
name: "browser-route-modules",
Expand Down Expand Up @@ -119,9 +110,7 @@ export function browserRouteModulesPlugin(
return mdxResult;
}

let transform = removeServerExports((loader: string) =>
onLoader(routeFile, loader)
);
let transform = removeServerExports();
mdxResult.contents = transform(
mdxResult.contents,
// Trick babel into allowing JSX syntax.
Expand All @@ -134,10 +123,7 @@ export function browserRouteModulesPlugin(

let value = cache.get(file);
if (!value || value.sourceCode !== sourceCode) {
let extractedLoader;
let transform = removeServerExports(
(loader: string) => (extractedLoader = loader)
);
let transform = removeServerExports();
let contents = transform(sourceCode, routeFile);

if (options.mode === "development" && config.future.unstable_dev) {
Expand All @@ -153,7 +139,6 @@ export function browserRouteModulesPlugin(
}
value = {
sourceCode,
extractedLoader,
output: {
contents,
loader: getLoaderForFile(routeFile),
Expand All @@ -163,10 +148,6 @@ export function browserRouteModulesPlugin(
cache.set(file, value);
}

if (value.extractedLoader) {
onLoader(routeFile, value.extractedLoader);
}

return value.output;
}
);
Expand Down
Loading