UNPKG

@needle-tools/engine

Version:

Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.

124 lines (105 loc) 5.31 kB
import { collectBuildDirectoryStats, createBuildInfoFile } from '../common/buildinfo.js'; import { formatBytes } from '../common/files.js'; import { closeLogStreams } from '../common/logger.js'; import { waitForBuildPipelineToFinish } from './build-pipeline.js'; import { getOutputDirectory } from './config.js'; import { needleBlue, needleDim, needleLog, needleSupportsColor } from './logging.js'; let level = 0; /** Create a buildinfo file in the build directory. * @param {"build" | "serve"} command * @param {import('../types/needleConfig').needleMeta | null | undefined} config * @param {import('../types').userSettings} userSettings * @returns {import('vite').Plugin | null} */ export function needleBuildInfo(command, config, userSettings) { if (userSettings?.noBuildInfo) return null; // let buildDirectory = null; // /** @type {import('../common/buildinfo').BuildInfo | null} */ // let beforeStats = null; return { name: 'needle:buildinfo', apply: "build", enforce: "post", buildStart: () => { level++; }, buildEnd(error) { if (--level > 0) { return; // nested build (e.g. worker entry) — skip } // waitForBuildPipelineToFinish().then(res => { // beforeStats = collectBuildDirectoryStats(buildDirectory); // }) }, closeBundle: () => { if (--level > 0) { return; // nested build (e.g. worker entry) — skip } const buildDirectory = getOutputDirectory(); const beforeStats = collectBuildDirectoryStats(buildDirectory); const handleClose = async () => { const task = waitForBuildPipelineToFinish(); if (task instanceof Promise) { if (process.env.DEBUG) needleLog("needle-buildinfo", "Waiting for build pipeline to finish"); await task.catch(() => { }); } // wait for gzip await delay(500); const closedHandles = closeDanglingNetworkHandles(); const result = createBuildInfoFile(buildDirectory, { log: false }); if (!result?.ok) { needleLog("needle-buildinfo", result?.error || "Failed to create build info file", "warn", { dimBody: false }); return; } const supportsColor = needleSupportsColor(); const key = (text) => supportsColor ? needleBlue(text) : text; const sizeDelta = (result.totalSizeBytes || 0) - (beforeStats.totalSizeBytes || 0); const fileDelta = (result.fileCount || 0) - (beforeStats.fileCount || 0); const sizePct = beforeStats.totalSizeBytes > 0 ? Math.round(sizeDelta / beforeStats.totalSizeBytes * 100) : 0; const strTotalSize = beforeStats.totalSizeBytes !== result.totalSizeBytes ? `${formatBytes(beforeStats.totalSizeBytes)}${formatBytes(result.totalSizeBytes)} (${sizePct >= 0 ? "+" : ""}${sizePct}%)` : formatBytes(result.totalSizeBytes); const strFileCount = beforeStats.fileCount !== result.fileCount ? `${beforeStats.fileCount}${result.fileCount} (${fileDelta >= 0 ? "+" : ""}${fileDelta})` : `${result.fileCount}`; const lines = []; lines.push(`${key("Build info")}: ${strTotalSize}, ${strFileCount} files`); lines.push( ...(process.env.DEBUG ? [ needleDim(`Begin collecting files in \"${result.buildDirectory}\"`), needleDim(`Collected ${result.fileCount} files (${result.totalSizeInMB.toFixed(2)} MB). Writing build info to \"${result.buildInfoPath}\"`), needleDim(`Build info file successfully written to \"${result.buildInfoPath}\"`), ] : []), ) if (closedHandles > 0) lines.push(`Closed ${closedHandles} dangling network handle(s)`); needleLog("needle-buildinfo", lines.join("\n"), "log", { dimBody: false }); closeLogStreams(); }; return handleClose(); } } } function delay(ms) { return new Promise(res => setTimeout(res, ms)); } function closeDanglingNetworkHandles() { /** @type {() => any[] | undefined} */ // @ts-ignore private node api const getActiveHandles = process._getActiveHandles; if (typeof getActiveHandles !== "function") { return 0; } let closed = 0; const handles = getActiveHandles() || []; for (const handle of handles) { if (!handle || handle === process.stdin || handle === process.stdout || handle === process.stderr) continue; const hasNetworkShape = typeof handle.destroy === "function" && (typeof handle.remotePort === "number" || typeof handle.remoteAddress === "string"); if (!hasNetworkShape) continue; if (typeof handle.remotePort === "number" && handle.remotePort > 0) { try { handle.destroy(); closed++; } catch { // ignore cleanup failures } } } return closed; }