Skip to content

Commit d46ae1f

Browse files
Fix Next.js deployments on Windows (#5499)
* Stream npm ls when bundling Next.js * replace node spawn with cross-spawn --------- Co-authored-by: James Daniels <jamesdaniels@google.com>
1 parent 7258fca commit d46ae1f

File tree

7 files changed

+64
-31
lines changed

7 files changed

+64
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111
- Fix various accessibility and usability issues in Emulator UI.
1212
- Support .env when deploying a web framework (#5501)
1313
- Fix various issues with "init hosting" and web frameworks (#5500)
14+
- Fix Next.js deployments on Windows (#5499)

src/frameworks/angular/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Target } from "@angular-devkit/architect";
22
import { join } from "path";
3-
import { execSync, spawn } from "child_process";
3+
import { execSync } from "child_process";
4+
import { spawn } from "cross-spawn";
45
import { copy, pathExists } from "fs-extra";
56
import { mkdir } from "fs/promises";
67

src/frameworks/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { join, relative, extname, basename } from "path";
22
import { exit } from "process";
3-
import { execSync, spawnSync } from "child_process";
3+
import { execSync } from "child_process";
4+
import { sync as spawnSync } from "cross-spawn";
45
import { readdirSync, statSync } from "fs";
56
import { pathToFileURL } from "url";
67
import { IncomingMessage, ServerResponse } from "http";

src/frameworks/next/index.ts

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { execSync, spawnSync } from "child_process";
1+
import { execSync } from "child_process";
2+
import { spawn, sync as spawnSync } from "cross-spawn";
23
import { mkdir, copyFile } from "fs/promises";
34
import { dirname, join } from "path";
45
import type { NextConfig } from "next";
@@ -11,6 +12,10 @@ import { existsSync } from "fs";
1112
import { gte } from "semver";
1213
import { IncomingMessage, ServerResponse } from "http";
1314
import * as clc from "colorette";
15+
import { chain } from "stream-chain";
16+
import { parser } from "stream-json";
17+
import { pick } from "stream-json/filters/Pick";
18+
import { streamObject } from "stream-json/streamers/StreamObject";
1419

1520
import {
1621
BuildResult,
@@ -34,7 +39,7 @@ import {
3439
isUsingMiddleware,
3540
allDependencyNames,
3641
} from "./utils";
37-
import type { Manifest, NpmLsReturn } from "./interfaces";
42+
import type { Manifest, NpmLsDepdendency } from "./interfaces";
3843
import { readJSON } from "../utils";
3944
import { warnIfCustomBuildScript } from "../utils";
4045
import type { EmulatorInfo } from "../../emulator/types";
@@ -345,33 +350,52 @@ export async function ɵcodegenPublicDirectory(sourceDir: string, destDir: strin
345350
export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: string) {
346351
const { distDir } = await getConfig(sourceDir);
347352
const packageJson = await readJSON(join(sourceDir, "package.json"));
353+
// Bundle their next.config.js with esbuild via NPX, pinned version was having troubles on m1
354+
// macs and older Node versions; either way, we should avoid taking on any deps in firebase-tools
355+
// Alternatively I tried using @swc/spack and the webpack bundled into Next.js but was
356+
// encountering difficulties with both of those
348357
if (existsSync(join(sourceDir, "next.config.js"))) {
349-
// Bundle their next.config.js with esbuild via NPX, pinned version was having troubles on m1
350-
// macs and older Node versions; either way, we should avoid taking on any deps in firebase-tools
351-
// Alternatively I tried using @swc/spack and the webpack bundled into Next.js but was
352-
// encountering difficulties with both of those
353-
const dependencyTree: NpmLsReturn = JSON.parse(
354-
spawnSync("npm", ["ls", "--omit=dev", "--all", "--json"], {
358+
try {
359+
const productionDeps = await new Promise<string[]>((resolve) => {
360+
const dependencies: string[] = [];
361+
const pipeline = chain([
362+
spawn("npm", ["ls", "--omit=dev", "--all", "--json"], { cwd: sourceDir }).stdout,
363+
parser({ packValues: false, packKeys: true, streamValues: false }),
364+
pick({ filter: "dependencies" }),
365+
streamObject(),
366+
({ key, value }: { key: string; value: NpmLsDepdendency }) => [
367+
key,
368+
...allDependencyNames(value),
369+
],
370+
]);
371+
pipeline.on("data", (it: string) => dependencies.push(it));
372+
pipeline.on("end", () => {
373+
resolve([...new Set(dependencies)]);
374+
});
375+
});
376+
// Mark all production deps as externals, so they aren't bundled
377+
// DevDeps won't be included in the Cloud Function, so they should be bundled
378+
const esbuildArgs = productionDeps
379+
.map((it) => `--external:${it}`)
380+
.concat(
381+
"--bundle",
382+
"--platform=node",
383+
`--target=node${NODE_VERSION}`,
384+
`--outdir=${destDir}`,
385+
"--log-level=error"
386+
);
387+
const bundle = spawnSync("npx", ["--yes", "esbuild", "next.config.js", ...esbuildArgs], {
355388
cwd: sourceDir,
356-
}).stdout.toString()
357-
);
358-
// Mark all production deps as externals, so they aren't bundled
359-
// DevDeps won't be included in the Cloud Function, so they should be bundled
360-
const esbuildArgs = allDependencyNames(dependencyTree)
361-
.map((it) => `--external:${it}`)
362-
.concat(
363-
"--bundle",
364-
"--platform=node",
365-
`--target=node${NODE_VERSION}`,
366-
`--outdir=${destDir}`,
367-
"--log-level=error"
389+
});
390+
if (bundle.status) {
391+
throw new FirebaseError(bundle.stderr.toString());
392+
}
393+
} catch (e: any) {
394+
console.warn(
395+
"Unable to bundle next.config.js for use in Cloud Functions, proceeding with deploy but problems may be enountered."
368396
);
369-
const bundle = spawnSync("npx", ["--yes", "esbuild", "next.config.js", ...esbuildArgs], {
370-
cwd: sourceDir,
371-
});
372-
if (bundle.status) {
373-
console.error(bundle.stderr.toString());
374-
throw new FirebaseError("Unable to bundle next.config.js for use in Cloud Functions");
397+
console.error(e.message);
398+
copy(join(sourceDir, "next.config.js"), join(destDir, "next.config.js"));
375399
}
376400
}
377401
if (await pathExists(join(sourceDir, "public"))) {

src/frameworks/next/utils.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,5 @@ export function allDependencyNames(mod: NpmLsDepdendency): string[] {
229229
(acc, it) => [...acc, it, ...allDependencyNames(mod.dependencies![it])],
230230
[] as string[]
231231
);
232-
// deduplicate the names
233-
return [...new Set(dependencyNames)];
232+
return dependencyNames;
234233
}

src/frameworks/vite/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { execSync, spawn } from "child_process";
1+
import { execSync } from "child_process";
2+
import { spawn } from "cross-spawn";
23
import { existsSync } from "fs";
34
import { copy, pathExists } from "fs-extra";
45
import { join } from "path";

src/test/frameworks/next/utils.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,9 +353,15 @@ describe("Next.js utils", () => {
353353
"sass",
354354
"styled-jsx",
355355
"client-only",
356+
"react",
357+
"react-dom",
356358
"loose-envify",
357359
"js-tokens",
360+
"react",
358361
"scheduler",
362+
"loose-envify",
363+
"react",
364+
"loose-envify",
359365
]);
360366
});
361367
});

0 commit comments

Comments
 (0)