Skip to content

Commit

Permalink
Fix Next.js deployments on Windows (#5499)
Browse files Browse the repository at this point in the history
* Stream npm ls when bundling Next.js
* replace node spawn with cross-spawn

---------

Co-authored-by: James Daniels <[email protected]>
  • Loading branch information
leoortizz and jamesdaniels authored Feb 13, 2023
1 parent 7258fca commit d46ae1f
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 31 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
- Fix various accessibility and usability issues in Emulator UI.
- Support .env when deploying a web framework (#5501)
- Fix various issues with "init hosting" and web frameworks (#5500)
- Fix Next.js deployments on Windows (#5499)
3 changes: 2 additions & 1 deletion src/frameworks/angular/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Target } from "@angular-devkit/architect";
import { join } from "path";
import { execSync, spawn } from "child_process";
import { execSync } from "child_process";
import { spawn } from "cross-spawn";
import { copy, pathExists } from "fs-extra";
import { mkdir } from "fs/promises";

Expand Down
3 changes: 2 additions & 1 deletion src/frameworks/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { join, relative, extname, basename } from "path";
import { exit } from "process";
import { execSync, spawnSync } from "child_process";
import { execSync } from "child_process";
import { sync as spawnSync } from "cross-spawn";
import { readdirSync, statSync } from "fs";
import { pathToFileURL } from "url";
import { IncomingMessage, ServerResponse } from "http";
Expand Down
76 changes: 50 additions & 26 deletions src/frameworks/next/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { execSync, spawnSync } from "child_process";
import { execSync } from "child_process";
import { spawn, sync as spawnSync } from "cross-spawn";
import { mkdir, copyFile } from "fs/promises";
import { dirname, join } from "path";
import type { NextConfig } from "next";
Expand All @@ -11,6 +12,10 @@ import { existsSync } from "fs";
import { gte } from "semver";
import { IncomingMessage, ServerResponse } from "http";
import * as clc from "colorette";
import { chain } from "stream-chain";
import { parser } from "stream-json";
import { pick } from "stream-json/filters/Pick";
import { streamObject } from "stream-json/streamers/StreamObject";

import {
BuildResult,
Expand All @@ -34,7 +39,7 @@ import {
isUsingMiddleware,
allDependencyNames,
} from "./utils";
import type { Manifest, NpmLsReturn } from "./interfaces";
import type { Manifest, NpmLsDepdendency } from "./interfaces";
import { readJSON } from "../utils";
import { warnIfCustomBuildScript } from "../utils";
import type { EmulatorInfo } from "../../emulator/types";
Expand Down Expand Up @@ -345,33 +350,52 @@ export async function ɵcodegenPublicDirectory(sourceDir: string, destDir: strin
export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: string) {
const { distDir } = await getConfig(sourceDir);
const packageJson = await readJSON(join(sourceDir, "package.json"));
// Bundle their next.config.js with esbuild via NPX, pinned version was having troubles on m1
// macs and older Node versions; either way, we should avoid taking on any deps in firebase-tools
// Alternatively I tried using @swc/spack and the webpack bundled into Next.js but was
// encountering difficulties with both of those
if (existsSync(join(sourceDir, "next.config.js"))) {
// Bundle their next.config.js with esbuild via NPX, pinned version was having troubles on m1
// macs and older Node versions; either way, we should avoid taking on any deps in firebase-tools
// Alternatively I tried using @swc/spack and the webpack bundled into Next.js but was
// encountering difficulties with both of those
const dependencyTree: NpmLsReturn = JSON.parse(
spawnSync("npm", ["ls", "--omit=dev", "--all", "--json"], {
try {
const productionDeps = await new Promise<string[]>((resolve) => {
const dependencies: string[] = [];
const pipeline = chain([
spawn("npm", ["ls", "--omit=dev", "--all", "--json"], { cwd: sourceDir }).stdout,
parser({ packValues: false, packKeys: true, streamValues: false }),
pick({ filter: "dependencies" }),
streamObject(),
({ key, value }: { key: string; value: NpmLsDepdendency }) => [
key,
...allDependencyNames(value),
],
]);
pipeline.on("data", (it: string) => dependencies.push(it));
pipeline.on("end", () => {
resolve([...new Set(dependencies)]);
});
});
// Mark all production deps as externals, so they aren't bundled
// DevDeps won't be included in the Cloud Function, so they should be bundled
const esbuildArgs = productionDeps
.map((it) => `--external:${it}`)
.concat(
"--bundle",
"--platform=node",
`--target=node${NODE_VERSION}`,
`--outdir=${destDir}`,
"--log-level=error"
);
const bundle = spawnSync("npx", ["--yes", "esbuild", "next.config.js", ...esbuildArgs], {
cwd: sourceDir,
}).stdout.toString()
);
// Mark all production deps as externals, so they aren't bundled
// DevDeps won't be included in the Cloud Function, so they should be bundled
const esbuildArgs = allDependencyNames(dependencyTree)
.map((it) => `--external:${it}`)
.concat(
"--bundle",
"--platform=node",
`--target=node${NODE_VERSION}`,
`--outdir=${destDir}`,
"--log-level=error"
});
if (bundle.status) {
throw new FirebaseError(bundle.stderr.toString());
}
} catch (e: any) {
console.warn(
"Unable to bundle next.config.js for use in Cloud Functions, proceeding with deploy but problems may be enountered."
);
const bundle = spawnSync("npx", ["--yes", "esbuild", "next.config.js", ...esbuildArgs], {
cwd: sourceDir,
});
if (bundle.status) {
console.error(bundle.stderr.toString());
throw new FirebaseError("Unable to bundle next.config.js for use in Cloud Functions");
console.error(e.message);
copy(join(sourceDir, "next.config.js"), join(destDir, "next.config.js"));
}
}
if (await pathExists(join(sourceDir, "public"))) {
Expand Down
3 changes: 1 addition & 2 deletions src/frameworks/next/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,5 @@ export function allDependencyNames(mod: NpmLsDepdendency): string[] {
(acc, it) => [...acc, it, ...allDependencyNames(mod.dependencies![it])],
[] as string[]
);
// deduplicate the names
return [...new Set(dependencyNames)];
return dependencyNames;
}
3 changes: 2 additions & 1 deletion src/frameworks/vite/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { execSync, spawn } from "child_process";
import { execSync } from "child_process";
import { spawn } from "cross-spawn";
import { existsSync } from "fs";
import { copy, pathExists } from "fs-extra";
import { join } from "path";
Expand Down
6 changes: 6 additions & 0 deletions src/test/frameworks/next/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,9 +353,15 @@ describe("Next.js utils", () => {
"sass",
"styled-jsx",
"client-only",
"react",
"react-dom",
"loose-envify",
"js-tokens",
"react",
"scheduler",
"loose-envify",
"react",
"loose-envify",
]);
});
});
Expand Down

0 comments on commit d46ae1f

Please sign in to comment.