"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveOptimizedCacheDir = exports.getDepHash = exports.optimizeDeps = exports.OPTIMIZE_CACHE_DIR = void 0; const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const crypto_1 = require("crypto"); const resolver_1 = require("../resolver"); const build_1 = require("../build"); const utils_1 = require("../utils"); const es_module_lexer_1 = require("es-module-lexer"); const chalk_1 = __importDefault(require("chalk")); const pluginAssets_1 = require("./pluginAssets"); const debug = require('debug')('vite:optimize'); const KNOWN_IGNORE_LIST = new Set([ 'vite', 'vitepress', 'tailwindcss', '@tailwindcss/ui', '@pika/react', '@pika/react-dom' ]); exports.OPTIMIZE_CACHE_DIR = `node_modules/.vite_opt_cache`; async function optimizeDeps(config, asCommand = false) { const log = asCommand ? console.log : debug; const root = config.root || process.cwd(); // warn presence of web_modules if (fs_extra_1.default.existsSync(path_1.default.join(root, 'web_modules'))) { console.warn(chalk_1.default.yellow(`[vite] vite 0.15 has built-in dependency pre-bundling and resolving ` + `from web_modules is no longer supported.`)); } const pkgPath = utils_1.lookupFile(root, [`package.json`], true /* pathOnly */); if (!pkgPath) { log(`package.json not found. Skipping.`); return; } const cacheDir = resolveOptimizedCacheDir(root, pkgPath); const hashPath = path_1.default.join(cacheDir, 'hash'); const depHash = getDepHash(root, config.__path); if (!config.force) { let prevhash; try { prevhash = await fs_extra_1.default.readFile(hashPath, 'utf-8'); } catch (e) { } // hash is consistent, no need to re-bundle if (prevhash === depHash) { log('Hash is consistent. Skipping. Use --force to override.'); return; } } await fs_extra_1.default.remove(cacheDir); await fs_extra_1.default.ensureDir(cacheDir); const options = config.optimizeDeps || {}; const resolver = resolver_1.createResolver(root, config.resolvers, config.alias, config.assetsInclude); // Determine deps to optimize. The goal is to only pre-bundle deps that falls // under one of the following categories: // 1. Has imports to relative files (e.g. lodash-es, lit-html) // 2. Has imports to bare modules that are not in the project's own deps // (i.e. esm that imports its own dependencies, e.g. styled-components) await es_module_lexer_1.init; const { qualified, external } = resolveQualifiedDeps(root, options, resolver); // Resolve deps from linked packages in a monorepo if (options.link) { options.link.forEach((linkedDep) => { const depRoot = path_1.default.dirname(utils_1.resolveFrom(root, `${linkedDep}/package.json`)); const { qualified: q, external: e } = resolveQualifiedDeps(depRoot, options, resolver); Object.keys(q).forEach((id) => { if (!qualified[id]) { qualified[id] = q[id]; } }); e.forEach((id) => { if (!external.includes(id)) { external.push(id); } }); }); } // Force included deps - these can also be deep paths if (options.include) { options.include.forEach((id) => { const pkg = resolver_1.resolveNodeModule(root, id, resolver); if (pkg && pkg.entryFilePath) { qualified[id] = pkg.entryFilePath; } else { const filePath = resolver_1.resolveNodeModuleFile(root, id); if (filePath) { qualified[id] = filePath; } } }); } if (!Object.keys(qualified).length) { await fs_extra_1.default.writeFile(hashPath, depHash); log(`No listed dependency requires optimization. Skipping.`); return; } if (!asCommand) { // This is auto run on server start - let the user know that we are // pre-optimizing deps console.log(chalk_1.default.greenBright(`[vite] Optimizable dependencies detected:`)); console.log(Object.keys(qualified) .map((id) => chalk_1.default.yellow(id)) .join(`, `)); } let spinner; const msg = asCommand ? `Pre-bundling dependencies to speed up dev server page load...` : `Pre-bundling them to speed up dev server page load...\n` + `(this will be run only when your dependencies have changed)`; if (process.env.DEBUG || process.env.NODE_ENV === 'test') { console.log(msg); } else { spinner = require('ora')(msg + '\n').start(); } try { const rollup = require('rollup'); const bundle = await rollup.rollup({ input: qualified, external, // treeshake: { moduleSideEffects: 'no-external' }, onwarn: build_1.onRollupWarning(spinner, options), ...config.rollupInputOptions, plugins: [ pluginAssets_1.createDepAssetExternalPlugin(resolver), ...(await build_1.createBaseRollupPlugins(root, resolver, config)), pluginAssets_1.createDepAssetPlugin(resolver, root), ...((config.rollupInputOptions && config.rollupInputOptions.pluginsOptimizer) || []) ] }); const { output } = await bundle.generate({ ...config.rollupOutputOptions, format: 'es', exports: 'named', entryFileNames: '[name].js', chunkFileNames: 'common/[name]-[hash].js' }); spinner && spinner.stop(); for (const chunk of output) { if (chunk.type === 'chunk') { const fileName = chunk.fileName; const filePath = path_1.default.join(cacheDir, fileName); await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath)); await fs_extra_1.default.writeFile(filePath, chunk.code); } } await fs_extra_1.default.writeFile(hashPath, depHash); } catch (e) { spinner && spinner.stop(); if (asCommand) { throw e; } else { console.error(chalk_1.default.red(`\n[vite] Dep optimization failed with error:`)); console.error(chalk_1.default.red(e.message)); if (e.code === 'PARSE_ERROR') { console.error(chalk_1.default.cyan(path_1.default.relative(root, e.loc.file))); console.error(chalk_1.default.dim(e.frame)); } else if (e.message.match('Node built-in')) { console.log(); console.log(chalk_1.default.yellow(`Tip:\nMake sure your "dependencies" only include packages that you\n` + `intend to use in the browser. If it's a Node.js package, it\n` + `should be in "devDependencies".\n\n` + `If you do intend to use this dependency in the browser and the\n` + `dependency does not actually use these Node built-ins in the\n` + `browser, you can add the dependency (not the built-in) to the\n` + `"optimizeDeps.allowNodeBuiltins" option in vite.config.js.\n\n` + `If that results in a runtime error, then unfortunately the\n` + `package is not distributed in a web-friendly format. You should\n` + `open an issue in its repo, or look for a modern alternative.`) // TODO link to docs once we have it ); } else { console.error(e); } process.exit(1); } } } exports.optimizeDeps = optimizeDeps; function resolveQualifiedDeps(root, options, resolver) { const { include, exclude, link } = options; const pkgContent = utils_1.lookupFile(root, ['package.json']); if (!pkgContent) { return { qualified: {}, external: [] }; } const pkg = JSON.parse(pkgContent); const deps = Object.keys(pkg.dependencies || {}); const qualifiedDeps = deps.filter((id) => { if (include && include.includes(id)) { // already force included return false; } if (exclude && exclude.includes(id)) { debug(`skipping ${id} (excluded)`); return false; } if (link && link.includes(id)) { debug(`skipping ${id} (link)`); return false; } if (KNOWN_IGNORE_LIST.has(id)) { debug(`skipping ${id} (internal excluded)`); return false; } // #804 if (id.startsWith('@types/')) { debug(`skipping ${id} (ts declaration)`); return false; } const pkgInfo = resolver_1.resolveNodeModule(root, id, resolver); if (!pkgInfo || !pkgInfo.entryFilePath) { debug(`skipping ${id} (cannot resolve entry)`); console.log(root, id); console.error(chalk_1.default.yellow(`[vite] cannot resolve entry for dependency ${chalk_1.default.cyan(id)}.`)); return false; } const { entryFilePath } = pkgInfo; if (!resolver_1.supportedExts.includes(path_1.default.extname(entryFilePath))) { debug(`skipping ${id} (entry is not js)`); return false; } if (!fs_extra_1.default.existsSync(entryFilePath)) { debug(`skipping ${id} (entry file does not exist)`); console.error(chalk_1.default.yellow(`[vite] dependency ${id} declares non-existent entry file ${entryFilePath}.`)); return false; } const content = fs_extra_1.default.readFileSync(entryFilePath, 'utf-8'); const [imports, exports] = es_module_lexer_1.parse(content); if (!exports.length && !/export\s+\*\s+from/.test(content)) { debug(`optimizing ${id} (no exports, likely commonjs)`); return true; } for (const { s, e } of imports) { let i = content.slice(s, e).trim(); i = resolver.alias(i) || i; if (i.startsWith('.')) { debug(`optimizing ${id} (contains relative imports)`); return true; } if (!deps.includes(i)) { debug(`optimizing ${id} (imports sub dependencies)`); return true; } } debug(`skipping ${id} (single esm file, doesn't need optimization)`); }); const qualified = {}; qualifiedDeps.forEach((id) => { qualified[id] = resolver_1.resolveNodeModule(root, id, resolver).entryFilePath; }); // mark non-optimized deps as external const external = deps .filter((id) => !qualifiedDeps.includes(id)) // make sure aliased deps are external // https://github.com/vitejs/vite-plugin-react/issues/4 .map((id) => resolver.alias(id) || id); return { qualified, external }; } const lockfileFormats = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml']; let cachedHash; function getDepHash(root, configPath) { if (cachedHash) { return cachedHash; } let content = utils_1.lookupFile(root, lockfileFormats) || ''; const pkg = JSON.parse(utils_1.lookupFile(root, [`package.json`]) || '{}'); content += JSON.stringify(pkg.dependencies); // also take config into account if (configPath) { content += fs_extra_1.default.readFileSync(configPath, 'utf-8'); } return crypto_1.createHash('sha1').update(content).digest('base64'); } exports.getDepHash = getDepHash; const cacheDirCache = new Map(); function resolveOptimizedCacheDir(root, pkgPath) { const cached = cacheDirCache.get(root); if (cached !== undefined) return cached; pkgPath = pkgPath || utils_1.lookupFile(root, [`package.json`], true /* pathOnly */); if (!pkgPath) { return null; } const cacheDir = path_1.default.join(path_1.default.dirname(pkgPath), exports.OPTIMIZE_CACHE_DIR); cacheDirCache.set(root, cacheDir); return cacheDir; } exports.resolveOptimizedCacheDir = resolveOptimizedCacheDir; //# sourceMappingURL=index.js.map