Skip to content

Commit

Permalink
fix: respect optimize deps entries (#8489)
Browse files Browse the repository at this point in the history
  • Loading branch information
patak-dev committed Jun 8, 2022
1 parent 51e9195 commit fba82d0
Show file tree
Hide file tree
Showing 12 changed files with 141 additions and 104 deletions.
7 changes: 7 additions & 0 deletions packages/vite/src/node/optimizer/index.ts
Expand Up @@ -45,11 +45,18 @@ export type ExportsData = {
export interface DepsOptimizer {
metadata: DepOptimizationMetadata
scanProcessing?: Promise<void>

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<any>) => void
registerWorkersSource: (id: string) => void
resetRegisteredIds: () => void

options: DepOptimizationOptions
}

Expand Down
89 changes: 89 additions & 0 deletions 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,
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -496,5 +505,85 @@ export async function initDepsOptimizer(
}, timeout)
}

const runOptimizerIfIdleAfterMs = 100

let registeredIds: { id: string; done: () => Promise<any> }[] = []
let seenIds = new Set<string>()
let workersSources = new Set<string>()
let waitingOn: string | undefined

function resetRegisteredIds() {
registeredIds = []
seenIds = new Set<string>()
workersSources = new Set<string>()
waitingOn = undefined
}

function registerWorkersSource(id: string): void {
workersSources.add(id)
if (waitingOn === id) {
waitingOn = undefined
}
}

function delayDepsOptimizerUntil(id: string, done: () => Promise<any>): 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<void> {
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)
})
}
}
}
13 changes: 6 additions & 7 deletions packages/vite/src/node/plugins/importAnalysis.ts
Expand Up @@ -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'

Expand Down Expand Up @@ -599,7 +596,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(
Expand All @@ -614,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)
}
})
}
Expand Down
94 changes: 2 additions & 92 deletions packages/vite/src/node/plugins/optimizedDeps.ts
Expand Up @@ -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<any> }[]
seenIds: Set<string>
workersSources: Set<string>
waitingOn: string | undefined
}

const runProcessingInfoMap = new WeakMap<ResolvedConfig, RunProcessingInfo>()

function initRunProcessingInfo(config: ResolvedConfig) {
config = config.mainConfig || config
const runProcessingInfo = {
ids: [],
seenIds: new Set<string>(),
workersSources: new Set<string>(),
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<any>
): 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
Expand Down Expand Up @@ -174,7 +84,7 @@ export function optimizedDepsBuildPlugin(config: ResolvedConfig): Plugin {

buildStart() {
if (!config.isWorker) {
initRunProcessingInfo(config)
getDepsOptimizer(config)?.resetRegisteredIds()
}
},

Expand All @@ -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 })
})
},
Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/plugins/worker.ts
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/plugins/workerImportMetaUrl.ts
Expand Up @@ -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*\*\//

Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions 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'
6 changes: 6 additions & 0 deletions 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"
}
11 changes: 11 additions & 0 deletions 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)
1 change: 1 addition & 0 deletions playground/optimize-deps/package.json
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion playground/optimize-deps/vite.config.js
Expand Up @@ -33,7 +33,8 @@ module.exports = {
}
}
]
}
},
entries: ['entry.js']
},

build: {
Expand Down

0 comments on commit fba82d0

Please sign in to comment.