From 76df629f77db6d0e5de92b2f2c96f2c22f9c31d8 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Thu, 30 Jun 2022 16:55:33 +0200 Subject: [PATCH 01/30] feat: optimizeDeps.devStrategy, new 'dynamic-scan' strat --- docs/guide/migration.md | 3 +- packages/vite/src/node/config.ts | 9 +- packages/vite/src/node/optimizer/index.ts | 19 +++- packages/vite/src/node/optimizer/optimizer.ts | 98 ++++++++++++++++--- packages/vite/src/node/optimizer/scan.ts | 31 ++++-- .../vite/src/node/plugins/importAnalysis.ts | 46 ++++----- .../vite/src/node/server/transformRequest.ts | 20 +++- .../__tests__/dynamic-import.spec.ts | 6 +- playground/dynamic-import/{ => files}/mxd.js | 0 .../dynamic-import/{ => files}/mxd.json | 0 playground/dynamic-import/nested/index.js | 12 +-- playground/dynamic-import/{ => views}/qux.js | 0 12 files changed, 173 insertions(+), 71 deletions(-) rename playground/dynamic-import/{ => files}/mxd.js (100%) rename playground/dynamic-import/{ => files}/mxd.json (100%) rename playground/dynamic-import/{ => views}/qux.js (100%) diff --git a/docs/guide/migration.md b/docs/guide/migration.md index 3be22c3bc96f3d..80869ce104b10f 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -34,7 +34,6 @@ This section describes the biggest architecture changes in Vite v3. To allow pro :::warning These options are marked as experimental and deprecated. They may be removed in a future v3 minor without respecting semver. Please pin the Vite version when using them. -- `legacy.devDepsScanner` - `legacy.buildRollupPluginCommonjs` - `legacy.buildSsrCjsExternalHeuristics` @@ -48,7 +47,7 @@ Vite's default dev server host is now `localhost`. You can use [`server.host`](. Vite optimizes dependencies with esbuild to both convert CJS-only deps to ESM and to reduce the number of modules the browser needs to request. In v3, the default strategy to discover and batch dependencies has changed. Vite no longer pre-scans user code with esbuild to get an initial list of dependencies on cold start. Instead, it delays the first dependency optimization run until every imported user module on load is processed. -To get back the v2 strategy, you can use `legacy.devDepsScanner`. +To get back the v2 strategy, you can use `optimizeDeps.devStrategy: 'pre-scan'`. ### Build Changes diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 67391c6e1e5830..c5f924dad08d2b 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -276,14 +276,6 @@ export interface ExperimentalOptions { } export interface LegacyOptions { - /** - * Revert vite dev to the v2.9 strategy. Enable esbuild based deps scanner. - * - * @experimental - * @deprecated - * @default false - */ - devDepsScanner?: boolean /** * Revert vite build to the v2.9 strategy. Disable esbuild deps optimization and adds `@rollup/plugin-commonjs` * @@ -618,6 +610,7 @@ export async function resolveConfig( packageCache: new Map(), createResolver, optimizeDeps: { + devStrategy: config.appType === 'mpa' ? 'pre-scan' : 'dynamic-scan', ...optimizeDeps, esbuildOptions: { preserveSymlinks: config.resolve?.preserveSymlinks, diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 11d9e83d6a0f90..70347acaaf47be 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -63,7 +63,7 @@ export interface DepsOptimizer { isOptimizedDepFile: (id: string) => boolean isOptimizedDepUrl: (url: string) => boolean getOptimizedDepId: (depInfo: OptimizedDepInfo) => string - + registerDynamicImport: (importInfo: { id: string; url: string }) => void delayDepsOptimizerUntil: (id: string, done: () => Promise) => void registerWorkersSource: (id: string) => void resetRegisteredIds: () => void @@ -142,6 +142,23 @@ export interface DepOptimizationOptions { * @experimental */ disabled?: boolean | 'build' | 'dev' + /** + * Defines the cold start strategy: + * 'dynamic-scan': delay optimization until static imports are crawled, then + * scan with esbuild dynamic import entries found in the source code + * 'pre-scan': pre scan user code with esbuild to find the first batch of + * dependecies to optimize + * 'lazy': only static imports are crawled, leading to the fastest cold start + * experience with the tradeoff of possible full page reload when navigating + * to dynamic routes + * 'eager': both static and dynamic imports are processed on cold start + * completely removing the need for full page reloads at the expense of a + * slower cold start + * + * @default 'dynamic-scan', and 'pre-scan' for appType 'mpa' + * @experimental + */ + devStrategy?: 'dynamic-scan' | 'pre-scan' | 'lazy' | 'eager' /** * Force dep pre-optimization regardless of whether deps have changed. * @experimental diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index def39f2e8c8361..b75cd992ef2806 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -2,10 +2,12 @@ import path from 'node:path' import colors from 'picocolors' import _debug from 'debug' import glob from 'fast-glob' +import micromatch from 'micromatch' import { FS_PREFIX } from '../constants' import { getHash } from '../utils' -import { transformRequest } from '../server/transformRequest' +import { preTransformRequest } from '../server/transformRequest' import type { ResolvedConfig, ViteDevServer } from '..' +import { filterScannableEntries, scanImportsInEntries } from './scan' import { addOptimizedDepInfo, createIsOptimizedDepUrl, @@ -21,6 +23,7 @@ import { loadCachedDepOptimizationMetadata, newDepOptimizationProcessing, optimizeServerSsrDeps, + optimizedDepInfoFromId, runOptimizeDeps } from '.' import type { @@ -71,7 +74,8 @@ async function createDepsOptimizer( const { logger } = config const isBuild = config.command === 'build' - const scan = config.command !== 'build' && config.legacy?.devDepsScanner + const { devStrategy } = config.optimizeDeps + const preScan = config.command !== 'build' && devStrategy === 'pre-scan' const sessionTimestamp = Date.now().toString() @@ -82,6 +86,16 @@ async function createDepsOptimizer( let metadata = cachedMetadata || initDepsOptimizerMetadata(config, sessionTimestamp) + const { entries } = config.optimizeDeps + const optOutEntries = ( + entries ? (Array.isArray(entries) ? entries : [entries]) : [] + ) + .filter((rule) => rule.startsWith('!')) + .map((rule) => rule.slice(1)) + + // Used for devStrategy === 'dynamic-scan' + let discoveredDynamicImports: string[] = [] + const depsOptimizer: DepsOptimizer = { metadata, registerMissingImport, @@ -94,6 +108,30 @@ async function createDepsOptimizer( delayDepsOptimizerUntil, resetRegisteredIds, ensureFirstRun, + registerDynamicImport: ({ id, url }) => { + if (!firstRunCalled && server) { + // devStrategy === 'eager' process all dynamic imports with the real + // plugins during cold start + if (devStrategy === 'eager') { + if (!micromatch.isMatch(id, optOutEntries)) { + preTransformRequest(url, server, { ssr: false }) + } + } + // devStrategy === 'dynamic-scan' aggregates the discovered dynamic + // imports and process them with esbuild once the server is iddle and + // before the first optimize batch to pre-discover missing deps + else if (devStrategy === 'dynamic-scan') { + if (!discoveredDynamicImports.includes(id)) { + discoveredDynamicImports.push(id) + } + } + // devStrategy === 'lazy' ignores dynamic imports, giving a faster cold + // start with potential full page reloads + + // for devStrategy === 'pre-scan', dynamic imports where already pre-scanned + // by esbuild so there is also nothing to do here + } + }, options: config.optimizeDeps } @@ -137,7 +175,7 @@ async function createDepsOptimizer( let firstRunCalled = !!cachedMetadata if (!cachedMetadata) { - if (!scan) { + if (!preScan) { // Initialize discovered deps with manually added optimizeDeps.include info const discovered = await initialProjectDependencies( config, @@ -195,6 +233,30 @@ async function createDepsOptimizer( } async function runOptimizer() { + if (!firstRunCalled && !isBuild && devStrategy === 'dynamic-scan') { + const entries = filterScannableEntries(discoveredDynamicImports) + discoveredDynamicImports = [] + const optimizeDepsEntries = config.optimizeDeps.entries + if (optimizeDepsEntries) { + const explicitEntries = filterScannableEntries( + await globExplicitEntries(optimizeDepsEntries, config) + ) + for (const explicitEntry of explicitEntries) { + if (!entries.includes(explicitEntry)) { + entries.push(explicitEntry) + } + } + } + if (entries.length) { + const { deps } = await scanImportsInEntries(entries, config) + for (const id of Object.keys(deps)) { + if (!optimizedDepInfoFromId(metadata, id)) { + registerMissingImport(id, deps[id]) + } + } + } + } + const isRerun = firstRunCalled firstRunCalled = true @@ -489,7 +551,7 @@ async function createDepsOptimizer( // we can get a list of every missing dependency before giving to the // browser a dependency that may be outdated, thus avoiding full page reloads - if (scan || firstRunCalled) { + if (preScan || firstRunCalled) { // Debounced rerun, let other missing dependencies be discovered before // the running next optimizeDeps debouncedProcessing() @@ -614,6 +676,7 @@ async function createDevSsrDepsOptimizer( 'Vite Internal Error: registerMissingImport is not supported in dev SSR' ) }, + registerDynamicImport: () => {}, // noop, there is no scanning during dev SSR // the optimizer blocks the server start run: () => {}, @@ -630,23 +693,28 @@ export async function preTransformOptimizeDepsEntries( server: ViteDevServer ): Promise { const { config } = server - const { root } = config const { entries } = config.optimizeDeps if (entries) { - const explicitEntries = await glob(entries, { - cwd: root, - ignore: ['**/node_modules/**', `**/${config.build.outDir}/**`], - absolute: true - }) + const explicitEntries = await globExplicitEntries(entries, config) // TODO: should we restrict the entries to JS and HTML like the // scanner did? I think we can let the user chose any entry for (const entry of explicitEntries) { - const url = entry.startsWith(root + '/') - ? entry.slice(root.length) + const url = entry.startsWith(config.root + '/') + ? entry.slice(config.root.length) : path.posix.join(FS_PREFIX + entry) - transformRequest(url, server, { ssr: false }).catch((e) => { - config.logger.error(e.message) - }) + + preTransformRequest(url, server, { ssr: false }) } } } + +async function globExplicitEntries( + entries: string | string[], + config: ResolvedConfig +): Promise { + return await glob(entries, { + cwd: config.root, + ignore: ['**/node_modules/**', `**/${config.build.outDir}/**`], + absolute: true + }) +} diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts index 5608472b4efe91..09d28da847c3c8 100644 --- a/packages/vite/src/node/optimizer/scan.ts +++ b/packages/vite/src/node/optimizer/scan.ts @@ -44,8 +44,6 @@ export async function scanImports(config: ResolvedConfig): Promise<{ deps: Record missing: Record }> { - const start = performance.now() - let entries: string[] = [] const explicitEntryPatterns = config.optimizeDeps.entries @@ -70,9 +68,7 @@ export async function scanImports(config: ResolvedConfig): Promise<{ // Non-supported entry file types and virtual files should not be scanned for // dependencies. - entries = entries.filter( - (entry) => isScannable(entry) && fs.existsSync(entry) - ) + entries = filterScannableEntries(entries) if (!entries.length) { if (!explicitEntryPatterns && !config.optimizeDeps.include) { @@ -85,10 +81,26 @@ export async function scanImports(config: ResolvedConfig): Promise<{ ) } return { deps: {}, missing: {} } - } else { - debug(`Crawling dependencies using entries:\n ${entries.join('\n ')}`) } + return scanImportsInEntries(entries, config) +} + +export function filterScannableEntries(entries: string[]): string[] { + return entries.filter((entry) => isScannable(entry) && fs.existsSync(entry)) +} + +export async function scanImportsInEntries( + entries: string[], + config: ResolvedConfig +): Promise<{ + deps: Record + missing: Record +}> { + const start = performance.now() + + debug(`Crawling dependencies using entries:\n ${entries.join('\n ')}`) + const deps: Record = {} const missing: Record = {} const container = await createPluginContainer(config) @@ -128,7 +140,10 @@ function orderedDependencies(deps: Record) { return Object.fromEntries(depsList) } -function globEntries(pattern: string | string[], config: ResolvedConfig) { +export async function globEntries( + pattern: string | string[], + config: ResolvedConfig +): Promise { return glob(pattern, { cwd: config.root, ignore: [ diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index f9e8fa879bb28a..a2c93c6aaf0365 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -49,17 +49,14 @@ import { cjsShouldExternalizeForSSR, shouldExternalizeForSSR } from '../ssr/ssrExternal' -import { transformRequest } from '../server/transformRequest' +import { preTransformRequest } from '../server/transformRequest' import { getDepsCacheDirPrefix, getDepsOptimizer, optimizedDepNeedsInterop } from '../optimizer' import { checkPublicFile } from './asset' -import { - ERR_OUTDATED_OPTIMIZED_DEP, - throwOutdatedRequest -} from './optimizedDeps' +import { throwOutdatedRequest } from './optimizedDeps' import { isCSSRequest, isDirectCSSRequest } from './css' import { browserExternalId } from './resolve' @@ -244,7 +241,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { let s: MagicString | undefined const str = () => s || (s = new MagicString(source)) const importedUrls = new Set() - const staticImportedUrls = new Set<{ url: string; id: string }>() + const importedUrlsToPreTransform = new Set<{ url: string; id: string }>() const acceptedUrls = new Set<{ url: string start: number @@ -531,9 +528,17 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { ) } + const importedUrl = { + id: resolvedId, + url: unwrapId(removeImportQuery(url)).replace( + NULL_BYTE_PLACEHOLDER, + '\0' + ) + } if (!isDynamicImport) { - // for pre-transforming - staticImportedUrls.add({ url: urlWithoutBase, id: resolvedId }) + importedUrlsToPreTransform.add(importedUrl) + } else { + depsOptimizer?.registerDynamicImport(importedUrl) } } else if (!importer.startsWith(clientDir) && !ssr) { // check @vite-ignore which suppresses dynamic import warning @@ -686,23 +691,14 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { ) // pre-transform known direct imports - // TODO: should we also crawl dynamic imports? or the experience is good enough to allow - // users to chose their tradeoffs by explicitily setting optimizeDeps.entries for the - // most common dynamic imports - if (config.server.preTransformRequests && staticImportedUrls.size) { - staticImportedUrls.forEach(({ url, id }) => { - url = unwrapId(removeImportQuery(url)).replace( - NULL_BYTE_PLACEHOLDER, - '\0' - ) - transformRequest(url, server, { ssr }).catch((e) => { - if (e?.code === ERR_OUTDATED_OPTIMIZED_DEP) { - // This are expected errors - return - } - // Unexpected error, log the issue but avoid an unhandled exception - config.logger.error(e.message) - }) + // These requests will also be registered in transformRequest to be awaited + // by the deps optimizer + if ( + config.server.preTransformRequests && + importedUrlsToPreTransform.size + ) { + importedUrlsToPreTransform.forEach(({ url }) => { + preTransformRequest(url, server, { ssr }) }) } diff --git a/packages/vite/src/node/server/transformRequest.ts b/packages/vite/src/node/server/transformRequest.ts index 782ad3ef1e1725..b1660161c1d8a5 100644 --- a/packages/vite/src/node/server/transformRequest.ts +++ b/packages/vite/src/node/server/transformRequest.ts @@ -16,6 +16,7 @@ import { timeFrom } from '../utils' import { checkPublicFile } from '../plugins/asset' +import { ERR_OUTDATED_OPTIMIZED_DEP } from '../plugins/optimizedDeps' import { ssrTransform } from '../ssr/ssrTransform' import { getDepsOptimizer } from '../optimizer' import { injectSourcesContent } from './sourcemap' @@ -145,9 +146,8 @@ async function doTransform( const result = loadAndTransform(id, url, server, options, timestamp) - const depsOptimizer = getDepsOptimizer(config, { ssr }) - if (depsOptimizer && !config.legacy?.devDepsScanner) { - depsOptimizer.delayDepsOptimizerUntil(id, () => result) + if (config.optimizeDeps.devStrategy !== 'pre-scan') { + getDepsOptimizer(config, { ssr })?.delayDepsOptimizerUntil(id, () => result) } return result @@ -279,3 +279,17 @@ async function loadAndTransform( return result } + +export async function preTransformRequest( + url: string, + server: ViteDevServer, + options: { ssr: boolean } = { ssr: false } +): Promise { + return transformRequest(url, server, options).catch((e) => { + if (e?.code !== ERR_OUTDATED_OPTIMIZED_DEP) { + // Unexpected error, log the issue but avoid an unhandled exception + server.config.logger.error(e.message) + } + return null + }) +} diff --git a/playground/dynamic-import/__tests__/dynamic-import.spec.ts b/playground/dynamic-import/__tests__/dynamic-import.spec.ts index 1eecd801f200c9..86c42a4d79a7e5 100644 --- a/playground/dynamic-import/__tests__/dynamic-import.spec.ts +++ b/playground/dynamic-import/__tests__/dynamic-import.spec.ts @@ -24,18 +24,18 @@ test('should load data URL of `data:`', async () => { await untilUpdated(() => page.textContent('.view'), 'data', true) }) -test('should have same reference on static and dynamic js import', async () => { +test('should have same reference on static and dynamic js import, .mxd', async () => { await page.click('.mxd') await untilUpdated(() => page.textContent('.view'), 'true', true) }) // in this case, it is not possible to detect the correct module -test('should have same reference on static and dynamic js import', async () => { +test('should have same reference on static and dynamic js import, .mxd2', async () => { await page.click('.mxd2') await untilUpdated(() => page.textContent('.view'), 'false', true) }) -test('should have same reference on static and dynamic js import', async () => { +test('should have same reference on static and dynamic js import, .mxdjson', async () => { await page.click('.mxdjson') await untilUpdated(() => page.textContent('.view'), 'true', true) }) diff --git a/playground/dynamic-import/mxd.js b/playground/dynamic-import/files/mxd.js similarity index 100% rename from playground/dynamic-import/mxd.js rename to playground/dynamic-import/files/mxd.js diff --git a/playground/dynamic-import/mxd.json b/playground/dynamic-import/files/mxd.json similarity index 100% rename from playground/dynamic-import/mxd.json rename to playground/dynamic-import/files/mxd.json diff --git a/playground/dynamic-import/nested/index.js b/playground/dynamic-import/nested/index.js index 31dcd07560836f..0b229055b32b7c 100644 --- a/playground/dynamic-import/nested/index.js +++ b/playground/dynamic-import/nested/index.js @@ -1,5 +1,5 @@ -import mxdStatic from '../mxd' -import mxdStaticJSON from '../mxd.json' +import mxdStatic from '../files/mxd' +import mxdStaticJSON from '../files/mxd.json' async function setView(view) { const { msg } = await import(`../views/${view}.js`) @@ -18,7 +18,7 @@ document.querySelector('.baz').addEventListener('click', async () => { // full dynamic const arr = ['qux.js'] -const view = `/${arr[0]}` +const view = `/views/${arr[0]}` document.querySelector('.qux').addEventListener('click', async () => { const { msg } = await import(/*@vite-ignore*/ view) text('.view', msg) @@ -27,12 +27,12 @@ document.querySelector('.qux').addEventListener('click', async () => { // mixed static and dynamic document.querySelector('.mxd').addEventListener('click', async () => { const view = 'mxd' - const { default: mxdDynamic } = await import(`../${view}.js`) + const { default: mxdDynamic } = await import(`../files/${view}.js`) text('.view', mxdStatic === mxdDynamic) }) document.querySelector('.mxd2').addEventListener('click', async () => { - const test = { jss: '../mxd.js' } + const test = { jss: '../files/mxd.js' } const ttest = test const view = 'mxd' const { default: mxdDynamic } = await import(/*@vite-ignore*/ test.jss) @@ -41,7 +41,7 @@ document.querySelector('.mxd2').addEventListener('click', async () => { document.querySelector('.mxdjson').addEventListener('click', async () => { const view = 'mxd' - const { default: mxdDynamicJSON } = await import(`../${view}.json`) + const { default: mxdDynamicJSON } = await import(`../files/${view}.json`) text('.view', mxdStaticJSON === mxdDynamicJSON) }) diff --git a/playground/dynamic-import/qux.js b/playground/dynamic-import/views/qux.js similarity index 100% rename from playground/dynamic-import/qux.js rename to playground/dynamic-import/views/qux.js From f3bb8348657ae856dd68217809f3bad9f7a5aa5d Mon Sep 17 00:00:00 2001 From: patak-dev Date: Thu, 30 Jun 2022 17:10:44 +0200 Subject: [PATCH 02/30] chore: update --- packages/vite/src/node/config.ts | 2 +- packages/vite/src/node/optimizer/index.ts | 2 +- playground/dynamic-import/vite.config.js | 14 ++++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 3dfd987d04f6d6..35236cca518c03 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -610,7 +610,7 @@ export async function resolveConfig( packageCache: new Map(), createResolver, optimizeDeps: { - devStrategy: config.appType === 'mpa' ? 'pre-scan' : 'dynamic-scan', + devStrategy: 'dynamic-scan', ...optimizeDeps, esbuildOptions: { preserveSymlinks: config.resolve?.preserveSymlinks, diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 70347acaaf47be..d546cbbac20f7d 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -155,7 +155,7 @@ export interface DepOptimizationOptions { * completely removing the need for full page reloads at the expense of a * slower cold start * - * @default 'dynamic-scan', and 'pre-scan' for appType 'mpa' + * @default 'dynamic-scan' * @experimental */ devStrategy?: 'dynamic-scan' | 'pre-scan' | 'lazy' | 'eager' diff --git a/playground/dynamic-import/vite.config.js b/playground/dynamic-import/vite.config.js index b12d8347287a2f..96baaa74798686 100644 --- a/playground/dynamic-import/vite.config.js +++ b/playground/dynamic-import/vite.config.js @@ -7,17 +7,19 @@ module.exports = vite.defineConfig({ { name: 'copy', writeBundle() { + fs.mkdirSync(path.resolve(__dirname, 'dist/views')) + fs.mkdirSync(path.resolve(__dirname, 'dist/files')) fs.copyFileSync( - path.resolve(__dirname, 'qux.js'), - path.resolve(__dirname, 'dist/qux.js') + path.resolve(__dirname, 'views/qux.js'), + path.resolve(__dirname, 'dist/views/qux.js') ) fs.copyFileSync( - path.resolve(__dirname, 'mxd.js'), - path.resolve(__dirname, 'dist/mxd.js') + path.resolve(__dirname, 'files/mxd.js'), + path.resolve(__dirname, 'dist/files/mxd.js') ) fs.copyFileSync( - path.resolve(__dirname, 'mxd.json'), - path.resolve(__dirname, 'dist/mxd.json') + path.resolve(__dirname, 'files/mxd.json'), + path.resolve(__dirname, 'dist/files/mxd.json') ) } } From b6f2d04885dbcf249b4e025acb0be05cc1c01ef1 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Thu, 30 Jun 2022 22:07:36 +0200 Subject: [PATCH 03/30] fix: use urlWithoutBase --- packages/vite/src/node/plugins/importAnalysis.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index a2c93c6aaf0365..d2555bc40c5fc6 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -530,7 +530,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { const importedUrl = { id: resolvedId, - url: unwrapId(removeImportQuery(url)).replace( + url: unwrapId(removeImportQuery(urlWithoutBase)).replace( NULL_BYTE_PLACEHOLDER, '\0' ) From fd46ccfccb668632cd2fce77fce0a07ee2efefc3 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Thu, 30 Jun 2022 22:40:31 +0200 Subject: [PATCH 04/30] test: add example of full-reload with 'lazy' in preload playground --- playground/preload/dep-a/index.js | 1 + playground/preload/dep-a/package.json | 7 +++++ playground/preload/dep-including-a/index.js | 3 ++ .../preload/dep-including-a/package.json | 10 ++++++ playground/preload/index.html | 2 ++ playground/preload/package.json | 4 ++- playground/preload/src/components/About.vue | 8 +++-- pnpm-lock.yaml | 31 ++++++++++++++++++- 8 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 playground/preload/dep-a/index.js create mode 100644 playground/preload/dep-a/package.json create mode 100644 playground/preload/dep-including-a/index.js create mode 100644 playground/preload/dep-including-a/package.json diff --git a/playground/preload/dep-a/index.js b/playground/preload/dep-a/index.js new file mode 100644 index 00000000000000..42c97c2d826ec6 --- /dev/null +++ b/playground/preload/dep-a/index.js @@ -0,0 +1 @@ +export const msgFromA = 'From dep-a' diff --git a/playground/preload/dep-a/package.json b/playground/preload/dep-a/package.json new file mode 100644 index 00000000000000..d45aab10cf9379 --- /dev/null +++ b/playground/preload/dep-a/package.json @@ -0,0 +1,7 @@ +{ + "name": "dep-a", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "index.js" +} diff --git a/playground/preload/dep-including-a/index.js b/playground/preload/dep-including-a/index.js new file mode 100644 index 00000000000000..4fd1e37a11dd09 --- /dev/null +++ b/playground/preload/dep-including-a/index.js @@ -0,0 +1,3 @@ +export { msgFromA } from 'dep-a' + +export const msg = 'From dep-including-a' diff --git a/playground/preload/dep-including-a/package.json b/playground/preload/dep-including-a/package.json new file mode 100644 index 00000000000000..0b6febf47c6266 --- /dev/null +++ b/playground/preload/dep-including-a/package.json @@ -0,0 +1,10 @@ +{ + "name": "dep-including-a", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "index.js", + "dependencies": { + "dep-a": "file:../dep-a" + } +} diff --git a/playground/preload/index.html b/playground/preload/index.html index affed21f9791cf..5b6acc4190a6d9 100644 --- a/playground/preload/index.html +++ b/playground/preload/index.html @@ -3,6 +3,8 @@ import { createApp } from 'vue' import router from './router.js' import App from './src/App.vue' + import { msgFromA } from 'dep-a' + console.log(msgFromA) createApp(App).use(router).mount('#app') diff --git a/playground/preload/package.json b/playground/preload/package.json index 2b76252c1e04ff..c72b346dece591 100644 --- a/playground/preload/package.json +++ b/playground/preload/package.json @@ -14,6 +14,8 @@ }, "devDependencies": { "@vitejs/plugin-vue": "workspace:*", - "terser": "^5.14.1" + "terser": "^5.14.1", + "dep-a": "file:./dep-a", + "dep-including-a": "file:./dep-including-a" } } diff --git a/playground/preload/src/components/About.vue b/playground/preload/src/components/About.vue index 2e73e80099446b..b19a3e3f196b5e 100644 --- a/playground/preload/src/components/About.vue +++ b/playground/preload/src/components/About.vue @@ -1,9 +1,13 @@ + + - - diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 67f75f4da8630d..17d0b2d186face 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -708,6 +708,9 @@ importers: playground/optimize-deps/nested-include: specifiers: {} + playground/optimize-deps/non-optimizable-include: + specifiers: {} + playground/optimize-missing-deps: specifiers: express: ^4.18.1 @@ -729,6 +732,8 @@ importers: playground/preload: specifiers: '@vitejs/plugin-vue': workspace:* + dep-a: file:./dep-a + dep-including-a: file:./dep-including-a terser: ^5.14.1 vue: ^3.2.37 vue-router: ^4.0.16 @@ -737,8 +742,19 @@ importers: vue-router: 4.0.16_vue@3.2.37 devDependencies: '@vitejs/plugin-vue': link:../../packages/plugin-vue + dep-a: file:playground/preload/dep-a + dep-including-a: file:playground/preload/dep-including-a terser: 5.14.1 + playground/preload/dep-a: + specifiers: {} + + playground/preload/dep-including-a: + specifiers: + dep-a: file:../dep-a + dependencies: + dep-a: file:playground/preload/dep-a + playground/preserve-symlinks: specifiers: '@symlinks/moduleA': link:./moduleA @@ -8837,6 +8853,19 @@ packages: version: 0.0.0 dev: false + file:playground/preload/dep-a: + resolution: {directory: playground/preload/dep-a, type: directory} + name: dep-a + version: 0.0.0 + + file:playground/preload/dep-including-a: + resolution: {directory: playground/preload/dep-including-a, type: directory} + name: dep-including-a + version: 0.0.0 + dependencies: + dep-a: file:playground/preload/dep-a + dev: true + file:playground/react/jsx-entry: resolution: {directory: playground/react/jsx-entry, type: directory} name: jsx-entry @@ -8877,7 +8906,7 @@ packages: file:playground/ssr-deps/no-external-css: resolution: {directory: playground/ssr-deps/no-external-css, type: directory} - name: primitive-export-css + name: no-external-css version: 0.0.0 dev: false From cda64d258ca78a7c955511fc3ca488c717a3d9f9 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Fri, 1 Jul 2022 22:57:03 +0200 Subject: [PATCH 05/30] refactor: dynamic-scan -> scan --- packages/vite/src/node/config.ts | 2 +- packages/vite/src/node/optimizer/index.ts | 78 ++++----- packages/vite/src/node/optimizer/optimizer.ts | 151 ++++++++++-------- .../vite/src/node/plugins/importAnalysis.ts | 2 +- .../src/node/plugins/importAnalysisBuild.ts | 2 +- packages/vite/src/node/plugins/resolve.ts | 2 +- packages/vite/src/node/server/index.ts | 2 +- 7 files changed, 118 insertions(+), 121 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 35236cca518c03..c73a995d361bc1 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -610,7 +610,7 @@ export async function resolveConfig( packageCache: new Map(), createResolver, optimizeDeps: { - devStrategy: 'dynamic-scan', + devStrategy: 'scan', ...optimizeDeps, esbuildOptions: { preserveSymlinks: config.resolve?.preserveSymlinks, diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index d546cbbac20f7d..976dc6783afb66 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -52,7 +52,7 @@ export type ExportsData = { export interface DepsOptimizer { metadata: DepOptimizationMetadata - scanProcessing?: Promise + preScanning?: Promise registerMissingImport: ( id: string, resolved: string, @@ -73,6 +73,25 @@ export interface DepsOptimizer { } export interface DepOptimizationOptions { + /** + * Defines the cold start strategy: + * 'scan': use esbuild to scan for deps in the background, then aggregate + * them with the found deps in the main route once the server is iddle. + * 'dynamic-scan': delay optimization until static imports are crawled, then + * scan with esbuild dynamic import entries found in the source code + * 'pre-scan': pre scan user code with esbuild to find the first batch of + * dependecies to optimize. Only deps found by the scanner are optimized at first. + * 'lazy': only static imports are crawled, leading to the fastest cold start + * experience with the tradeoff of possible full page reload when navigating + * to dynamic routes + * 'eager': both static and dynamic imports are processed on cold start + * completely removing the need for full page reloads at the expense of a + * slower cold start + * + * @default 'scan' + * @experimental + */ + devStrategy?: 'scan' | 'pre-scan' | 'lazy' | 'eager' /** * By default, Vite will crawl your `index.html` to detect dependencies that * need to be pre-bundled. If `build.rollupOptions.input` is specified, Vite @@ -142,23 +161,6 @@ export interface DepOptimizationOptions { * @experimental */ disabled?: boolean | 'build' | 'dev' - /** - * Defines the cold start strategy: - * 'dynamic-scan': delay optimization until static imports are crawled, then - * scan with esbuild dynamic import entries found in the source code - * 'pre-scan': pre scan user code with esbuild to find the first batch of - * dependecies to optimize - * 'lazy': only static imports are crawled, leading to the fastest cold start - * experience with the tradeoff of possible full page reload when navigating - * to dynamic routes - * 'eager': both static and dynamic imports are processed on cold start - * completely removing the need for full page reloads at the expense of a - * slower cold start - * - * @default 'dynamic-scan' - * @experimental - */ - devStrategy?: 'dynamic-scan' | 'pre-scan' | 'lazy' | 'eager' /** * Force dep pre-optimization regardless of whether deps have changed. * @experimental @@ -249,11 +251,15 @@ export async function optimizeDeps( if (cachedMetadata) { return cachedMetadata } - const depsInfo = await discoverProjectDependencies(config) + const deps = await discoverProjectDependencies(config) - const depsString = depsLogString(Object.keys(depsInfo)) + const depsString = depsLogString(Object.keys(deps)) log(colors.green(`Optimizing dependencies:\n ${depsString}`)) + await addManuallyIncludedOptimizeDeps(deps, config) + + const depsInfo = toDiscoveredDependencies(config, deps) + const result = await runOptimizeDeps(config, depsInfo) await result.commit() @@ -299,7 +305,7 @@ export async function optimizeServerSsrDeps( noExternalFilter ) - const depsInfo = toDiscoveredDependencies(config, deps, true) + const depsInfo = toDiscoveredDependencies(config, deps, '', true) const result = await runOptimizeDeps(config, depsInfo, true) @@ -382,9 +388,8 @@ export function loadCachedDepOptimizationMetadata( * find deps to pre-bundle and include user hard-coded dependencies */ export async function discoverProjectDependencies( - config: ResolvedConfig, - timestamp?: string -): Promise> { + config: ResolvedConfig +): Promise> { const { deps, missing } = await scanImports(config) const missingIds = Object.keys(missing) @@ -401,31 +406,14 @@ export async function discoverProjectDependencies( ) } - return initialProjectDependencies(config, timestamp, deps) -} - -/** - * Create the initial discovered deps list. At build time we only - * have the manually included deps. During dev, a scan phase is - * performed and knownDeps is the list of discovered deps - */ -export async function initialProjectDependencies( - config: ResolvedConfig, - timestamp?: string, - knownDeps?: Record -): Promise> { - const deps: Record = knownDeps ?? {} - - await addManuallyIncludedOptimizeDeps(deps, config) - - return toDiscoveredDependencies(config, deps, !!config.build.ssr, timestamp) + return deps } export function toDiscoveredDependencies( config: ResolvedConfig, deps: Record, - ssr: boolean, - timestamp?: string + timestamp: string = '', + ssr: boolean = !!config.build.ssr ): Record { const browserHash = getOptimizedBrowserHash( getDepHash(config), @@ -673,7 +661,7 @@ export async function findKnownImports( return Object.keys(deps) } -async function addManuallyIncludedOptimizeDeps( +export async function addManuallyIncludedOptimizeDeps( deps: Record, config: ResolvedConfig, extra: string[] = [], diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index b75cd992ef2806..b166ac91dd5839 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -7,8 +7,9 @@ import { FS_PREFIX } from '../constants' import { getHash } from '../utils' import { preTransformRequest } from '../server/transformRequest' import type { ResolvedConfig, ViteDevServer } from '..' -import { filterScannableEntries, scanImportsInEntries } from './scan' + import { + addManuallyIncludedOptimizeDeps, addOptimizedDepInfo, createIsOptimizedDepUrl, debuggerViteDeps as debug, @@ -18,13 +19,13 @@ import { extractExportsData, getOptimizedDepPath, initDepsOptimizerMetadata, - initialProjectDependencies, isOptimizedDepFile, loadCachedDepOptimizationMetadata, newDepOptimizationProcessing, optimizeServerSsrDeps, optimizedDepInfoFromId, - runOptimizeDeps + runOptimizeDeps, + toDiscoveredDependencies } from '.' import type { DepOptimizationProcessing, @@ -75,7 +76,8 @@ async function createDepsOptimizer( const isBuild = config.command === 'build' const { devStrategy } = config.optimizeDeps - const preScan = config.command !== 'build' && devStrategy === 'pre-scan' + const preScan = !isBuild && devStrategy === 'pre-scan' + const lazyScan = !isBuild && devStrategy === 'scan' const sessionTimestamp = Date.now().toString() @@ -93,8 +95,7 @@ async function createDepsOptimizer( .filter((rule) => rule.startsWith('!')) .map((rule) => rule.slice(1)) - // Used for devStrategy === 'dynamic-scan' - let discoveredDynamicImports: string[] = [] + let scannedDepsPromise: Promise> | undefined const depsOptimizer: DepsOptimizer = { metadata, @@ -117,14 +118,6 @@ async function createDepsOptimizer( preTransformRequest(url, server, { ssr: false }) } } - // devStrategy === 'dynamic-scan' aggregates the discovered dynamic - // imports and process them with esbuild once the server is iddle and - // before the first optimize batch to pre-discover missing deps - else if (devStrategy === 'dynamic-scan') { - if (!discoveredDynamicImports.includes(id)) { - discoveredDynamicImports.push(id) - } - } // devStrategy === 'lazy' ignores dynamic imports, giving a faster cold // start with potential full page reloads @@ -175,31 +168,24 @@ async function createDepsOptimizer( let firstRunCalled = !!cachedMetadata if (!cachedMetadata) { - if (!preScan) { - // Initialize discovered deps with manually added optimizeDeps.include info - const discovered = await initialProjectDependencies( - config, - sessionTimestamp - ) - for (const depInfo of Object.values(discovered)) { - addOptimizedDepInfo(metadata, 'discovered', { - ...depInfo, - processing: depOptimizationProcessing.promise - }) - } - } else { - // Perform a esbuild base scan of user code to discover dependencies + if (preScan) { + // Perform a esbuild based scan of user code to discover dependencies currentlyProcessing = true const scanPhaseProcessing = newDepOptimizationProcessing() - depsOptimizer.scanProcessing = scanPhaseProcessing.promise + depsOptimizer.preScanning = scanPhaseProcessing.promise setTimeout(async () => { try { - debug(colors.green(`scanning for dependencies...`)) + debug(colors.green(`pre-scanning for dependencies...`)) + + const deps = await discoverProjectDependencies(config) - const discovered = await discoverProjectDependencies( + await addManuallyIncludedOptimizeDeps(deps, config) + + const discovered = toDiscoveredDependencies( config, + deps, sessionTimestamp ) @@ -213,45 +199,63 @@ async function createDepsOptimizer( debug( colors.green( - `dependencies found: ${depsLogString(Object.keys(discovered))}` + `dependencies found by pre-scanner: ${depsLogString( + Object.keys(discovered) + )}` ) ) scanPhaseProcessing.resolve() - depsOptimizer.scanProcessing = undefined + depsOptimizer.preScanning = undefined await runOptimizer() } catch (e) { logger.error(e.message) - if (depsOptimizer.scanProcessing) { + if (depsOptimizer.preScanning) { scanPhaseProcessing.resolve() - depsOptimizer.scanProcessing = undefined + depsOptimizer.preScanning = undefined } } }, 0) + } else { + // Initialize discovered deps with manually added optimizeDeps.include info + + const deps: Record = {} + await addManuallyIncludedOptimizeDeps(deps, config) + + const discovered = await toDiscoveredDependencies( + config, + deps, + sessionTimestamp + ) + + for (const depInfo of Object.values(discovered)) { + addOptimizedDepInfo(metadata, 'discovered', { + ...depInfo, + processing: depOptimizationProcessing.promise + }) + } + + // For build time, or 'lazy' and 'eager' dev, we don't use the scanner + // For 'dynanic-scan' dev, we use the scanner on the discovered dynamic imports + // For 'scan' dev, we use the scanner on the background and use the deps + // found to complete the list once the server is iddle + if (lazyScan) { + debug(colors.green(`scanning for dependencies in the background...`)) + scannedDepsPromise = discoverProjectDependencies(config) + } } } async function runOptimizer() { - if (!firstRunCalled && !isBuild && devStrategy === 'dynamic-scan') { - const entries = filterScannableEntries(discoveredDynamicImports) - discoveredDynamicImports = [] - const optimizeDepsEntries = config.optimizeDeps.entries - if (optimizeDepsEntries) { - const explicitEntries = filterScannableEntries( - await globExplicitEntries(optimizeDepsEntries, config) - ) - for (const explicitEntry of explicitEntries) { - if (!entries.includes(explicitEntry)) { - entries.push(explicitEntry) - } - } - } - if (entries.length) { - const { deps } = await scanImportsInEntries(entries, config) - for (const id of Object.keys(deps)) { + if (!firstRunCalled) { + // Await until the scanner is done + const scannedDeps = await scannedDepsPromise + scannedDepsPromise = undefined + if (scannedDeps) { + for (const id of Object.keys(scannedDeps)) { if (!optimizedDepInfoFromId(metadata, id)) { - registerMissingImport(id, deps[id]) + addMissingDep(id, scannedDeps[id]) } } } @@ -502,14 +506,14 @@ async function createDepsOptimizer( resolved: string, ssr?: boolean ): OptimizedDepInfo { - if (depsOptimizer.scanProcessing) { + if (depsOptimizer.preScanning) { config.logger.error( 'Vite internal error: registering missing import before initial scanning is over' ) } if (!isBuild && ssr) { config.logger.error( - `Error: ${id} is a missing dependency in SSR dev server, it needs to be added to optimizeDeps.include` + 'Vite internal error: ssr dep registerd as a browser dep' ) } const optimized = metadata.optimized[id] @@ -527,7 +531,27 @@ async function createDepsOptimizer( return missing } newDepsDiscovered = true - missing = addOptimizedDepInfo(metadata, 'discovered', { + + missing = addMissingDep(id, resolved, ssr) + + // Until the first optimize run is called, avoid triggering processing + // We'll wait until the user codebase is eagerly processed by Vite so + // we can get a list of every missing dependency before giving to the + // browser a dependency that may be outdated, thus avoiding full page reloads + + if (preScan || firstRunCalled) { + // Debounced rerun, let other missing dependencies be discovered before + // the running next optimizeDeps + debouncedProcessing() + } + + // Return the path for the optimized bundle, this path is known before + // esbuild is run to generate the pre-bundle + return missing + } + + function addMissingDep(id: string, resolved: string, ssr?: boolean) { + return addOptimizedDepInfo(metadata, 'discovered', { id, file: getOptimizedDepPath(id, config, ssr), src: resolved, @@ -545,21 +569,6 @@ async function createDepsOptimizer( processing: depOptimizationProcessing.promise, exportsData: extractExportsData(resolved, config) }) - - // Until the first optimize run is called, avoid triggering processing - // We'll wait until the user codebase is eagerly processed by Vite so - // we can get a list of every missing dependency before giving to the - // browser a dependency that may be outdated, thus avoiding full page reloads - - if (preScan || firstRunCalled) { - // Debounced rerun, let other missing dependencies be discovered before - // the running next optimizeDeps - debouncedProcessing() - } - - // Return the path for the optimized bundle, this path is known before - // esbuild is run to generate the pre-bundle - return missing } function debouncedProcessing(timeout = debounceMs) { diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index d2555bc40c5fc6..f45abd179a3fe9 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -266,7 +266,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { let importerFile = importer if (moduleListContains(config.optimizeDeps?.exclude, url)) { if (depsOptimizer) { - await depsOptimizer.scanProcessing + await depsOptimizer.preScanning // if the dependency encountered in the optimized file was excluded from the optimization // the dependency needs to be resolved starting from the original source location of the optimized file diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index 613d7ca5a08503..ebef2984d363b3 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -164,7 +164,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { if (moduleListContains(config.optimizeDeps?.exclude, url)) { if (depsOptimizer) { - await depsOptimizer.scanProcessing + await depsOptimizer.preScanning // if the dependency encountered in the optimized file was excluded from the optimization // the dependency needs to be resolved starting from the original source location of the optimized file diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 4967b7975c9a54..e6c5ca97e4ab70 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -719,7 +719,7 @@ export async function tryOptimizedResolve( id: string, importer?: string ): Promise { - await depsOptimizer.scanProcessing + await depsOptimizer.preScanning const metadata = depsOptimizer.metadata diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 055e49d89a19e3..5871666e2ced43 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -777,7 +777,7 @@ async function updateCjsSsrExternals(server: ViteDevServer) { const depsOptimizer = getDepsOptimizer(server.config, { ssr: false }) if (depsOptimizer) { - await depsOptimizer.scanProcessing + await depsOptimizer.preScanning knownImports = [ ...Object.keys(depsOptimizer.metadata.optimized), ...Object.keys(depsOptimizer.metadata.discovered) From a7cf18f9c70aa483b52c7240fa8ce1d65a00d148 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 2 Jul 2022 15:11:32 +0200 Subject: [PATCH 06/30] feat: scan strategy, fix alias issue by making it the default --- packages/vite/src/node/optimizer/index.ts | 26 +- packages/vite/src/node/optimizer/optimizer.ts | 305 +++++++++++------- .../vite/src/node/plugins/importAnalysis.ts | 3 +- .../src/node/plugins/importAnalysisBuild.ts | 3 +- packages/vite/src/node/plugins/resolve.ts | 6 +- packages/vite/src/node/server/index.ts | 2 +- .../vite/src/node/server/transformRequest.ts | 4 +- 7 files changed, 208 insertions(+), 141 deletions(-) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 976dc6783afb66..5715207c627bfd 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -52,7 +52,7 @@ export type ExportsData = { export interface DepsOptimizer { metadata: DepOptimizationMetadata - preScanning?: Promise + scanning?: Promise registerMissingImport: ( id: string, resolved: string, @@ -75,12 +75,11 @@ export interface DepsOptimizer { export interface DepOptimizationOptions { /** * Defines the cold start strategy: - * 'scan': use esbuild to scan for deps in the background, then aggregate - * them with the found deps in the main route once the server is iddle. - * 'dynamic-scan': delay optimization until static imports are crawled, then - * scan with esbuild dynamic import entries found in the source code - * 'pre-scan': pre scan user code with esbuild to find the first batch of - * dependecies to optimize. Only deps found by the scanner are optimized at first. + * 'scan': use esbuild to scan for deps in the background and optimize them. + * Await until the server is iddle so we also get the list of deps found while + * crawling static imports. Use the optimization result if every dep has already + * been optimized. If there are new dependencies, trigger a new optimization + * step discarding the previous optimization result. * 'lazy': only static imports are crawled, leading to the fastest cold start * experience with the tradeoff of possible full page reload when navigating * to dynamic routes @@ -91,7 +90,7 @@ export interface DepOptimizationOptions { * @default 'scan' * @experimental */ - devStrategy?: 'scan' | 'pre-scan' | 'lazy' | 'eager' + devStrategy?: 'scan' | 'lazy' | 'eager' /** * By default, Vite will crawl your `index.html` to detect dependencies that * need to be pre-bundled. If `build.rollupOptions.input` is specified, Vite @@ -436,7 +435,7 @@ export function toDiscoveredDependencies( export function depsLogString(qualifiedIds: string[]): string { if (isDebugEnabled) { - return colors.yellow(qualifiedIds.join(`\n `)) + return colors.yellow(qualifiedIds.join(`, `)) } else { const total = qualifiedIds.length const maxListed = 5 @@ -499,6 +498,9 @@ export async function runOptimizeDeps( const processingResult: DepOptimizationResult = { metadata, async commit() { + // We let the optimizeDeps caller modify the browserHash of dependencies before committing + commitMetadata() + // Write metadata file, delete `deps` folder and rename the `processing` folder to `deps` // Processing is done, we can now replace the depsCacheDir with processingCacheDir // Rewire the file paths from the temporal processing dir to the final deps cache dir @@ -645,8 +647,10 @@ export async function runOptimizeDeps( } } - const dataPath = path.join(processingCacheDir, '_metadata.json') - writeFile(dataPath, stringifyDepsOptimizerMetadata(metadata, depsCacheDir)) + function commitMetadata() { + const dataPath = path.join(processingCacheDir, '_metadata.json') + writeFile(dataPath, stringifyDepsOptimizerMetadata(metadata, depsCacheDir)) + } debug(`deps bundled in ${(performance.now() - start).toFixed(2)}ms`) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index b166ac91dd5839..b2c3f22dfca721 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -23,12 +23,12 @@ import { loadCachedDepOptimizationMetadata, newDepOptimizationProcessing, optimizeServerSsrDeps, - optimizedDepInfoFromId, runOptimizeDeps, toDiscoveredDependencies } from '.' import type { DepOptimizationProcessing, + DepOptimizationResult, DepsOptimizer, OptimizedDepInfo } from '.' @@ -76,8 +76,7 @@ async function createDepsOptimizer( const isBuild = config.command === 'build' const { devStrategy } = config.optimizeDeps - const preScan = !isBuild && devStrategy === 'pre-scan' - const lazyScan = !isBuild && devStrategy === 'scan' + const scan = !isBuild && devStrategy === 'scan' const sessionTimestamp = Date.now().toString() @@ -95,8 +94,6 @@ async function createDepsOptimizer( .filter((rule) => rule.startsWith('!')) .map((rule) => rule.slice(1)) - let scannedDepsPromise: Promise> | undefined - const depsOptimizer: DepsOptimizer = { metadata, registerMissingImport, @@ -120,9 +117,6 @@ async function createDepsOptimizer( } // devStrategy === 'lazy' ignores dynamic imports, giving a faster cold // start with potential full page reloads - - // for devStrategy === 'pre-scan', dynamic imports where already pre-scanned - // by esbuild so there is also nothing to do here } }, options: config.optimizeDeps @@ -167,116 +161,100 @@ async function createDepsOptimizer( // If there wasn't a cache or it is outdated, we need to prepare a first run let firstRunCalled = !!cachedMetadata + let postScanOptimizationResult: Promise | undefined + if (!cachedMetadata) { - if (preScan) { - // Perform a esbuild based scan of user code to discover dependencies - currentlyProcessing = true + // Enter processing state until crawl of static imports ends + currentlyProcessing = true + + // Initialize discovered deps with manually added optimizeDeps.include info + + const deps: Record = {} + await addManuallyIncludedOptimizeDeps(deps, config) + + const discovered = await toDiscoveredDependencies( + config, + deps, + sessionTimestamp + ) + for (const depInfo of Object.values(discovered)) { + addOptimizedDepInfo(metadata, 'discovered', { + ...depInfo, + processing: depOptimizationProcessing.promise + }) + } + + // For 'scan' dev, we run the scanner and the first optimization + // run on the background, but we wait until crawling has ended + // to decide if we send this result to the browser or we need to + // do another optimize step + if (scan) { const scanPhaseProcessing = newDepOptimizationProcessing() - depsOptimizer.preScanning = scanPhaseProcessing.promise + depsOptimizer.scanning = scanPhaseProcessing.promise setTimeout(async () => { try { - debug(colors.green(`pre-scanning for dependencies...`)) + debug(colors.green(`scanning for dependencies...`)) const deps = await discoverProjectDependencies(config) - await addManuallyIncludedOptimizeDeps(deps, config) + // Add these dependencies to the discovered list, as these are currently + // used by the preAliasPlugin to support aliased and optimized deps. + // This is also used by the CJS externalization heuristics in legacy mode + for (const id of Object.keys(deps)) { + if (!metadata.discovered[id]) { + addMissingDep(id, deps[id]) + } + } - const discovered = toDiscoveredDependencies( + const discoveredByScanner = toDiscoveredDependencies( config, deps, sessionTimestamp ) - // Respect the scan phase discover order to improve reproducibility - for (const depInfo of Object.values(discovered)) { - addOptimizedDepInfo(metadata, 'discovered', { - ...depInfo, - processing: depOptimizationProcessing.promise - }) - } - debug( colors.green( - `dependencies found by pre-scanner: ${depsLogString( - Object.keys(discovered) + `dependencies found by scanner: ${depsLogString( + Object.keys(discoveredByScanner) )}` ) ) - scanPhaseProcessing.resolve() - depsOptimizer.preScanning = undefined - - await runOptimizer() + setTimeout(() => { + try { + postScanOptimizationResult = runOptimizeDeps( + config, + discoveredByScanner + ) + } catch (e) { + logger.error(e.message) + } + }, 0) } catch (e) { logger.error(e.message) - if (depsOptimizer.preScanning) { - scanPhaseProcessing.resolve() - depsOptimizer.preScanning = undefined - } + } finally { + scanPhaseProcessing.resolve() + depsOptimizer.scanning = undefined } }, 0) - } else { - // Initialize discovered deps with manually added optimizeDeps.include info - - const deps: Record = {} - await addManuallyIncludedOptimizeDeps(deps, config) - - const discovered = await toDiscoveredDependencies( - config, - deps, - sessionTimestamp - ) - - for (const depInfo of Object.values(discovered)) { - addOptimizedDepInfo(metadata, 'discovered', { - ...depInfo, - processing: depOptimizationProcessing.promise - }) - } - - // For build time, or 'lazy' and 'eager' dev, we don't use the scanner - // For 'dynanic-scan' dev, we use the scanner on the discovered dynamic imports - // For 'scan' dev, we use the scanner on the background and use the deps - // found to complete the list once the server is iddle - if (lazyScan) { - debug(colors.green(`scanning for dependencies in the background...`)) - scannedDepsPromise = discoverProjectDependencies(config) - } } } - async function runOptimizer() { - if (!firstRunCalled) { - // Await until the scanner is done - const scannedDeps = await scannedDepsPromise - scannedDepsPromise = undefined - if (scannedDeps) { - for (const id of Object.keys(scannedDeps)) { - if (!optimizedDepInfoFromId(metadata, id)) { - addMissingDep(id, scannedDeps[id]) - } - } - } - } - - const isRerun = firstRunCalled - firstRunCalled = true - - // Ensure that rerun is called sequentially - enqueuedRerun = undefined - - // Ensure that a rerun will not be issued for current discovered deps - if (handle) clearTimeout(handle) + async function startNextDiscoveredBatch() { + newDepsDiscovered = false - if (Object.keys(metadata.discovered).length === 0) { - currentlyProcessing = false - return - } + // Add the current depOptimizationProcessing to the queue, these + // promises are going to be resolved once a rerun is committed + depOptimizationProcessingQueue.push(depOptimizationProcessing) - currentlyProcessing = true + // Create a new promise for the next rerun, discovered missing + // dependencies will be asigned this promise from this point + depOptimizationProcessing = newDepOptimizationProcessing() + } + async function optimizeNewDeps() { // a succesful completion of the optimizeDeps rerun will end up // creating new bundled version of all current and discovered deps // in the cache dir and a new metadata info object assigned @@ -301,36 +279,37 @@ async function createDepsOptimizer( newDeps[dep] = info } - newDepsDiscovered = false + startNextDiscoveredBatch() - // Add the current depOptimizationProcessing to the queue, these - // promises are going to be resolved once a rerun is committed - depOptimizationProcessingQueue.push(depOptimizationProcessing) + return await runOptimizeDeps(config, newDeps) + } - // Create a new promise for the next rerun, discovered missing - // dependencies will be asigned this promise from this point - depOptimizationProcessing = newDepOptimizationProcessing() + async function runOptimizer(preRunResult?: DepOptimizationResult) { + const isRerun = firstRunCalled + firstRunCalled = true + + // Ensure that rerun is called sequentially + enqueuedRerun = undefined + + // Ensure that a rerun will not be issued for current discovered deps + if (handle) clearTimeout(handle) + + if (Object.keys(metadata.discovered).length === 0) { + currentlyProcessing = false + return + } + + currentlyProcessing = true try { - const processingResult = await runOptimizeDeps(config, newDeps) + const processingResult = preRunResult ?? (await optimizeNewDeps()) const newData = processingResult.metadata - const needsInteropMismatch = [] - for (const dep in metadata.discovered) { - const discoveredDepInfo = metadata.discovered[dep] - const depInfo = newData.optimized[dep] - if (depInfo) { - if ( - discoveredDepInfo.needsInterop !== undefined && - depInfo.needsInterop !== discoveredDepInfo.needsInterop - ) { - // This only happens when a discovered dependency has mixed ESM and CJS syntax - // and it hasn't been manually added to optimizeDeps.needsInterop - needsInteropMismatch.push(dep) - } - } - } + const needsInteropMismatch = findInteropMismatches( + metadata.discovered, + newData.optimized + ) // After a re-optimization, if the internal bundled chunks change a full page reload // is required. If the files are stable, we can avoid the reload that is expensive @@ -405,7 +384,15 @@ async function createDepsOptimizer( logNewlyDiscoveredDeps() }, 2 * debounceMs) } else { - debug(colors.green(`✨ optimized dependencies unchanged`)) + debug( + colors.green( + `✨ ${ + !isRerun + ? `dependencies optimized` + : `optimized dependencies unchanged` + }` + ) + ) } } else { if (newDepsDiscovered) { @@ -506,11 +493,6 @@ async function createDepsOptimizer( resolved: string, ssr?: boolean ): OptimizedDepInfo { - if (depsOptimizer.preScanning) { - config.logger.error( - 'Vite internal error: registering missing import before initial scanning is over' - ) - } if (!isBuild && ssr) { config.logger.error( 'Vite internal error: ssr dep registerd as a browser dep' @@ -539,7 +521,7 @@ async function createDepsOptimizer( // we can get a list of every missing dependency before giving to the // browser a dependency that may be outdated, thus avoiding full page reloads - if (preScan || firstRunCalled) { + if (firstRunCalled) { // Debounced rerun, let other missing dependencies be discovered before // the running next optimizeDeps debouncedProcessing() @@ -587,6 +569,64 @@ async function createDepsOptimizer( }, timeout) } + async function onCrawlEnd() { + if (firstRunCalled) { + return + } + + currentlyProcessing = false + + if (scan) { + // Await for the scan+optimize step running in the background + // It normally should be over by the time crawling of user code ended + await depsOptimizer.scanning + const result = (await postScanOptimizationResult) as DepOptimizationResult + + const scanDeps = Object.keys(result.metadata.optimized) + const crawlDeps = Object.keys(metadata.discovered) + + const needsInteropMismatch = findInteropMismatches( + metadata.discovered, + result.metadata.optimized + ) + const scannerMissedDeps = crawlDeps.some((dep) => !scanDeps.includes(dep)) + const outdatedResult = + needsInteropMismatch.length > 0 || scannerMissedDeps + + if (outdatedResult) { + // Drop this scan result, and perform a new optimization to avoid a full reload + result.cancel() + + // Add deps found by the scanner to the discovered deps while crawling + for (const dep of scanDeps) { + if (!crawlDeps.includes(dep)) { + addMissingDep(dep, result.metadata.optimized[dep].src!) + } + } + if (scannerMissedDeps) { + debug( + colors.yellow( + `✨ new dependencies were found while crawling that weren't detected by the scanner` + ) + ) + } + debug(colors.green(`✨ re-running optimizer`)) + debouncedProcessing(0) + } else { + debug( + colors.green( + `✨ using post-scan optimizer result, the scanner found every needed deps` + ) + ) + startNextDiscoveredBatch() + runOptimizer(result) + } + } else { + // 'lazy' and 'eager', queue the first optimizer run + debouncedProcessing(0) + } + } + const runOptimizerIfIdleAfterMs = 100 let registeredIds: { id: string; done: () => Promise }[] = [] @@ -609,8 +649,8 @@ async function createDepsOptimizer( function ensureFirstRun() { if (!firstRunEnsured && !firstRunCalled && registeredIds.length === 0) { setTimeout(() => { - if (!firstRunCalled && registeredIds.length === 0) { - debouncedProcessing(0) // queue the optimizer run + if (registeredIds.length === 0) { + onCrawlEnd() } }, runOptimizerIfIdleAfterMs) } @@ -647,11 +687,11 @@ async function createDepsOptimizer( waitingOn = next.id const afterLoad = () => { waitingOn = undefined - if (!firstRunCalled && !workersSources.has(next.id)) { + if (!workersSources.has(next.id)) { if (registeredIds.length > 0) { runOptimizerWhenIdle() } else { - debouncedProcessing(0) // queue the optimizer run + onCrawlEnd() } } } @@ -727,3 +767,26 @@ async function globExplicitEntries( absolute: true }) } + +function findInteropMismatches( + discovered: Record, + optimized: Record +) { + const needsInteropMismatch = [] + for (const dep in discovered) { + const discoveredDepInfo = discovered[dep] + const depInfo = optimized[dep] + if (depInfo) { + if ( + discoveredDepInfo.needsInterop !== undefined && + depInfo.needsInterop !== discoveredDepInfo.needsInterop + ) { + // This only happens when a discovered dependency has mixed ESM and CJS syntax + // and it hasn't been manually added to optimizeDeps.needsInterop + needsInteropMismatch.push(dep) + debug(colors.cyan(`✨ needsInterop mismatch detected for ${dep}`)) + } + } + } + return needsInteropMismatch +} diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index f45abd179a3fe9..98ad3bb95a77dc 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -266,8 +266,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { let importerFile = importer if (moduleListContains(config.optimizeDeps?.exclude, url)) { if (depsOptimizer) { - await depsOptimizer.preScanning - + await depsOptimizer.scanning // if the dependency encountered in the optimized file was excluded from the optimization // the dependency needs to be resolved starting from the original source location of the optimized file // because starting from node_modules/.vite will not find the dependency if it was not hoisted diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index ebef2984d363b3..f3f310bcfc6034 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -164,8 +164,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { if (moduleListContains(config.optimizeDeps?.exclude, url)) { if (depsOptimizer) { - await depsOptimizer.preScanning - + await depsOptimizer.scanning // if the dependency encountered in the optimized file was excluded from the optimization // the dependency needs to be resolved starting from the original source location of the optimized file // because starting from node_modules/.vite will not find the dependency if it was not hoisted diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index e6c5ca97e4ab70..ea230153d33df0 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -719,7 +719,11 @@ export async function tryOptimizedResolve( id: string, importer?: string ): Promise { - await depsOptimizer.preScanning + // TODO: we need to wait until scanning is done here as this function + // is used in the preAliasPlugin to decide if an aliased dep is optimized, + // and avoid replacing the bare import with the resolved path. + // We should be able to remove this in the future + await depsOptimizer.scanning const metadata = depsOptimizer.metadata diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 5871666e2ced43..eb6622ba2de13f 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -777,7 +777,7 @@ async function updateCjsSsrExternals(server: ViteDevServer) { const depsOptimizer = getDepsOptimizer(server.config, { ssr: false }) if (depsOptimizer) { - await depsOptimizer.preScanning + await depsOptimizer.scanning knownImports = [ ...Object.keys(depsOptimizer.metadata.optimized), ...Object.keys(depsOptimizer.metadata.discovered) diff --git a/packages/vite/src/node/server/transformRequest.ts b/packages/vite/src/node/server/transformRequest.ts index b1660161c1d8a5..94a3bd028457eb 100644 --- a/packages/vite/src/node/server/transformRequest.ts +++ b/packages/vite/src/node/server/transformRequest.ts @@ -146,9 +146,7 @@ async function doTransform( const result = loadAndTransform(id, url, server, options, timestamp) - if (config.optimizeDeps.devStrategy !== 'pre-scan') { - getDepsOptimizer(config, { ssr })?.delayDepsOptimizerUntil(id, () => result) - } + getDepsOptimizer(config, { ssr })?.delayDepsOptimizerUntil(id, () => result) return result } From d7da60e45e5e5abda7a70a664d736f037a05536c Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 2 Jul 2022 15:17:29 +0200 Subject: [PATCH 07/30] chore: remove unneeded changes to scan.ts --- packages/vite/src/node/optimizer/scan.ts | 28 +++++++----------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts index 09d28da847c3c8..f848af4af03869 100644 --- a/packages/vite/src/node/optimizer/scan.ts +++ b/packages/vite/src/node/optimizer/scan.ts @@ -44,6 +44,8 @@ export async function scanImports(config: ResolvedConfig): Promise<{ deps: Record missing: Record }> { + const start = performance.now() + let entries: string[] = [] const explicitEntryPatterns = config.optimizeDeps.entries @@ -68,7 +70,9 @@ export async function scanImports(config: ResolvedConfig): Promise<{ // Non-supported entry file types and virtual files should not be scanned for // dependencies. - entries = filterScannableEntries(entries) + entries = entries.filter( + (entry) => isScannable(entry) && fs.existsSync(entry) + ) if (!entries.length) { if (!explicitEntryPatterns && !config.optimizeDeps.include) { @@ -81,26 +85,10 @@ export async function scanImports(config: ResolvedConfig): Promise<{ ) } return { deps: {}, missing: {} } + } else { + debug(`Crawling dependencies using entries:\n ${entries.join('\n ')}`) } - return scanImportsInEntries(entries, config) -} - -export function filterScannableEntries(entries: string[]): string[] { - return entries.filter((entry) => isScannable(entry) && fs.existsSync(entry)) -} - -export async function scanImportsInEntries( - entries: string[], - config: ResolvedConfig -): Promise<{ - deps: Record - missing: Record -}> { - const start = performance.now() - - debug(`Crawling dependencies using entries:\n ${entries.join('\n ')}`) - const deps: Record = {} const missing: Record = {} const container = await createPluginContainer(config) @@ -140,7 +128,7 @@ function orderedDependencies(deps: Record) { return Object.fromEntries(depsList) } -export async function globEntries( +export function globEntries( pattern: string | string[], config: ResolvedConfig ): Promise { From 6d288faf8b4e80e14bb8fac227f83d315982c709 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 2 Jul 2022 15:35:07 +0200 Subject: [PATCH 08/30] fix: use scanner during build time, remove devStrategy option --- docs/guide/migration.md | 4 - packages/vite/src/node/config.ts | 1 - packages/vite/src/node/optimizer/index.ts | 19 --- packages/vite/src/node/optimizer/optimizer.ts | 159 +++++------------- .../vite/src/node/plugins/importAnalysis.ts | 2 - 5 files changed, 44 insertions(+), 141 deletions(-) diff --git a/docs/guide/migration.md b/docs/guide/migration.md index 80869ce104b10f..f0474bcacc34fa 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -45,10 +45,6 @@ Vite's default dev server port is now 5173. You can use [`server.port`](../confi Vite's default dev server host is now `localhost`. You can use [`server.host`](../config/server-options.md#server-host) to set it to `127.0.0.1`. -Vite optimizes dependencies with esbuild to both convert CJS-only deps to ESM and to reduce the number of modules the browser needs to request. In v3, the default strategy to discover and batch dependencies has changed. Vite no longer pre-scans user code with esbuild to get an initial list of dependencies on cold start. Instead, it delays the first dependency optimization run until every imported user module on load is processed. - -To get back the v2 strategy, you can use `optimizeDeps.devStrategy: 'pre-scan'`. - ### Build Changes In v3, Vite uses esbuild to optimize dependencies by default. Doing so, it removes one of the most significant differences between dev and prod present in v2. Because esbuild converts CJS-only dependencies to ESM, [`@rollupjs/plugin-commonjs`](https://github.com/rollup/plugins/tree/master/packages/commonjs) is no longer used. diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index c73a995d361bc1..2cf9551370a7f4 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -610,7 +610,6 @@ export async function resolveConfig( packageCache: new Map(), createResolver, optimizeDeps: { - devStrategy: 'scan', ...optimizeDeps, esbuildOptions: { preserveSymlinks: config.resolve?.preserveSymlinks, diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 5715207c627bfd..7f02ac23790ff4 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -63,7 +63,6 @@ export interface DepsOptimizer { isOptimizedDepFile: (id: string) => boolean isOptimizedDepUrl: (url: string) => boolean getOptimizedDepId: (depInfo: OptimizedDepInfo) => string - registerDynamicImport: (importInfo: { id: string; url: string }) => void delayDepsOptimizerUntil: (id: string, done: () => Promise) => void registerWorkersSource: (id: string) => void resetRegisteredIds: () => void @@ -73,24 +72,6 @@ export interface DepsOptimizer { } export interface DepOptimizationOptions { - /** - * Defines the cold start strategy: - * 'scan': use esbuild to scan for deps in the background and optimize them. - * Await until the server is iddle so we also get the list of deps found while - * crawling static imports. Use the optimization result if every dep has already - * been optimized. If there are new dependencies, trigger a new optimization - * step discarding the previous optimization result. - * 'lazy': only static imports are crawled, leading to the fastest cold start - * experience with the tradeoff of possible full page reload when navigating - * to dynamic routes - * 'eager': both static and dynamic imports are processed on cold start - * completely removing the need for full page reloads at the expense of a - * slower cold start - * - * @default 'scan' - * @experimental - */ - devStrategy?: 'scan' | 'lazy' | 'eager' /** * By default, Vite will crawl your `index.html` to detect dependencies that * need to be pre-bundled. If `build.rollupOptions.input` is specified, Vite diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index b2c3f22dfca721..b9da9323c4056d 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -1,11 +1,6 @@ -import path from 'node:path' import colors from 'picocolors' import _debug from 'debug' -import glob from 'fast-glob' -import micromatch from 'micromatch' -import { FS_PREFIX } from '../constants' import { getHash } from '../utils' -import { preTransformRequest } from '../server/transformRequest' import type { ResolvedConfig, ViteDevServer } from '..' import { @@ -75,9 +70,6 @@ async function createDepsOptimizer( const { logger } = config const isBuild = config.command === 'build' - const { devStrategy } = config.optimizeDeps - const scan = !isBuild && devStrategy === 'scan' - const sessionTimestamp = Date.now().toString() const cachedMetadata = loadCachedDepOptimizationMetadata(config) @@ -87,13 +79,6 @@ async function createDepsOptimizer( let metadata = cachedMetadata || initDepsOptimizerMetadata(config, sessionTimestamp) - const { entries } = config.optimizeDeps - const optOutEntries = ( - entries ? (Array.isArray(entries) ? entries : [entries]) : [] - ) - .filter((rule) => rule.startsWith('!')) - .map((rule) => rule.slice(1)) - const depsOptimizer: DepsOptimizer = { metadata, registerMissingImport, @@ -106,19 +91,6 @@ async function createDepsOptimizer( delayDepsOptimizerUntil, resetRegisteredIds, ensureFirstRun, - registerDynamicImport: ({ id, url }) => { - if (!firstRunCalled && server) { - // devStrategy === 'eager' process all dynamic imports with the real - // plugins during cold start - if (devStrategy === 'eager') { - if (!micromatch.isMatch(id, optOutEntries)) { - preTransformRequest(url, server, { ssr: false }) - } - } - // devStrategy === 'lazy' ignores dynamic imports, giving a faster cold - // start with potential full page reloads - } - }, options: config.optimizeDeps } @@ -130,13 +102,10 @@ async function createDepsOptimizer( let newDepsToLogHandle: NodeJS.Timeout | undefined const logNewlyDiscoveredDeps = () => { if (newDepsToLog.length) { + const depsString = depsLogString(newDepsToLog) config.logger.info( - colors.green( - `✨ new dependencies optimized: ${depsLogString(newDepsToLog)}` - ), - { - timestamp: true - } + colors.green(`✨ new dependencies optimized: ${depsString}`), + { timestamp: true } ) newDepsToLog = [] } @@ -155,9 +124,6 @@ async function createDepsOptimizer( let enqueuedRerun: (() => void) | undefined let currentlyProcessing = false - // Only pretransform optimizeDeps.entries on cold start - let optimizeDepsEntriesVisited = !!cachedMetadata - // If there wasn't a cache or it is outdated, we need to prepare a first run let firstRunCalled = !!cachedMetadata @@ -185,44 +151,42 @@ async function createDepsOptimizer( }) } - // For 'scan' dev, we run the scanner and the first optimization - // run on the background, but we wait until crawling has ended - // to decide if we send this result to the browser or we need to - // do another optimize step - if (scan) { - const scanPhaseProcessing = newDepOptimizationProcessing() - depsOptimizer.scanning = scanPhaseProcessing.promise - - setTimeout(async () => { - try { - debug(colors.green(`scanning for dependencies...`)) - - const deps = await discoverProjectDependencies(config) - - // Add these dependencies to the discovered list, as these are currently - // used by the preAliasPlugin to support aliased and optimized deps. - // This is also used by the CJS externalization heuristics in legacy mode - for (const id of Object.keys(deps)) { - if (!metadata.discovered[id]) { - addMissingDep(id, deps[id]) - } - } + // TODO: We need the scan during build time, until preAliasPlugin + // is refactored to work without the scanned deps. We could skip + // this for build later. - const discoveredByScanner = toDiscoveredDependencies( - config, - deps, - sessionTimestamp - ) + const scanPhaseProcessing = newDepOptimizationProcessing() + depsOptimizer.scanning = scanPhaseProcessing.promise - debug( - colors.green( - `dependencies found by scanner: ${depsLogString( - Object.keys(discoveredByScanner) - )}` - ) - ) + setTimeout(async () => { + try { + debug(colors.green(`scanning for dependencies...`)) + + const deps = await discoverProjectDependencies(config) + const depsString = depsLogString(Object.keys(deps)) + debug(colors.green(`dependencies found by scanner: ${depsString}`)) + + // Add these dependencies to the discovered list, as these are currently + // used by the preAliasPlugin to support aliased and optimized deps. + // This is also used by the CJS externalization heuristics in legacy mode + for (const id of Object.keys(deps)) { + if (!metadata.discovered[id]) { + addMissingDep(id, deps[id]) + } + } + + if (!isBuild) { + // For dev, we run the scanner and the first optimization + // run on the background, but we wait until crawling has ended + // to decide if we send this result to the browser or we need to + // do another optimize step setTimeout(() => { + const discoveredByScanner = toDiscoveredDependencies( + config, + deps, + sessionTimestamp + ) try { postScanOptimizationResult = runOptimizeDeps( config, @@ -232,14 +196,14 @@ async function createDepsOptimizer( logger.error(e.message) } }, 0) - } catch (e) { - logger.error(e.message) - } finally { - scanPhaseProcessing.resolve() - depsOptimizer.scanning = undefined } - }, 0) - } + } catch (e) { + logger.error(e.message) + } finally { + scanPhaseProcessing.resolve() + depsOptimizer.scanning = undefined + } + }, 0) } async function startNextDiscoveredBatch() { @@ -576,7 +540,7 @@ async function createDepsOptimizer( currentlyProcessing = false - if (scan) { + if (!isBuild) { // Await for the scan+optimize step running in the background // It normally should be over by the time crawling of user code ended await depsOptimizer.scanning @@ -622,7 +586,7 @@ async function createDepsOptimizer( runOptimizer(result) } } else { - // 'lazy' and 'eager', queue the first optimizer run + // queue the first optimizer run debouncedProcessing(0) } } @@ -674,10 +638,6 @@ async function createDepsOptimizer( registeredIds.push({ id, done }) runOptimizerWhenIdle() } - if (server && !optimizeDepsEntriesVisited) { - optimizeDepsEntriesVisited = true - preTransformOptimizeDepsEntries(server) - } } function runOptimizerWhenIdle() { @@ -725,7 +685,6 @@ async function createDevSsrDepsOptimizer( 'Vite Internal Error: registerMissingImport is not supported in dev SSR' ) }, - registerDynamicImport: () => {}, // noop, there is no scanning during dev SSR // the optimizer blocks the server start run: () => {}, @@ -738,36 +697,6 @@ async function createDevSsrDepsOptimizer( devSsrDepsOptimizerMap.set(config, depsOptimizer) } -export async function preTransformOptimizeDepsEntries( - server: ViteDevServer -): Promise { - const { config } = server - const { entries } = config.optimizeDeps - if (entries) { - const explicitEntries = await globExplicitEntries(entries, config) - // TODO: should we restrict the entries to JS and HTML like the - // scanner did? I think we can let the user chose any entry - for (const entry of explicitEntries) { - const url = entry.startsWith(config.root + '/') - ? entry.slice(config.root.length) - : path.posix.join(FS_PREFIX + entry) - - preTransformRequest(url, server, { ssr: false }) - } - } -} - -async function globExplicitEntries( - entries: string | string[], - config: ResolvedConfig -): Promise { - return await glob(entries, { - cwd: config.root, - ignore: ['**/node_modules/**', `**/${config.build.outDir}/**`], - absolute: true - }) -} - function findInteropMismatches( discovered: Record, optimized: Record diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index 98ad3bb95a77dc..ce2e981ec01a3d 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -536,8 +536,6 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { } if (!isDynamicImport) { importedUrlsToPreTransform.add(importedUrl) - } else { - depsOptimizer?.registerDynamicImport(importedUrl) } } else if (!importer.startsWith(clientDir) && !ssr) { // check @vite-ignore which suppresses dynamic import warning From 39f98589a3ee087de48c5dd6beeaf7d282fb9280 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 2 Jul 2022 16:17:09 +0200 Subject: [PATCH 09/30] chore: update --- packages/vite/src/node/optimizer/optimizer.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index b9da9323c4056d..4c411bd331cebe 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -540,11 +540,12 @@ async function createDepsOptimizer( currentlyProcessing = false - if (!isBuild) { + if (!isBuild && postScanOptimizationResult) { // Await for the scan+optimize step running in the background // It normally should be over by the time crawling of user code ended await depsOptimizer.scanning - const result = (await postScanOptimizationResult) as DepOptimizationResult + const result = await postScanOptimizationResult + postScanOptimizationResult = undefined const scanDeps = Object.keys(result.metadata.optimized) const crawlDeps = Object.keys(metadata.discovered) From 7ad496276ae9f570e5ab5d5aae896928fab811ea Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 2 Jul 2022 16:56:10 +0200 Subject: [PATCH 10/30] fix: preAliasPlugin during build --- packages/vite/src/node/plugins/index.ts | 2 +- playground/alias/index.html | 4 ++++ playground/alias/package.json | 3 ++- playground/alias/vite.config.js | 5 +++++ pnpm-lock.yaml | 2 ++ 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 9e01808139c4af..0bab69f50eeb98 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -41,7 +41,7 @@ export async function resolvePlugins( return [ isWatch ? ensureWatchPlugin() : null, isBuild ? metadataPlugin() : null, - isBuild ? null : preAliasPlugin(config), + preAliasPlugin(config), aliasPlugin({ entries: config.resolve.alias }), ...prePlugins, config.build.polyfillModulePreload diff --git a/playground/alias/index.html b/playground/alias/index.html index 947db06127ed41..909aaf6ef5411e 100644 --- a/playground/alias/index.html +++ b/playground/alias/index.html @@ -42,6 +42,10 @@

Alias

} } }).mount('.optimized') + + // aliased to an absolute URL in CJS, should be optimized + import { isFunction } from '@vue/shared' + console.log(isFunction(() => {})) diff --git a/playground/alias/package.json b/playground/alias/package.json index ee345bf1f9ce13..4b49ca8d346ebf 100644 --- a/playground/alias/package.json +++ b/playground/alias/package.json @@ -10,7 +10,8 @@ }, "dependencies": { "aliased-module": "file:./dir/module", - "vue": "^3.2.37" + "vue": "^3.2.37", + "@vue/shared": "^3.2.37" }, "devDependencies": { "resolve-linked": "workspace:*" diff --git a/playground/alias/vite.config.js b/playground/alias/vite.config.js index 530fe86a3de2cb..634871877a0f56 100644 --- a/playground/alias/vite.config.js +++ b/playground/alias/vite.config.js @@ -16,6 +16,11 @@ module.exports = { { find: '/@', replacement: path.resolve(__dirname, 'dir') }, // aliasing an optimized dep { find: 'vue', replacement: 'vue/dist/vue.esm-bundler.js' }, + // aliasing an optimized dep to absolute URL + { + find: '@vue/shared', + replacement: require.resolve('@vue/shared/dist/shared.cjs.prod.js') + }, // aliasing one unoptimized dep to an optimized dep { find: 'foo', replacement: 'vue' }, { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16ec03fe5f28a9..1874e44d194ecb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -343,10 +343,12 @@ importers: playground/alias: specifiers: + '@vue/shared': ^3.2.37 aliased-module: file:./dir/module resolve-linked: workspace:* vue: ^3.2.37 dependencies: + '@vue/shared': 3.2.37 aliased-module: file:playground/alias/dir/module vue: 3.2.37 devDependencies: From 89fef626a059997cced137d1baaf50067f5537f9 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 2 Jul 2022 18:06:02 +0200 Subject: [PATCH 11/30] fix: restrict preAlias to aliased bareImports --- packages/vite/src/node/plugins/preAlias.ts | 40 ++++++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/packages/vite/src/node/plugins/preAlias.ts b/packages/vite/src/node/plugins/preAlias.ts index a99d9b02aafc02..2623a9d4ec97bc 100644 --- a/packages/vite/src/node/plugins/preAlias.ts +++ b/packages/vite/src/node/plugins/preAlias.ts @@ -1,4 +1,4 @@ -import type { ResolvedConfig } from '..' +import type { Alias, AliasOptions, ResolvedConfig } from '..' import type { Plugin } from '../plugin' import { bareImportRE } from '../utils' import { getDepsOptimizer } from '../optimizer' @@ -8,14 +8,48 @@ import { tryOptimizedResolve } from './resolve' * A plugin to avoid an aliased AND optimized dep from being aliased in src */ export function preAliasPlugin(config: ResolvedConfig): Plugin { + const findPatterns = getAliasPatterns(config.resolve.alias) return { name: 'vite:pre-alias', async resolveId(id, importer, options) { const ssr = options?.ssr === true const depsOptimizer = getDepsOptimizer(config, { ssr }) - if (depsOptimizer && bareImportRE.test(id) && !options?.scan) { - return await tryOptimizedResolve(depsOptimizer, id, importer) + if ( + importer && + depsOptimizer && + bareImportRE.test(id) && + !options?.scan + ) { + if (findPatterns.find((pattern) => matches(pattern, id))) { + return await tryOptimizedResolve(depsOptimizer, id, importer) + } } } } } + +// In sync with rollup plugin alias logic +function matches(pattern: string | RegExp, importee: string) { + if (pattern instanceof RegExp) { + return pattern.test(importee) + } + if (importee.length < pattern.length) { + return false + } + if (importee === pattern) { + return true + } + return importee.startsWith(pattern + '/') +} + +function getAliasPatterns( + entries: (AliasOptions | undefined) & Alias[] +): (string | RegExp)[] { + if (!entries) { + return [] + } + if (Array.isArray(entries)) { + return entries.map((entry) => entry.find) + } + return Object.entries(entries).map(([find]) => find) +} From 6eb114dd59d574114c107513a152df4d4919b0b4 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 2 Jul 2022 18:35:44 +0200 Subject: [PATCH 12/30] fix: post scan optimization --- packages/vite/src/node/optimizer/optimizer.ts | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index 4c411bd331cebe..d4d11cf2d93b69 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -177,21 +177,15 @@ async function createDepsOptimizer( } if (!isBuild) { + const knownDeps = prepareKnownDeps() + // For dev, we run the scanner and the first optimization // run on the background, but we wait until crawling has ended // to decide if we send this result to the browser or we need to // do another optimize step setTimeout(() => { - const discoveredByScanner = toDiscoveredDependencies( - config, - deps, - sessionTimestamp - ) try { - postScanOptimizationResult = runOptimizeDeps( - config, - discoveredByScanner - ) + postScanOptimizationResult = runOptimizeDeps(config, knownDeps) } catch (e) { logger.error(e.message) } @@ -231,21 +225,25 @@ async function createDepsOptimizer( // All deps, previous known and newly discovered are rebundled, // respect insertion order to keep the metadata file stable - const newDeps: Record = {} + const knownDeps = prepareKnownDeps() + startNextDiscoveredBatch() + + return await runOptimizeDeps(config, knownDeps) + } + + function prepareKnownDeps() { + const knownDeps: Record = {} // Clone optimized info objects, fileHash, browserHash may be changed for them for (const dep of Object.keys(metadata.optimized)) { - newDeps[dep] = { ...metadata.optimized[dep] } + knownDeps[dep] = { ...metadata.optimized[dep] } } for (const dep of Object.keys(metadata.discovered)) { // Clone the discovered info discarding its processing promise const { processing, ...info } = metadata.discovered[dep] - newDeps[dep] = info + knownDeps[dep] = info } - - startNextDiscoveredBatch() - - return await runOptimizeDeps(config, newDeps) + return knownDeps } async function runOptimizer(preRunResult?: DepOptimizationResult) { From 387b18e0cc996a9262f8baaee80c204af2e421f5 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 2 Jul 2022 20:16:19 +0200 Subject: [PATCH 13/30] chore: try serialize esbuild --- packages/vite/src/node/optimizer/index.ts | 37 ++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 7f02ac23790ff4..55b7ab10a984b0 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -429,11 +429,46 @@ export function depsLogString(qualifiedIds: string[]): string { } } +const esbuildQueue: (() => Promise)[] = [] +let esbuildProcessing = false +export function enqueueEsbuildTask(task: () => Promise): void { + esbuildQueue.push(task) + if (!esbuildProcessing) { + processNext() + } +} +async function processNext() { + const task = esbuildQueue.shift() + if (task) { + esbuildProcessing = true + await task() + if (esbuildQueue.length > 0) { + processNext() + } else { + esbuildProcessing = false + } + } +} + +export function runOptimizeDeps( + resolvedConfig: ResolvedConfig, + depsInfo: Record, + ssr: boolean = !!resolvedConfig.build.ssr +): Promise { + return new Promise((resolve) => { + enqueueEsbuildTask(() => { + const result = _runOptimizeDeps(resolvedConfig, depsInfo, ssr) + resolve(result) + return result + }) + }) +} + /** * Internally, Vite uses this function to prepare a optimizeDeps run. When Vite starts, we can get * the metadata and start the server without waiting for the optimizeDeps processing to be completed */ -export async function runOptimizeDeps( +async function _runOptimizeDeps( resolvedConfig: ResolvedConfig, depsInfo: Record, ssr: boolean = !!resolvedConfig.build.ssr From 80e7a4d82f6f116a260f4b9dc07a42b6744b244f Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 2 Jul 2022 20:42:43 +0200 Subject: [PATCH 14/30] chore: serialize esbuild scan calls --- packages/vite/src/node/optimizer/scan.ts | 29 +++++++++++++++--------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts index f848af4af03869..eaa40fac562e31 100644 --- a/packages/vite/src/node/optimizer/scan.ts +++ b/packages/vite/src/node/optimizer/scan.ts @@ -24,6 +24,7 @@ import { import type { PluginContainer } from '../server/pluginContainer' import { createPluginContainer } from '../server/pluginContainer' import { transformGlobImport } from '../plugins/importMetaGlob' +import { enqueueEsbuildTask } from './index' const debug = createDebugger('vite:deps') @@ -98,18 +99,24 @@ export async function scanImports(config: ResolvedConfig): Promise<{ config.optimizeDeps?.esbuildOptions ?? {} await Promise.all( - entries.map((entry) => - build({ - absWorkingDir: process.cwd(), - write: false, - entryPoints: [entry], - bundle: true, - format: 'esm', - logLevel: 'error', - plugins: [...plugins, plugin], - ...esbuildOptions + entries.map((entry) => { + return new Promise((resolve) => { + return enqueueEsbuildTask(() => { + const result = build({ + absWorkingDir: process.cwd(), + write: false, + entryPoints: [entry], + bundle: true, + format: 'esm', + logLevel: 'error', + plugins: [...plugins, plugin], + ...esbuildOptions + }) + resolve(result) + return result + }) }) - ) + }) ) debug(`Scan completed in ${(performance.now() - start).toFixed(2)}ms:`, deps) From 04f34b2939131edaae16b4f0c5a80978b6ff1210 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 2 Jul 2022 21:06:29 +0200 Subject: [PATCH 15/30] fix: await scanner before optimizing SSR deps --- packages/vite/src/node/optimizer/optimizer.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index d4d11cf2d93b69..6d6e77c3325b32 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -671,7 +671,11 @@ async function createDepsOptimizer( async function createDevSsrDepsOptimizer( config: ResolvedConfig ): Promise { + // Important: scanning needs to be done before running the optimizer + await getDepsOptimizer(config, { ssr: false })?.scanning + const metadata = await optimizeServerSsrDeps(config) + const depsOptimizer = { metadata, isOptimizedDepFile: (id: string) => isOptimizedDepFile(id, config), From aa58dd8ea7d430ed7e7a20b447abc7e18514ec5b Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 2 Jul 2022 21:24:22 +0200 Subject: [PATCH 16/30] chore: revert 04f34b2939131edaae16b4f0c5a80978b6ff1210 --- packages/vite/src/node/optimizer/optimizer.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index 6d6e77c3325b32..d4d11cf2d93b69 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -671,11 +671,7 @@ async function createDepsOptimizer( async function createDevSsrDepsOptimizer( config: ResolvedConfig ): Promise { - // Important: scanning needs to be done before running the optimizer - await getDepsOptimizer(config, { ssr: false })?.scanning - const metadata = await optimizeServerSsrDeps(config) - const depsOptimizer = { metadata, isOptimizedDepFile: (id: string) => isOptimizedDepFile(id, config), From 1ec2f5b6d9da70dc2e6d8bcaf9b1b0d25b47fffb Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 2 Jul 2022 21:28:07 +0200 Subject: [PATCH 17/30] chore: update --- packages/vite/src/node/optimizer/optimizer.ts | 4 +++ packages/vite/src/node/optimizer/scan.ts | 29 +++++++------------ 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index d4d11cf2d93b69..6d6e77c3325b32 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -671,7 +671,11 @@ async function createDepsOptimizer( async function createDevSsrDepsOptimizer( config: ResolvedConfig ): Promise { + // Important: scanning needs to be done before running the optimizer + await getDepsOptimizer(config, { ssr: false })?.scanning + const metadata = await optimizeServerSsrDeps(config) + const depsOptimizer = { metadata, isOptimizedDepFile: (id: string) => isOptimizedDepFile(id, config), diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts index eaa40fac562e31..f848af4af03869 100644 --- a/packages/vite/src/node/optimizer/scan.ts +++ b/packages/vite/src/node/optimizer/scan.ts @@ -24,7 +24,6 @@ import { import type { PluginContainer } from '../server/pluginContainer' import { createPluginContainer } from '../server/pluginContainer' import { transformGlobImport } from '../plugins/importMetaGlob' -import { enqueueEsbuildTask } from './index' const debug = createDebugger('vite:deps') @@ -99,24 +98,18 @@ export async function scanImports(config: ResolvedConfig): Promise<{ config.optimizeDeps?.esbuildOptions ?? {} await Promise.all( - entries.map((entry) => { - return new Promise((resolve) => { - return enqueueEsbuildTask(() => { - const result = build({ - absWorkingDir: process.cwd(), - write: false, - entryPoints: [entry], - bundle: true, - format: 'esm', - logLevel: 'error', - plugins: [...plugins, plugin], - ...esbuildOptions - }) - resolve(result) - return result - }) + entries.map((entry) => + build({ + absWorkingDir: process.cwd(), + write: false, + entryPoints: [entry], + bundle: true, + format: 'esm', + logLevel: 'error', + plugins: [...plugins, plugin], + ...esbuildOptions }) - }) + ) ) debug(`Scan completed in ${(performance.now() - start).toFixed(2)}ms:`, deps) From e491071cebc9a0172c8df39e1caba63e86181a4c Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 2 Jul 2022 23:06:57 +0200 Subject: [PATCH 18/30] chore: test CI, await scanner and ssr deps optimization before server start --- packages/vite/src/node/optimizer/optimizer.ts | 68 +++++++++---------- packages/vite/src/node/server/index.ts | 5 +- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index 6d6e77c3325b32..20602bf47bfc93 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -158,46 +158,46 @@ async function createDepsOptimizer( const scanPhaseProcessing = newDepOptimizationProcessing() depsOptimizer.scanning = scanPhaseProcessing.promise - setTimeout(async () => { - try { - debug(colors.green(`scanning for dependencies...`)) + //setTimeout(async () => { + try { + debug(colors.green(`scanning for dependencies...`)) - const deps = await discoverProjectDependencies(config) + const deps = await discoverProjectDependencies(config) - const depsString = depsLogString(Object.keys(deps)) - debug(colors.green(`dependencies found by scanner: ${depsString}`)) + const depsString = depsLogString(Object.keys(deps)) + debug(colors.green(`dependencies found by scanner: ${depsString}`)) - // Add these dependencies to the discovered list, as these are currently - // used by the preAliasPlugin to support aliased and optimized deps. - // This is also used by the CJS externalization heuristics in legacy mode - for (const id of Object.keys(deps)) { - if (!metadata.discovered[id]) { - addMissingDep(id, deps[id]) - } + // Add these dependencies to the discovered list, as these are currently + // used by the preAliasPlugin to support aliased and optimized deps. + // This is also used by the CJS externalization heuristics in legacy mode + for (const id of Object.keys(deps)) { + if (!metadata.discovered[id]) { + addMissingDep(id, deps[id]) } + } - if (!isBuild) { - const knownDeps = prepareKnownDeps() - - // For dev, we run the scanner and the first optimization - // run on the background, but we wait until crawling has ended - // to decide if we send this result to the browser or we need to - // do another optimize step - setTimeout(() => { - try { - postScanOptimizationResult = runOptimizeDeps(config, knownDeps) - } catch (e) { - logger.error(e.message) - } - }, 0) - } - } catch (e) { - logger.error(e.message) - } finally { - scanPhaseProcessing.resolve() - depsOptimizer.scanning = undefined + if (!isBuild) { + const knownDeps = prepareKnownDeps() + + // For dev, we run the scanner and the first optimization + // run on the background, but we wait until crawling has ended + // to decide if we send this result to the browser or we need to + // do another optimize step + setTimeout(() => { + try { + postScanOptimizationResult = runOptimizeDeps(config, knownDeps) + } catch (e) { + logger.error(e.message) + } + }, 0) } - }, 0) + } catch (e) { + logger.error(e.message) + } finally { + scanPhaseProcessing.resolve() + depsOptimizer.scanning = undefined + } + //}, 0) } async function startNextDiscoveredBatch() { diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index eb6622ba2de13f..7420773f0c9d62 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -311,7 +311,7 @@ export async function createServer( let exitProcess: () => void - let creatingDevSsrOptimizer: Promise | null = null + // let creatingDevSsrOptimizer: Promise | null = null const server: ViteDevServer = { config, @@ -331,6 +331,7 @@ export async function createServer( }, transformIndexHtml: null!, // to be immediately set async ssrLoadModule(url, opts?: { fixStacktrace?: boolean }) { + /* if (!getDepsOptimizer(config, { ssr: true })) { if (!creatingDevSsrOptimizer) { creatingDevSsrOptimizer = initDevSsrDepsOptimizer(config) @@ -338,6 +339,7 @@ export async function createServer( await creatingDevSsrOptimizer creatingDevSsrOptimizer = null } + */ await updateCjsSsrExternals(server) return ssrLoadModule( url, @@ -537,6 +539,7 @@ export async function createServer( const initOptimizer = async () => { if (isDepsOptimizerEnabled(config)) { await initDepsOptimizer(config, server) + await initDevSsrDepsOptimizer(config) } } From 9b335386ac14cf67d1778d6a3d2c227bc1417063 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 2 Jul 2022 23:35:33 +0200 Subject: [PATCH 19/30] chore: sync set of depsOptimizer.scanning --- packages/vite/src/node/optimizer/optimizer.ts | 9 +++--- packages/vite/src/node/server/index.ts | 32 +++++++++++-------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index 20602bf47bfc93..814b5d0462424d 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -155,10 +155,13 @@ async function createDepsOptimizer( // is refactored to work without the scanned deps. We could skip // this for build later. + runScanner() + } + + async function runScanner() { const scanPhaseProcessing = newDepOptimizationProcessing() depsOptimizer.scanning = scanPhaseProcessing.promise - //setTimeout(async () => { try { debug(colors.green(`scanning for dependencies...`)) @@ -197,7 +200,6 @@ async function createDepsOptimizer( scanPhaseProcessing.resolve() depsOptimizer.scanning = undefined } - //}, 0) } async function startNextDiscoveredBatch() { @@ -671,9 +673,6 @@ async function createDepsOptimizer( async function createDevSsrDepsOptimizer( config: ResolvedConfig ): Promise { - // Important: scanning needs to be done before running the optimizer - await getDepsOptimizer(config, { ssr: false })?.scanning - const metadata = await optimizeServerSsrDeps(config) const depsOptimizer = { diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 7420773f0c9d62..da6cb27c728c3b 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -311,7 +311,24 @@ export async function createServer( let exitProcess: () => void - // let creatingDevSsrOptimizer: Promise | null = null + let creatingDevSsrOptimizer: Promise | null = null + async function initSsrServer() { + // Important: scanning needs to be done before starting the SSR dev optimizer + const optimizer = getDepsOptimizer(config, { ssr: false }) + if (optimizer) { + await optimizer.scanning + } else { + config.logger.error('Error: ssrLoadModule called before server started') + } + if (!getDepsOptimizer(config, { ssr: true })) { + if (!creatingDevSsrOptimizer) { + creatingDevSsrOptimizer = initDevSsrDepsOptimizer(config) + } + await creatingDevSsrOptimizer + creatingDevSsrOptimizer = null + } + await updateCjsSsrExternals(server) + } const server: ViteDevServer = { config, @@ -331,16 +348,7 @@ export async function createServer( }, transformIndexHtml: null!, // to be immediately set async ssrLoadModule(url, opts?: { fixStacktrace?: boolean }) { - /* - if (!getDepsOptimizer(config, { ssr: true })) { - if (!creatingDevSsrOptimizer) { - creatingDevSsrOptimizer = initDevSsrDepsOptimizer(config) - } - await creatingDevSsrOptimizer - creatingDevSsrOptimizer = null - } - */ - await updateCjsSsrExternals(server) + await initSsrServer() return ssrLoadModule( url, server, @@ -539,7 +547,6 @@ export async function createServer( const initOptimizer = async () => { if (isDepsOptimizerEnabled(config)) { await initDepsOptimizer(config, server) - await initDevSsrDepsOptimizer(config) } } @@ -778,7 +785,6 @@ async function updateCjsSsrExternals(server: ViteDevServer) { // for backwards compatibility in case user needs to fallback to the // legacy scheme. It may be removed in a future v3 minor. const depsOptimizer = getDepsOptimizer(server.config, { ssr: false }) - if (depsOptimizer) { await depsOptimizer.scanning knownImports = [ From 967dfd47a15faee71c761bcefb845ceb4fa08cc2 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sun, 3 Jul 2022 00:26:29 +0200 Subject: [PATCH 20/30] test: move SSR module error test to ssr-vue --- .../ssr/__tests__/ssrModuleLoader.spec.ts | 32 ----------------- .../__tests__/fixtures/ssrModuleLoader-bad.js | 0 playground/ssr-vue/__tests__/serve.ts | 4 +++ playground/ssr-vue/__tests__/ssr-vue.spec.ts | 34 ++++++++++++++++++- playground/vitestSetup.ts | 7 +++- 5 files changed, 43 insertions(+), 34 deletions(-) delete mode 100644 packages/vite/src/node/ssr/__tests__/ssrModuleLoader.spec.ts rename {packages/vite/src/node/ssr => playground/ssr-vue}/__tests__/fixtures/ssrModuleLoader-bad.js (100%) diff --git a/packages/vite/src/node/ssr/__tests__/ssrModuleLoader.spec.ts b/packages/vite/src/node/ssr/__tests__/ssrModuleLoader.spec.ts deleted file mode 100644 index 8451be5b3af745..00000000000000 --- a/packages/vite/src/node/ssr/__tests__/ssrModuleLoader.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { resolve } from 'node:path' -import { fileURLToPath } from 'node:url' -import { expect, test, vi } from 'vitest' -import { createServer } from '../../index' - -const __filename = fileURLToPath(import.meta.url) -const badjs = resolve(__filename, '../fixtures/ssrModuleLoader-bad.js') -const THROW_MESSAGE = 'it is an expected error' - -test('always throw error when evaluating an wrong SSR module', async () => { - const viteServer = await createServer() - const spy = vi.spyOn(console, 'error').mockImplementation(() => {}) - const expectedErrors = [] - for (const _ of [0, 1]) { - try { - await viteServer.ssrLoadModule(badjs, { fixStacktrace: true }) - } catch (e) { - expectedErrors.push(e) - } - } - await viteServer.close() - expect(expectedErrors).toHaveLength(2) - expect(expectedErrors[0]).toBe(expectedErrors[1]) - expectedErrors.forEach((error) => { - expect(error?.message).toContain(THROW_MESSAGE) - }) - expect(spy).toBeCalledTimes(1) - const [firstParameter] = spy.mock.calls[0] - expect(firstParameter).toContain('Error when evaluating SSR module') - expect(firstParameter).toContain(THROW_MESSAGE) - spy.mockClear() -}) diff --git a/packages/vite/src/node/ssr/__tests__/fixtures/ssrModuleLoader-bad.js b/playground/ssr-vue/__tests__/fixtures/ssrModuleLoader-bad.js similarity index 100% rename from packages/vite/src/node/ssr/__tests__/fixtures/ssrModuleLoader-bad.js rename to playground/ssr-vue/__tests__/fixtures/ssrModuleLoader-bad.js diff --git a/playground/ssr-vue/__tests__/serve.ts b/playground/ssr-vue/__tests__/serve.ts index 99b02e10c94a19..e03810017bc04e 100644 --- a/playground/ssr-vue/__tests__/serve.ts +++ b/playground/ssr-vue/__tests__/serve.ts @@ -3,10 +3,13 @@ import path from 'node:path' import kill from 'kill-port' +import type { ViteDevServer } from 'vite' import { hmrPorts, isBuild, ports, rootDir } from '~utils' export const port = ports['ssr-vue'] +export let viteServer: ViteDevServer + export async function serve(): Promise<{ close(): Promise }> { if (isBuild) { // build first @@ -44,6 +47,7 @@ export async function serve(): Promise<{ close(): Promise }> { isBuild, hmrPorts['ssr-vue'] ) + viteServer = vite return new Promise((resolve, reject) => { try { diff --git a/playground/ssr-vue/__tests__/ssr-vue.spec.ts b/playground/ssr-vue/__tests__/ssr-vue.spec.ts index 7b61c2634f7c5b..7d4d1ec0f7ab87 100644 --- a/playground/ssr-vue/__tests__/ssr-vue.spec.ts +++ b/playground/ssr-vue/__tests__/ssr-vue.spec.ts @@ -1,4 +1,5 @@ import { resolve } from 'node:path' +import { fileURLToPath } from 'node:url' import fetch from 'node-fetch' import { port } from './serve' import { @@ -7,7 +8,8 @@ import { getColor, isBuild, page, - untilUpdated + untilUpdated, + viteServer } from '~utils' const url = `http://localhost:${port}/test/` @@ -202,3 +204,33 @@ test.runIf(isBuild)('dynamic css file should be preloaded', async () => { expect(homeHtml).toMatch(file) } }) + +test.runIf(!isBuild)( + 'always throw error when evaluating an wrong SSR module', + async () => { + const __filename = fileURLToPath(import.meta.url) + const badjs = resolve(__filename, '../fixtures/ssrModuleLoader-bad.js') + const THROW_MESSAGE = 'it is an expected error' + + const spy = vi.spyOn(console, 'error').mockImplementation(() => {}) + const expectedErrors = [] + for (const _ of [0, 1]) { + try { + console.log(viteServer) + await viteServer.ssrLoadModule(badjs, { fixStacktrace: true }) + } catch (e) { + expectedErrors.push(e) + } + } + expect(expectedErrors).toHaveLength(2) + expect(expectedErrors[0]).toBe(expectedErrors[1]) + expectedErrors.forEach((error) => { + expect(error?.message).toContain(THROW_MESSAGE) + }) + expect(spy).toBeCalledTimes(1) + const [firstParameter] = spy.mock.calls[0] + expect(firstParameter).toContain('Error when evaluating SSR module') + expect(firstParameter).toContain(THROW_MESSAGE) + spy.mockClear() + } +) diff --git a/playground/vitestSetup.ts b/playground/vitestSetup.ts index de0877302e5118..7b960811eb3f7e 100644 --- a/playground/vitestSetup.ts +++ b/playground/vitestSetup.ts @@ -35,6 +35,10 @@ export const viteBinPath = path.posix.join( let server: ViteDevServer | http.Server +/** + * Vite Dev Server when testing serve + */ +export let viteServer: ViteDevServer /** * Root of the Vite fixture */ @@ -146,6 +150,7 @@ beforeAll(async (s) => { } if (serve) { server = await serve() + viteServer = mod.viteServer return } } else { @@ -212,7 +217,7 @@ export async function startDefaultServe(): Promise { process.env.VITE_INLINE = 'inline-serve' const testConfig = mergeConfig(options, config || {}) viteConfig = testConfig - server = await (await createServer(testConfig)).listen() + viteServer = server = await (await createServer(testConfig)).listen() // use resolved port/base from server const devBase = server.config.base viteTestUrl = `http://localhost:${server.config.server.port}${ From 6c727361fea1926581690c152a3b3325be0d8f2d Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sun, 3 Jul 2022 14:20:17 +0200 Subject: [PATCH 21/30] test: avoid starting two servers concurrently in a test --- .../__tests__/css-sourcemap.spec.ts | 443 ++++++++++++++++++ .../css-sourcemap/__tests__/serve.spec.ts | 219 --------- .../postcss-plugins-different-dir.spec.ts | 2 +- .../postcss-plugins-different-dir/serve.ts | 10 + playground/css/postcss-caching/serve.ts | 10 + .../js-sourcemap/__tests__/build.spec.ts | 7 - .../{serve.spec.ts => js-sourcemap.spec.ts} | 6 + .../__tests__/build.spec.ts | 7 - .../__tests__/serve.spec.ts | 7 - .../__tests__/tailwind-sourcemap.spec.ts} | 6 + .../tailwind-sourcemap/postcss.config.js | 2 + ...pec.ts.snap => vue-sourcemap.spec.ts.snap} | 0 .../vue-sourcemap/__tests__/build.spec.ts | 7 - .../{serve.spec.ts => vue-sourcemap.spec.ts} | 10 +- 14 files changed, 487 insertions(+), 249 deletions(-) create mode 100644 playground/css-sourcemap/__tests__/css-sourcemap.spec.ts delete mode 100644 playground/css-sourcemap/__tests__/serve.spec.ts rename playground/css/__tests__/{ => postcss-plugins-different-dir}/postcss-plugins-different-dir.spec.ts (93%) create mode 100644 playground/css/__tests__/postcss-plugins-different-dir/serve.ts create mode 100644 playground/css/postcss-caching/serve.ts delete mode 100644 playground/js-sourcemap/__tests__/build.spec.ts rename playground/js-sourcemap/__tests__/{serve.spec.ts => js-sourcemap.spec.ts} (86%) delete mode 100644 playground/tailwind-sourcemap/__tests__/build.spec.ts delete mode 100644 playground/tailwind-sourcemap/__tests__/serve.spec.ts rename playground/{css-sourcemap/__tests__/build.spec.ts => tailwind-sourcemap/__tests__/tailwind-sourcemap.spec.ts} (53%) rename playground/vue-sourcemap/__tests__/__snapshots__/{serve.spec.ts.snap => vue-sourcemap.spec.ts.snap} (100%) delete mode 100644 playground/vue-sourcemap/__tests__/build.spec.ts rename playground/vue-sourcemap/__tests__/{serve.spec.ts => vue-sourcemap.spec.ts} (93%) diff --git a/playground/css-sourcemap/__tests__/css-sourcemap.spec.ts b/playground/css-sourcemap/__tests__/css-sourcemap.spec.ts new file mode 100644 index 00000000000000..f63ef8946eba40 --- /dev/null +++ b/playground/css-sourcemap/__tests__/css-sourcemap.spec.ts @@ -0,0 +1,443 @@ +import { URL } from 'node:url' +import { + extractSourcemap, + formatSourcemapForSnapshot, + isBuild, + isServe, + page, + serverLogs +} from '~utils' + +test.runIf(isBuild)('should not output sourcemap warning (#4939)', () => { + serverLogs.forEach((log) => { + expect(log).not.toMatch('Sourcemap is likely to be incorrect') + }) +}) + +describe.runIf(isServe)('serve', () => { + const getStyleTagContentIncluding = async (content: string) => { + const styles = await page.$$('style') + for (const style of styles) { + const text = await style.textContent() + if (text.includes(content)) { + return text + } + } + throw new Error('Not found') + } + + test('linked css', async () => { + const res = await page.request.get( + new URL('./linked.css', page.url()).href, + { + headers: { + accept: 'text/css' + } + } + ) + const css = await res.text() + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACT,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACb,CAAC;", + "sources": [ + "/root/linked.css", + ], + "sourcesContent": [ + ".linked { + color: red; + } + ", + ], + "version": 3, + } + `) + }) + + test('linked css with import', async () => { + const res = await page.request.get( + new URL('./linked-with-import.css', page.url()).href, + { + headers: { + accept: 'text/css' + } + } + ) + const css = await res.text() + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AAAA;EACE,UAAU;AACZ;;ACAA;EACE,UAAU;AACZ", + "sources": [ + "/root/be-imported.css", + "/root/linked-with-import.css", + ], + "sourcesContent": [ + ".be-imported { + color: red; + } + ", + "@import '@/be-imported.css'; + + .linked-with-import { + color: red; + } + ", + ], + "version": 3, + } + `) + }) + + test('imported css', async () => { + const css = await getStyleTagContentIncluding('.imported ') + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACX,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACb,CAAC;", + "sources": [ + "/root/imported.css", + ], + "sourcesContent": [ + ".imported { + color: red; + } + ", + ], + "version": 3, + } + `) + }) + + test('imported css with import', async () => { + const css = await getStyleTagContentIncluding('.imported-with-import ') + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AAAA;EACE,UAAU;AACZ;;ACAA;EACE,UAAU;AACZ", + "sources": [ + "/root/be-imported.css", + "/root/imported-with-import.css", + ], + "sourcesContent": [ + ".be-imported { + color: red; + } + ", + "@import '@/be-imported.css'; + + .imported-with-import { + color: red; + } + ", + ], + "version": 3, + } + `) + }) + + test('imported sass', async () => { + const css = await getStyleTagContentIncluding('.imported-sass ') + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AACE;EACE", + "sources": [ + "/root/imported.sass", + ], + "sourcesContent": [ + ".imported + &-sass + color: red + ", + ], + "version": 3, + } + `) + }) + + test('imported sass module', async () => { + const css = await getStyleTagContentIncluding('._imported-sass-module_') + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AACE;EACE", + "sources": [ + "/root/imported.module.sass", + ], + "sourcesContent": [ + ".imported + &-sass-module + color: red + ", + ], + "version": 3, + } + `) + }) + + test('imported less', async () => { + const css = await getStyleTagContentIncluding('.imported-less ') + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AACE;EACE", + "sources": [ + "/root/imported.less", + ], + "sourcesContent": [ + ".imported { + &-less { + color: @color; + } + } + ", + ], + "version": 3, + } + `) + }) + + test('imported stylus', async () => { + const css = await getStyleTagContentIncluding('.imported-stylus ') + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AACE;EACE,cAAM", + "sources": [ + "/root/imported.styl", + ], + "sourcesContent": [ + ".imported + &-stylus + color blue-red-mixed + ", + ], + "version": 3, + } + `) + }) + + test('should not output missing source file warning', () => { + serverLogs.forEach((log) => { + expect(log).not.toMatch(/Sourcemap for .+ points to missing source files/) + }) + }) +}) + +describe.runIf(isServe)('serve', () => { + const getStyleTagContentIncluding = async (content: string) => { + const styles = await page.$$('style') + for (const style of styles) { + const text = await style.textContent() + if (text.includes(content)) { + return text + } + } + throw new Error('Not found') + } + + test('linked css', async () => { + const res = await page.request.get( + new URL('./linked.css', page.url()).href, + { + headers: { + accept: 'text/css' + } + } + ) + const css = await res.text() + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACT,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACb,CAAC;", + "sources": [ + "/root/linked.css", + ], + "sourcesContent": [ + ".linked { + color: red; + } + ", + ], + "version": 3, + } + `) + }) + + test('linked css with import', async () => { + const res = await page.request.get( + new URL('./linked-with-import.css', page.url()).href, + { + headers: { + accept: 'text/css' + } + } + ) + const css = await res.text() + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AAAA;EACE,UAAU;AACZ;;ACAA;EACE,UAAU;AACZ", + "sources": [ + "/root/be-imported.css", + "/root/linked-with-import.css", + ], + "sourcesContent": [ + ".be-imported { + color: red; + } + ", + "@import '@/be-imported.css'; + + .linked-with-import { + color: red; + } + ", + ], + "version": 3, + } + `) + }) + + test('imported css', async () => { + const css = await getStyleTagContentIncluding('.imported ') + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACX,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACb,CAAC;", + "sources": [ + "/root/imported.css", + ], + "sourcesContent": [ + ".imported { + color: red; + } + ", + ], + "version": 3, + } + `) + }) + + test('imported css with import', async () => { + const css = await getStyleTagContentIncluding('.imported-with-import ') + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AAAA;EACE,UAAU;AACZ;;ACAA;EACE,UAAU;AACZ", + "sources": [ + "/root/be-imported.css", + "/root/imported-with-import.css", + ], + "sourcesContent": [ + ".be-imported { + color: red; + } + ", + "@import '@/be-imported.css'; + + .imported-with-import { + color: red; + } + ", + ], + "version": 3, + } + `) + }) + + test('imported sass', async () => { + const css = await getStyleTagContentIncluding('.imported-sass ') + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AACE;EACE", + "sources": [ + "/root/imported.sass", + ], + "sourcesContent": [ + ".imported + &-sass + color: red + ", + ], + "version": 3, + } + `) + }) + + test('imported sass module', async () => { + const css = await getStyleTagContentIncluding('._imported-sass-module_') + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AACE;EACE", + "sources": [ + "/root/imported.module.sass", + ], + "sourcesContent": [ + ".imported + &-sass-module + color: red + ", + ], + "version": 3, + } + `) + }) + + test('imported less', async () => { + const css = await getStyleTagContentIncluding('.imported-less ') + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AACE;EACE", + "sources": [ + "/root/imported.less", + ], + "sourcesContent": [ + ".imported { + &-less { + color: @color; + } + } + ", + ], + "version": 3, + } + `) + }) + + test('imported stylus', async () => { + const css = await getStyleTagContentIncluding('.imported-stylus ') + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + { + "mappings": "AACE;EACE,cAAM", + "sources": [ + "/root/imported.styl", + ], + "sourcesContent": [ + ".imported + &-stylus + color blue-red-mixed + ", + ], + "version": 3, + } + `) + }) + + test('should not output missing source file warning', () => { + serverLogs.forEach((log) => { + expect(log).not.toMatch(/Sourcemap for .+ points to missing source files/) + }) + }) +}) + +test.runIf(isBuild)('should not output sourcemap warning (#4939)', () => { + serverLogs.forEach((log) => { + expect(log).not.toMatch('Sourcemap is likely to be incorrect') + }) +}) diff --git a/playground/css-sourcemap/__tests__/serve.spec.ts b/playground/css-sourcemap/__tests__/serve.spec.ts deleted file mode 100644 index 6e7392952c3caa..00000000000000 --- a/playground/css-sourcemap/__tests__/serve.spec.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { URL } from 'node:url' -import { - extractSourcemap, - formatSourcemapForSnapshot, - isServe, - page, - serverLogs -} from '~utils' - -describe.runIf(isServe)('serve', () => { - const getStyleTagContentIncluding = async (content: string) => { - const styles = await page.$$('style') - for (const style of styles) { - const text = await style.textContent() - if (text.includes(content)) { - return text - } - } - throw new Error('Not found') - } - - test('linked css', async () => { - const res = await page.request.get( - new URL('./linked.css', page.url()).href, - { - headers: { - accept: 'text/css' - } - } - ) - const css = await res.text() - const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` - { - "mappings": "AAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACT,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACb,CAAC;", - "sources": [ - "/root/linked.css", - ], - "sourcesContent": [ - ".linked { - color: red; - } - ", - ], - "version": 3, - } - `) - }) - - test('linked css with import', async () => { - const res = await page.request.get( - new URL('./linked-with-import.css', page.url()).href, - { - headers: { - accept: 'text/css' - } - } - ) - const css = await res.text() - const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` - { - "mappings": "AAAA;EACE,UAAU;AACZ;;ACAA;EACE,UAAU;AACZ", - "sources": [ - "/root/be-imported.css", - "/root/linked-with-import.css", - ], - "sourcesContent": [ - ".be-imported { - color: red; - } - ", - "@import '@/be-imported.css'; - - .linked-with-import { - color: red; - } - ", - ], - "version": 3, - } - `) - }) - - test('imported css', async () => { - const css = await getStyleTagContentIncluding('.imported ') - const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` - { - "mappings": "AAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACX,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACb,CAAC;", - "sources": [ - "/root/imported.css", - ], - "sourcesContent": [ - ".imported { - color: red; - } - ", - ], - "version": 3, - } - `) - }) - - test('imported css with import', async () => { - const css = await getStyleTagContentIncluding('.imported-with-import ') - const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` - { - "mappings": "AAAA;EACE,UAAU;AACZ;;ACAA;EACE,UAAU;AACZ", - "sources": [ - "/root/be-imported.css", - "/root/imported-with-import.css", - ], - "sourcesContent": [ - ".be-imported { - color: red; - } - ", - "@import '@/be-imported.css'; - - .imported-with-import { - color: red; - } - ", - ], - "version": 3, - } - `) - }) - - test('imported sass', async () => { - const css = await getStyleTagContentIncluding('.imported-sass ') - const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` - { - "mappings": "AACE;EACE", - "sources": [ - "/root/imported.sass", - ], - "sourcesContent": [ - ".imported - &-sass - color: red - ", - ], - "version": 3, - } - `) - }) - - test('imported sass module', async () => { - const css = await getStyleTagContentIncluding('._imported-sass-module_') - const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` - { - "mappings": "AACE;EACE", - "sources": [ - "/root/imported.module.sass", - ], - "sourcesContent": [ - ".imported - &-sass-module - color: red - ", - ], - "version": 3, - } - `) - }) - - test('imported less', async () => { - const css = await getStyleTagContentIncluding('.imported-less ') - const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` - { - "mappings": "AACE;EACE", - "sources": [ - "/root/imported.less", - ], - "sourcesContent": [ - ".imported { - &-less { - color: @color; - } - } - ", - ], - "version": 3, - } - `) - }) - - test('imported stylus', async () => { - const css = await getStyleTagContentIncluding('.imported-stylus ') - const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` - { - "mappings": "AACE;EACE,cAAM", - "sources": [ - "/root/imported.styl", - ], - "sourcesContent": [ - ".imported - &-stylus - color blue-red-mixed - ", - ], - "version": 3, - } - `) - }) - - test('should not output missing source file warning', () => { - serverLogs.forEach((log) => { - expect(log).not.toMatch(/Sourcemap for .+ points to missing source files/) - }) - }) -}) diff --git a/playground/css/__tests__/postcss-plugins-different-dir.spec.ts b/playground/css/__tests__/postcss-plugins-different-dir/postcss-plugins-different-dir.spec.ts similarity index 93% rename from playground/css/__tests__/postcss-plugins-different-dir.spec.ts rename to playground/css/__tests__/postcss-plugins-different-dir/postcss-plugins-different-dir.spec.ts index a1e0b4810c8ee5..346309f7fcc32d 100644 --- a/playground/css/__tests__/postcss-plugins-different-dir.spec.ts +++ b/playground/css/__tests__/postcss-plugins-different-dir/postcss-plugins-different-dir.spec.ts @@ -6,7 +6,7 @@ import { getBgColor, getColor, page, ports } from '~utils' test('postcss plugins in different dir', async () => { const port = ports['css/postcss-plugins-different-dir'] const server = await createServer({ - root: path.join(__dirname, '..', '..', 'tailwind'), + root: path.join(__dirname, '..', '..', '..', 'tailwind'), logLevel: 'silent', server: { port, diff --git a/playground/css/__tests__/postcss-plugins-different-dir/serve.ts b/playground/css/__tests__/postcss-plugins-different-dir/serve.ts new file mode 100644 index 00000000000000..16e7d655f5ec15 --- /dev/null +++ b/playground/css/__tests__/postcss-plugins-different-dir/serve.ts @@ -0,0 +1,10 @@ +// this is automatically detected by playground/vitestSetup.ts and will replace +// the default e2e test serve behavior + +// The server is started in the test, so we need to have a custom serve +// function or a default server will be created +export async function serve(): Promise<{ close(): Promise }> { + return { + close: () => Promise.resolve() + } +} diff --git a/playground/css/postcss-caching/serve.ts b/playground/css/postcss-caching/serve.ts new file mode 100644 index 00000000000000..16e7d655f5ec15 --- /dev/null +++ b/playground/css/postcss-caching/serve.ts @@ -0,0 +1,10 @@ +// this is automatically detected by playground/vitestSetup.ts and will replace +// the default e2e test serve behavior + +// The server is started in the test, so we need to have a custom serve +// function or a default server will be created +export async function serve(): Promise<{ close(): Promise }> { + return { + close: () => Promise.resolve() + } +} diff --git a/playground/js-sourcemap/__tests__/build.spec.ts b/playground/js-sourcemap/__tests__/build.spec.ts deleted file mode 100644 index b30284731a76d9..00000000000000 --- a/playground/js-sourcemap/__tests__/build.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { isBuild, serverLogs } from '~utils' - -test.runIf(isBuild)('should not output sourcemap warning (#4939)', () => { - serverLogs.forEach((log) => { - expect(log).not.toMatch('Sourcemap is likely to be incorrect') - }) -}) diff --git a/playground/js-sourcemap/__tests__/serve.spec.ts b/playground/js-sourcemap/__tests__/js-sourcemap.spec.ts similarity index 86% rename from playground/js-sourcemap/__tests__/serve.spec.ts rename to playground/js-sourcemap/__tests__/js-sourcemap.spec.ts index 5cd5c0e448b72f..a7ecbdf145af4a 100644 --- a/playground/js-sourcemap/__tests__/serve.spec.ts +++ b/playground/js-sourcemap/__tests__/js-sourcemap.spec.ts @@ -44,3 +44,9 @@ if (!isBuild) { expect(true).toBe(true) }) } + +test.runIf(isBuild)('should not output sourcemap warning (#4939)', () => { + serverLogs.forEach((log) => { + expect(log).not.toMatch('Sourcemap is likely to be incorrect') + }) +}) diff --git a/playground/tailwind-sourcemap/__tests__/build.spec.ts b/playground/tailwind-sourcemap/__tests__/build.spec.ts deleted file mode 100644 index b30284731a76d9..00000000000000 --- a/playground/tailwind-sourcemap/__tests__/build.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { isBuild, serverLogs } from '~utils' - -test.runIf(isBuild)('should not output sourcemap warning (#4939)', () => { - serverLogs.forEach((log) => { - expect(log).not.toMatch('Sourcemap is likely to be incorrect') - }) -}) diff --git a/playground/tailwind-sourcemap/__tests__/serve.spec.ts b/playground/tailwind-sourcemap/__tests__/serve.spec.ts deleted file mode 100644 index 62e46cc7781482..00000000000000 --- a/playground/tailwind-sourcemap/__tests__/serve.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { isBuild, serverLogs } from '~utils' - -test.runIf(isBuild)('should not output missing source file warning', () => { - serverLogs.forEach((log) => { - expect(log).not.toMatch(/Sourcemap for .+ points to missing source files/) - }) -}) diff --git a/playground/css-sourcemap/__tests__/build.spec.ts b/playground/tailwind-sourcemap/__tests__/tailwind-sourcemap.spec.ts similarity index 53% rename from playground/css-sourcemap/__tests__/build.spec.ts rename to playground/tailwind-sourcemap/__tests__/tailwind-sourcemap.spec.ts index b30284731a76d9..86238ab800f24e 100644 --- a/playground/css-sourcemap/__tests__/build.spec.ts +++ b/playground/tailwind-sourcemap/__tests__/tailwind-sourcemap.spec.ts @@ -1,5 +1,11 @@ import { isBuild, serverLogs } from '~utils' +test.runIf(!isBuild)('should not output missing source file warning', () => { + serverLogs.forEach((log) => { + expect(log).not.toMatch(/Sourcemap for .+ points to missing source files/) + }) +}) + test.runIf(isBuild)('should not output sourcemap warning (#4939)', () => { serverLogs.forEach((log) => { expect(log).not.toMatch('Sourcemap is likely to be incorrect') diff --git a/playground/tailwind-sourcemap/postcss.config.js b/playground/tailwind-sourcemap/postcss.config.js index eab3760cbc7b42..ad6adc688915c1 100644 --- a/playground/tailwind-sourcemap/postcss.config.js +++ b/playground/tailwind-sourcemap/postcss.config.js @@ -1,3 +1,5 @@ +console.log(__dirname + '/tailwind.config.js') + module.exports = { plugins: { tailwindcss: { config: __dirname + '/tailwind.config.js' } diff --git a/playground/vue-sourcemap/__tests__/__snapshots__/serve.spec.ts.snap b/playground/vue-sourcemap/__tests__/__snapshots__/vue-sourcemap.spec.ts.snap similarity index 100% rename from playground/vue-sourcemap/__tests__/__snapshots__/serve.spec.ts.snap rename to playground/vue-sourcemap/__tests__/__snapshots__/vue-sourcemap.spec.ts.snap diff --git a/playground/vue-sourcemap/__tests__/build.spec.ts b/playground/vue-sourcemap/__tests__/build.spec.ts deleted file mode 100644 index b30284731a76d9..00000000000000 --- a/playground/vue-sourcemap/__tests__/build.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { isBuild, serverLogs } from '~utils' - -test.runIf(isBuild)('should not output sourcemap warning (#4939)', () => { - serverLogs.forEach((log) => { - expect(log).not.toMatch('Sourcemap is likely to be incorrect') - }) -}) diff --git a/playground/vue-sourcemap/__tests__/serve.spec.ts b/playground/vue-sourcemap/__tests__/vue-sourcemap.spec.ts similarity index 93% rename from playground/vue-sourcemap/__tests__/serve.spec.ts rename to playground/vue-sourcemap/__tests__/vue-sourcemap.spec.ts index 1ace0d7d1f4306..adf0dfbccb6bda 100644 --- a/playground/vue-sourcemap/__tests__/serve.spec.ts +++ b/playground/vue-sourcemap/__tests__/vue-sourcemap.spec.ts @@ -2,8 +2,10 @@ import { URL } from 'node:url' import { extractSourcemap, formatSourcemapForSnapshot, + isBuild, isServe, - page + page, + serverLogs } from '~utils' describe.runIf(isServe)('serve:vue-sourcemap', () => { @@ -98,3 +100,9 @@ describe.runIf(isServe)('serve:vue-sourcemap', () => { expect(formatSourcemapForSnapshot(map)).toMatchSnapshot() }) }) + +test.runIf(isBuild)('should not output sourcemap warning (#4939)', () => { + serverLogs.forEach((log) => { + expect(log).not.toMatch('Sourcemap is likely to be incorrect') + }) +}) From e814d41555d3305fa12585f923c8d9ea4d8ba903 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sun, 3 Jul 2022 15:21:53 +0200 Subject: [PATCH 22/30] fix: improve init logic --- packages/vite/src/node/optimizer/optimizer.ts | 92 ++++++++++++++----- packages/vite/src/node/server/index.ts | 62 ++++++------- 2 files changed, 98 insertions(+), 56 deletions(-) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index 814b5d0462424d..a7d30403245a78 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -2,7 +2,6 @@ import colors from 'picocolors' import _debug from 'debug' import { getHash } from '../utils' import type { ResolvedConfig, ViteDevServer } from '..' - import { addManuallyIncludedOptimizeDeps, addOptimizedDepInfo, @@ -54,13 +53,35 @@ export async function initDepsOptimizer( config: ResolvedConfig, server?: ViteDevServer ): Promise { - await createDepsOptimizer(config, server) + if (!getDepsOptimizer(config, { ssr: false })) { + await createDepsOptimizer(config, server) + } } +let creatingDevSsrOptimizer: Promise | undefined export async function initDevSsrDepsOptimizer( - config: ResolvedConfig + config: ResolvedConfig, + server: ViteDevServer ): Promise { - await createDevSsrDepsOptimizer(config) + if (getDepsOptimizer(config, { ssr: true })) { + return + } + if (creatingDevSsrOptimizer) { + return creatingDevSsrOptimizer + } + creatingDevSsrOptimizer = (async function () { + // Important: scanning needs to be done before starting the SSR dev optimizer + // If ssrLoadModule is called before server.listen(), the main deps optimizer + // will not be yet created + if (!getDepsOptimizer(config, { ssr: false })) { + await initDepsOptimizer(config, server) + } + await getDepsOptimizer(config, { ssr: false })!.scanning + + await createDevSsrDepsOptimizer(config) + creatingDevSsrOptimizer = undefined + })() + return await creatingDevSsrOptimizer } async function createDepsOptimizer( @@ -149,6 +170,7 @@ async function createDepsOptimizer( ...depInfo, processing: depOptimizationProcessing.promise }) + newDepsDiscovered = true } // TODO: We need the scan during build time, until preAliasPlugin @@ -167,8 +189,15 @@ async function createDepsOptimizer( const deps = await discoverProjectDependencies(config) - const depsString = depsLogString(Object.keys(deps)) - debug(colors.green(`dependencies found by scanner: ${depsString}`)) + debug( + colors.green( + Object.keys(deps).length > 0 + ? `dependencies found by scanner: ${depsLogString( + Object.keys(deps) + )}` + : `no dependencies found by scanner` + ) + ) // Add these dependencies to the discovered list, as these are currently // used by the preAliasPlugin to support aliased and optimized deps. @@ -186,13 +215,7 @@ async function createDepsOptimizer( // run on the background, but we wait until crawling has ended // to decide if we send this result to the browser or we need to // do another optimize step - setTimeout(() => { - try { - postScanOptimizationResult = runOptimizeDeps(config, knownDeps) - } catch (e) { - logger.error(e.message) - } - }, 0) + postScanOptimizationResult = runOptimizeDeps(config, knownDeps) } } catch (e) { logger.error(e.message) @@ -476,7 +499,6 @@ async function createDepsOptimizer( // It will be processed in the next rerun call return missing } - newDepsDiscovered = true missing = addMissingDep(id, resolved, ssr) @@ -497,6 +519,8 @@ async function createDepsOptimizer( } function addMissingDep(id: string, resolved: string, ssr?: boolean) { + newDepsDiscovered = true + return addOptimizedDepInfo(metadata, 'discovered', { id, file: getOptimizedDepPath(id, config, ssr), @@ -518,6 +542,9 @@ async function createDepsOptimizer( } function debouncedProcessing(timeout = debounceMs) { + if (!newDepsDiscovered) { + return + } // Debounced rerun, let other missing dependencies be discovered before // the running next optimizeDeps enqueuedRerun = undefined @@ -534,21 +561,35 @@ async function createDepsOptimizer( } async function onCrawlEnd() { + debug(colors.green(`✨ static imports crawl ended`)) if (firstRunCalled) { return } currentlyProcessing = false + const crawlDeps = Object.keys(metadata.discovered) + + // Await for the scan+optimize step running in the background + // It normally should be over by the time crawling of user code ended + await depsOptimizer.scanning + if (!isBuild && postScanOptimizationResult) { - // Await for the scan+optimize step running in the background - // It normally should be over by the time crawling of user code ended - await depsOptimizer.scanning const result = await postScanOptimizationResult postScanOptimizationResult = undefined const scanDeps = Object.keys(result.metadata.optimized) - const crawlDeps = Object.keys(metadata.discovered) + + if (scanDeps.length === 0 && crawlDeps.length === 0) { + debug( + colors.green( + `✨ no dependencies found by the scanner or crawling static imports` + ) + ) + result.cancel() + firstRunCalled = true + return + } const needsInteropMismatch = findInteropMismatches( metadata.discovered, @@ -580,15 +621,24 @@ async function createDepsOptimizer( } else { debug( colors.green( - `✨ using post-scan optimizer result, the scanner found every needed deps` + `✨ using post-scan optimizer result, the scanner found every used dependency` ) ) startNextDiscoveredBatch() runOptimizer(result) } } else { - // queue the first optimizer run - debouncedProcessing(0) + if (crawlDeps.length === 0) { + debug( + colors.green( + `✨ no dependencies found while crawling the static imports` + ) + ) + firstRunCalled = true + } else { + // queue the first optimizer run + debouncedProcessing(0) + } } } diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index da6cb27c728c3b..a666f6c3a6665e 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -311,25 +311,6 @@ export async function createServer( let exitProcess: () => void - let creatingDevSsrOptimizer: Promise | null = null - async function initSsrServer() { - // Important: scanning needs to be done before starting the SSR dev optimizer - const optimizer = getDepsOptimizer(config, { ssr: false }) - if (optimizer) { - await optimizer.scanning - } else { - config.logger.error('Error: ssrLoadModule called before server started') - } - if (!getDepsOptimizer(config, { ssr: true })) { - if (!creatingDevSsrOptimizer) { - creatingDevSsrOptimizer = initDevSsrDepsOptimizer(config) - } - await creatingDevSsrOptimizer - creatingDevSsrOptimizer = null - } - await updateCjsSsrExternals(server) - } - const server: ViteDevServer = { config, middlewares, @@ -348,7 +329,10 @@ export async function createServer( }, transformIndexHtml: null!, // to be immediately set async ssrLoadModule(url, opts?: { fixStacktrace?: boolean }) { - await initSsrServer() + if (isDepsOptimizerEnabled(config)) { + await initDevSsrDepsOptimizer(config, server) + } + await updateCjsSsrExternals(server) return ssrLoadModule( url, server, @@ -544,32 +528,40 @@ export async function createServer( // error handler middlewares.use(errorMiddleware(server, middlewareMode)) - const initOptimizer = async () => { - if (isDepsOptimizerEnabled(config)) { - await initDepsOptimizer(config, server) + let initingServer: Promise | undefined + let serverInited = false + const initServer = async () => { + if (serverInited) { + return + } + if (initingServer) { + return initingServer } + initingServer = (async function () { + await container.buildStart({}) + if (isDepsOptimizerEnabled(config)) { + await initDepsOptimizer(config, server) + } + initingServer = undefined + serverInited = true + })() + return await initServer } if (!middlewareMode && httpServer) { - let isOptimized = false // overwrite listen to init optimizer before server start const listen = httpServer.listen.bind(httpServer) httpServer.listen = (async (port: number, ...args: any[]) => { - if (!isOptimized) { - try { - await container.buildStart({}) - await initOptimizer() - isOptimized = true - } catch (e) { - httpServer.emit('error', e) - return - } + try { + await initServer() + } catch (e) { + httpServer.emit('error', e) + return } return listen(port, ...args) }) as any } else { - await container.buildStart({}) - await initOptimizer() + await initServer() } return server From 0cb384b2bb5653ca14045ca51e33d273bbb38310 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sun, 3 Jul 2022 16:00:08 +0200 Subject: [PATCH 23/30] chore: use hints for snapshots --- .../__snapshots__/vue-sourcemap.spec.ts.snap | 24 +++++++------- .../__tests__/vue-sourcemap.spec.ts | 32 ++++++++++++------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/playground/vue-sourcemap/__tests__/__snapshots__/vue-sourcemap.spec.ts.snap b/playground/vue-sourcemap/__tests__/__snapshots__/vue-sourcemap.spec.ts.snap index e91b6ce384c562..d2600ee6edccce 100644 --- a/playground/vue-sourcemap/__tests__/__snapshots__/vue-sourcemap.spec.ts.snap +++ b/playground/vue-sourcemap/__tests__/__snapshots__/vue-sourcemap.spec.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1 -exports[`serve:vue-sourcemap > css 1`] = ` +exports[`serve:vue-sourcemap > css > serve-css 1`] = ` { "mappings": ";AAQA;EACE,UAAU;AACZ", "sources": [ @@ -47,7 +47,7 @@ exports[`serve:vue-sourcemap > css 1`] = ` } `; -exports[`serve:vue-sourcemap > css module 1`] = ` +exports[`serve:vue-sourcemap > css module > serve-css-module 1`] = ` { "mappings": ";AAcA;EACE,UAAU;AACZ", "sources": [ @@ -94,7 +94,7 @@ exports[`serve:vue-sourcemap > css module 1`] = ` } `; -exports[`serve:vue-sourcemap > css scoped 1`] = ` +exports[`serve:vue-sourcemap > css scoped > serve-css-scoped 1`] = ` { "mappings": ";AAoBA;EACE,UAAU;AACZ", "sources": [ @@ -141,7 +141,7 @@ exports[`serve:vue-sourcemap > css scoped 1`] = ` } `; -exports[`serve:vue-sourcemap > js 1`] = ` +exports[`serve:vue-sourcemap > js > serve-js 1`] = ` { "mappings": "AAKA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;;;;;;AAGP;AACd,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;;;;;;;;;;;wBARlB,oBAAiB,WAAd,MAAU", "sources": [ @@ -165,7 +165,7 @@ console.log('setup') } `; -exports[`serve:vue-sourcemap > less with additionalData 1`] = ` +exports[`serve:vue-sourcemap > less with additionalData > serve-less-with-additionalData 1`] = ` { "mappings": "AAKA;EACE", "sources": [ @@ -187,7 +187,7 @@ exports[`serve:vue-sourcemap > less with additionalData 1`] = ` } `; -exports[`serve:vue-sourcemap > no script 1`] = ` +exports[`serve:vue-sourcemap > no script > serve-no-script 1`] = ` { "mappings": ";;;wBACE,oBAAwB,WAArB,aAAiB", "sourceRoot": "", @@ -204,7 +204,7 @@ exports[`serve:vue-sourcemap > no script 1`] = ` } `; -exports[`serve:vue-sourcemap > no template 1`] = ` +exports[`serve:vue-sourcemap > no template > serve-no-template 1`] = ` { "mappings": "AACA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;;;;;;AAGP;AACd,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;;;;;;;", "sources": [ @@ -224,7 +224,7 @@ console.log('setup') } `; -exports[`serve:vue-sourcemap > sass 1`] = ` +exports[`serve:vue-sourcemap > sass > serve-sass 1`] = ` { "mappings": "AAKA;EACE", "sources": [ @@ -245,7 +245,7 @@ exports[`serve:vue-sourcemap > sass 1`] = ` } `; -exports[`serve:vue-sourcemap > sass with import 1`] = ` +exports[`serve:vue-sourcemap > sass with import > serve-sass-with-import 1`] = ` { "mappings": "AAAA;EACE;;ACOF;EACE", "sources": [ @@ -273,7 +273,7 @@ exports[`serve:vue-sourcemap > sass with import 1`] = ` } `; -exports[`serve:vue-sourcemap > src imported 1`] = ` +exports[`serve:vue-sourcemap > src imported > serve-src-imported 1`] = ` { "mappings": "AAAA;EACE,UAAU;AACZ", "sources": [ @@ -289,7 +289,7 @@ exports[`serve:vue-sourcemap > src imported 1`] = ` } `; -exports[`serve:vue-sourcemap > src imported sass 1`] = ` +exports[`serve:vue-sourcemap > src imported sass > serve-src-imported-sass 1`] = ` { "mappings": "AAAA;EACE;;ACCF;EACE", "sources": [ @@ -310,7 +310,7 @@ exports[`serve:vue-sourcemap > src imported sass 1`] = ` } `; -exports[`serve:vue-sourcemap > ts 1`] = ` +exports[`serve:vue-sourcemap > ts > serve-ts 1`] = ` { "mappings": ";AAKA,QAAQ,IAAI,WAAW;;;;;AAIvB,YAAQ,IAAI,UAAU;;;;;;;;uBARpB,oBAAiB,WAAd,MAAU", "sources": [ diff --git a/playground/vue-sourcemap/__tests__/vue-sourcemap.spec.ts b/playground/vue-sourcemap/__tests__/vue-sourcemap.spec.ts index adf0dfbccb6bda..2241af413eee94 100644 --- a/playground/vue-sourcemap/__tests__/vue-sourcemap.spec.ts +++ b/playground/vue-sourcemap/__tests__/vue-sourcemap.spec.ts @@ -24,62 +24,70 @@ describe.runIf(isServe)('serve:vue-sourcemap', () => { const res = await page.request.get(new URL('./Js.vue', page.url()).href) const js = await res.text() const map = extractSourcemap(js) - expect(formatSourcemapForSnapshot(map)).toMatchSnapshot() + expect(formatSourcemapForSnapshot(map)).toMatchSnapshot('serve-js') }) test('ts', async () => { const res = await page.request.get(new URL('./Ts.vue', page.url()).href) const js = await res.text() const map = extractSourcemap(js) - expect(formatSourcemapForSnapshot(map)).toMatchSnapshot() + expect(formatSourcemapForSnapshot(map)).toMatchSnapshot('serve-ts') }) test('css', async () => { const css = await getStyleTagContentIncluding('.css ') const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchSnapshot() + expect(formatSourcemapForSnapshot(map)).toMatchSnapshot('serve-css') }) test('css module', async () => { const css = await getStyleTagContentIncluding('._css-module_') const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchSnapshot() + expect(formatSourcemapForSnapshot(map)).toMatchSnapshot('serve-css-module') }) test('css scoped', async () => { const css = await getStyleTagContentIncluding('.css-scoped[data-v-') const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchSnapshot() + expect(formatSourcemapForSnapshot(map)).toMatchSnapshot('serve-css-scoped') }) test('sass', async () => { const css = await getStyleTagContentIncluding('.sass ') const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchSnapshot() + expect(formatSourcemapForSnapshot(map)).toMatchSnapshot('serve-sass') }) test('sass with import', async () => { const css = await getStyleTagContentIncluding('.sass-with-import ') const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchSnapshot() + expect(formatSourcemapForSnapshot(map)).toMatchSnapshot( + 'serve-sass-with-import' + ) }) test('less with additionalData', async () => { const css = await getStyleTagContentIncluding('.less ') const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchSnapshot() + expect(formatSourcemapForSnapshot(map)).toMatchSnapshot( + 'serve-less-with-additionalData' + ) }) test('src imported', async () => { const css = await getStyleTagContentIncluding('.src-import[data-v-') const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchSnapshot() + expect(formatSourcemapForSnapshot(map)).toMatchSnapshot( + 'serve-src-imported' + ) }) test('src imported sass', async () => { const css = await getStyleTagContentIncluding('.src-import-sass[data-v-') const map = extractSourcemap(css) - expect(formatSourcemapForSnapshot(map)).toMatchSnapshot() + expect(formatSourcemapForSnapshot(map)).toMatchSnapshot( + 'serve-src-imported-sass' + ) }) test('no script', async () => { @@ -88,7 +96,7 @@ describe.runIf(isServe)('serve:vue-sourcemap', () => { ) const js = await res.text() const map = extractSourcemap(js) - expect(formatSourcemapForSnapshot(map)).toMatchSnapshot() + expect(formatSourcemapForSnapshot(map)).toMatchSnapshot('serve-no-script') }) test('no template', async () => { @@ -97,7 +105,7 @@ describe.runIf(isServe)('serve:vue-sourcemap', () => { ) const js = await res.text() const map = extractSourcemap(js) - expect(formatSourcemapForSnapshot(map)).toMatchSnapshot() + expect(formatSourcemapForSnapshot(map)).toMatchSnapshot('serve-no-template') }) }) From 23df866d8a71c9db915a378c1886b954d29e3dee Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sun, 3 Jul 2022 16:07:34 +0200 Subject: [PATCH 24/30] chore: remove optimizeDeps guard --- packages/vite/src/node/optimizer/index.ts | 37 +---------------------- 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 55b7ab10a984b0..7f02ac23790ff4 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -429,46 +429,11 @@ export function depsLogString(qualifiedIds: string[]): string { } } -const esbuildQueue: (() => Promise)[] = [] -let esbuildProcessing = false -export function enqueueEsbuildTask(task: () => Promise): void { - esbuildQueue.push(task) - if (!esbuildProcessing) { - processNext() - } -} -async function processNext() { - const task = esbuildQueue.shift() - if (task) { - esbuildProcessing = true - await task() - if (esbuildQueue.length > 0) { - processNext() - } else { - esbuildProcessing = false - } - } -} - -export function runOptimizeDeps( - resolvedConfig: ResolvedConfig, - depsInfo: Record, - ssr: boolean = !!resolvedConfig.build.ssr -): Promise { - return new Promise((resolve) => { - enqueueEsbuildTask(() => { - const result = _runOptimizeDeps(resolvedConfig, depsInfo, ssr) - resolve(result) - return result - }) - }) -} - /** * Internally, Vite uses this function to prepare a optimizeDeps run. When Vite starts, we can get * the metadata and start the server without waiting for the optimizeDeps processing to be completed */ -async function _runOptimizeDeps( +export async function runOptimizeDeps( resolvedConfig: ResolvedConfig, depsInfo: Record, ssr: boolean = !!resolvedConfig.build.ssr From d81a855726f41985b7ca2a79d32b6380db2e6804 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sun, 3 Jul 2022 23:51:07 +0200 Subject: [PATCH 25/30] fix: SSR deps external/noExternal optimization --- .../src/node/optimizer/esbuildDepPlugin.ts | 13 ++++- packages/vite/src/node/optimizer/index.ts | 2 +- .../ssr-deps/__tests__/ssr-deps.spec.ts | 14 ++++++ playground/ssr-deps/nested-external/index.js | 9 ++++ .../ssr-deps/nested-external/package.json | 7 +++ .../index.js | 3 ++ .../package.json | 10 ++++ .../index.js | 5 ++ .../package.json | 8 +++ .../optimized-with-nested-external/index.js | 5 ++ .../package.json | 10 ++++ playground/ssr-deps/package.json | 5 +- playground/ssr-deps/server.js | 9 +++- playground/ssr-deps/src/app.js | 15 ++++++ pnpm-lock.yaml | 49 +++++++++++++++++++ 15 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 playground/ssr-deps/nested-external/index.js create mode 100644 playground/ssr-deps/nested-external/package.json create mode 100644 playground/ssr-deps/non-optimized-with-nested-external/index.js create mode 100644 playground/ssr-deps/non-optimized-with-nested-external/package.json create mode 100644 playground/ssr-deps/optimized-cjs-with-nested-external/index.js create mode 100644 playground/ssr-deps/optimized-cjs-with-nested-external/package.json create mode 100644 playground/ssr-deps/optimized-with-nested-external/index.js create mode 100644 playground/ssr-deps/optimized-with-nested-external/package.json diff --git a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts index d41caca22d2d4a..31d55e8e2e1b91 100644 --- a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts +++ b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts @@ -46,6 +46,17 @@ export function esbuildDepPlugin( config: ResolvedConfig, ssr: boolean ): Plugin { + let explicitExternal = config.optimizeDeps?.exclude + const ssrExternal = config.ssr?.external + if (ssr && ssrExternal?.length) { + explicitExternal ||= [] + for (const id of ssrExternal) { + if (!explicitExternal.includes(id)) { + explicitExternal.push(id) + } + } + } + // remove optimizable extensions from `externalTypes` list const allExternalTypes = config.optimizeDeps.extensions ? externalTypes.filter( @@ -163,7 +174,7 @@ export function esbuildDepPlugin( build.onResolve( { filter: /^[\w@][^:]/ }, async ({ path: id, importer, kind }) => { - if (moduleListContains(config.optimizeDeps?.exclude, id)) { + if (moduleListContains(explicitExternal, id)) { return { path: id, external: true diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 7f02ac23790ff4..b7008385a78512 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -271,7 +271,7 @@ export async function optimizeServerSsrDeps( noExternalFilter = noExternal === true ? (dep: unknown) => false - : createFilter(noExternal, config.optimizeDeps?.exclude, { + : createFilter(undefined, config.optimizeDeps?.exclude, { resolve: false }) } diff --git a/playground/ssr-deps/__tests__/ssr-deps.spec.ts b/playground/ssr-deps/__tests__/ssr-deps.spec.ts index dbb169647fc603..1e81b5fc689486 100644 --- a/playground/ssr-deps/__tests__/ssr-deps.spec.ts +++ b/playground/ssr-deps/__tests__/ssr-deps.spec.ts @@ -77,3 +77,17 @@ test('msg from no external cjs', async () => { await page.goto(url) expect(await page.textContent('.no-external-cjs-msg')).toMatch('Hello World!') }) + +test('msg from optimized with nested external', async () => { + await page.goto(url) + expect(await page.textContent('.optimized-with-nested-external')).toMatch( + 'Hello World!' + ) +}) + +test('msg from optimized cjs with nested external', async () => { + await page.goto(url) + expect(await page.textContent('.optimized-cjs-with-nested-external')).toMatch( + 'Hello World!' + ) +}) diff --git a/playground/ssr-deps/nested-external/index.js b/playground/ssr-deps/nested-external/index.js new file mode 100644 index 00000000000000..1e211c295b64ef --- /dev/null +++ b/playground/ssr-deps/nested-external/index.js @@ -0,0 +1,9 @@ +// Module with state, to check that it is properly externalized and +// not bundled in the optimized deps +let msg +export function setMessage(externalMsg) { + msg = externalMsg +} +export default function getMessage() { + return msg +} diff --git a/playground/ssr-deps/nested-external/package.json b/playground/ssr-deps/nested-external/package.json new file mode 100644 index 00000000000000..40d1fd957af246 --- /dev/null +++ b/playground/ssr-deps/nested-external/package.json @@ -0,0 +1,7 @@ +{ + "name": "nested-external", + "private": true, + "version": "0.0.0", + "main": "index.js", + "type": "module" +} diff --git a/playground/ssr-deps/non-optimized-with-nested-external/index.js b/playground/ssr-deps/non-optimized-with-nested-external/index.js new file mode 100644 index 00000000000000..cab1f9715fa51e --- /dev/null +++ b/playground/ssr-deps/non-optimized-with-nested-external/index.js @@ -0,0 +1,3 @@ +import { setMessage } from 'nested-external' + +setMessage('Hello World!') diff --git a/playground/ssr-deps/non-optimized-with-nested-external/package.json b/playground/ssr-deps/non-optimized-with-nested-external/package.json new file mode 100644 index 00000000000000..dc212bfb46caec --- /dev/null +++ b/playground/ssr-deps/non-optimized-with-nested-external/package.json @@ -0,0 +1,10 @@ +{ + "name": "non-optimized-with-nested-external", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "index.js", + "dependencies": { + "nested-external": "file:../nested-external" + } +} diff --git a/playground/ssr-deps/optimized-cjs-with-nested-external/index.js b/playground/ssr-deps/optimized-cjs-with-nested-external/index.js new file mode 100644 index 00000000000000..adbbb819ed31ea --- /dev/null +++ b/playground/ssr-deps/optimized-cjs-with-nested-external/index.js @@ -0,0 +1,5 @@ +const getMessage = require('nested-external') + +module.exports = { + hello: getMessage +} diff --git a/playground/ssr-deps/optimized-cjs-with-nested-external/package.json b/playground/ssr-deps/optimized-cjs-with-nested-external/package.json new file mode 100644 index 00000000000000..d2b17b82d7cdea --- /dev/null +++ b/playground/ssr-deps/optimized-cjs-with-nested-external/package.json @@ -0,0 +1,8 @@ +{ + "name": "optimized-cjs-with-nested-external", + "private": true, + "version": "0.0.0", + "dependencies": { + "nested-external": "file:../nested-external" + } +} diff --git a/playground/ssr-deps/optimized-with-nested-external/index.js b/playground/ssr-deps/optimized-with-nested-external/index.js new file mode 100644 index 00000000000000..3b0d9135b3e5b1 --- /dev/null +++ b/playground/ssr-deps/optimized-with-nested-external/index.js @@ -0,0 +1,5 @@ +import getMessage from 'nested-external' + +export function hello() { + return getMessage() +} diff --git a/playground/ssr-deps/optimized-with-nested-external/package.json b/playground/ssr-deps/optimized-with-nested-external/package.json new file mode 100644 index 00000000000000..a1596220e04091 --- /dev/null +++ b/playground/ssr-deps/optimized-with-nested-external/package.json @@ -0,0 +1,10 @@ +{ + "name": "optimized-with-nested-external", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "index.js", + "dependencies": { + "nested-external": "file:../nested-external" + } +} diff --git a/playground/ssr-deps/package.json b/playground/ssr-deps/package.json index 81068e24d3474d..20efa9753bebc9 100644 --- a/playground/ssr-deps/package.json +++ b/playground/ssr-deps/package.json @@ -21,7 +21,10 @@ "ts-transpiled-exports": "file:./ts-transpiled-exports", "no-external-cjs": "file:./no-external-cjs", "import-builtin-cjs": "file:./import-builtin-cjs", - "no-external-css": "file:./no-external-css" + "no-external-css": "file:./no-external-css", + "non-optimized-with-nested-external": "file:./non-optimized-with-nested-external", + "optimized-with-nested-external": "file:./optimized-with-nested-external", + "optimized-cjs-with-nested-external": "file:./optimized-with-nested-external" }, "devDependencies": { "cross-env": "^7.0.3", diff --git a/playground/ssr-deps/server.js b/playground/ssr-deps/server.js index 432ff69ca3b7d8..ea34614018ee3e 100644 --- a/playground/ssr-deps/server.js +++ b/playground/ssr-deps/server.js @@ -35,7 +35,14 @@ export async function createServer(root = process.cwd(), hmrPort) { }, appType: 'custom', ssr: { - noExternal: ['no-external-cjs', 'import-builtin-cjs', 'no-external-css'] + noExternal: ['no-external-cjs', 'import-builtin-cjs', 'no-external-css'], + external: ['nested-external'] + }, + optimizeDeps: { + include: [ + 'optimized-with-nested-external', + 'optimized-cjs-with-nested-external' + ] } }) // use vite's connect instance as middleware diff --git a/playground/ssr-deps/src/app.js b/playground/ssr-deps/src/app.js index f5b0ad810421fb..26df58bde7439d 100644 --- a/playground/ssr-deps/src/app.js +++ b/playground/ssr-deps/src/app.js @@ -12,6 +12,14 @@ import requireAbsolute from 'require-absolute' import noExternalCjs from 'no-external-cjs' import importBuiltinCjs from 'import-builtin-cjs' +// This import will set a 'Hello World!" message in the nested-external non-entry dependency +import 'non-optimized-with-nested-external' + +// These two are optimized and get the message from nested-external, if the dependency is +// not properly externalized and ends up bundled, the message will be undefined +import optimizedWithNestedExternal from 'optimized-with-nested-external' +import optimizedCjsWithNestedExternal from 'optimized-cjs-with-nested-external' + export async function render(url, rootDir) { let html = '' @@ -53,5 +61,12 @@ export async function render(url, rootDir) { const importBuiltinCjsMessage = importBuiltinCjs.hello() html += `\n

message from import-builtin-cjs: ${importBuiltinCjsMessage}

` + const optimizedWithNestedExternalMessage = optimizedWithNestedExternal.hello() + html += `\n

message from optimized-with-nested-external: ${optimizedWithNestedExternalMessage}

` + + const optimizedCjsWithNestedExternalMessage = + optimizedCjsWithNestedExternal.hello() + html += `\n

message from optimized-cjs-with-nested-external: ${optimizedCjsWithNestedExternalMessage}

` + return html + '\n' } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1874e44d194ecb..e34d43a7bf8237 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -882,8 +882,11 @@ importers: import-builtin-cjs: file:./import-builtin-cjs no-external-cjs: file:./no-external-cjs no-external-css: file:./no-external-css + non-optimized-with-nested-external: file:./non-optimized-with-nested-external object-assigned-exports: file:./object-assigned-exports only-object-assigned-exports: file:./only-object-assigned-exports + optimized-cjs-with-nested-external: file:./optimized-with-nested-external + optimized-with-nested-external: file:./optimized-with-nested-external primitive-export: file:./primitive-export read-file-content: file:./read-file-content require-absolute: file:./require-absolute @@ -896,8 +899,11 @@ importers: import-builtin-cjs: file:playground/ssr-deps/import-builtin-cjs no-external-cjs: file:playground/ssr-deps/no-external-cjs no-external-css: file:playground/ssr-deps/no-external-css + non-optimized-with-nested-external: file:playground/ssr-deps/non-optimized-with-nested-external object-assigned-exports: file:playground/ssr-deps/object-assigned-exports only-object-assigned-exports: file:playground/ssr-deps/only-object-assigned-exports + optimized-cjs-with-nested-external: file:playground/ssr-deps/optimized-with-nested-external + optimized-with-nested-external: file:playground/ssr-deps/optimized-with-nested-external primitive-export: file:playground/ssr-deps/primitive-export read-file-content: file:playground/ssr-deps/read-file-content require-absolute: file:playground/ssr-deps/require-absolute @@ -921,18 +927,39 @@ importers: playground/ssr-deps/import-builtin-cjs: specifiers: {} + playground/ssr-deps/nested-external: + specifiers: {} + playground/ssr-deps/no-external-cjs: specifiers: {} playground/ssr-deps/no-external-css: specifiers: {} + playground/ssr-deps/non-optimized-with-nested-external: + specifiers: + nested-external: file:../nested-external + dependencies: + nested-external: file:playground/ssr-deps/nested-external + playground/ssr-deps/object-assigned-exports: specifiers: {} playground/ssr-deps/only-object-assigned-exports: specifiers: {} + playground/ssr-deps/optimized-cjs-with-nested-external: + specifiers: + nested-external: file:../nested-external + dependencies: + nested-external: file:playground/ssr-deps/nested-external + + playground/ssr-deps/optimized-with-nested-external: + specifiers: + nested-external: file:../nested-external + dependencies: + nested-external: file:playground/ssr-deps/nested-external + playground/ssr-deps/primitive-export: specifiers: {} @@ -8911,6 +8938,12 @@ packages: version: 0.0.0 dev: false + file:playground/ssr-deps/nested-external: + resolution: {directory: playground/ssr-deps/nested-external, type: directory} + name: nested-external + version: 0.0.0 + dev: false + file:playground/ssr-deps/no-external-cjs: resolution: {directory: playground/ssr-deps/no-external-cjs, type: directory} name: no-external-cjs @@ -8923,6 +8956,14 @@ packages: version: 0.0.0 dev: false + file:playground/ssr-deps/non-optimized-with-nested-external: + resolution: {directory: playground/ssr-deps/non-optimized-with-nested-external, type: directory} + name: non-optimized-with-nested-external + version: 0.0.0 + dependencies: + nested-external: file:playground/ssr-deps/nested-external + dev: false + file:playground/ssr-deps/object-assigned-exports: resolution: {directory: playground/ssr-deps/object-assigned-exports, type: directory} name: object-assigned-exports @@ -8935,6 +8976,14 @@ packages: version: 0.0.0 dev: false + file:playground/ssr-deps/optimized-with-nested-external: + resolution: {directory: playground/ssr-deps/optimized-with-nested-external, type: directory} + name: optimized-with-nested-external + version: 0.0.0 + dependencies: + nested-external: file:playground/ssr-deps/nested-external + dev: false + file:playground/ssr-deps/primitive-export: resolution: {directory: playground/ssr-deps/primitive-export, type: directory} name: primitive-export From 1a58e7d046e8ada5c97082679cb0ed3799798c35 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Mon, 4 Jul 2022 00:55:13 +0200 Subject: [PATCH 26/30] fix: don't collide external/noExternal with optimizeDeps --- .../src/node/optimizer/esbuildDepPlugin.ts | 13 +--------- packages/vite/src/node/optimizer/index.ts | 25 +------------------ playground/ssr-deps/server.js | 5 +++- 3 files changed, 6 insertions(+), 37 deletions(-) diff --git a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts index 31d55e8e2e1b91..d41caca22d2d4a 100644 --- a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts +++ b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts @@ -46,17 +46,6 @@ export function esbuildDepPlugin( config: ResolvedConfig, ssr: boolean ): Plugin { - let explicitExternal = config.optimizeDeps?.exclude - const ssrExternal = config.ssr?.external - if (ssr && ssrExternal?.length) { - explicitExternal ||= [] - for (const id of ssrExternal) { - if (!explicitExternal.includes(id)) { - explicitExternal.push(id) - } - } - } - // remove optimizable extensions from `externalTypes` list const allExternalTypes = config.optimizeDeps.extensions ? externalTypes.filter( @@ -174,7 +163,7 @@ export function esbuildDepPlugin( build.onResolve( { filter: /^[\w@][^:]/ }, async ({ path: id, importer, kind }) => { - if (moduleListContains(explicitExternal, id)) { + if (moduleListContains(config.optimizeDeps?.exclude, id)) { return { path: id, external: true diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index b7008385a78512..724c64b750fe85 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -6,10 +6,8 @@ import colors from 'picocolors' import type { BuildOptions as EsbuildBuildOptions } from 'esbuild' import { build } from 'esbuild' import { init, parse } from 'es-module-lexer' -import { createFilter } from '@rollup/pluginutils' import type { ResolvedConfig } from '../config' import { - arraify, createDebugger, emptyDir, flattenId, @@ -260,30 +258,9 @@ export async function optimizeServerSsrDeps( return cachedMetadata } - let alsoInclude: string[] | undefined - let noExternalFilter: ((id: unknown) => boolean) | undefined - - const noExternal = config.ssr?.noExternal - if (noExternal) { - alsoInclude = arraify(noExternal).filter( - (ne) => typeof ne === 'string' - ) as string[] - noExternalFilter = - noExternal === true - ? (dep: unknown) => false - : createFilter(undefined, config.optimizeDeps?.exclude, { - resolve: false - }) - } - const deps: Record = {} - await addManuallyIncludedOptimizeDeps( - deps, - config, - alsoInclude, - noExternalFilter - ) + await addManuallyIncludedOptimizeDeps(deps, config) const depsInfo = toDiscoveredDependencies(config, deps, '', true) diff --git a/playground/ssr-deps/server.js b/playground/ssr-deps/server.js index ea34614018ee3e..7075cbcacc4d7a 100644 --- a/playground/ssr-deps/server.js +++ b/playground/ssr-deps/server.js @@ -40,9 +40,12 @@ export async function createServer(root = process.cwd(), hmrPort) { }, optimizeDeps: { include: [ + 'no-external-cjs', + 'import-builtin-cjs', 'optimized-with-nested-external', 'optimized-cjs-with-nested-external' - ] + ], + exclude: ['nested-external'] } }) // use vite's connect instance as middleware From 48b215e54a0a98f27f62594b02472e66196dd625 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Mon, 4 Jul 2022 08:10:39 +0200 Subject: [PATCH 27/30] fix: correct init server await --- packages/vite/src/node/server/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index a666f6c3a6665e..ec982266647131 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -545,7 +545,7 @@ export async function createServer( initingServer = undefined serverInited = true })() - return await initServer + return initingServer } if (!middlewareMode && httpServer) { From 96b9170f7db566b828cfc895501bc6b70bd3667e Mon Sep 17 00:00:00 2001 From: patak-dev Date: Mon, 4 Jul 2022 08:25:53 +0200 Subject: [PATCH 28/30] chore: reduce changeset --- packages/vite/src/node/optimizer/index.ts | 19 ++++----- packages/vite/src/node/optimizer/optimizer.ts | 9 +++-- packages/vite/src/node/optimizer/scan.ts | 2 +- .../vite/src/node/plugins/importAnalysis.ts | 40 ++++++++++--------- .../src/node/plugins/importAnalysisBuild.ts | 2 +- packages/vite/src/node/plugins/resolve.ts | 2 +- packages/vite/src/node/server/index.ts | 2 +- .../vite/src/node/server/transformRequest.ts | 15 ------- 8 files changed, 38 insertions(+), 53 deletions(-) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 724c64b750fe85..0985e85f3a8538 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -50,7 +50,7 @@ export type ExportsData = { export interface DepsOptimizer { metadata: DepOptimizationMetadata - scanning?: Promise + scanProcessing?: Promise registerMissingImport: ( id: string, resolved: string, @@ -236,7 +236,7 @@ export async function optimizeDeps( await addManuallyIncludedOptimizeDeps(deps, config) - const depsInfo = toDiscoveredDependencies(config, deps) + const depsInfo = toDiscoveredDependencies(config, deps, !!config.build.ssr) const result = await runOptimizeDeps(config, depsInfo) @@ -262,7 +262,7 @@ export async function optimizeServerSsrDeps( await addManuallyIncludedOptimizeDeps(deps, config) - const depsInfo = toDiscoveredDependencies(config, deps, '', true) + const depsInfo = toDiscoveredDependencies(config, deps, true) const result = await runOptimizeDeps(config, depsInfo, true) @@ -369,8 +369,8 @@ export async function discoverProjectDependencies( export function toDiscoveredDependencies( config: ResolvedConfig, deps: Record, - timestamp: string = '', - ssr: boolean = !!config.build.ssr + ssr: boolean, + timestamp: string = '' ): Record { const browserHash = getOptimizedBrowserHash( getDepHash(config), @@ -456,9 +456,6 @@ export async function runOptimizeDeps( const processingResult: DepOptimizationResult = { metadata, async commit() { - // We let the optimizeDeps caller modify the browserHash of dependencies before committing - commitMetadata() - // Write metadata file, delete `deps` folder and rename the `processing` folder to `deps` // Processing is done, we can now replace the depsCacheDir with processingCacheDir // Rewire the file paths from the temporal processing dir to the final deps cache dir @@ -605,10 +602,8 @@ export async function runOptimizeDeps( } } - function commitMetadata() { - const dataPath = path.join(processingCacheDir, '_metadata.json') - writeFile(dataPath, stringifyDepsOptimizerMetadata(metadata, depsCacheDir)) - } + const dataPath = path.join(processingCacheDir, '_metadata.json') + writeFile(dataPath, stringifyDepsOptimizerMetadata(metadata, depsCacheDir)) debug(`deps bundled in ${(performance.now() - start).toFixed(2)}ms`) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index a7d30403245a78..ea3b2e0a22358f 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -76,7 +76,7 @@ export async function initDevSsrDepsOptimizer( if (!getDepsOptimizer(config, { ssr: false })) { await initDepsOptimizer(config, server) } - await getDepsOptimizer(config, { ssr: false })!.scanning + await getDepsOptimizer(config, { ssr: false })!.scanProcessing await createDevSsrDepsOptimizer(config) creatingDevSsrOptimizer = undefined @@ -162,6 +162,7 @@ async function createDepsOptimizer( const discovered = await toDiscoveredDependencies( config, deps, + !!config.build.ssr, sessionTimestamp ) @@ -182,7 +183,7 @@ async function createDepsOptimizer( async function runScanner() { const scanPhaseProcessing = newDepOptimizationProcessing() - depsOptimizer.scanning = scanPhaseProcessing.promise + depsOptimizer.scanProcessing = scanPhaseProcessing.promise try { debug(colors.green(`scanning for dependencies...`)) @@ -221,7 +222,7 @@ async function createDepsOptimizer( logger.error(e.message) } finally { scanPhaseProcessing.resolve() - depsOptimizer.scanning = undefined + depsOptimizer.scanProcessing = undefined } } @@ -572,7 +573,7 @@ async function createDepsOptimizer( // Await for the scan+optimize step running in the background // It normally should be over by the time crawling of user code ended - await depsOptimizer.scanning + await depsOptimizer.scanProcessing if (!isBuild && postScanOptimizationResult) { const result = await postScanOptimizationResult diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts index f848af4af03869..4954b56b709755 100644 --- a/packages/vite/src/node/optimizer/scan.ts +++ b/packages/vite/src/node/optimizer/scan.ts @@ -128,7 +128,7 @@ function orderedDependencies(deps: Record) { return Object.fromEntries(depsList) } -export function globEntries( +function globEntries( pattern: string | string[], config: ResolvedConfig ): Promise { diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index ce2e981ec01a3d..4fdc46fa2c611f 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -49,14 +49,17 @@ import { cjsShouldExternalizeForSSR, shouldExternalizeForSSR } from '../ssr/ssrExternal' -import { preTransformRequest } from '../server/transformRequest' +import { transformRequest } from '../server/transformRequest' import { getDepsCacheDirPrefix, getDepsOptimizer, optimizedDepNeedsInterop } from '../optimizer' import { checkPublicFile } from './asset' -import { throwOutdatedRequest } from './optimizedDeps' +import { + ERR_OUTDATED_OPTIMIZED_DEP, + throwOutdatedRequest +} from './optimizedDeps' import { isCSSRequest, isDirectCSSRequest } from './css' import { browserExternalId } from './resolve' @@ -241,7 +244,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { let s: MagicString | undefined const str = () => s || (s = new MagicString(source)) const importedUrls = new Set() - const importedUrlsToPreTransform = new Set<{ url: string; id: string }>() + const staticImportedUrls = new Set<{ url: string; id: string }>() const acceptedUrls = new Set<{ url: string start: number @@ -266,7 +269,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { let importerFile = importer if (moduleListContains(config.optimizeDeps?.exclude, url)) { if (depsOptimizer) { - await depsOptimizer.scanning + await depsOptimizer.scanProcessing // if the dependency encountered in the optimized file was excluded from the optimization // the dependency needs to be resolved starting from the original source location of the optimized file // because starting from node_modules/.vite will not find the dependency if it was not hoisted @@ -527,15 +530,8 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { ) } - const importedUrl = { - id: resolvedId, - url: unwrapId(removeImportQuery(urlWithoutBase)).replace( - NULL_BYTE_PLACEHOLDER, - '\0' - ) - } if (!isDynamicImport) { - importedUrlsToPreTransform.add(importedUrl) + staticImportedUrls.add({ url: urlWithoutBase, id: resolvedId }) } } else if (!importer.startsWith(clientDir) && !ssr) { // check @vite-ignore which suppresses dynamic import warning @@ -690,12 +686,20 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { // pre-transform known direct imports // These requests will also be registered in transformRequest to be awaited // by the deps optimizer - if ( - config.server.preTransformRequests && - importedUrlsToPreTransform.size - ) { - importedUrlsToPreTransform.forEach(({ url }) => { - preTransformRequest(url, server, { ssr }) + if (config.server.preTransformRequests && staticImportedUrls.size) { + staticImportedUrls.forEach(({ url, id }) => { + url = unwrapId(removeImportQuery(url)).replace( + NULL_BYTE_PLACEHOLDER, + '\0' + ) + transformRequest(url, server, { ssr }).catch((e) => { + if (e?.code === ERR_OUTDATED_OPTIMIZED_DEP) { + // This are expected errors + return + } + // Unexpected error, log the issue but avoid an unhandled exception + config.logger.error(e.message) + }) }) } diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index f3f310bcfc6034..c8c2e50fc8ba6b 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -164,7 +164,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { if (moduleListContains(config.optimizeDeps?.exclude, url)) { if (depsOptimizer) { - await depsOptimizer.scanning + await depsOptimizer.scanProcessing // if the dependency encountered in the optimized file was excluded from the optimization // the dependency needs to be resolved starting from the original source location of the optimized file // because starting from node_modules/.vite will not find the dependency if it was not hoisted diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index ea230153d33df0..a18a30d72ee073 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -723,7 +723,7 @@ export async function tryOptimizedResolve( // is used in the preAliasPlugin to decide if an aliased dep is optimized, // and avoid replacing the bare import with the resolved path. // We should be able to remove this in the future - await depsOptimizer.scanning + await depsOptimizer.scanProcessing const metadata = depsOptimizer.metadata diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index ec982266647131..9b3c9f9960b10b 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -778,7 +778,7 @@ async function updateCjsSsrExternals(server: ViteDevServer) { // legacy scheme. It may be removed in a future v3 minor. const depsOptimizer = getDepsOptimizer(server.config, { ssr: false }) if (depsOptimizer) { - await depsOptimizer.scanning + await depsOptimizer.scanProcessing knownImports = [ ...Object.keys(depsOptimizer.metadata.optimized), ...Object.keys(depsOptimizer.metadata.discovered) diff --git a/packages/vite/src/node/server/transformRequest.ts b/packages/vite/src/node/server/transformRequest.ts index 94a3bd028457eb..574e69b27524a5 100644 --- a/packages/vite/src/node/server/transformRequest.ts +++ b/packages/vite/src/node/server/transformRequest.ts @@ -16,7 +16,6 @@ import { timeFrom } from '../utils' import { checkPublicFile } from '../plugins/asset' -import { ERR_OUTDATED_OPTIMIZED_DEP } from '../plugins/optimizedDeps' import { ssrTransform } from '../ssr/ssrTransform' import { getDepsOptimizer } from '../optimizer' import { injectSourcesContent } from './sourcemap' @@ -277,17 +276,3 @@ async function loadAndTransform( return result } - -export async function preTransformRequest( - url: string, - server: ViteDevServer, - options: { ssr: boolean } = { ssr: false } -): Promise { - return transformRequest(url, server, options).catch((e) => { - if (e?.code !== ERR_OUTDATED_OPTIMIZED_DEP) { - // Unexpected error, log the issue but avoid an unhandled exception - server.config.logger.error(e.message) - } - return null - }) -} From 81b399272c0a5c21e3d301adbffbd6859c430fb4 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Mon, 4 Jul 2022 08:32:32 +0200 Subject: [PATCH 29/30] chore: reduce changeset --- packages/vite/src/node/optimizer/index.ts | 2 +- packages/vite/src/node/optimizer/optimizer.ts | 9 ++++++--- packages/vite/src/node/optimizer/scan.ts | 5 +---- packages/vite/src/node/plugins/importAnalysisBuild.ts | 1 + packages/vite/src/node/server/index.ts | 1 + 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 0985e85f3a8538..1ebcff01926568 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -370,7 +370,7 @@ export function toDiscoveredDependencies( config: ResolvedConfig, deps: Record, ssr: boolean, - timestamp: string = '' + timestamp?: string ): Record { const browserHash = getOptimizedBrowserHash( getDepHash(config), diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index ea3b2e0a22358f..5432f9493a7348 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -123,10 +123,13 @@ async function createDepsOptimizer( let newDepsToLogHandle: NodeJS.Timeout | undefined const logNewlyDiscoveredDeps = () => { if (newDepsToLog.length) { - const depsString = depsLogString(newDepsToLog) config.logger.info( - colors.green(`✨ new dependencies optimized: ${depsString}`), - { timestamp: true } + colors.green( + `✨ new dependencies optimized: ${depsLogString(newDepsToLog)}` + ), + { + timestamp: true + } ) newDepsToLog = [] } diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts index 4954b56b709755..5608472b4efe91 100644 --- a/packages/vite/src/node/optimizer/scan.ts +++ b/packages/vite/src/node/optimizer/scan.ts @@ -128,10 +128,7 @@ function orderedDependencies(deps: Record) { return Object.fromEntries(depsList) } -function globEntries( - pattern: string | string[], - config: ResolvedConfig -): Promise { +function globEntries(pattern: string | string[], config: ResolvedConfig) { return glob(pattern, { cwd: config.root, ignore: [ diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index c8c2e50fc8ba6b..613d7ca5a08503 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -165,6 +165,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { if (moduleListContains(config.optimizeDeps?.exclude, url)) { if (depsOptimizer) { await depsOptimizer.scanProcessing + // if the dependency encountered in the optimized file was excluded from the optimization // the dependency needs to be resolved starting from the original source location of the optimized file // because starting from node_modules/.vite will not find the dependency if it was not hoisted diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 9b3c9f9960b10b..9c722ed667a4e7 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -777,6 +777,7 @@ async function updateCjsSsrExternals(server: ViteDevServer) { // for backwards compatibility in case user needs to fallback to the // legacy scheme. It may be removed in a future v3 minor. const depsOptimizer = getDepsOptimizer(server.config, { ssr: false }) + if (depsOptimizer) { await depsOptimizer.scanProcessing knownImports = [ From 8861550072a64709c548d0b7148a90ee025fe868 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Mon, 4 Jul 2022 08:45:18 +0200 Subject: [PATCH 30/30] chore: remove changes to SSR/optimizeDeps, TBD in another PR --- packages/vite/src/node/optimizer/index.ts | 25 ++++++++++++++++++- .../vite/src/node/plugins/importAnalysis.ts | 2 ++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 1ebcff01926568..142a9519bb4b59 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -6,8 +6,10 @@ import colors from 'picocolors' import type { BuildOptions as EsbuildBuildOptions } from 'esbuild' import { build } from 'esbuild' import { init, parse } from 'es-module-lexer' +import { createFilter } from '@rollup/pluginutils' import type { ResolvedConfig } from '../config' import { + arraify, createDebugger, emptyDir, flattenId, @@ -258,9 +260,30 @@ export async function optimizeServerSsrDeps( return cachedMetadata } + let alsoInclude: string[] | undefined + let noExternalFilter: ((id: unknown) => boolean) | undefined + + const noExternal = config.ssr?.noExternal + if (noExternal) { + alsoInclude = arraify(noExternal).filter( + (ne) => typeof ne === 'string' + ) as string[] + noExternalFilter = + noExternal === true + ? (dep: unknown) => false + : createFilter(noExternal, config.optimizeDeps?.exclude, { + resolve: false + }) + } + const deps: Record = {} - await addManuallyIncludedOptimizeDeps(deps, config) + await addManuallyIncludedOptimizeDeps( + deps, + config, + alsoInclude, + noExternalFilter + ) const depsInfo = toDiscoveredDependencies(config, deps, true) diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index 4fdc46fa2c611f..5e87c92d2613c6 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -270,6 +270,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { if (moduleListContains(config.optimizeDeps?.exclude, url)) { if (depsOptimizer) { await depsOptimizer.scanProcessing + // if the dependency encountered in the optimized file was excluded from the optimization // the dependency needs to be resolved starting from the original source location of the optimized file // because starting from node_modules/.vite will not find the dependency if it was not hoisted @@ -531,6 +532,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { } if (!isDynamicImport) { + // for pre-transforming staticImportedUrls.add({ url: urlWithoutBase, id: resolvedId }) } } else if (!importer.startsWith(clientDir) && !ssr) {