Skip to content

Commit

Permalink
feat: ssr.optimizeDeps (#8917)
Browse files Browse the repository at this point in the history
  • Loading branch information
patak-dev committed Jul 5, 2022
1 parent 722f514 commit f280dd9
Show file tree
Hide file tree
Showing 20 changed files with 285 additions and 229 deletions.
4 changes: 2 additions & 2 deletions packages/vite/src/node/build.ts
Expand Up @@ -398,7 +398,7 @@ async function doBuild(
external = await cjsSsrResolveExternal(config, userExternal)
}

if (isDepsOptimizerEnabled(config)) {
if (isDepsOptimizerEnabled(config, ssr)) {
await initDepsOptimizer(config)
}

Expand Down Expand Up @@ -739,7 +739,7 @@ async function cjsSsrResolveExternal(
} catch (e) {}
if (!knownImports) {
// no dev deps optimization data, do a fresh scan
knownImports = await findKnownImports(config)
knownImports = await findKnownImports(config, false) // needs to use non-ssr
}
const ssrExternals = cjsSsrResolveExternals(config, knownImports)

Expand Down
119 changes: 26 additions & 93 deletions packages/vite/src/node/config.ts
Expand Up @@ -7,7 +7,6 @@ import colors from 'picocolors'
import type { Alias, AliasOptions } from 'types/alias'
import aliasPlugin from '@rollup/plugin-alias'
import { build } from 'esbuild'
import type { Plugin as ESBuildPlugin } from 'esbuild'
import type { RollupOptions } from 'rollup'
import type { Plugin } from './plugin'
import type {
Expand Down Expand Up @@ -47,7 +46,7 @@ import type { InternalResolveOptions, ResolveOptions } from './plugins/resolve'
import { resolvePlugin } from './plugins/resolve'
import type { LogLevel, Logger } from './logger'
import { createLogger } from './logger'
import type { DepOptimizationOptions } from './optimizer'
import type { DepOptimizationConfig, DepOptimizationOptions } from './optimizer'
import type { JsonOptions } from './plugins/json'
import type { PluginContainer } from './server/pluginContainer'
import { createPluginContainer } from './server/pluginContainer'
Expand Down Expand Up @@ -334,7 +333,7 @@ export type ResolvedConfig = Readonly<
server: ResolvedServerOptions
build: ResolvedBuildOptions
preview: ResolvedPreviewOptions
ssr: ResolvedSSROptions | undefined
ssr: ResolvedSSROptions
assetsInclude: (file: string) => boolean
logger: Logger
createResolver: (options?: Partial<InternalResolveOptions>) => ResolveFn
Expand Down Expand Up @@ -560,15 +559,14 @@ export async function resolveConfig(
: ''

const server = resolveServerOptions(resolvedRoot, config.server, logger)
let ssr = resolveSSROptions(config.ssr)
if (config.legacy?.buildSsrCjsExternalHeuristics) {
if (ssr) ssr.format = 'cjs'
else ssr = { target: 'node', format: 'cjs' }
}
const ssr = resolveSSROptions(
config.ssr,
config.legacy?.buildSsrCjsExternalHeuristics,
config.resolve?.preserveSymlinks
)

const middlewareMode = config?.server?.middlewareMode

config = mergeConfig(config, externalConfigCompat(config, configEnv))
const optimizeDeps = config.optimizeDeps || {}

if (process.env.VITE_TEST_LEGACY_CJS_PLUGIN) {
Expand Down Expand Up @@ -668,6 +666,12 @@ export async function resolveConfig(
} else if (optimizerDisabled === 'dev') {
resolved.optimizeDeps.disabled = true // Also disabled during build
}
const ssrOptimizerDisabled = resolved.ssr.optimizeDeps.disabled
if (!ssrOptimizerDisabled) {
resolved.ssr.optimizeDeps.disabled = 'build'
} else if (ssrOptimizerDisabled === 'dev') {
resolved.ssr.optimizeDeps.disabled = true // Also disabled during build
}
}

// Some plugins that aren't intended to work in the bundling of workers (doing post-processing at build time for example).
Expand Down Expand Up @@ -1004,92 +1008,21 @@ async function loadConfigFromBundledFile(
return raw.__esModule ? raw.default : raw
}

export function isDepsOptimizerEnabled(config: ResolvedConfig): boolean {
const { command, optimizeDeps } = config
const { disabled } = optimizeDeps
export function getDepOptimizationConfig(
config: ResolvedConfig,
ssr: boolean
): DepOptimizationConfig {
return ssr ? config.ssr.optimizeDeps : config.optimizeDeps
}
export function isDepsOptimizerEnabled(
config: ResolvedConfig,
ssr: boolean
): boolean {
const { command } = config
const { disabled } = getDepOptimizationConfig(config, ssr)
return !(
disabled === true ||
(command === 'build' && disabled === 'build') ||
(command === 'serve' && optimizeDeps.disabled === 'dev')
(command === 'serve' && disabled === 'dev')
)
}

// esbuild doesn't transpile `require('foo')` into `import` statements if 'foo' is externalized
// https://github.com/evanw/esbuild/issues/566#issuecomment-735551834
function esbuildCjsExternalPlugin(externals: string[]): ESBuildPlugin {
return {
name: 'cjs-external',
setup(build) {
const escape = (text: string) =>
`^${text.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}$`
const filter = new RegExp(externals.map(escape).join('|'))

build.onResolve({ filter: /.*/, namespace: 'external' }, (args) => ({
path: args.path,
external: true
}))

build.onResolve({ filter }, (args) => ({
path: args.path,
namespace: 'external'
}))

build.onLoad({ filter: /.*/, namespace: 'external' }, (args) => ({
contents: `export * from ${JSON.stringify(args.path)}`
}))
}
}
}

// Support `rollupOptions.external` when `legacy.buildRollupPluginCommonjs` is disabled
function externalConfigCompat(config: UserConfig, { command }: ConfigEnv) {
// Only affects the build command
if (command !== 'build') {
return {}
}

// Skip if using Rollup CommonJS plugin
if (
config.legacy?.buildRollupPluginCommonjs ||
config.optimizeDeps?.disabled === 'build'
) {
return {}
}

// Skip if no `external` configured
const external = config?.build?.rollupOptions?.external
if (!external) {
return {}
}

let normalizedExternal = external
if (typeof external === 'string') {
normalizedExternal = [external]
}

// TODO: decide whether to support RegExp and function options
// They're not supported yet because `optimizeDeps.exclude` currently only accepts strings
if (
!Array.isArray(normalizedExternal) ||
normalizedExternal.some((ext) => typeof ext !== 'string')
) {
throw new Error(
`[vite] 'build.rollupOptions.external' can only be an array of strings or a string.\n` +
`You can turn on 'legacy.buildRollupPluginCommonjs' to support more advanced options.`
)
}

const additionalConfig: UserConfig = {
optimizeDeps: {
exclude: normalizedExternal as string[],
esbuildOptions: {
plugins: [
// TODO: maybe it can be added globally/unconditionally?
esbuildCjsExternalPlugin(normalizedExternal as string[])
]
}
}
}

return additionalConfig
}
2 changes: 2 additions & 0 deletions packages/vite/src/node/index.ts
Expand Up @@ -35,6 +35,7 @@ export type {
export type {
DepOptimizationMetadata,
DepOptimizationOptions,
DepOptimizationConfig,
DepOptimizationResult,
DepOptimizationProcessing,
OptimizedDepInfo,
Expand All @@ -43,6 +44,7 @@ export type {
} from './optimizer'
export type {
ResolvedSSROptions,
SsrDepOptimizationOptions,
SSROptions,
SSRFormat,
SSRTarget
Expand Down
39 changes: 34 additions & 5 deletions packages/vite/src/node/optimizer/esbuildDepPlugin.ts
Expand Up @@ -2,6 +2,7 @@ import path from 'node:path'
import { promises as fs } from 'node:fs'
import type { ImportKind, Plugin } from 'esbuild'
import { KNOWN_ASSET_TYPES } from '../constants'
import { getDepOptimizationConfig } from '..'
import type { ResolvedConfig } from '..'
import {
flattenId,
Expand Down Expand Up @@ -43,14 +44,15 @@ const externalTypes = [
export function esbuildDepPlugin(
qualified: Record<string, string>,
exportsData: Record<string, ExportsData>,
external: string[],
config: ResolvedConfig,
ssr: boolean
): Plugin {
const { extensions } = getDepOptimizationConfig(config, ssr)

// remove optimizable extensions from `externalTypes` list
const allExternalTypes = config.optimizeDeps.extensions
? externalTypes.filter(
(type) => !config.optimizeDeps.extensions?.includes('.' + type)
)
const allExternalTypes = extensions
? externalTypes.filter((type) => !extensions?.includes('.' + type))
: externalTypes

// default resolver which prefers ESM
Expand Down Expand Up @@ -163,7 +165,7 @@ export function esbuildDepPlugin(
build.onResolve(
{ filter: /^[\w@][^:]/ },
async ({ path: id, importer, kind }) => {
if (moduleListContains(config.optimizeDeps?.exclude, id)) {
if (moduleListContains(external, id)) {
return {
path: id,
external: true
Expand Down Expand Up @@ -301,3 +303,30 @@ module.exports = Object.create(new Proxy({}, {
}
}
}

// esbuild doesn't transpile `require('foo')` into `import` statements if 'foo' is externalized
// https://github.com/evanw/esbuild/issues/566#issuecomment-735551834
export function esbuildCjsExternalPlugin(externals: string[]): Plugin {
return {
name: 'cjs-external',
setup(build) {
const escape = (text: string) =>
`^${text.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}$`
const filter = new RegExp(externals.map(escape).join('|'))

build.onResolve({ filter: /.*/, namespace: 'external' }, (args) => ({
path: args.path,
external: true
}))

build.onResolve({ filter }, (args) => ({
path: args.path,
namespace: 'external'
}))

build.onLoad({ filter: /.*/, namespace: 'external' }, (args) => ({
contents: `export * from ${JSON.stringify(args.path)}`
}))
}
}
}

0 comments on commit f280dd9

Please sign in to comment.