index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.resolveOptimizedCacheDir = exports.getDepHash = exports.optimizeDeps = exports.OPTIMIZE_CACHE_DIR = void 0;
  7. const fs_extra_1 = __importDefault(require("fs-extra"));
  8. const path_1 = __importDefault(require("path"));
  9. const crypto_1 = require("crypto");
  10. const resolver_1 = require("../resolver");
  11. const build_1 = require("../build");
  12. const utils_1 = require("../utils");
  13. const es_module_lexer_1 = require("es-module-lexer");
  14. const chalk_1 = __importDefault(require("chalk"));
  15. const pluginAssets_1 = require("./pluginAssets");
  16. const debug = require('debug')('vite:optimize');
  17. const KNOWN_IGNORE_LIST = new Set([
  18. 'vite',
  19. 'vitepress',
  20. 'tailwindcss',
  21. '@tailwindcss/ui',
  22. '@pika/react',
  23. '@pika/react-dom'
  24. ]);
  25. exports.OPTIMIZE_CACHE_DIR = `node_modules/.vite_opt_cache`;
  26. async function optimizeDeps(config, asCommand = false) {
  27. const log = asCommand ? console.log : debug;
  28. const root = config.root || process.cwd();
  29. // warn presence of web_modules
  30. if (fs_extra_1.default.existsSync(path_1.default.join(root, 'web_modules'))) {
  31. console.warn(chalk_1.default.yellow(`[vite] vite 0.15 has built-in dependency pre-bundling and resolving ` +
  32. `from web_modules is no longer supported.`));
  33. }
  34. const pkgPath = utils_1.lookupFile(root, [`package.json`], true /* pathOnly */);
  35. if (!pkgPath) {
  36. log(`package.json not found. Skipping.`);
  37. return;
  38. }
  39. const cacheDir = resolveOptimizedCacheDir(root, pkgPath);
  40. const hashPath = path_1.default.join(cacheDir, 'hash');
  41. const depHash = getDepHash(root, config.__path);
  42. if (!config.force) {
  43. let prevhash;
  44. try {
  45. prevhash = await fs_extra_1.default.readFile(hashPath, 'utf-8');
  46. }
  47. catch (e) { }
  48. // hash is consistent, no need to re-bundle
  49. if (prevhash === depHash) {
  50. log('Hash is consistent. Skipping. Use --force to override.');
  51. return;
  52. }
  53. }
  54. await fs_extra_1.default.remove(cacheDir);
  55. await fs_extra_1.default.ensureDir(cacheDir);
  56. const options = config.optimizeDeps || {};
  57. const resolver = resolver_1.createResolver(root, config.resolvers, config.alias, config.assetsInclude);
  58. // Determine deps to optimize. The goal is to only pre-bundle deps that falls
  59. // under one of the following categories:
  60. // 1. Has imports to relative files (e.g. lodash-es, lit-html)
  61. // 2. Has imports to bare modules that are not in the project's own deps
  62. // (i.e. esm that imports its own dependencies, e.g. styled-components)
  63. await es_module_lexer_1.init;
  64. const { qualified, external } = resolveQualifiedDeps(root, options, resolver);
  65. // Resolve deps from linked packages in a monorepo
  66. if (options.link) {
  67. options.link.forEach((linkedDep) => {
  68. const depRoot = path_1.default.dirname(utils_1.resolveFrom(root, `${linkedDep}/package.json`));
  69. const { qualified: q, external: e } = resolveQualifiedDeps(depRoot, options, resolver);
  70. Object.keys(q).forEach((id) => {
  71. if (!qualified[id]) {
  72. qualified[id] = q[id];
  73. }
  74. });
  75. e.forEach((id) => {
  76. if (!external.includes(id)) {
  77. external.push(id);
  78. }
  79. });
  80. });
  81. }
  82. // Force included deps - these can also be deep paths
  83. if (options.include) {
  84. options.include.forEach((id) => {
  85. const pkg = resolver_1.resolveNodeModule(root, id, resolver);
  86. if (pkg && pkg.entryFilePath) {
  87. qualified[id] = pkg.entryFilePath;
  88. }
  89. else {
  90. const filePath = resolver_1.resolveNodeModuleFile(root, id);
  91. if (filePath) {
  92. qualified[id] = filePath;
  93. }
  94. }
  95. });
  96. }
  97. if (!Object.keys(qualified).length) {
  98. await fs_extra_1.default.writeFile(hashPath, depHash);
  99. log(`No listed dependency requires optimization. Skipping.`);
  100. return;
  101. }
  102. if (!asCommand) {
  103. // This is auto run on server start - let the user know that we are
  104. // pre-optimizing deps
  105. console.log(chalk_1.default.greenBright(`[vite] Optimizable dependencies detected:`));
  106. console.log(Object.keys(qualified)
  107. .map((id) => chalk_1.default.yellow(id))
  108. .join(`, `));
  109. }
  110. let spinner;
  111. const msg = asCommand
  112. ? `Pre-bundling dependencies to speed up dev server page load...`
  113. : `Pre-bundling them to speed up dev server page load...\n` +
  114. `(this will be run only when your dependencies have changed)`;
  115. if (process.env.DEBUG || process.env.NODE_ENV === 'test') {
  116. console.log(msg);
  117. }
  118. else {
  119. spinner = require('ora')(msg + '\n').start();
  120. }
  121. try {
  122. const rollup = require('rollup');
  123. const bundle = await rollup.rollup({
  124. input: qualified,
  125. external,
  126. // treeshake: { moduleSideEffects: 'no-external' },
  127. onwarn: build_1.onRollupWarning(spinner, options),
  128. ...config.rollupInputOptions,
  129. plugins: [
  130. pluginAssets_1.createDepAssetExternalPlugin(resolver),
  131. ...(await build_1.createBaseRollupPlugins(root, resolver, config)),
  132. pluginAssets_1.createDepAssetPlugin(resolver, root),
  133. ...((config.rollupInputOptions &&
  134. config.rollupInputOptions.pluginsOptimizer) ||
  135. [])
  136. ]
  137. });
  138. const { output } = await bundle.generate({
  139. ...config.rollupOutputOptions,
  140. format: 'es',
  141. exports: 'named',
  142. entryFileNames: '[name].js',
  143. chunkFileNames: 'common/[name]-[hash].js'
  144. });
  145. spinner && spinner.stop();
  146. for (const chunk of output) {
  147. if (chunk.type === 'chunk') {
  148. const fileName = chunk.fileName;
  149. const filePath = path_1.default.join(cacheDir, fileName);
  150. await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
  151. await fs_extra_1.default.writeFile(filePath, chunk.code);
  152. }
  153. }
  154. await fs_extra_1.default.writeFile(hashPath, depHash);
  155. }
  156. catch (e) {
  157. spinner && spinner.stop();
  158. if (asCommand) {
  159. throw e;
  160. }
  161. else {
  162. console.error(chalk_1.default.red(`\n[vite] Dep optimization failed with error:`));
  163. console.error(chalk_1.default.red(e.message));
  164. if (e.code === 'PARSE_ERROR') {
  165. console.error(chalk_1.default.cyan(path_1.default.relative(root, e.loc.file)));
  166. console.error(chalk_1.default.dim(e.frame));
  167. }
  168. else if (e.message.match('Node built-in')) {
  169. console.log();
  170. console.log(chalk_1.default.yellow(`Tip:\nMake sure your "dependencies" only include packages that you\n` +
  171. `intend to use in the browser. If it's a Node.js package, it\n` +
  172. `should be in "devDependencies".\n\n` +
  173. `If you do intend to use this dependency in the browser and the\n` +
  174. `dependency does not actually use these Node built-ins in the\n` +
  175. `browser, you can add the dependency (not the built-in) to the\n` +
  176. `"optimizeDeps.allowNodeBuiltins" option in vite.config.js.\n\n` +
  177. `If that results in a runtime error, then unfortunately the\n` +
  178. `package is not distributed in a web-friendly format. You should\n` +
  179. `open an issue in its repo, or look for a modern alternative.`)
  180. // TODO link to docs once we have it
  181. );
  182. }
  183. else {
  184. console.error(e);
  185. }
  186. process.exit(1);
  187. }
  188. }
  189. }
  190. exports.optimizeDeps = optimizeDeps;
  191. function resolveQualifiedDeps(root, options, resolver) {
  192. const { include, exclude, link } = options;
  193. const pkgContent = utils_1.lookupFile(root, ['package.json']);
  194. if (!pkgContent) {
  195. return {
  196. qualified: {},
  197. external: []
  198. };
  199. }
  200. const pkg = JSON.parse(pkgContent);
  201. const deps = Object.keys(pkg.dependencies || {});
  202. const qualifiedDeps = deps.filter((id) => {
  203. if (include && include.includes(id)) {
  204. // already force included
  205. return false;
  206. }
  207. if (exclude && exclude.includes(id)) {
  208. debug(`skipping ${id} (excluded)`);
  209. return false;
  210. }
  211. if (link && link.includes(id)) {
  212. debug(`skipping ${id} (link)`);
  213. return false;
  214. }
  215. if (KNOWN_IGNORE_LIST.has(id)) {
  216. debug(`skipping ${id} (internal excluded)`);
  217. return false;
  218. }
  219. // #804
  220. if (id.startsWith('@types/')) {
  221. debug(`skipping ${id} (ts declaration)`);
  222. return false;
  223. }
  224. const pkgInfo = resolver_1.resolveNodeModule(root, id, resolver);
  225. if (!pkgInfo || !pkgInfo.entryFilePath) {
  226. debug(`skipping ${id} (cannot resolve entry)`);
  227. console.log(root, id);
  228. console.error(chalk_1.default.yellow(`[vite] cannot resolve entry for dependency ${chalk_1.default.cyan(id)}.`));
  229. return false;
  230. }
  231. const { entryFilePath } = pkgInfo;
  232. if (!resolver_1.supportedExts.includes(path_1.default.extname(entryFilePath))) {
  233. debug(`skipping ${id} (entry is not js)`);
  234. return false;
  235. }
  236. if (!fs_extra_1.default.existsSync(entryFilePath)) {
  237. debug(`skipping ${id} (entry file does not exist)`);
  238. console.error(chalk_1.default.yellow(`[vite] dependency ${id} declares non-existent entry file ${entryFilePath}.`));
  239. return false;
  240. }
  241. const content = fs_extra_1.default.readFileSync(entryFilePath, 'utf-8');
  242. const [imports, exports] = es_module_lexer_1.parse(content);
  243. if (!exports.length && !/export\s+\*\s+from/.test(content)) {
  244. debug(`optimizing ${id} (no exports, likely commonjs)`);
  245. return true;
  246. }
  247. for (const { s, e } of imports) {
  248. let i = content.slice(s, e).trim();
  249. i = resolver.alias(i) || i;
  250. if (i.startsWith('.')) {
  251. debug(`optimizing ${id} (contains relative imports)`);
  252. return true;
  253. }
  254. if (!deps.includes(i)) {
  255. debug(`optimizing ${id} (imports sub dependencies)`);
  256. return true;
  257. }
  258. }
  259. debug(`skipping ${id} (single esm file, doesn't need optimization)`);
  260. });
  261. const qualified = {};
  262. qualifiedDeps.forEach((id) => {
  263. qualified[id] = resolver_1.resolveNodeModule(root, id, resolver).entryFilePath;
  264. });
  265. // mark non-optimized deps as external
  266. const external = deps
  267. .filter((id) => !qualifiedDeps.includes(id))
  268. // make sure aliased deps are external
  269. // https://github.com/vitejs/vite-plugin-react/issues/4
  270. .map((id) => resolver.alias(id) || id);
  271. return {
  272. qualified,
  273. external
  274. };
  275. }
  276. const lockfileFormats = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml'];
  277. let cachedHash;
  278. function getDepHash(root, configPath) {
  279. if (cachedHash) {
  280. return cachedHash;
  281. }
  282. let content = utils_1.lookupFile(root, lockfileFormats) || '';
  283. const pkg = JSON.parse(utils_1.lookupFile(root, [`package.json`]) || '{}');
  284. content += JSON.stringify(pkg.dependencies);
  285. // also take config into account
  286. if (configPath) {
  287. content += fs_extra_1.default.readFileSync(configPath, 'utf-8');
  288. }
  289. return crypto_1.createHash('sha1').update(content).digest('base64');
  290. }
  291. exports.getDepHash = getDepHash;
  292. const cacheDirCache = new Map();
  293. function resolveOptimizedCacheDir(root, pkgPath) {
  294. const cached = cacheDirCache.get(root);
  295. if (cached !== undefined)
  296. return cached;
  297. pkgPath = pkgPath || utils_1.lookupFile(root, [`package.json`], true /* pathOnly */);
  298. if (!pkgPath) {
  299. return null;
  300. }
  301. const cacheDir = path_1.default.join(path_1.default.dirname(pkgPath), exports.OPTIMIZE_CACHE_DIR);
  302. cacheDirCache.set(root, cacheDir);
  303. return cacheDir;
  304. }
  305. exports.resolveOptimizedCacheDir = resolveOptimizedCacheDir;
  306. //# sourceMappingURL=index.js.map