Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/orange-cars-sip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": patch
---

Custom builds for `dev` and `publish`
1 change: 0 additions & 1 deletion .github/workflows/tests-typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,3 @@ jobs:

- name: Test
run: npm run test
working-directory: packages/wrangler
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
packages/wrangler/vendor/
packages/wrangler/wrangler-dist/
packages/example-worker-app/dist/
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"scripts": {
"lint": "eslint packages/**",
"check": "prettier packages/** --check && tsc && npm run lint",
"prettify": "prettier packages/** --write"
"prettify": "prettier packages/** --write",
"test": "npm run test --workspace=wrangler"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this is fine for now, we should think about a more generalised solution for when the other packages in this workspace have tests to run.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agreed, just did this as a quick dx win for myself rn.

},
"engines": {
"node": ">=16.0.0"
Expand Down
1 change: 1 addition & 0 deletions packages/example-worker-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
91 changes: 87 additions & 4 deletions packages/wrangler/src/dev.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import esbuild from "esbuild";
import { readFile } from "fs/promises";
import { existsSync } from "fs";
import type { DirectoryResult } from "tmp-promise";
import tmp from "tmp-promise";
import type { CfPreviewToken } from "./api/preview";
Expand All @@ -16,14 +17,15 @@ import onExit from "signal-exit";
import { syncAssets } from "./sites";
import clipboardy from "clipboardy";
import http from "node:http";
import serveStatic from "serve-static";
import commandExists from "command-exists";
import assert from "assert";
import { getAPIToken } from "./user";
import fetch from "node-fetch";
import makeModuleCollector from "./module-collection";
import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
import { createHttpProxy } from "./proxy";
import { execa } from "execa";
import { watch } from "chokidar";

type CfScriptFormat = void | "modules" | "service-worker";

Expand All @@ -42,6 +44,11 @@ type Props = {
compatibilityDate: void | string;
compatibilityFlags: void | string[];
usageModel: void | "bundled" | "unbound";
buildCommand: {
command?: undefined | string;
cwd?: undefined | string;
watch_dir?: undefined | string;
};
};

function Dev(props: Props): JSX.Element {
Expand All @@ -54,8 +61,13 @@ function Dev(props: Props): JSX.Element {
const apiToken = getAPIToken();
const directory = useTmpDir();

// if there isn't a build command, we just return the entry immediately
// ideally there would be a conditional here, but the rules of hooks
// kinda forbid that, so we thread the entry through useCustomBuild
const entry = useCustomBuild(props.entry, props.buildCommand);

const bundle = useEsbuild({
entry: props.entry,
entry,
destination: directory,
staticRoot: props.public,
jsxFactory: props.jsxFactory,
Expand Down Expand Up @@ -361,6 +373,77 @@ function useTmpDir(): string | void {
return directory?.path;
}

function runCommand() {}

function useCustomBuild(
expectedEntry: string,
props: {
command?: undefined | string;
cwd?: undefined | string;
watch_dir?: undefined | string;
}
): void | string {
const [entry, setEntry] = useState<string | void>(
// if there's no build command, just return the expected entry
props.command ? null : expectedEntry
);
const { command, cwd, watch_dir } = props;
useEffect(() => {
if (!command) return;
let cmd, interval;
console.log("running:", command);
const commandPieces = command.split(" ");
cmd = execa(commandPieces[0], commandPieces.slice(1), {
...(cwd && { cwd }),
stderr: "inherit",
stdout: "inherit",
});
if (watch_dir) {
watch(watch_dir, { persistent: true, ignoreInitial: true }).on(
"all",
(_event, _path) => {
console.log(`The file ${path} changed, restarting build...`);
cmd.kill();
cmd = execa(commandPieces[0], commandPieces.slice(1), {
...(cwd && { cwd }),
stderr: "inherit",
stdout: "inherit",
});
Comment on lines 407 to 411
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block is now repeated - let's move it into its own function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just one statement, doesn't pass my threshold for a new abstraction yet imo

}
);
}

// check every so often whether `expectedEntry` exists
// if it does, we're done
const startedAt = Date.now();
interval = setInterval(() => {
if (existsSync(expectedEntry)) {
clearInterval(interval);
setEntry(expectedEntry);
} else {
const elapsed = Date.now() - startedAt;
// timeout after 30 seconds of waiting
if (elapsed > 1000 * 60 * 30) {
console.error("⎔ Build timed out.");
clearInterval(interval);
cmd.kill();
}
}
}, 200);
// TODO: we could probably timeout here after a while

return () => {
if (cmd) {
cmd.kill();
cmd = undefined;
}
clearInterval(interval);
interval = undefined;
};
}, [command, cwd, expectedEntry, watch_dir]);
return entry;
}

type EsbuildBundle = {
id: number;
path: string;
Expand All @@ -371,7 +454,7 @@ type EsbuildBundle = {
};

function useEsbuild(props: {
entry: string;
entry: void | string;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OOC - what's the difference between void and undefined in this type?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to revisit very void and replace it with undefined. I just got trigger happy and regret it now.

destination: string | void;
staticRoot: void | string;
jsxFactory: string | void;
Expand All @@ -382,7 +465,7 @@ function useEsbuild(props: {
useEffect(() => {
let result: esbuild.BuildResult;
async function build() {
if (!destination) return;
if (!destination || !entry) return;
const moduleCollector = makeModuleCollector();
result = await esbuild.build({
entryPoints: [entry],
Expand Down
1 change: 1 addition & 0 deletions packages/wrangler/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ export async function main(argv: string[]): Promise<void> {
<Dev
name={args.name || config.name}
entry={filename}
buildCommand={config.build || {}}
format={format}
initialMode={args.local ? "local" : "remote"}
jsxFactory={args["jsx-factory"] || envRootObj?.jsx_factory}
Expand Down
12 changes: 12 additions & 0 deletions packages/wrangler/src/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import cfetch from "./cfetch";
import assert from "node:assert";
import { syncAssets } from "./sites";
import makeModuleCollector from "./module-collection";
import { execa } from "execa";

type CfScriptFormat = void | "modules" | "service-worker";

Expand Down Expand Up @@ -76,6 +77,17 @@ export default async function publish(props: Props): Promise<void> {

const destination = await tmp.dir({ unsafeCleanup: true });

if (props.config.build?.command) {
// TODO: add a deprecation message here?
console.log("running:", props.config.build.command);
const buildCommandPieces = props.config.build.command.split(" ");
await execa(buildCommandPieces[0], buildCommandPieces.slice(1), {
stdout: "inherit",
stderr: "inherit",
...(props.config.build?.cwd && { cwd: props.config.build.cwd }),
});
}

const moduleCollector = makeModuleCollector();
const result = await esbuild.build({
...(props.public
Expand Down