UNPKG

@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
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 };