From 6dab302cfa70f681c5661cecd2834338b0dee14f Mon Sep 17 00:00:00 2001 From: patak-dev Date: Wed, 1 Jun 2022 11:23:37 +0200 Subject: [PATCH 1/3] feat: support cjs noExternal in SSR dev --- packages/vite/src/node/build.ts | 2 +- packages/vite/src/node/optimizer/index.ts | 105 +++++++++++++----- packages/vite/src/node/optimizer/optimizer.ts | 54 ++++++--- .../vite/src/node/plugins/importAnalysis.ts | 9 +- .../src/node/plugins/importAnalysisBuild.ts | 5 +- .../vite/src/node/plugins/optimizedDeps.ts | 12 +- packages/vite/src/node/plugins/preAlias.ts | 10 +- packages/vite/src/node/plugins/resolve.ts | 22 ++-- packages/vite/src/node/server/index.ts | 6 +- 9 files changed, 157 insertions(+), 68 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 5e2da01695a0a1..c6cd9331ad86f7 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -723,7 +723,7 @@ async function cjsSsrResolveExternal( ): Promise { // see if we have cached deps data available let knownImports: string[] | undefined - const dataPath = path.join(getDepsCacheDir(config), '_metadata.json') + const dataPath = path.join(getDepsCacheDir(config, false), '_metadata.json') try { const data = JSON.parse( fs.readFileSync(dataPath, 'utf-8') diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index ea291675ccc8cf..e80bc4559c9284 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -6,6 +6,7 @@ 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 { createDebugger, @@ -43,9 +44,13 @@ export type ExportsData = { } export interface DepsOptimizer { - metadata: DepOptimizationMetadata + metadata: (options: { ssr: boolean }) => DepOptimizationMetadata scanProcessing?: Promise - registerMissingImport: (id: string, resolved: string) => OptimizedDepInfo + registerMissingImport: ( + id: string, + resolved: string, + ssr?: boolean + ) => OptimizedDepInfo run: () => void isOptimizedDepFile: (id: string) => boolean isOptimizedDepUrl: (url: string) => boolean @@ -227,6 +232,38 @@ export async function optimizeDeps( return result.metadata } +export async function optimizeServerSsrDeps( + config: ResolvedConfig +): Promise { + const cachedMetadata = loadCachedDepOptimizationMetadata( + config, + config.force, + false, + true // ssr + ) + if (cachedMetadata) { + return cachedMetadata + } + + const { noExternal } = config.ssr! + const noExternalFilter = + noExternal === true + ? (dep: string) => false + : createFilter(noExternal, undefined, { resolve: false }) + + const deps: Record = {} + + await addManuallyIncludedOptimizeDeps(deps, config, noExternalFilter) + + const depsInfo = toDiscoveredDependencies(config, deps, true) + + const result = await runOptimizeDeps(config, depsInfo, true) + + await result.commit() + + return result.metadata +} + export function initDepsOptimizerMetadata( config: ResolvedConfig, timestamp?: string @@ -259,7 +296,8 @@ export function addOptimizedDepInfo( export function loadCachedDepOptimizationMetadata( config: ResolvedConfig, force = config.force, - asCommand = false + asCommand = false, + ssr = !!config.build.ssr ): DepOptimizationMetadata | undefined { const log = asCommand ? config.logger.info : debug @@ -269,7 +307,7 @@ export function loadCachedDepOptimizationMetadata( emptyDir(config.cacheDir) } - const depsCacheDir = getDepsCacheDir(config) + const depsCacheDir = getDepsCacheDir(config, ssr) if (!force) { let cachedMetadata: DepOptimizationMetadata | undefined @@ -336,6 +374,15 @@ export async function initialProjectDependencies( await addManuallyIncludedOptimizeDeps(deps, config) + return toDiscoveredDependencies(config, deps, !!config.build.ssr, timestamp) +} + +export function toDiscoveredDependencies( + config: ResolvedConfig, + deps: Record, + ssr: boolean, + timestamp?: string +): Record { const browserHash = getOptimizedBrowserHash( getDepHash(config), deps, @@ -346,7 +393,7 @@ export async function initialProjectDependencies( const src = deps[id] discovered[id] = { id, - file: getOptimizedDepPath(id, config), + file: getOptimizedDepPath(id, config, ssr), src, browserHash: browserHash, exportsData: extractExportsData(src, config) @@ -376,15 +423,16 @@ export function depsLogString(qualifiedIds: string[]): string { */ export async function runOptimizeDeps( resolvedConfig: ResolvedConfig, - depsInfo: Record + depsInfo: Record, + ssr: boolean = !!resolvedConfig.build.ssr ): Promise { const config: ResolvedConfig = { ...resolvedConfig, command: 'build' } - const depsCacheDir = getDepsCacheDir(resolvedConfig) - const processingCacheDir = getProcessingDepsCacheDir(resolvedConfig) + const depsCacheDir = getDepsCacheDir(resolvedConfig, ssr) + const processingCacheDir = getProcessingDepsCacheDir(resolvedConfig, ssr) // Create a temporal directory so we don't need to delete optimized deps // until they have been processed. This also avoids leaving the deps cache @@ -526,7 +574,7 @@ export async function runOptimizeDeps( const id = path .relative(processingCacheDirOutputPath, o) .replace(jsExtensionRE, '') - const file = getOptimizedDepPath(id, resolvedConfig) + const file = getOptimizedDepPath(id, resolvedConfig, ssr) if ( !findOptimizedDepInfoInRecord( metadata.optimized, @@ -561,7 +609,8 @@ export async function findKnownImports( async function addManuallyIncludedOptimizeDeps( deps: Record, - config: ResolvedConfig + config: ResolvedConfig, + filter?: (id: string) => boolean | undefined ): Promise { const include = config.optimizeDeps?.include if (include) { @@ -570,7 +619,7 @@ async function addManuallyIncludedOptimizeDeps( // normalize 'foo >bar` as 'foo > bar' to prevent same id being added // and for pretty printing const normalizedId = normalizeId(id) - if (!deps[normalizedId]) { + if (!deps[normalizedId] && filter?.(normalizedId) !== false) { const entry = await resolve(id) if (entry) { deps[normalizedId] = entry @@ -603,45 +652,51 @@ export function depsFromOptimizedDepInfo( export function getOptimizedDepPath( id: string, - config: ResolvedConfig + config: ResolvedConfig, + ssr: boolean = !!config.build.ssr ): string { return normalizePath( - path.resolve(getDepsCacheDir(config), flattenId(id) + '.js') + path.resolve(getDepsCacheDir(config, ssr), flattenId(id) + '.js') ) } -function getDepsCacheSuffix(config: ResolvedConfig): string { +function getDepsCacheSuffix(config: ResolvedConfig, ssr: boolean): string { let suffix = '' if (config.command === 'build') { suffix += '_build' - if (config.build.ssr) { - suffix += '_ssr' - } + } + if (ssr) { + suffix += '_ssr' } return suffix } -export function getDepsCacheDir(config: ResolvedConfig): string { - const dirName = 'deps' + getDepsCacheSuffix(config) - return normalizePath(path.resolve(config.cacheDir, dirName)) + +export function getDepsCacheDir(config: ResolvedConfig, ssr: boolean): string { + return getDepsCacheDirPrefix(config) + getDepsCacheSuffix(config, ssr) +} + +function getProcessingDepsCacheDir(config: ResolvedConfig, ssr: boolean) { + return ( + getDepsCacheDirPrefix(config) + getDepsCacheSuffix(config, ssr) + '_temp' + ) } -function getProcessingDepsCacheDir(config: ResolvedConfig) { - const dirName = 'deps' + getDepsCacheSuffix(config) + '_temp' - return normalizePath(path.resolve(config.cacheDir, dirName)) +export function getDepsCacheDirPrefix(config: ResolvedConfig): string { + return normalizePath(path.resolve(config.cacheDir, 'deps')) } export function isOptimizedDepFile( id: string, config: ResolvedConfig ): boolean { - return id.startsWith(getDepsCacheDir(config)) + return id.startsWith(getDepsCacheDirPrefix(config)) } export function createIsOptimizedDepUrl( config: ResolvedConfig ): (url: string) => boolean { const { root } = config - const depsCacheDir = getDepsCacheDir(config) + const depsCacheDir = getDepsCacheDirPrefix(config) // determine the url prefix of files inside cache directory const depsCacheDirRelative = normalizePath(path.relative(root, depsCacheDir)) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index d1846903d95269..823b191ed709e0 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -16,9 +16,11 @@ import { isOptimizedDepFile, loadCachedDepOptimizationMetadata, newDepOptimizationProcessing, + optimizeServerSsrDeps, runOptimizeDeps } from '.' import type { + DepOptimizationMetadata, DepOptimizationProcessing, DepsOptimizer, OptimizedDepInfo @@ -56,9 +58,18 @@ export async function initDepsOptimizer( let handle: NodeJS.Timeout | undefined + let ssrServerDepsMetadata: DepOptimizationMetadata + let _metadata = + cachedMetadata || initDepsOptimizerMetadata(config, sessionTimestamp) + const depsOptimizer: DepsOptimizer = { - metadata: - cachedMetadata || initDepsOptimizerMetadata(config, sessionTimestamp), + metadata: (options: { ssr: boolean }) => { + if (isBuild || !options.ssr) { + return _metadata + } else { + return ssrServerDepsMetadata + } + }, registerMissingImport, run: () => debouncedProcessing(0), isOptimizedDepFile: (id: string) => isOptimizedDepFile(id, config), @@ -70,6 +81,16 @@ export async function initDepsOptimizer( depsOptimizerMap.set(config, depsOptimizer) + if (!isBuild && config.ssr && config.optimizeDeps) { + const { noExternal } = config.ssr + const { include } = config.optimizeDeps + if (noExternal && include) { + ssrServerDepsMetadata = await optimizeServerSsrDeps(config) + } else { + ssrServerDepsMetadata = initDepsOptimizerMetadata(config) + } + } + let newDepsDiscovered = false let newDepsToLog: string[] = [] @@ -110,7 +131,7 @@ export async function initDepsOptimizer( config, sessionTimestamp ) - const { metadata } = depsOptimizer + const metadata = _metadata for (const depInfo of Object.values(discovered)) { addOptimizedDepInfo(metadata, 'discovered', { ...depInfo, @@ -130,7 +151,7 @@ export async function initDepsOptimizer( timestamp: true }) - const { metadata } = depsOptimizer + const metadata = _metadata const discovered = await discoverProjectDependencies( config, @@ -179,7 +200,7 @@ export async function initDepsOptimizer( // Ensure that a rerun will not be issued for current discovered deps if (handle) clearTimeout(handle) - if (Object.keys(depsOptimizer.metadata.discovered).length === 0) { + if (Object.keys(_metadata.discovered).length === 0) { currentlyProcessing = false return } @@ -189,13 +210,13 @@ export async function initDepsOptimizer( // 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 - // to optimizeDeps.metadata. A fullReload is only issued if - // the previous bundled dependencies have changed. + // to _metadata. A fullReload is only issued if the previous bundled + // dependencies have changed. - // if the rerun fails, optimizeDeps.metadata remains untouched, - // current discovered deps are cleaned, and a fullReload is issued + // if the rerun fails, _metadata remains untouched, current discovered + // deps are cleaned, and a fullReload is issued - let { metadata } = depsOptimizer + let metadata = _metadata // All deps, previous known and newly discovered are rebundled, // respect insertion order to keep the metadata file stable @@ -302,7 +323,7 @@ export async function initDepsOptimizer( ) } - metadata = depsOptimizer.metadata = newData + metadata = _metadata = newData resolveEnqueuedProcessingPromises() } @@ -401,7 +422,7 @@ export async function initDepsOptimizer( // debounce time to wait for new missing deps finished, issue a new // optimization of deps (both old and newly found) once the previous // optimizeDeps processing is finished - const deps = Object.keys(depsOptimizer.metadata.discovered) + const deps = Object.keys(_metadata.discovered) const depsString = depsLogString(deps) debug(colors.green(`new dependencies found: ${depsString}`), { timestamp: true @@ -429,7 +450,12 @@ export async function initDepsOptimizer( 'Vite internal error: registering missing import before initial scanning is over' ) } - const { metadata } = depsOptimizer + if (!isBuild && ssr) { + config.logger.error( + `Error: ${id} is a missing dependency in SSR dev server, it needs to be added to optimizeDeps.include` + ) + } + const metadata = _metadata const optimized = metadata.optimized[id] if (optimized) { return optimized @@ -447,7 +473,7 @@ export async function initDepsOptimizer( newDepsDiscovered = true missing = addOptimizedDepInfo(metadata, 'discovered', { id, - file: getOptimizedDepPath(id, config), + file: getOptimizedDepPath(id, config, ssr), src: resolved, // Assing a browserHash to this missing dependency that is unique to // the current state of known + missing deps. If its optimizeDeps run diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index ba221b4b1d5283..837f0fc0965867 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -48,7 +48,7 @@ import { } from '../ssr/ssrExternal' import { transformRequest } from '../server/transformRequest' import { - getDepsCacheDir, + getDepsCacheDirPrefix, getDepsOptimizer, optimizedDepNeedsInterop } from '../optimizer' @@ -218,7 +218,8 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { // 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 // (that is, if it is under node_modules directory in the package source of the optimized file) - for (const optimizedModule of depsOptimizer.metadata.depInfoList) { + for (const optimizedModule of depsOptimizer.metadata({ ssr }) + .depInfoList) { if (!optimizedModule.src) continue // Ignore chunks if (optimizedModule.file === importerModule.file) { importerFile = optimizedModule.src @@ -252,7 +253,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { // in root: infer short absolute path from root url = resolved.id.slice(root.length) } else if ( - resolved.id.startsWith(getDepsCacheDir(config)) || + resolved.id.startsWith(getDepsCacheDirPrefix(config)) || fs.existsSync(cleanUrl(resolved.id)) ) { // an optimized deps may not yet exists in the filesystem, or @@ -416,7 +417,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { const file = cleanUrl(resolvedId) // Remove ?v={hash} const needsInterop = await optimizedDepNeedsInterop( - depsOptimizer.metadata, + depsOptimizer.metadata({ ssr }), file, config ) diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index 9de2e066624e2a..849bd4e236d626 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -172,7 +172,8 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { // 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 // (that is, if it is under node_modules directory in the package source of the optimized file) - for (const optimizedModule of depsOptimizer.metadata.depInfoList) { + for (const optimizedModule of depsOptimizer.metadata({ ssr }) + .depInfoList) { if (!optimizedModule.src) continue // Ignore chunks if (optimizedModule.file === importer) { importerFile = optimizedModule.src @@ -263,7 +264,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { const file = cleanUrl(resolvedId) // Remove ?v={hash} const needsInterop = await optimizedDepNeedsInterop( - depsOptimizer.metadata, + depsOptimizer.metadata({ ssr }), file, config ) diff --git a/packages/vite/src/node/plugins/optimizedDeps.ts b/packages/vite/src/node/plugins/optimizedDeps.ts index 49bcc69bf6f25e..3c0852f53c1b65 100644 --- a/packages/vite/src/node/plugins/optimizedDeps.ts +++ b/packages/vite/src/node/plugins/optimizedDeps.ts @@ -117,10 +117,11 @@ export function optimizedDepsPlugin(config: ResolvedConfig): Plugin { // The logic to register an id to wait until it is processed // is in importAnalysis, see call to delayDepsOptimizerUntil - async load(id) { + async load(id, options) { + const ssr = options?.ssr ?? false const depsOptimizer = getDepsOptimizer(config) if (depsOptimizer?.isOptimizedDepFile(id)) { - const metadata = depsOptimizer?.metadata + const metadata = depsOptimizer?.metadata({ ssr }) if (metadata) { const file = cleanUrl(id) const versionMatch = id.match(DEP_VERSION_RE) @@ -144,7 +145,7 @@ export function optimizedDepsPlugin(config: ResolvedConfig): Plugin { throwProcessingError(id) return } - const newMetadata = depsOptimizer.metadata + const newMetadata = depsOptimizer.metadata({ ssr }) if (metadata !== newMetadata) { const currentInfo = optimizedDepInfoFromFile(newMetadata!, file) if (info.browserHash !== currentInfo?.browserHash) { @@ -190,9 +191,10 @@ export function optimizedDepsBuildPlugin(config: ResolvedConfig): Plugin { }) }, - async load(id) { + async load(id, options) { + const ssr = options?.ssr ?? false const depsOptimizer = getDepsOptimizer(config) - const metadata = depsOptimizer?.metadata + const metadata = depsOptimizer?.metadata({ ssr }) if (!metadata || !depsOptimizer?.isOptimizedDepFile(id)) { return } diff --git a/packages/vite/src/node/plugins/preAlias.ts b/packages/vite/src/node/plugins/preAlias.ts index 0d6076b03a329f..46bc2436b0686a 100644 --- a/packages/vite/src/node/plugins/preAlias.ts +++ b/packages/vite/src/node/plugins/preAlias.ts @@ -11,14 +11,10 @@ export function preAliasPlugin(config: ResolvedConfig): Plugin { return { name: 'vite:pre-alias', async resolveId(id, importer, options) { + const ssr = options?.ssr ?? false const depsOptimizer = getDepsOptimizer(config) - if ( - depsOptimizer && - !options?.ssr && - bareImportRE.test(id) && - !options?.scan - ) { - return await tryOptimizedResolve(depsOptimizer, id, importer) + if (depsOptimizer && bareImportRE.test(id) && !options?.scan) { + return await tryOptimizedResolve(depsOptimizer, ssr, id, importer) } } } diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 538389828ed2c2..fdc72218bc4c7b 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -89,7 +89,6 @@ export interface InternalResolveOptions extends ResolveOptions { export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { const { root, - isBuild, isProduction, asSrc, ssrConfig, @@ -187,7 +186,7 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { // Inject the current browserHash version if the path doesn't have one if (!normalizedFsPath.match(DEP_VERSION_RE)) { const browserHash = optimizedDepInfoFromFile( - depsOptimizer.metadata, + depsOptimizer.metadata({ ssr }), normalizedFsPath )?.browserHash if (browserHash) { @@ -267,9 +266,8 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { !external && asSrc && depsOptimizer && - (isBuild || !ssr) && !options.scan && - (res = await tryOptimizedResolve(depsOptimizer, id, importer)) + (res = await tryOptimizedResolve(depsOptimizer, ssr, id, importer)) ) { return res } @@ -541,6 +539,8 @@ export function tryNodeResolve( ): PartialResolvedId | undefined { const { root, dedupe, isBuild, preserveSymlinks, packageCache } = options + ssr ??= false + // split id by last '>' for nested selected packages, for example: // 'foo > bar > baz' => 'foo > bar' & 'baz' // 'foo' => '' & 'foo' @@ -677,7 +677,7 @@ export function tryNodeResolve( // otherwise we may introduce duplicated modules for externalized files // from pre-bundled deps. if (!isBuild) { - const versionHash = depsOptimizer.metadata.browserHash + const versionHash = depsOptimizer.metadata({ ssr }).browserHash if (versionHash && isJsType) { resolved = injectQuery(resolved, `v=${versionHash}`) } @@ -685,7 +685,7 @@ export function tryNodeResolve( } else { // this is a missing import, queue optimize-deps re-run and // get a resolved its optimized info - const optimizedInfo = depsOptimizer.registerMissingImport(id, resolved) + const optimizedInfo = depsOptimizer.registerMissingImport(id, resolved, ssr) resolved = depsOptimizer.getOptimizedDepId(optimizedInfo) } @@ -703,12 +703,18 @@ export function tryNodeResolve( export async function tryOptimizedResolve( depsOptimizer: DepsOptimizer, + ssr: boolean, id: string, importer?: string ): Promise { await depsOptimizer.scanProcessing - const depInfo = optimizedDepInfoFromId(depsOptimizer.metadata, id) + const metadata = depsOptimizer.metadata({ ssr }) + if (!metadata) { + return + } + + const depInfo = optimizedDepInfoFromId(metadata, id) if (depInfo) { return depsOptimizer.getOptimizedDepId(depInfo) } @@ -718,7 +724,7 @@ export async function tryOptimizedResolve( // further check if id is imported by nested dependency let resolvedSrc: string | undefined - for (const optimizedData of depsOptimizer.metadata.depInfoList) { + for (const optimizedData of metadata.depInfoList) { if (!optimizedData.src) continue // Ignore chunks const pkgPath = optimizedData.id diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 095c7027c68558..0456680d7660f4 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -747,13 +747,15 @@ async function restartServer(server: ViteDevServer) { async function updateCjsSsrExternals(server: ViteDevServer) { if (!server._ssrExternals) { + // We use the non-ssr optimized deps to find known imports let knownImports: string[] = [] const depsOptimizer = getDepsOptimizer(server.config) if (depsOptimizer) { await depsOptimizer.scanProcessing + const metadata = depsOptimizer.metadata({ ssr: false }) knownImports = [ - ...Object.keys(depsOptimizer.metadata.optimized), - ...Object.keys(depsOptimizer.metadata.discovered) + ...Object.keys(metadata.optimized), + ...Object.keys(metadata.discovered) ] } server._ssrExternals = cjsSsrResolveExternals(server.config, knownImports) From 8be5dbc5ebc95f9c8d28375d6428be18accdd96b Mon Sep 17 00:00:00 2001 From: patak-dev Date: Wed, 1 Jun 2022 15:57:33 +0200 Subject: [PATCH 2/3] feat: auto include explicit no external + test case --- packages/vite/src/node/optimizer/index.ts | 31 ++++++++++++----- packages/vite/src/node/optimizer/optimizer.ts | 10 ++---- .../ssr-deps/__tests__/ssr-deps.spec.ts | 5 +++ playground/ssr-deps/no-external-cjs/index.js | 3 ++ .../ssr-deps/no-external-cjs/package.json | 6 ++++ playground/ssr-deps/package.json | 3 +- playground/ssr-deps/server.js | 3 ++ playground/ssr-deps/src/app.js | 4 +++ pnpm-lock.yaml | 33 ++++++++++++++----- 9 files changed, 72 insertions(+), 26 deletions(-) create mode 100644 playground/ssr-deps/no-external-cjs/index.js create mode 100644 playground/ssr-deps/no-external-cjs/package.json diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index e80bc4559c9284..8981c6f91a7823 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -9,6 +9,7 @@ import { init, parse } from 'es-module-lexer' import { createFilter } from '@rollup/pluginutils' import type { ResolvedConfig } from '../config' import { + arraify, createDebugger, emptyDir, flattenId, @@ -245,15 +246,28 @@ export async function optimizeServerSsrDeps( return cachedMetadata } - const { noExternal } = config.ssr! - const noExternalFilter = - noExternal === true - ? (dep: string) => false - : createFilter(noExternal, undefined, { resolve: false }) + 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, undefined, { resolve: false }) + } const deps: Record = {} - await addManuallyIncludedOptimizeDeps(deps, config, noExternalFilter) + await addManuallyIncludedOptimizeDeps( + deps, + config, + alsoInclude, + noExternalFilter + ) const depsInfo = toDiscoveredDependencies(config, deps, true) @@ -610,9 +624,10 @@ export async function findKnownImports( async function addManuallyIncludedOptimizeDeps( deps: Record, config: ResolvedConfig, - filter?: (id: string) => boolean | undefined + extra?: string[], + filter?: (id: string) => boolean ): Promise { - const include = config.optimizeDeps?.include + const include = [...(config.optimizeDeps?.include ?? []), ...(extra ?? [])] if (include) { const resolve = config.createResolver({ asSrc: false, scan: true }) for (const id of include) { diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index 823b191ed709e0..167241783c254f 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -81,14 +81,8 @@ export async function initDepsOptimizer( depsOptimizerMap.set(config, depsOptimizer) - if (!isBuild && config.ssr && config.optimizeDeps) { - const { noExternal } = config.ssr - const { include } = config.optimizeDeps - if (noExternal && include) { - ssrServerDepsMetadata = await optimizeServerSsrDeps(config) - } else { - ssrServerDepsMetadata = initDepsOptimizerMetadata(config) - } + if (!isBuild && config.ssr) { + ssrServerDepsMetadata = await optimizeServerSsrDeps(config) } let newDepsDiscovered = false diff --git a/playground/ssr-deps/__tests__/ssr-deps.spec.ts b/playground/ssr-deps/__tests__/ssr-deps.spec.ts index 75903a1c39c943..dbb169647fc603 100644 --- a/playground/ssr-deps/__tests__/ssr-deps.spec.ts +++ b/playground/ssr-deps/__tests__/ssr-deps.spec.ts @@ -72,3 +72,8 @@ test('msg from only object assigned exports', async () => { 'Hello World!' ) }) + +test('msg from no external cjs', async () => { + await page.goto(url) + expect(await page.textContent('.no-external-cjs-msg')).toMatch('Hello World!') +}) diff --git a/playground/ssr-deps/no-external-cjs/index.js b/playground/ssr-deps/no-external-cjs/index.js new file mode 100644 index 00000000000000..d4fc9da147c88f --- /dev/null +++ b/playground/ssr-deps/no-external-cjs/index.js @@ -0,0 +1,3 @@ +exports.hello = function () { + return 'Hello World!' +} diff --git a/playground/ssr-deps/no-external-cjs/package.json b/playground/ssr-deps/no-external-cjs/package.json new file mode 100644 index 00000000000000..799658987d7b2c --- /dev/null +++ b/playground/ssr-deps/no-external-cjs/package.json @@ -0,0 +1,6 @@ +{ + "name": "primitive-export", + "private": true, + "type": "commonjs", + "version": "0.0.0" +} diff --git a/playground/ssr-deps/package.json b/playground/ssr-deps/package.json index 74bbf77dd97bdd..4dd1ee49aea717 100644 --- a/playground/ssr-deps/package.json +++ b/playground/ssr-deps/package.json @@ -18,7 +18,8 @@ "primitive-export": "file:./primitive-export", "read-file-content": "file:./read-file-content", "require-absolute": "file:./require-absolute", - "ts-transpiled-exports": "file:./ts-transpiled-exports" + "ts-transpiled-exports": "file:./ts-transpiled-exports", + "no-external-cjs": "file:./no-external-cjs" }, "devDependencies": { "cross-env": "^7.0.3", diff --git a/playground/ssr-deps/server.js b/playground/ssr-deps/server.js index f205255320cfe2..f1707892ab6aca 100644 --- a/playground/ssr-deps/server.js +++ b/playground/ssr-deps/server.js @@ -32,6 +32,9 @@ export async function createServer(root = process.cwd(), hmrPort) { hmr: { port: hmrPort } + }, + ssr: { + noExternal: ['no-external-cjs'] } }) // use vite's connect instance as middleware diff --git a/playground/ssr-deps/src/app.js b/playground/ssr-deps/src/app.js index 9646cdcf2bf688..6cf0c32374913b 100644 --- a/playground/ssr-deps/src/app.js +++ b/playground/ssr-deps/src/app.js @@ -9,6 +9,7 @@ import definePropertiesExports from 'define-properties-exports' import definePropertyExports from 'define-property-exports' import onlyObjectAssignedExports from 'only-object-assigned-exports' import requireAbsolute from 'require-absolute' +import noExternalCjs from 'no-external-cjs' export async function render(url, rootDir) { let html = '' @@ -45,5 +46,8 @@ export async function render(url, rootDir) { const requireAbsoluteMessage = requireAbsolute.hello() html += `\n

message from require-absolute: ${requireAbsoluteMessage}

` + const noExternalCjsMessage = noExternalCjs.hello() + html += `\n

message from no-external-cjs: ${noExternalCjsMessage}

` + return html + '\n' } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index acef1fe7331aef..90f102938fc4cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -775,6 +775,15 @@ importers: dependencies: bignumber.js: 9.0.2 + playground/ssr-cjs: + specifiers: + cookie: ^0.4.0 + express: ^4.18.1 + dependencies: + cookie: 0.4.2 + devDependencies: + express: 4.18.1 + playground/ssr-deps: specifiers: bcrypt: ^5.0.1 @@ -783,6 +792,7 @@ importers: define-property-exports: file:./define-property-exports express: ^4.18.1 forwarded-export: file:./forwarded-export + no-external-cjs: file:./no-external-cjs object-assigned-exports: file:./object-assigned-exports only-object-assigned-exports: file:./only-object-assigned-exports primitive-export: file:./primitive-export @@ -794,6 +804,7 @@ importers: define-properties-exports: file:playground/ssr-deps/define-properties-exports define-property-exports: file:playground/ssr-deps/define-property-exports forwarded-export: file:playground/ssr-deps/forwarded-export + no-external-cjs: file:playground/ssr-deps/no-external-cjs object-assigned-exports: file:playground/ssr-deps/object-assigned-exports only-object-assigned-exports: file:playground/ssr-deps/only-object-assigned-exports primitive-export: file:playground/ssr-deps/primitive-export @@ -816,6 +827,9 @@ importers: dependencies: object-assigned-exports: file:playground/ssr-deps/object-assigned-exports + playground/ssr-deps/no-external-cjs: + specifiers: {} + playground/ssr-deps/object-assigned-exports: specifiers: {} @@ -3596,7 +3610,6 @@ packages: /cookie/0.4.2: resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} engines: {node: '>= 0.6'} - dev: true /cookie/0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} @@ -3964,7 +3977,7 @@ packages: dev: true /encodeurl/1.0.2: - resolution: {integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=} + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} dev: true @@ -4416,7 +4429,7 @@ packages: engines: {node: '>=6'} /escape-html/1.0.3: - resolution: {integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=} + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} dev: true /escape-string-regexp/1.0.5: @@ -5968,7 +5981,7 @@ packages: engines: {node: '>= 8'} /methods/1.1.2: - resolution: {integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=} + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} dev: true @@ -6343,10 +6356,6 @@ packages: engines: {node: '>= 6'} dev: false - /object-inspect/1.12.0: - resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==} - dev: true - /object-inspect/1.12.2: resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} dev: true @@ -7580,7 +7589,7 @@ packages: dependencies: call-bind: 1.0.2 get-intrinsic: 1.1.1 - object-inspect: 1.12.0 + object-inspect: 1.12.2 dev: true /signal-exit/3.0.7: @@ -8882,6 +8891,12 @@ packages: object-assigned-exports: file:playground/ssr-deps/object-assigned-exports dev: false + file:playground/ssr-deps/no-external-cjs: + resolution: {directory: playground/ssr-deps/no-external-cjs, type: directory} + name: primitive-export + version: 0.0.0 + dev: false + file:playground/ssr-deps/object-assigned-exports: resolution: {directory: playground/ssr-deps/object-assigned-exports, type: directory} name: object-assigned-exports From 828ac065f8714b86c087e84a13fc168c148ad70f Mon Sep 17 00:00:00 2001 From: patak-dev Date: Wed, 1 Jun 2022 16:16:03 +0200 Subject: [PATCH 3/3] fix: respect optimizeDeps.exclude --- packages/vite/src/node/optimizer/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 8981c6f91a7823..ba61fa3ed2f9a3 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -257,7 +257,9 @@ export async function optimizeServerSsrDeps( noExternalFilter = noExternal === true ? (dep: unknown) => false - : createFilter(noExternal, undefined, { resolve: false }) + : createFilter(noExternal, config.optimizeDeps?.exclude, { + resolve: false + }) } const deps: Record = {}