Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ssr.optimizeDeps #8917

Merged
merged 5 commits into from Jul 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)}`
}))
}
}
}