UNPKG

vike

Version:

The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.

336 lines (335 loc) 16.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.handleAssetsManifest = handleAssetsManifest; exports.handleAssetsManifest_getBuildConfig = handleAssetsManifest_getBuildConfig; exports.handleAssetsManifest_isFixEnabled = handleAssetsManifest_isFixEnabled; exports.handleAssetsManifest_assertUsageCssCodeSplit = handleAssetsManifest_assertUsageCssCodeSplit; exports.handleAssetsManifest_assertUsageCssTarget = handleAssetsManifest_assertUsageCssTarget; const promises_1 = __importDefault(require("node:fs/promises")); const node_fs_1 = __importDefault(require("node:fs")); const node_path_1 = __importDefault(require("node:path")); const node_fs_2 = require("node:fs"); const utils_js_1 = require("../../utils.js"); const virtualFilePageConfigLazy_js_1 = require("../../../shared/virtualFiles/virtualFilePageConfigLazy.js"); const pluginBuildConfig_js_1 = require("./pluginBuildConfig.js"); const getAssetsDir_js_1 = require("../../shared/getAssetsDir.js"); const picocolors_1 = __importDefault(require("@brillout/picocolors")); const resolveVikeConfigInternal_js_1 = require("../../shared/resolveVikeConfigInternal.js"); const getOutDirs_js_1 = require("../../shared/getOutDirs.js"); const isViteServerBuild_js_1 = require("../../shared/isViteServerBuild.js"); const pluginBuildEntry_js_1 = require("./pluginBuildEntry.js"); (0, utils_js_1.assertIsSingleModuleInstance)('build/handleAssetsManifest.ts'); let assetsJsonFilePath; // true => use workaround config.build.ssrEmitAssets // false => use workaround extractAssets plugin function handleAssetsManifest_isFixEnabled(config) { // Allow user to toggle between the two workarounds? E.g. based on https://vike.dev/includeAssetsImportedByServer. return (0, resolveVikeConfigInternal_js_1.isV1Design)(); } /** https://github.com/vikejs/vike/issues/1339 */ async function fixServerAssets(config) { const outDirs = (0, getOutDirs_js_1.getOutDirs)(config); const clientManifest = await readManifestFile(outDirs.outDirClient); const serverManifest = await readManifestFile(outDirs.outDirServer); const { clientManifestMod, serverManifestMod, filesToMove, filesToRemove } = addServerAssets(clientManifest, serverManifest); await copyAssets(filesToMove, filesToRemove, config); return { clientManifestMod, serverManifestMod }; } async function copyAssets(filesToMove, filesToRemove, config) { const { outDirClient, outDirServer } = (0, getOutDirs_js_1.getOutDirs)(config); const assetsDir = (0, getAssetsDir_js_1.getAssetsDir)(config); const assetsDirServer = node_path_1.default.posix.join(outDirServer, assetsDir); if (!filesToMove.length && !filesToRemove.length && !(0, node_fs_2.existsSync)(assetsDirServer)) return; (0, utils_js_1.assert)((0, node_fs_2.existsSync)(assetsDirServer)); const concurrencyLimit = (0, utils_js_1.pLimit)(10); await Promise.all(filesToMove.map((file) => concurrencyLimit(async () => { const source = node_path_1.default.posix.join(outDirServer, file); const target = node_path_1.default.posix.join(outDirClient, file); await promises_1.default.mkdir(node_path_1.default.posix.dirname(target), { recursive: true }); await promises_1.default.rename(source, target); }))); filesToRemove.forEach((file) => { const filePath = node_path_1.default.posix.join(outDirServer, file); node_fs_1.default.unlinkSync(filePath); }); /* We cannot do that because, with some edge case Rollup settings (outputting JavaScript chunks and static assets to the same directory), this removes JavaScript chunks, see https://github.com/vikejs/vike/issues/1154#issuecomment-1975762404 await fs.rm(assetsDirServer, { recursive: true }) */ removeEmptyDirectories(assetsDirServer); } // Add serverManifest resources to clientManifest function addServerAssets(clientManifest, serverManifest) { var _a, _b, _c, _d; const entriesClient = new Map(); const entriesServer = new Map(); for (const [key, entry] of Object.entries(clientManifest)) { const pageId = getPageId(key); if (!pageId) continue; const resources = collectResources(entry, clientManifest); (0, utils_js_1.assert)(!entriesClient.has(pageId)); entriesClient.set(pageId, { key, ...resources }); } for (const [key, entry] of Object.entries(serverManifest)) { const pageId = getPageId(key); if (!pageId) continue; const resources = collectResources(entry, serverManifest); (0, utils_js_1.assert)(!entriesServer.has(pageId)); entriesServer.set(pageId, { key, ...resources }); } let filesToMove = []; let filesToRemove = []; // Copy page assets for (const [pageId, entryClient] of entriesClient.entries()) { const entryServer = entriesServer.get(pageId); if (!entryServer) continue; const cssToMove = []; const cssToRemove = []; const assetsToMove = []; const assetsToRemove = []; entryServer.css.forEach((cssServer) => { if (!entryClient.css.some((cssClient) => cssServer.hash === cssClient.hash)) { cssToMove.push(cssServer.src); } else { cssToRemove.push(cssServer.src); } }); entryServer.assets.forEach((assetServer) => { if (!entryClient.assets.some((assetClient) => assetServer.hash === assetClient.hash)) { assetsToMove.push(assetServer.src); } else { assetsToRemove.push(assetServer.src); } }); if (cssToMove.length) { const { key } = entryClient; filesToMove.push(...cssToMove); (_a = clientManifest[key]).css ?? (_a.css = []); clientManifest[key].css?.push(...cssToMove); } if (cssToRemove.length) { const { key } = entryServer; filesToRemove.push(...cssToRemove); (_b = serverManifest[key]).css ?? (_b.css = []); serverManifest[key].css = serverManifest[key].css.filter((entry) => !cssToRemove.includes(entry)); } if (assetsToMove.length) { const { key } = entryClient; filesToMove.push(...assetsToMove); (_c = clientManifest[key]).assets ?? (_c.assets = []); clientManifest[key].assets?.push(...assetsToMove); } if (assetsToRemove.length) { const { key } = entryServer; filesToRemove.push(...assetsToRemove); (_d = serverManifest[key]).assets ?? (_d.assets = []); serverManifest[key].assets = serverManifest[key].assets.filter((entry) => !assetsToRemove.includes(entry)); } } // Also copy assets of virtual:@brillout/vite-plugin-server-entry:serverEntry { const filesClientAll = []; for (const key in clientManifest) { const entry = clientManifest[key]; filesClientAll.push(entry.file); filesClientAll.push(...(entry.assets ?? [])); filesClientAll.push(...(entry.css ?? [])); } for (const key in serverManifest) { const entry = serverManifest[key]; if (!entry.isEntry) continue; const resources = collectResources(entry, serverManifest); const css = resources.css.map((css) => css.src).filter((file) => !filesClientAll.includes(file)); const assets = resources.assets.map((asset) => asset.src).filter((file) => !filesClientAll.includes(file)); filesToMove.push(...css, ...assets); if (css.length > 0 || assets.length > 0) { (0, utils_js_1.assert)(!clientManifest[key]); clientManifest[key] = { ...entry, css, assets, dynamicImports: undefined, imports: undefined, }; } } } const clientManifestMod = clientManifest; const serverManifestMod = serverManifest; filesToMove = (0, utils_js_1.unique)(filesToMove); filesToRemove = (0, utils_js_1.unique)(filesToRemove).filter((file) => !filesToMove.includes(file)); return { clientManifestMod, serverManifestMod, filesToMove, filesToRemove }; } function getPageId(key) { // Normalize from: // ../../virtual:vike:pageConfigLazy:client:/pages/index // to: // virtual:vike:pageConfigLazy:client:/pages/index // (This seems to be needed only for vitest tests that use Vite's build() API with an inline config.) key = key.substring(key.indexOf('virtual:vike')); const result = (0, virtualFilePageConfigLazy_js_1.isVirtualFileIdPageConfigLazy)(key); return result && result.pageId; } function collectResources(entryRoot, manifest) { const css = []; const assets = []; const entries = new Set([entryRoot]); for (const entry of entries) { for (const entryImport of entry.imports ?? []) { entries.add(manifest[entryImport]); } const entryCss = entry.css ?? []; if (entry.file.endsWith('.css')) entryCss.push(entry.file); for (const src of entryCss) { const hash = getHash(src); css.push({ src, hash }); } const entryAssets = entry.assets ?? []; for (const src of entryAssets) { const hash = getHash(src); assets.push({ src, hash }); } } return { css, assets }; } // Use the hash of resources to determine whether they are equal. We need this, otherwise we get: // ```html // <head> // <link rel="stylesheet" type="text/css" href="/assets/static/onRenderClient.2j6TxKIB.css"> // <link rel="stylesheet" type="text/css" href="/assets/static/onRenderHtml.2j6TxKIB.css"> // </head> // ``` function getHash(src) { // src is guaranteed to end with `.[hash][extname]`, see pluginDistFileNames.ts const hash = src.split('.').at(-2); (0, utils_js_1.assert)(hash); return hash; } // https://github.com/vikejs/vike/issues/1993 function handleAssetsManifest_assertUsageCssCodeSplit(config) { if (!handleAssetsManifest_isFixEnabled(config)) return; (0, utils_js_1.assertWarning)(config.build.cssCodeSplit, `${picocolors_1.default.cyan('build.cssCodeSplit')} shouldn't be set to ${picocolors_1.default.cyan('false')} (https://github.com/vikejs/vike/issues/1993)`, { onlyOnce: true }); } const targets = []; function handleAssetsManifest_assertUsageCssTarget(config) { if (!handleAssetsManifest_isFixEnabled(config)) return; const isServerSide = (0, isViteServerBuild_js_1.isViteServerBuild)(config); (0, utils_js_1.assert)(typeof isServerSide === 'boolean'); (0, utils_js_1.assert)(config.build.target !== undefined); targets.push({ global: config.build.target, css: config.build.cssTarget, isServerSide }); const targetsServer = targets.filter((t) => t.isServerSide); const targetsClient = targets.filter((t) => !t.isServerSide); targetsClient.forEach((targetClient) => { const targetCssResolvedClient = resolveCssTarget(targetClient); targetsServer.forEach((targetServer) => { const targetCssResolvedServer = resolveCssTarget(targetServer); (0, utils_js_1.assertWarning)((0, utils_js_1.isEqualStringList)(targetCssResolvedClient, targetCssResolvedServer), [ 'The CSS browser target should be the same for both client and server, but we got:', `Client: ${picocolors_1.default.cyan(JSON.stringify(targetCssResolvedClient))}`, `Server: ${picocolors_1.default.cyan(JSON.stringify(targetCssResolvedServer))}`, `Different targets lead to CSS duplication, see ${picocolors_1.default.underline('https://github.com/vikejs/vike/issues/1815#issuecomment-2507002979')} for more information.`, ].join('\n'), { showStackTrace: true, onlyOnce: 'different-css-target', }); }); }); } function resolveCssTarget(target) { return target.css ?? target.global; } /** * Recursively remove all empty directories in a given directory. */ function removeEmptyDirectories(dirPath) { // Read the directory contents const files = node_fs_1.default.readdirSync(dirPath); // Iterate through the files and subdirectories for (const file of files) { const fullPath = node_path_1.default.join(dirPath, file); // Check if it's a directory if (node_fs_1.default.statSync(fullPath).isDirectory()) { // Recursively clean up the subdirectory removeEmptyDirectories(fullPath); } } // Re-check the directory; remove it if it's now empty if (node_fs_1.default.readdirSync(dirPath).length === 0) { node_fs_1.default.rmdirSync(dirPath); } } async function readManifestFile(outDir) { const manifestFilePath = node_path_1.default.posix.join(outDir, pluginBuildConfig_js_1.manifestTempFile); const manifestFileContent = await promises_1.default.readFile(manifestFilePath, 'utf-8'); (0, utils_js_1.assert)(manifestFileContent); const manifest = JSON.parse(manifestFileContent); (0, utils_js_1.assert)(manifest); (0, utils_js_1.assert)((0, utils_js_1.isObject)(manifest)); return manifest; } async function writeManifestFile(manifest, manifestFilePath) { (0, utils_js_1.assert)((0, utils_js_1.isObject)(manifest)); const manifestFileContent = JSON.stringify(manifest, null, 2); await promises_1.default.writeFile(manifestFilePath, manifestFileContent, 'utf-8'); } async function handleAssetsManifest_getBuildConfig(config) { const vikeConfig = await (0, resolveVikeConfigInternal_js_1.getVikeConfigInternal)(); const isFixEnabled = handleAssetsManifest_isFixEnabled(config); return { // https://github.com/vikejs/vike/issues/1339 ssrEmitAssets: isFixEnabled ? true : undefined, // Required if `ssrEmitAssets: true`, see https://github.com/vitejs/vite/pull/11430#issuecomment-1454800934 cssMinify: isFixEnabled ? 'esbuild' : undefined, manifest: pluginBuildConfig_js_1.manifestTempFile, copyPublicDir: vikeConfig.config.vite6BuilderApp ? // Already set by vike:build:pluginBuildApp undefined : !(0, isViteServerBuild_js_1.isViteServerBuild)(config), }; } async function handleAssetsManifest(config, viteEnv, options, bundle) { const isSsREnv = (0, isViteServerBuild_js_1.isViteServerBuild_onlySsrEnv)(config, viteEnv); if (isSsREnv) { (0, utils_js_1.assert)(!assetsJsonFilePath); const outDirs = (0, getOutDirs_js_1.getOutDirs)(config, viteEnv); assetsJsonFilePath = node_path_1.default.posix.join(outDirs.outDirRoot, 'assets.json'); await writeAssetsManifestFile(outDirs, assetsJsonFilePath, config); } if ((0, isViteServerBuild_js_1.isViteServerBuild)(config, viteEnv)) { const outDir = options.dir; (0, utils_js_1.assert)(outDir); // Replace __VITE_ASSETS_MANIFEST__ in server builds // - Always replace it in dist/server/ // - Also in some other server builds such as dist/vercel/ from vike-vercel // - Don't replace it in dist/rsc/ from vike-react-rsc since __VITE_ASSETS_MANIFEST__ doesn't exist there const noop = await (0, pluginBuildEntry_js_1.set_macro_ASSETS_MANIFEST)(assetsJsonFilePath, bundle, outDir); if (isSsREnv) (0, utils_js_1.assert)(!noop); // dist/server should always contain __VITE_ASSETS_MANIFEST__ } } async function writeAssetsManifestFile(outDirs, assetsJsonFilePath, config) { const isFixEnabled = handleAssetsManifest_isFixEnabled(config); const clientManifestFilePath = node_path_1.default.posix.join(outDirs.outDirClient, pluginBuildConfig_js_1.manifestTempFile); const serverManifestFilePath = node_path_1.default.posix.join(outDirs.outDirServer, pluginBuildConfig_js_1.manifestTempFile); if (!isFixEnabled) { await promises_1.default.copyFile(clientManifestFilePath, assetsJsonFilePath); } else { const { clientManifestMod } = await fixServerAssets(config); await writeManifestFile(clientManifestMod, assetsJsonFilePath); } await promises_1.default.rm(clientManifestFilePath); await promises_1.default.rm(serverManifestFilePath); }