From bef5de7f3d1b847e16849398de6d9fd1f2d13cb9 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Tue, 7 Jun 2022 16:00:15 +0200 Subject: [PATCH 1/3] fix: respect optimize deps entries --- .../vite/src/node/plugins/importAnalysis.ts | 4 +++- packages/vite/src/node/server/index.ts | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index 0c279da0601727..754afbd62ed350 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -599,7 +599,9 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { ) // pre-transform known direct imports - // TODO: we should also crawl dynamic 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( diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 33565d7390d601..08a5010b8cb789 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -7,6 +7,7 @@ import connect from 'connect' import corsMiddleware from 'cors' import colors from 'picocolors' import chokidar from 'chokidar' +import glob from 'fast-glob' import type { FSWatcher, WatchOptions } from 'types/chokidar' import type { Connect } from 'types/connect' import launchEditorMiddleware from 'launch-editor-middleware' @@ -521,6 +522,7 @@ export async function createServer( const initOptimizer = async () => { if (isDepsOptimizerEnabled(config)) { await initDepsOptimizer(config, server) + pretransformOptimizeDepsEntries(server) } } @@ -761,3 +763,24 @@ async function updateCjsSsrExternals(server: ViteDevServer) { server._ssrExternals = cjsSsrResolveExternals(server.config, knownImports) } } + +export async function pretransformOptimizeDepsEntries( + server: ViteDevServer +): Promise { + const { config } = server + const { entries } = config.optimizeDeps + if (entries) { + const explicitEntries = await glob(entries, { + cwd: config.root, + ignore: ['**/node_modules/**', `**/${config.build.outDir}/**`], + absolute: true + }) + // 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) { + transformRequest(entry, server, { ssr: false }).catch((e) => { + config.logger.error(e.message) + }) + } + } +} From 0628896610a0a6d2e9fc01a51811e37ace492b2a Mon Sep 17 00:00:00 2001 From: patak-dev Date: Tue, 7 Jun 2022 22:50:46 +0200 Subject: [PATCH 2/3] refactor: only pretransform entries on cold start --- packages/vite/src/node/optimizer/index.ts | 7 ++ packages/vite/src/node/optimizer/optimizer.ts | 89 ++++++++++++++++++ .../vite/src/node/plugins/importAnalysis.ts | 9 +- .../vite/src/node/plugins/optimizedDeps.ts | 94 +------------------ packages/vite/src/node/plugins/worker.ts | 4 +- .../src/node/plugins/workerImportMetaUrl.ts | 4 +- packages/vite/src/node/server/index.ts | 23 ----- .../optimize-deps/added-in-entries/index.js | 2 + .../added-in-entries/package.json | 6 ++ playground/optimize-deps/entry.js | 11 +++ playground/optimize-deps/package.json | 1 + playground/optimize-deps/vite.config.js | 3 +- pnpm-lock.yaml | 11 +++ 13 files changed, 138 insertions(+), 126 deletions(-) create mode 100644 playground/optimize-deps/added-in-entries/index.js create mode 100644 playground/optimize-deps/added-in-entries/package.json create mode 100644 playground/optimize-deps/entry.js diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index ea291675ccc8cf..8160ee0b0d83a0 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -45,11 +45,18 @@ export type ExportsData = { export interface DepsOptimizer { metadata: DepOptimizationMetadata scanProcessing?: Promise + registerMissingImport: (id: string, resolved: string) => OptimizedDepInfo run: () => void + isOptimizedDepFile: (id: string) => boolean isOptimizedDepUrl: (url: string) => boolean getOptimizedDepId: (depInfo: OptimizedDepInfo) => string + + delayDepsOptimizerUntil: (id: string, done: () => Promise) => void + registerWorkersSource: (id: string) => void + resetRegisteredIds: () => void + options: DepOptimizationOptions } diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index d1846903d95269..1cc97e2b6b6fba 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -1,6 +1,8 @@ import colors from 'picocolors' import _debug from 'debug' +import glob from 'fast-glob' import { getHash } from '../utils' +import { transformRequest } from '../server/transformRequest' import type { ResolvedConfig, ViteDevServer } from '..' import { addOptimizedDepInfo, @@ -65,6 +67,9 @@ export async function initDepsOptimizer( isOptimizedDepUrl: createIsOptimizedDepUrl(config), getOptimizedDepId: (depInfo: OptimizedDepInfo) => isBuild ? depInfo.file : `${depInfo.file}?v=${depInfo.browserHash}`, + registerWorkersSource, + delayDepsOptimizerUntil, + resetRegisteredIds, options: config.optimizeDeps } @@ -101,8 +106,12 @@ export async function initDepsOptimizer( 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 + if (!cachedMetadata) { if (!scan) { // Initialize discovered deps with manually added optimizeDeps.include info @@ -496,5 +505,85 @@ export async function initDepsOptimizer( }, timeout) } + const runOptimizerIfIdleAfterMs = 100 + + let registeredIds: { id: string; done: () => Promise }[] = [] + let seenIds = new Set() + let workersSources = new Set() + let waitingOn: string | undefined + + function resetRegisteredIds() { + registeredIds = [] + seenIds = new Set() + workersSources = new Set() + waitingOn = undefined + } + + function registerWorkersSource(id: string): void { + workersSources.add(id) + if (waitingOn === id) { + waitingOn = undefined + } + } + + function delayDepsOptimizerUntil(id: string, done: () => Promise): void { + if (!depsOptimizer.isOptimizedDepFile(id) && !seenIds.has(id)) { + seenIds.add(id) + registeredIds.push({ id, done }) + runOptimizerWhenIdle() + } + if (server && !optimizeDepsEntriesVisited) { + optimizeDepsEntriesVisited = true + pretransformOptimizeDepsEntries(server) + } + } + + function runOptimizerWhenIdle() { + if (!waitingOn) { + const next = registeredIds.pop() + if (next) { + waitingOn = next.id + const afterLoad = () => { + waitingOn = undefined + if (registeredIds.length > 0) { + runOptimizerWhenIdle() + } else if (!workersSources.has(next.id)) { + getDepsOptimizer(config)?.run() + } + } + next + .done() + .then(() => { + setTimeout( + afterLoad, + registeredIds.length > 0 ? 0 : runOptimizerIfIdleAfterMs + ) + }) + .catch(afterLoad) + } + } + } + return depsOptimizer } + +export async function pretransformOptimizeDepsEntries( + server: ViteDevServer +): Promise { + const { config } = server + const { entries } = config.optimizeDeps + if (entries) { + const explicitEntries = await glob(entries, { + cwd: config.root, + ignore: ['**/node_modules/**', `**/${config.build.outDir}/**`], + absolute: true + }) + // 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) { + transformRequest(entry, server, { ssr: false }).catch((e) => { + config.logger.error(e.message) + }) + } + } +} diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index 754afbd62ed350..2b3507f25c0931 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -53,10 +53,7 @@ import { optimizedDepNeedsInterop } from '../optimizer' import { checkPublicFile } from './asset' -import { - ERR_OUTDATED_OPTIMIZED_DEP, - delayDepsOptimizerUntil -} from './optimizedDeps' +import { ERR_OUTDATED_OPTIMIZED_DEP } from './optimizedDeps' import { isCSSRequest, isDirectCSSRequest } from './css' import { browserExternalId } from './resolve' @@ -616,8 +613,8 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { // Unexpected error, log the issue but avoid an unhandled exception config.logger.error(e.message) }) - if (!config.optimizeDeps.devScan) { - delayDepsOptimizerUntil(config, id, () => request) + if (depsOptimizer && !config.optimizeDeps.devScan) { + depsOptimizer.delayDepsOptimizerUntil(id, () => request) } }) } diff --git a/packages/vite/src/node/plugins/optimizedDeps.ts b/packages/vite/src/node/plugins/optimizedDeps.ts index 49bcc69bf6f25e..0e1cba664341f5 100644 --- a/packages/vite/src/node/plugins/optimizedDeps.ts +++ b/packages/vite/src/node/plugins/optimizedDeps.ts @@ -13,100 +13,10 @@ export const ERR_OUTDATED_OPTIMIZED_DEP = 'ERR_OUTDATED_OPTIMIZED_DEP' const isDebug = process.env.DEBUG const debug = createDebugger('vite:optimize-deps') -const runOptimizerIfIdleAfterMs = 100 - -interface RunProcessingInfo { - ids: { id: string; done: () => Promise }[] - seenIds: Set - workersSources: Set - waitingOn: string | undefined -} - -const runProcessingInfoMap = new WeakMap() - -function initRunProcessingInfo(config: ResolvedConfig) { - config = config.mainConfig || config - const runProcessingInfo = { - ids: [], - seenIds: new Set(), - workersSources: new Set(), - waitingOn: undefined - } - runProcessingInfoMap.set(config, runProcessingInfo) - return runProcessingInfo -} - -function getRunProcessingInfo(config: ResolvedConfig): RunProcessingInfo { - return ( - runProcessingInfoMap.get(config.mainConfig || config) ?? - initRunProcessingInfo(config) - ) -} - -export function registerWorkersSource( - config: ResolvedConfig, - id: string -): void { - const info = getRunProcessingInfo(config) - info.workersSources.add(id) - if (info.waitingOn === id) { - info.waitingOn = undefined - } -} - -export function delayDepsOptimizerUntil( - config: ResolvedConfig, - id: string, - done: () => Promise -): void { - const info = getRunProcessingInfo(config) - if ( - !getDepsOptimizer(config)?.isOptimizedDepFile(id) && - !info.seenIds.has(id) - ) { - info.seenIds.add(id) - info.ids.push({ id, done }) - runOptimizerWhenIdle(config) - } -} - -function runOptimizerWhenIdle(config: ResolvedConfig) { - const info = getRunProcessingInfo(config) - if (!info.waitingOn) { - const next = info.ids.pop() - if (next) { - info.waitingOn = next.id - const afterLoad = () => { - info.waitingOn = undefined - if (info.ids.length > 0) { - runOptimizerWhenIdle(config) - } else if (!info.workersSources.has(next.id)) { - getDepsOptimizer(config)?.run() - } - } - next - .done() - .then(() => { - setTimeout( - afterLoad, - info.ids.length > 0 ? 0 : runOptimizerIfIdleAfterMs - ) - }) - .catch(afterLoad) - } - } -} - export function optimizedDepsPlugin(config: ResolvedConfig): Plugin { return { name: 'vite:optimized-deps', - buildStart() { - if (!config.isWorker) { - initRunProcessingInfo(config) - } - }, - async resolveId(id) { if (getDepsOptimizer(config)?.isOptimizedDepFile(id)) { return id @@ -174,7 +84,7 @@ export function optimizedDepsBuildPlugin(config: ResolvedConfig): Plugin { buildStart() { if (!config.isWorker) { - initRunProcessingInfo(config) + getDepsOptimizer(config)?.resetRegisteredIds() } }, @@ -185,7 +95,7 @@ export function optimizedDepsBuildPlugin(config: ResolvedConfig): Plugin { }, transform(_code, id) { - delayDepsOptimizerUntil(config, id, async () => { + getDepsOptimizer(config)?.delayDepsOptimizerUntil(id, async () => { await this.load({ id }) }) }, diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index 1e38a18f3dbdbe..2f50acfcdfa0fe 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -13,8 +13,8 @@ import { parseRequest } from '../utils' import { onRollupWarning } from '../build' +import { getDepsOptimizer } from '../optimizer' import { fileToUrl } from './asset' -import { registerWorkersSource } from './optimizedDeps' interface WorkerCache { // save worker all emit chunk avoid rollup make the same asset unique. @@ -269,7 +269,7 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { : 'module' const workerOptions = workerType === 'classic' ? '' : ',{type: "module"}' if (isBuild) { - registerWorkersSource(config, id) + getDepsOptimizer(config)?.registerWorkersSource(id) if (query.inline != null) { const chunk = await bundleWorkerEntry(config, id, query) // inline as blob data url diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index cfb10fdb2e8591..510ae644a1a11b 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -12,10 +12,10 @@ import { parseRequest, transformResult } from '../utils' +import { getDepsOptimizer } from '../optimizer' import type { WorkerType } from './worker' import { WORKER_FILE_ID, workerFileToUrl } from './worker' import { fileToUrl } from './asset' -import { registerWorkersSource } from './optimizedDeps' const ignoreFlagRE = /\/\*\s*@vite-ignore\s*\*\// @@ -123,7 +123,7 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { let url: string if (isBuild) { - registerWorkersSource(config, id) + getDepsOptimizer(config)?.registerWorkersSource(id) url = await workerFileToUrl(config, file, query) } else { url = await fileToUrl(cleanUrl(file), config, this) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 08a5010b8cb789..33565d7390d601 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -7,7 +7,6 @@ import connect from 'connect' import corsMiddleware from 'cors' import colors from 'picocolors' import chokidar from 'chokidar' -import glob from 'fast-glob' import type { FSWatcher, WatchOptions } from 'types/chokidar' import type { Connect } from 'types/connect' import launchEditorMiddleware from 'launch-editor-middleware' @@ -522,7 +521,6 @@ export async function createServer( const initOptimizer = async () => { if (isDepsOptimizerEnabled(config)) { await initDepsOptimizer(config, server) - pretransformOptimizeDepsEntries(server) } } @@ -763,24 +761,3 @@ async function updateCjsSsrExternals(server: ViteDevServer) { server._ssrExternals = cjsSsrResolveExternals(server.config, knownImports) } } - -export async function pretransformOptimizeDepsEntries( - server: ViteDevServer -): Promise { - const { config } = server - const { entries } = config.optimizeDeps - if (entries) { - const explicitEntries = await glob(entries, { - cwd: config.root, - ignore: ['**/node_modules/**', `**/${config.build.outDir}/**`], - absolute: true - }) - // 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) { - transformRequest(entry, server, { ssr: false }).catch((e) => { - config.logger.error(e.message) - }) - } - } -} diff --git a/playground/optimize-deps/added-in-entries/index.js b/playground/optimize-deps/added-in-entries/index.js new file mode 100644 index 00000000000000..f660e74d01dd01 --- /dev/null +++ b/playground/optimize-deps/added-in-entries/index.js @@ -0,0 +1,2 @@ +// written in cjs, optimization should convert this to esm +module.exports = 'added-in-entries' diff --git a/playground/optimize-deps/added-in-entries/package.json b/playground/optimize-deps/added-in-entries/package.json new file mode 100644 index 00000000000000..b770dc1c053537 --- /dev/null +++ b/playground/optimize-deps/added-in-entries/package.json @@ -0,0 +1,6 @@ +{ + "name": "added-in-entries", + "private": true, + "version": "1.0.0", + "main": "index.js" +} diff --git a/playground/optimize-deps/entry.js b/playground/optimize-deps/entry.js new file mode 100644 index 00000000000000..fe7746a556c6f4 --- /dev/null +++ b/playground/optimize-deps/entry.js @@ -0,0 +1,11 @@ +import msg from 'added-in-entries' + +// This is an entry file that is added to optimizeDeps.entries +// When the deps aren't cached, these entries are also processed +// to discover dependencies in them. This should only be needed +// for code splitted sections that are commonly visited after +// first load where a full-reload wants to be avoided at the expense +// of extra processing on cold start. Another option is to add +// the missing dependencies to optimizeDeps.include directly + +console.log(msg) diff --git a/playground/optimize-deps/package.json b/playground/optimize-deps/package.json index 3a274ff63b5fdf..4673bbf16d9cc5 100644 --- a/playground/optimize-deps/package.json +++ b/playground/optimize-deps/package.json @@ -22,6 +22,7 @@ "dep-with-builtin-module-cjs": "file:./dep-with-builtin-module-cjs", "dep-with-builtin-module-esm": "file:./dep-with-builtin-module-esm", "dep-with-dynamic-import": "file:./dep-with-dynamic-import", + "added-in-entries": "file:./added-in-entries", "lodash-es": "^4.17.21", "nested-exclude": "file:./nested-exclude", "phoenix": "^1.6.10", diff --git a/playground/optimize-deps/vite.config.js b/playground/optimize-deps/vite.config.js index d5da62f3331fc2..e268510d5c8d76 100644 --- a/playground/optimize-deps/vite.config.js +++ b/playground/optimize-deps/vite.config.js @@ -33,7 +33,8 @@ module.exports = { } } ] - } + }, + entries: ['entry.js'] }, build: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 438930772bdd48..53a3bbc4ab0e5c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -572,6 +572,7 @@ importers: playground/optimize-deps: specifiers: '@vitejs/plugin-vue': workspace:* + added-in-entries: file:./added-in-entries axios: ^0.27.2 clipboard: ^2.0.11 dep-cjs-compiled-from-cjs: file:./dep-cjs-compiled-from-cjs @@ -595,6 +596,7 @@ importers: vue: ^3.2.37 vuex: ^4.0.2 dependencies: + added-in-entries: file:playground/optimize-deps/added-in-entries axios: 0.27.2 clipboard: 2.0.11 dep-cjs-compiled-from-cjs: file:playground/optimize-deps/dep-cjs-compiled-from-cjs @@ -620,6 +622,9 @@ importers: devDependencies: '@vitejs/plugin-vue': link:../../packages/plugin-vue + playground/optimize-deps/added-in-entries: + specifiers: {} + playground/optimize-deps/dep-cjs-compiled-from-cjs: specifiers: {} @@ -8853,6 +8858,12 @@ packages: version: 0.0.0 dev: true + file:playground/optimize-deps/added-in-entries: + resolution: {directory: playground/optimize-deps/added-in-entries, type: directory} + name: added-in-entries + version: 1.0.0 + dev: false + file:playground/optimize-deps/dep-cjs-compiled-from-cjs: resolution: {directory: playground/optimize-deps/dep-cjs-compiled-from-cjs, type: directory} name: dep-cjs-compiled-from-cjs From 77be51a14e423c60f6139fcb231acb112f9d1ca4 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Wed, 8 Jun 2022 18:02:05 +0200 Subject: [PATCH 3/3] chore: better function name --- packages/vite/src/node/optimizer/optimizer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index 1cc97e2b6b6fba..a7c5391de79b5f 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -534,7 +534,7 @@ export async function initDepsOptimizer( } if (server && !optimizeDepsEntriesVisited) { optimizeDepsEntriesVisited = true - pretransformOptimizeDepsEntries(server) + preTransformOptimizeDepsEntries(server) } } @@ -567,7 +567,7 @@ export async function initDepsOptimizer( return depsOptimizer } -export async function pretransformOptimizeDepsEntries( +export async function preTransformOptimizeDepsEntries( server: ViteDevServer ): Promise { const { config } = server