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

vite: profiling #8493

Merged
merged 5 commits into from
Jan 12, 2024
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
12 changes: 12 additions & 0 deletions .changeset/twenty-pandas-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@remix-run/dev": patch
---

Vite: Performance profiling

Run `remix vite:build --profile` to generate a `.cpuprofile` that can be shared or uploaded to speedscope.app

In dev, press `p + enter` to start a new profiling session or stop the current session.
If you need to profile dev server startup, run `remix vite:dev --profile` to initialize the dev server with a running profiling session.

For more, see the new docs: Vite > Performance
48 changes: 42 additions & 6 deletions docs/future/vite.md
Original file line number Diff line number Diff line change
Expand Up @@ -659,15 +659,48 @@ const posts = import.meta.glob("./posts/*.mdx", {
});
```

## Troubleshooting
## Debugging

You can use the [`NODE_OPTIONS` environment variable][node-options] to start a debugging session:

```shellscript nonumber
NODE_OPTIONS="--inspect-brk" npm run dev`
```

Then you can attach a debugger from your browser.
For example, in Chrome you can open up `chrome://inspect` or click the NodeJS icon in the dev tools to attach the debugger.

Check out the [known issues with the Remix Vite plugin on GitHub][issues-vite] before filing a new bug report!
#### vite-plugin-inspect

#### Resources for general debugging
[`vite-plugin-inspect`][vite-plugin-inspect] shows you each how each Vite plugin transforms your code and how long each plugin takes.

## Performance

Remix includes a `--profile` flag for performance profiling.

```shellscript nonumber
remix vite:build --profile
```

When running with `--profile`, a `.cpuprofile` file will be generated that can be shared or upload to speedscope.app to for analysis.

You can also profile in dev by pressing `p + enter` while the dev server is running to start a new profiling session or stop the current session.
If you need to profile dev server startup, you can also use the `--profile` flag to initialize a profiling session on startup:

```shellscript nonumber
remix vite:dev --profile
```

Remember that you can always check the [Vite performance docs][vite-perf] for more tips!

#### Bundle analysis

To visualize and analyze your bundle, you can use the [rollup-plugin-visualizer][rollup-plugin-visualizer] plugin.

## Troubleshooting

The [Inspect plugin][vite-plugin-inspect] shows you each transformation that Vite performs on your code.
It can be useful to see which plugin is causing the unexpected behavior.
Not only that, but it can give you a better mental model for exactly how Remix handles splitting client and server code.
Check the [debugging](#debugging) and [performance](#performance) sections for general troubleshooting tips.
Also, see if anyone else is having a similar problem by looking through the [known issues with the remix vite plugin on github][issues-vite].

#### HMR

Expand Down Expand Up @@ -817,3 +850,6 @@ We're definitely late to the Vite party, but we're excited to be here now!
[server-bundles]: ./server-bundles
[fullstack-components]: https://www.epicweb.dev/full-stack-components
[vite-plugin-inspect]: https://github.com/antfu/vite-plugin-inspect
[vite-perf]: https://vitejs.dev/guide/performance.html
[node-options]: https://nodejs.org/api/cli.html#node_optionsoptions
[rollup-plugin-visualizer]: https://github.com/btd/rollup-plugin-visualizer
15 changes: 14 additions & 1 deletion packages/remix-dev/cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import getPort, { makeRange } from "get-port";
import prettyMs from "pretty-ms";
import PackageJson from "@npmcli/package-json";
import pc from "picocolors";
import exitHook from "exit-hook";

import * as colors from "../colors";
import * as compiler from "../compiler";
Expand All @@ -20,6 +21,7 @@ import { transpile as convertFileToJS } from "./useJavascript";
import type { Options } from "../compiler/options";
import { createFileWatchCache } from "../compiler/fileWatchCache";
import { logger } from "../tux";
import * as profiler from "../vite/profiler";

type InitFlags = {
deleteScript?: boolean;
Expand Down Expand Up @@ -143,7 +145,14 @@ export async function viteBuild(
}

let { build } = await import("../vite/build");
await build(root, options);
if (options.profile) {
await profiler.start();
}
try {
await build(root, options);
} finally {
await profiler.stop(logger.info);
}
}

export async function watch(
Expand Down Expand Up @@ -191,6 +200,10 @@ export async function dev(

export async function viteDev(root: string, options: ViteDevOptions = {}) {
let { dev } = await import("../vite/dev");
if (options.profile) {
await profiler.start();
}
exitHook(() => profiler.stop(console.info));
await dev(root, options);
pcattori marked this conversation as resolved.
Show resolved Hide resolved

// keep `remix vite-dev` alive by waiting indefinitely
Expand Down
1 change: 1 addition & 0 deletions packages/remix-dev/cli/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export async function run(argv: string[] = process.argv.slice(2)) {
"-m": "--mode",
"--open": isBooleanFlag("--open") ? Boolean : String,
"--strictPort": Boolean,
"--profile": Boolean,
}
: {
// Non Vite commands
Expand Down
1 change: 1 addition & 0 deletions packages/remix-dev/vite/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ export interface ViteBuildOptions {
logLevel?: Vite.LogLevel;
minify?: Vite.BuildOptions["minify"];
mode?: string;
profile?: boolean;
}

export async function build(
Expand Down
21 changes: 20 additions & 1 deletion packages/remix-dev/vite/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type * as Vite from "vite";
import colors from "picocolors";

import { preloadViteEsm } from "./import-vite-esm-sync";
import * as profiler from "./profiler";

export interface ViteDevOptions {
clearScreen?: boolean;
Expand All @@ -14,6 +15,7 @@ export interface ViteDevOptions {
open?: boolean | string;
port?: number;
strictPort?: boolean;
profile?: boolean;
}

export async function dev(
Expand Down Expand Up @@ -53,5 +55,22 @@ export async function dev(

await server.listen();
server.printUrls();
server.bindCLIShortcuts({ print: true });

let customShortcuts: Vite.CLIShortcut<typeof server>[] = [
{
key: "p",
description: "start/stop the profiler",
async action(server) {
if (profiler.getSession()) {
await profiler.stop(server.config.logger.info);
} else {
await profiler.start(() => {
server.config.logger.info("Profiler started");
});
}
},
},
];

server.bindCLIShortcuts({ print: true, customShortcuts });
}
44 changes: 44 additions & 0 deletions packages/remix-dev/vite/profiler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Adapted from:
// - https://github.com/vitejs/vite/blob/9fc5d9cb3a1b9df067e00959faa9da43ae03f776/packages/vite/bin/vite.js
// - https://github.com/vitejs/vite/blob/9fc5d9cb3a1b9df067e00959faa9da43ae03f776/packages/vite/src/node/cli.ts

import fs from "node:fs";
import type { Session } from "node:inspector";
import path from "node:path";
import colors from "picocolors";

declare namespace global {
let __remix_profile_session: Session | undefined;
}

export const getSession = () => global.__remix_profile_session;

export const start = async (callback?: () => void | Promise<void>) => {
let inspector = await import("node:inspector").then((r) => r.default);
let session = (global.__remix_profile_session = new inspector.Session());
session.connect();
session.post("Profiler.enable", () => {
session.post("Profiler.start", callback);
});
};

let profileCount = 0;

export const stop = (log: (message: string) => void): void | Promise<void> => {
let session = getSession();
if (!session) return;
return new Promise((res, rej) => {
session!.post("Profiler.stop", (err, { profile }) => {
if (err) return rej(err);
let outPath = path.resolve(`./remix-${profileCount++}.cpuprofile`);
fs.writeFileSync(outPath, JSON.stringify(profile));
log(
colors.yellow(
`CPU profile written to ${colors.white(colors.dim(outPath))}`
)
);
global.__remix_profile_session = undefined;
res();
});
});
};