vike
Version:
The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.
336 lines (335 loc) • 16.7 kB
JavaScript
;
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);
}