@edgeone/nuxt-pages
Version:
A professional deployment package that seamlessly deploys your Nuxt 3/4 applications to Tencent Cloud EdgeOne platform with optimized performance and intelligent caching.
571 lines (566 loc) • 18.3 kB
JavaScript
var require = await (async () => {
var { createRequire } = await import("node:module");
return createRequire(import.meta.url);
})();
import {
getModulesWithAST,
getPrerenderRoutesWithAST,
getRouteRulesWithAST,
getRoutesArrayWithAST
} from "./chunk-5VJRCUAW.js";
// src/build/plugin-context.ts
import { existsSync, readFileSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { join, relative, resolve } from "node:path";
import { join as posixJoin } from "node:path/posix";
import { fileURLToPath } from "node:url";
var MODULE_DIR = fileURLToPath(new URL(".", import.meta.url));
var PLUGIN_DIR = join(MODULE_DIR, "../..");
var DEFAULT_BUILD_DIR = ".nuxt";
var DEFAULT_OUTPUT_DIR = ".edgeone";
var NUXT_STATIC_DIR = "assets";
var SERVER_HANDLER_NAME = "server-handler";
var EDGE_HANDLER_NAME = "edgeone-edge-handler";
var PluginContext = class {
edgeoneConfig;
pluginName;
pluginVersion;
utils;
constants;
packageJSON;
_nuxtConfigPath = null;
_nuxtServerFile = null;
/** The directory where the plugin is located */
pluginDir = PLUGIN_DIR;
serverlessWrapHandler;
get relPublishDir() {
return this.constants.PUBLISH_DIR ?? join(this.constants.PACKAGE_PATH || "", DEFAULT_OUTPUT_DIR);
}
/** Temporary directory for stashing the build output */
get tempPublishDir() {
return this.resolveFromPackagePath(".edgeone/server-handler");
}
/** Absolute path of the publish directory */
get publishDir() {
return resolve(this.relPublishDir);
}
/**
* Relative package path in non monorepo setups this is an empty string
* This path is provided by Nuxt build manifest
* @example ''
* @example 'apps/my-app'
*/
get relativeAppDir() {
return this.buildManifest?.buildDir ?? "";
}
/**
* The working directory inside the lambda that is used for monorepos to execute the serverless function
*/
get lambdaWorkingDirectory() {
return "";
}
/**
* Retrieves the root of the `.output` directory
*/
get outputRootDir() {
return join(this.publishDir, "server-handler");
}
/**
* The resolved relative nuxt build directory defaults to `.nuxt`,
* but can be configured through the nuxt.config.js. For monorepos this will include the packagePath
* If we need just the plain build dir use the `nuxtBuildDir`
*/
get buildDir() {
const dir = this.buildConfig?.buildDir ?? DEFAULT_BUILD_DIR;
return relative(process.cwd(), resolve(this.relativeAppDir, dir));
}
/** Represents the parent directory of the .nuxt folder or custom buildDir */
get distDirParent() {
return join(this.buildDir, "..");
}
/** The `.nuxt` folder or what the custom build dir is set to */
get nuxtBuildDir() {
return relative(this.distDirParent, this.buildDir);
}
/** The directory where the Nuxt output is stored */
get outputDir() {
return DEFAULT_OUTPUT_DIR;
}
/** Retrieves the `.output/server/` directory monorepo aware */
get serverDir() {
return join(this.outputRootDir);
}
/** The directory where the Nuxt static files are stored */
get nuxtStaticDir() {
return join(this.outputDir, NUXT_STATIC_DIR);
}
/**
* Absolute path of the directory that is published and deployed to the CDN
* Will be swapped with the publish directory
* `.edgeone/static`
*/
get staticDir() {
return this.resolveFromPackagePath(".edgeone/assets");
}
/**
* Absolute path of the directory that will be deployed to the blob store
* region aware: `.edgeone/deploy/v1/blobs/deploy`
* default: `.edgeone/blobs/deploy`
*/
get blobDir() {
if (this.useRegionalBlobs) {
return this.resolveFromPackagePath(".edgeone/deploy/v1/blobs/deploy");
}
return this.resolveFromPackagePath(".edgeone/blobs/deploy");
}
get buildVersion() {
return this.constants.BUILD_VERSION || "v0.0.0";
}
get useRegionalBlobs() {
const REQUIRED_BUILD_VERSION = ">=29.41.5";
const currentVersion = this.buildVersion.replace("v", "");
const requiredVersion = REQUIRED_BUILD_VERSION.replace(">=", "");
return currentVersion >= requiredVersion;
}
/**
* Absolute path of the directory containing the files for the serverless lambda function
* `.edgeone/functions-internal`
*/
get serverFunctionsDir() {
return this.resolveFromPackagePath(".edgeone");
}
/** Absolute path of the server handler */
get serverHandlerRootDir() {
return join(this.serverFunctionsDir, SERVER_HANDLER_NAME);
}
get serverHandlerDir() {
return this.serverHandlerRootDir;
}
get serverHandlerRuntimeModulesDir() {
return join(this.serverHandlerDir, ".edgeone");
}
get nuxtServerHandler() {
return "./.edgeone/dist/run/handlers/server.js";
}
/**
* Absolute path of the directory containing the files for deno edge functions
* `.edgeone/edge-functions`
*/
get edgeFunctionsDir() {
return this.resolveFromPackagePath(".edgeone/edge-functions");
}
/** Absolute path of the edge handler */
get edgeHandlerDir() {
return join(this.edgeFunctionsDir, EDGE_HANDLER_NAME);
}
constructor(options) {
options = {
...options,
functions: {
"*": {}
},
constants: {
// BUILD_VERSION: '32.1.4',
...options.constants,
PUBLISH_DIR: options.constants?.PUBLISH_DIR || ".output"
}
};
this.constants = options.constants;
this.packageJSON = JSON.parse(readFileSync(join(PLUGIN_DIR, "package.json"), "utf-8"));
this.pluginName = this.packageJSON.name;
this.pluginVersion = this.packageJSON.version;
}
/** Resolves a path correctly with mono repository awareness for .edgeone directories mainly */
resolveFromPackagePath(...args) {
return resolve(this.constants.PACKAGE_PATH || "", ...args);
}
/** Resolves a path correctly from site directory */
resolveFromSiteDir(...args) {
return resolve(this.buildManifest?.buildDir || process.cwd(), ...args);
}
/** Get the nuxt routes manifest */
async getRoutesManifest() {
try {
let routesData = await this.getRouteRules();
if (!routesData) {
return { routes: [], prerendered: [] };
}
const routes = Array.isArray(routesData) ? routesData.map((route) => ({
path: route.path || route.route,
file: route.file || "",
prerender: route.prerender || false,
ssr: route.ssr !== false,
swr: route.swr ? route.swr : route.isr ? route.isr : false
})) : [];
return { routes, prerendered: routes.filter((r) => r.prerender).map((r) => r.path) };
} catch (error) {
return { routes: [], prerendered: [] };
}
}
/** Get the nuxt build manifest */
async getBuildManifest() {
const buildInfoPath = join(this.nuxtBuildDir, "build-info.json");
const nitroConfigPath = join(this.outputDir, "nitro.json");
try {
let manifestData;
if (existsSync(buildInfoPath)) {
manifestData = JSON.parse(await readFile(buildInfoPath, "utf-8"));
} else if (existsSync(nitroConfigPath)) {
const nitroConfig = JSON.parse(await readFile(nitroConfigPath, "utf-8"));
manifestData = {
version: nitroConfig.version || "1.0.0",
config: nitroConfig.config || {},
buildDir: this.buildDir,
outputDir: this.outputDir,
routes: nitroConfig.routes || {},
assets: nitroConfig.assets || {}
};
} else {
manifestData = {
version: "1.0.0",
config: {},
buildDir: DEFAULT_BUILD_DIR,
outputDir: DEFAULT_OUTPUT_DIR,
routes: {},
assets: {}
};
}
return manifestData;
} catch (error) {
return {
version: "1.0.0",
config: {},
buildDir: DEFAULT_BUILD_DIR,
outputDir: DEFAULT_OUTPUT_DIR,
routes: {},
assets: {}
};
}
}
/**
* Uses various heuristics to try to find the .output dir.
* Works by looking for nitro.json, so requires the site to have been built
*/
findDotOutput() {
for (const dir of [
// The publish directory
this.publishDir,
// In the root
resolve(DEFAULT_OUTPUT_DIR),
// The sibling of the publish directory
resolve(this.publishDir, "..", DEFAULT_OUTPUT_DIR),
// In the package dir
resolve(this.constants.PACKAGE_PATH || "", DEFAULT_OUTPUT_DIR)
]) {
if (existsSync(join(dir, "nitro.json"))) {
return dir;
}
}
return false;
}
/**
* Get Nuxt nitro config from the build output
*/
async getNitroConfig() {
const nitroPath = join(this.publishDir, "nitro.json");
try {
if (!existsSync(nitroPath)) {
return {};
}
return JSON.parse(await readFile(nitroPath, "utf-8"));
} catch (error) {
return {};
}
}
// don't make private as it is handy inside testing to override the config
_buildManifest = null;
/** Get Build manifest from build output **/
get buildManifest() {
if (!this._buildManifest) {
let buildManifestJson = join(this.publishDir, "build-manifest.json");
if (!existsSync(buildManifestJson)) {
const dotOutput = this.findDotOutput();
if (dotOutput) {
buildManifestJson = join(dotOutput, "build-manifest.json");
}
}
if (existsSync(buildManifestJson)) {
this._buildManifest = JSON.parse(
readFileSync(buildManifestJson, "utf-8")
);
} else {
this._buildManifest = {
version: "1.0.0",
config: {},
buildDir: DEFAULT_BUILD_DIR,
outputDir: DEFAULT_OUTPUT_DIR,
routes: {},
assets: {}
};
}
}
return this._buildManifest;
}
#exportDetail = null;
/** Get metadata when output = static */
get exportDetail() {
if (this.buildConfig?.nitro?.prerender !== true) {
return null;
}
if (!this.#exportDetail) {
const detailFile = join(
this.buildManifest.buildDir,
"export-detail.json"
);
if (!existsSync(detailFile)) {
return null;
}
try {
this.#exportDetail = JSON.parse(readFileSync(detailFile, "utf-8"));
} catch {
}
}
return this.#exportDetail;
}
/** Get Nuxt Config from build output **/
get buildConfig() {
return this.buildManifest.config;
}
/** Get the path to nuxt.config.ts file */
get nuxtConfigPath() {
if (!this._nuxtConfigPath) {
this._nuxtConfigPath = this.detectNuxtConfigPath();
}
return this._nuxtConfigPath;
}
get nuxtServerFile() {
if (!this._nuxtServerFile) {
this._nuxtServerFile = this.detectNuxtServerFile();
}
return this._nuxtServerFile;
}
/** Detect and locate nuxt.config.ts file */
detectNuxtConfigPath() {
const configFiles = ["nuxt.config.ts", "nuxt.config.js", "nuxt.config.mjs"];
for (const configFile of configFiles) {
const configPath = this.resolveFromPackagePath(configFile);
if (existsSync(configPath)) {
return configPath;
}
}
console.log("Can't find nuxt.config.ts file");
return null;
}
/** Detect and locate server.mjs file */
detectNuxtServerFile() {
const serverFilePath = this.resolveFromPackagePath(".edgeone", "server-handler", "chunks", "build", "server.mjs");
if (existsSync(serverFilePath)) {
return serverFilePath;
}
return null;
}
/** Read and parse nuxt.config.ts file content */
async getNuxtConfig() {
const configPath = this.nuxtConfigPath;
if (!configPath) {
console.warn("No Nuxt config file found");
return null;
}
try {
console.log("Loading Nuxt config from:", configPath);
const configContent = readFileSync(configPath, "utf-8");
console.log("Reading config file content...");
return configContent;
} catch (error) {
console.warn(`Failed to load Nuxt config from ${configPath}:`, error);
return null;
}
}
async getNuxtServerFile() {
const serverFilePath = this.nuxtServerFile;
if (!serverFilePath) {
console.warn("No Nuxt server file found");
return null;
}
try {
console.log("Loading Nuxt server file from:", serverFilePath);
const serverContent = readFileSync(serverFilePath, "utf-8");
console.log("Reading server file content...");
return serverContent;
} catch (error) {
console.warn(`Failed to load Nuxt server file from ${serverFilePath}:`, error);
return null;
}
}
/** Get routeRules from nuxt.config.ts */
async getRouteRules() {
const nitroPrerenderRoutes = await this.getPrerenderRoutes();
const nuxtConfigRoutes = getRouteRulesWithAST(await this.getNuxtConfig() || "");
const result = [];
for (const key in nuxtConfigRoutes) {
const obj = {
path: key,
prerender: nuxtConfigRoutes[key].prerender === true,
ssr: !nuxtConfigRoutes[key].prerender && !(nuxtConfigRoutes[key].hasOwnProperty("swr") || nuxtConfigRoutes[key].hasOwnProperty("isr")),
swr: false
};
if (nuxtConfigRoutes[key].hasOwnProperty("swr")) {
obj.swr = nuxtConfigRoutes[key].swr;
}
if (nuxtConfigRoutes[key].hasOwnProperty("isr")) {
obj.swr = nuxtConfigRoutes[key].isr;
}
if (nuxtConfigRoutes[key].hasOwnProperty("prerender") && nuxtConfigRoutes[key].prerender) {
Object.defineProperty(obj, "file", {
value: `asset${key}/index.html`,
enumerable: true
});
}
result.push(obj);
}
const serverRoutes = getRoutesArrayWithAST(await this.getNuxtServerFile() || "");
serverRoutes.forEach((item) => {
const keys = result.map((obj) => obj.path);
if (!keys.includes(item.path)) {
if (nitroPrerenderRoutes.includes(item.path)) {
result.push({
path: item.path,
prerender: true,
ssr: false,
swr: false
});
} else {
result.push({
path: item.path,
prerender: false,
ssr: true,
swr: false
});
}
}
});
return result;
}
/** Extract _routes array from server.mjs content */
extractRoutesFromServerMjs(content) {
try {
const routesArray = getRoutesArrayWithAST(content);
if (!routesArray) {
console.log("_routes array not found in server.mjs");
return null;
}
console.log("Found _routes array with", routesArray.length, "routes");
return routesArray;
} catch (error) {
console.error("Error extracting routes from server.mjs:", error);
return null;
}
}
/**
* Get Nuxt assets manifest from the build output
* This handles the asset optimization routes for Nuxt
*/
async getAssetsManifest() {
const clientManifestPath = join(this.nuxtBuildDir, "dist/client/manifest.json");
const publicManifestPath = join(this.outputDir, "public/_nuxt/manifest.json");
const fallbackPath = join(this.publishDir, "assets-manifest.json");
try {
let manifestPath;
if (existsSync(clientManifestPath)) {
manifestPath = clientManifestPath;
} else if (existsSync(publicManifestPath)) {
manifestPath = publicManifestPath;
} else if (existsSync(fallbackPath)) {
manifestPath = fallbackPath;
} else {
return {};
}
const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
if (manifest && typeof manifest === "object") {
return manifest;
}
return {};
} catch (error) {
return {};
}
}
#nuxtVersion = void 0;
/**
* Get Nuxt version that was used to build the site
*/
get nuxtVersion() {
if (this.#nuxtVersion === void 0) {
try {
const serverHandlerRequire = createRequire(posixJoin(this.outputRootDir, ":internal:"));
const { version } = serverHandlerRequire("nuxt/package.json");
this.#nuxtVersion = version;
} catch {
this.#nuxtVersion = null;
}
}
return this.#nuxtVersion;
}
#nuxtModules = null;
async nuxtModules() {
if (!this.#nuxtModules) {
const nuxtConfig = await this.getNuxtConfig();
try {
const modules = getModulesWithAST(nuxtConfig);
this.#nuxtModules = modules;
} catch (error) {
console.error("Error extracting modules from nuxt.config.ts:", error);
this.#nuxtModules = null;
}
}
return this.#nuxtModules;
}
#staticPages = null;
/**
* Get an array of static pages that are fully prerendered.
* Those are being served as-is without involving server-side rendering
*/
async getStaticPages() {
if (!this.#staticPages) {
const routesManifest = await this.getRoutesManifest();
this.#staticPages = routesManifest.routes.filter((route) => route.prerender === true).map((route) => route.path);
}
return this.#staticPages;
}
/** Get prerender routes from nuxt.config.ts */
async getPrerenderRoutes() {
try {
const configPath = join(process.cwd(), "nuxt.config.ts");
if (!existsSync(configPath)) {
console.warn("nuxt.config.ts not found, returning empty prerender routes");
return [];
}
const configContent = await readFile(configPath, "utf-8");
const prerenderRoutes = getPrerenderRoutesWithAST(configContent);
return prerenderRoutes;
} catch (error) {
console.error("Error reading prerender routes from nuxt.config.ts:", error);
return [];
}
}
async nuxtStaticGeneration() {
const nitroJSON = await readFile(join(this.outputDir, "nitro.json"), "utf-8") || "{}";
if (!nitroJSON) return false;
const nitroJSONData = JSON.parse(nitroJSON);
if (nitroJSONData.preset && nitroJSONData.preset === "static") return true;
return false;
}
/** Fails a build with a message and an optional error */
failBuild(message, error) {
console.error(message);
if (error) {
console.error(error);
}
process.exit(1);
}
};
export {
SERVER_HANDLER_NAME,
EDGE_HANDLER_NAME,
PluginContext
};