Skip to content

Commit 978a10f

Browse files
fofiujamesdanielsleoortizzaustincrim
authored
Nuxt support (#5321)
Refactor the Nuxt build workflow to accommodate all the ssr/target combinations Co-authored-by: James Daniels <jamesdaniels@google.com> Co-authored-by: Leonardo Ortiz <leo@monogram.io> Co-authored-by: Austin Crim <aust.crim@gmail.com>
1 parent accea7a commit 978a10f

File tree

7 files changed

+253
-34
lines changed

7 files changed

+253
-34
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
- Refactors Functions Emulator. (#5422)
22
- Fixes race condition when discovering functions. (#5444)
3+
- Added support for Nuxt 2 and Nuxt 3. (#5321)
34
- Fixes issue where `init firestore` was unecessarilly checking for default resource location. (#5230 and #5452)
45
- Pass `trailingSlash` from Next.js config to `firebase.json` (#5445)
56
- Don't use Next.js internal redirects for the backend test (#5445)

src/frameworks/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,13 @@ export function relativeRequire(
157157
export function relativeRequire(dir: string, mod: "next"): typeof import("next");
158158
export function relativeRequire(dir: string, mod: "vite"): typeof import("vite");
159159
export function relativeRequire(dir: string, mod: "jsonc-parser"): typeof import("jsonc-parser");
160+
160161
// TODO the types for @nuxt/kit are causing a lot of troubles, need to do something other than any
162+
// Nuxt 2
163+
export function relativeRequire(dir: string, mod: "nuxt/dist/nuxt.js"): Promise<any>;
164+
// Nuxt 3
161165
export function relativeRequire(dir: string, mod: "@nuxt/kit"): Promise<any>;
166+
162167
/**
163168
*
164169
*/

src/frameworks/nuxt/index.ts

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,53 @@
11
import { copy, pathExists } from "fs-extra";
22
import { readFile } from "fs/promises";
3-
import { basename, join } from "path";
3+
import { join } from "path";
44
import { gte } from "semver";
5-
import { BuildResult, findDependency, FrameworkType, relativeRequire, SupportLevel } from "..";
5+
import { findDependency, FrameworkType, relativeRequire, SupportLevel } from "..";
66
import { warnIfCustomBuildScript } from "../utils";
77

88
export const name = "Nuxt";
99
export const support = SupportLevel.Experimental;
1010
export const type = FrameworkType.Toolchain;
1111

12+
import { NuxtDependency } from "./interfaces";
13+
import { nuxtConfigFilesExist } from "./utils";
14+
1215
const DEFAULT_BUILD_SCRIPT = ["nuxt build"];
1316

14-
export async function discover(dir: string) {
17+
/**
18+
*
19+
* @param dir current directory
20+
* @return undefined if project is not Nuxt 2, {mayWantBackend: true } otherwise
21+
*/
22+
export async function discover(dir: string): Promise<{ mayWantBackend: true } | undefined> {
1523
if (!(await pathExists(join(dir, "package.json")))) return;
16-
const nuxtDependency = findDependency("nuxt", { cwd: dir, depth: 0, omitDev: false });
17-
const configFilesExist = await Promise.all([
18-
pathExists(join(dir, "nuxt.config.js")),
19-
pathExists(join(dir, "nuxt.config.ts")),
20-
]);
21-
const anyConfigFileExists = configFilesExist.some((it) => it);
24+
const nuxtDependency = findDependency("nuxt", {
25+
cwd: dir,
26+
depth: 0,
27+
omitDev: false,
28+
}) as NuxtDependency;
29+
30+
const version = nuxtDependency?.version;
31+
const anyConfigFileExists = await nuxtConfigFilesExist(dir);
32+
2233
if (!anyConfigFileExists && !nuxtDependency) return;
23-
return { mayWantBackend: true };
34+
if (version && gte(version, "3.0.0-0")) return { mayWantBackend: true };
35+
36+
return;
2437
}
2538

26-
export async function build(root: string): Promise<BuildResult> {
39+
export async function build(root: string) {
2740
const { buildNuxt } = await relativeRequire(root, "@nuxt/kit");
28-
const nuxtApp = await getNuxtApp(root);
41+
const nuxtApp = await getNuxt3App(root);
2942

3043
await warnIfCustomBuildScript(root, name, DEFAULT_BUILD_SCRIPT);
3144

3245
await buildNuxt(nuxtApp);
3346
return { wantsBackend: true };
3447
}
3548

36-
async function getNuxtApp(cwd: string) {
49+
// Nuxt 3
50+
async function getNuxt3App(cwd: string) {
3751
const { loadNuxt } = await relativeRequire(cwd, "@nuxt/kit");
3852
return await loadNuxt({
3953
cwd,
@@ -45,32 +59,19 @@ async function getNuxtApp(cwd: string) {
4559
});
4660
}
4761

48-
function isNuxt3(cwd: string) {
49-
const { version } = findDependency("nuxt", { cwd, depth: 0, omitDev: false });
50-
return gte(version, "3.0.0-0");
51-
}
52-
5362
export async function ɵcodegenPublicDirectory(root: string, dest: string) {
54-
const app = await getNuxtApp(root);
55-
const distPath = isNuxt3(root) ? join(root, ".output", "public") : app.options.generate.dir;
63+
const distPath = join(root, ".output", "public");
5664
await copy(distPath, dest);
5765
}
5866

5967
export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: string) {
6068
const packageJsonBuffer = await readFile(join(sourceDir, "package.json"));
6169
const packageJson = JSON.parse(packageJsonBuffer.toString());
62-
if (isNuxt3(sourceDir)) {
63-
const outputPackageJsonBuffer = await readFile(
64-
join(sourceDir, ".output", "server", "package.json")
65-
);
66-
const outputPackageJson = JSON.parse(outputPackageJsonBuffer.toString());
67-
await copy(join(sourceDir, ".output", "server"), destDir);
68-
return { packageJson: { ...packageJson, ...outputPackageJson }, frameworksEntry: "nuxt3" };
69-
} else {
70-
const {
71-
options: { buildDir },
72-
} = await getNuxtApp(sourceDir);
73-
await copy(buildDir, join(destDir, basename(buildDir)));
74-
return { packageJson };
75-
}
70+
71+
const outputPackageJsonBuffer = await readFile(
72+
join(sourceDir, ".output", "server", "package.json")
73+
);
74+
const outputPackageJson = JSON.parse(outputPackageJsonBuffer.toString());
75+
await copy(join(sourceDir, ".output", "server"), destDir);
76+
return { packageJson: { ...packageJson, ...outputPackageJson }, frameworksEntry: "nuxt3" };
7677
}

src/frameworks/nuxt/interfaces.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface NuxtDependency {
2+
version?: string;
3+
resolved?: string;
4+
overridden?: boolean;
5+
}

src/frameworks/nuxt/utils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { pathExists } from "fs-extra";
2+
import { join } from "path";
3+
4+
/**
5+
*
6+
* @param dir current app directory
7+
* @return true or false if Nuxt config file was found in the directory
8+
*/
9+
export async function nuxtConfigFilesExist(dir: string): Promise<boolean> {
10+
const configFilesExist = await Promise.all([
11+
pathExists(join(dir, "nuxt.config.js")),
12+
pathExists(join(dir, "nuxt.config.ts")),
13+
]);
14+
15+
return configFilesExist.some((it) => it);
16+
}

src/frameworks/nuxt2/index.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { copy, pathExists } from "fs-extra";
2+
import { readFile } from "fs/promises";
3+
import { join } from "path";
4+
import { lt } from "semver";
5+
import { findDependency, FrameworkType, relativeRequire, SupportLevel } from "..";
6+
7+
import { NuxtDependency } from "../nuxt/interfaces";
8+
import { nuxtConfigFilesExist } from "../nuxt/utils";
9+
10+
export const name = "Nuxt";
11+
export const support = SupportLevel.Experimental;
12+
export const type = FrameworkType.MetaFramework;
13+
14+
/**
15+
*
16+
* @param dir current directory
17+
* @return undefined if project is not Nuxt 2, {mayWantBackend: true } otherwise
18+
*/
19+
export async function discover(dir: string): Promise<{ mayWantBackend: true } | undefined> {
20+
if (!(await pathExists(join(dir, "package.json")))) return;
21+
const nuxtDependency = <NuxtDependency>findDependency("nuxt", {
22+
cwd: dir,
23+
depth: 0,
24+
omitDev: false,
25+
});
26+
27+
const version = nuxtDependency?.version;
28+
const anyConfigFileExists = await nuxtConfigFilesExist(dir);
29+
30+
if (!anyConfigFileExists && !nuxtDependency) return;
31+
if (version && lt(version, "3.0.0-0")) return { mayWantBackend: true };
32+
33+
return;
34+
}
35+
36+
/**
37+
* Get the Nuxt app
38+
* @param cwd
39+
* @return Nuxt app object
40+
*/
41+
async function getNuxtApp(cwd: string): Promise<any> {
42+
return await relativeRequire(cwd, "nuxt/dist/nuxt.js");
43+
}
44+
45+
/**
46+
*
47+
* @param root nuxt project root
48+
* @return whether backend is needed or not
49+
*/
50+
export async function build(root: string): Promise<{ wantsBackend: boolean }> {
51+
const nuxt = await getNuxtApp(root);
52+
53+
const nuxtApp = await nuxt.loadNuxt({
54+
for: "build",
55+
rootDir: root,
56+
});
57+
58+
const {
59+
options: { ssr, target },
60+
} = await nuxt.build(nuxtApp);
61+
62+
if (ssr === true && target === "server") {
63+
return { wantsBackend: true };
64+
} else {
65+
// Inform the user that static target is not supported with `ssr: false`,
66+
// and continue with building for client side as per current Nuxt 2.
67+
if (ssr === false && target === "static") {
68+
console.log(
69+
"Firebase: Nuxt 2: Static target is not supported with `ssr: false`. Please use `target: 'server'` in your `nuxt.config.js` file."
70+
);
71+
console.log("Firebase: Nuxt 2: Bundling only for client side.\n");
72+
}
73+
74+
await buildAndGenerate(nuxt, root);
75+
return { wantsBackend: false };
76+
}
77+
}
78+
/**
79+
* Build and generate the Nuxt app
80+
*
81+
* @param nuxt nuxt object
82+
* @param root root directory
83+
* @return void
84+
*/
85+
async function buildAndGenerate(nuxt: any, root: string): Promise<void> {
86+
const nuxtApp = await nuxt.loadNuxt({
87+
for: "start",
88+
rootDir: root,
89+
});
90+
91+
const builder = await nuxt.getBuilder(nuxtApp);
92+
const generator = new nuxt.Generator(nuxtApp, builder);
93+
await generator.generate({ build: false, init: true });
94+
}
95+
96+
/**
97+
* Copy the static files to the destination directory whether it's a static build or server build.
98+
* @param root
99+
* @param dest
100+
*/
101+
export async function ɵcodegenPublicDirectory(root: string, dest: string) {
102+
const nuxt = await getNuxtApp(root);
103+
const nuxtConfig = await nuxt.loadNuxtConfig();
104+
const { ssr, target } = nuxtConfig;
105+
106+
// If `target` is set to `static`, copy the generated files
107+
// to the destination directory (i.e. `/hosting`).
108+
if (!(ssr === true && target === "server")) {
109+
const source =
110+
nuxtConfig?.generate?.dir !== undefined
111+
? join(root, nuxtConfig?.generate?.dir)
112+
: join(root, "dist");
113+
114+
await copy(source, dest);
115+
}
116+
117+
// Copy static assets if they exist.
118+
const staticPath = join(root, "static");
119+
if (await pathExists(staticPath)) {
120+
await copy(staticPath, dest);
121+
}
122+
}
123+
124+
export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: string) {
125+
const packageJsonBuffer = await readFile(join(sourceDir, "package.json"));
126+
const packageJson = JSON.parse(packageJsonBuffer.toString());
127+
128+
// Get the nuxt config into an object so we can check the `target` and `ssr` properties.
129+
const nuxt = await getNuxtApp(sourceDir);
130+
const nuxtConfig = await nuxt.loadNuxtConfig();
131+
132+
// When starting the Nuxt 2 server, we need to copy the `.nuxt` to the destination directory (`functions`)
133+
// with the same folder name (.firebase/<project-name>/functions/.nuxt).
134+
// This is because `loadNuxt` (called from `firebase-frameworks`) will only look
135+
// for the `.nuxt` directory in the destination directory.
136+
await copy(join(sourceDir, ".nuxt"), join(destDir, ".nuxt"));
137+
138+
// When using `SSR: false`, we need to copy the `nuxt.config.js` to the destination directory (`functions`)
139+
// This is because `loadNuxt` (called from `firebase-frameworks`) will look
140+
// for the `nuxt.config.js` file in the destination directory.
141+
if (!nuxtConfig.ssr) {
142+
const nuxtConfigFile = nuxtConfig._nuxtConfigFile.split("/").pop();
143+
await copy(nuxtConfig._nuxtConfigFile, join(destDir, nuxtConfigFile));
144+
}
145+
146+
return { packageJson: { ...packageJson }, frameworksEntry: "nuxt" };
147+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { expect } from "chai";
2+
// import * as fs from "fs";
3+
import * as fsExtra from "fs-extra";
4+
import * as sinon from "sinon";
5+
import * as frameworksFunctions from "../../../frameworks";
6+
7+
import { discover as discoverNuxt2 } from "../../../frameworks/nuxt2";
8+
import { discover as discoverNuxt3 } from "../../../frameworks/nuxt";
9+
10+
describe("Nuxt 2 utils", () => {
11+
describe("nuxtAppDiscovery", () => {
12+
let sandbox: sinon.SinonSandbox;
13+
14+
beforeEach(() => {
15+
sandbox = sinon.createSandbox();
16+
});
17+
18+
afterEach(() => {
19+
sandbox.restore();
20+
});
21+
22+
it("should find a Nuxt 2 app", async () => {
23+
sandbox.stub(fsExtra, "pathExists").resolves(true);
24+
sandbox.stub(frameworksFunctions, "findDependency").returns({
25+
version: "2.15.8",
26+
resolved: "https://registry.npmjs.org/nuxt/-/nuxt-2.15.8.tgz",
27+
overridden: false,
28+
});
29+
30+
expect(await discoverNuxt2(".")).to.deep.equal({ mayWantBackend: true });
31+
});
32+
33+
it("should find a Nuxt 3 app", async () => {
34+
sandbox.stub(fsExtra, "pathExists").resolves(true);
35+
sandbox.stub(frameworksFunctions, "findDependency").returns({
36+
version: "3.0.0",
37+
resolved: "https://registry.npmjs.org/nuxt/-/nuxt-3.0.0.tgz",
38+
overridden: false,
39+
});
40+
41+
expect(await discoverNuxt3(".")).to.deep.equal({ mayWantBackend: true });
42+
});
43+
});
44+
});

0 commit comments

Comments
 (0)