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

refactor: introduce mergeWithDefaults and organize how default values for config options are set #18550

Merged
merged 22 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions packages/vite/src/node/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
getLocalhostAddressIfDiffersFromDNS,
injectQuery,
isFileReadable,
mergeWithDefaults,
posToNumber,
processSrcSetSync,
resolveHostname,
Expand Down Expand Up @@ -449,3 +450,52 @@ describe('flattenId', () => {
expect(result2).toHaveLength(170)
})
})

describe('mergeWithDefaults', () => {
test('merges with defaults', () => {
const actual = mergeWithDefaults(
{
useDefault: 1,
useValueIfNull: 2,
replaceArray: [0, 1],
nested: {
foo: 'bar',
},
},
{
useDefault: undefined,
useValueIfNull: null,
useValueIfNoDefault: 'foo',
replaceArray: [2, 3],
nested: {
foo2: 'bar2',
},
},
)
expect(actual).toStrictEqual({
useDefault: 1,
useValueIfNull: null,
useValueIfNoDefault: 'foo',
replaceArray: [2, 3],
nested: {
foo: 'bar',
foo2: 'bar2',
},
})

const defaults = {
object: {},
array: [],
regex: /foo/,
function: () => {},
}
const actual2 = mergeWithDefaults(defaults, {})
expect(actual2.object).toStrictEqual({})
expect(actual2.array).toStrictEqual([])
expect(actual2.regex).toStrictEqual(/foo/)
expect(actual2.function).toStrictEqual(expect.any(Function))
// cloned
expect(actual2.object).not.toBe(defaults.object)
expect(actual2.array).not.toBe(defaults.array)
})
})
58 changes: 58 additions & 0 deletions packages/vite/src/node/__tests_dts__/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { Equal, ExpectTrue } from '@type-challenges/utils'
import { mergeWithDefaults } from '../utils'

const useDefaultTypeForUndefined1 = mergeWithDefaults(
{
foo: 1,
},
{},
)

const useDefaultTypeForUndefined2 = mergeWithDefaults(
{
foo: 1,
},
{
foo: 2 as number | undefined,
},
)

const includeKeyNotIncludedInDefault1 = mergeWithDefaults(
{},
{
foo: 2,
},
)

const extendTypeWithValueType = mergeWithDefaults(
{
foo: 1,
},
{
foo: 'string' as string | number,
},
)

const plainObject = mergeWithDefaults({ foo: { bar: 1 } }, { foo: { baz: 2 } })

const nonPlainObject = mergeWithDefaults(
{ foo: ['foo'] },
{ foo: [0] as number[] | undefined },
)

const optionalNested = mergeWithDefaults({ foo: { bar: true } }, {
foo: { bar: false },
} as { foo?: { bar?: boolean } })

export type cases1 = [
ExpectTrue<Equal<typeof useDefaultTypeForUndefined1, { foo: number }>>,
ExpectTrue<Equal<typeof useDefaultTypeForUndefined2, { foo: number }>>,
ExpectTrue<Equal<typeof includeKeyNotIncludedInDefault1, { foo: number }>>,
ExpectTrue<Equal<typeof extendTypeWithValueType, { foo: string | number }>>,
ExpectTrue<Equal<typeof plainObject, { foo: { bar: number; baz: number } }>>,
ExpectTrue<Equal<typeof nonPlainObject, { foo: string[] | number[] }>>,
ExpectTrue<
Equal<typeof optionalNested, { foo: { bar: boolean } | { bar: boolean } }>
>,
]
155 changes: 82 additions & 73 deletions packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
emptyDir,
getPkgName,
joinUrlSegments,
mergeWithDefaults,
normalizePath,
partialEncodeURIPath,
} from './utils'
Expand Down Expand Up @@ -347,6 +348,44 @@ export interface ResolvedBuildOptions
modulePreload: false | ResolvedModulePreloadOptions
}

export const buildEnvironmentOptionsDefaults = Object.freeze({
target: 'modules',
/** @deprecated */
polyfillModulePreload: true,
modulePreload: true,
outDir: 'dist',
assetsDir: 'assets',
assetsInlineLimit: DEFAULT_ASSETS_INLINE_LIMIT,
// cssCodeSplit
// cssTarget
// cssMinify
sourcemap: false,
// minify
terserOptions: {},
rollupOptions: {},
commonjsOptions: {
include: [/node_modules/],
extensions: ['.js', '.cjs'],
},
dynamicImportVarsOptions: {
warnOnError: true,
exclude: [/node_modules/],
},
write: true,
emptyOutDir: null,
copyPublicDir: true,
manifest: false,
lib: false,
// ssr
ssrManifest: false,
ssrEmitAssets: false,
// emitAssets
reportCompressedSize: true,
chunkSizeWarningLimit: 500,
watch: null,
// createEnvironment
})

export function resolveBuildEnvironmentOptions(
raw: BuildEnvironmentOptions,
logger: Logger,
Expand All @@ -369,84 +408,49 @@ export function resolveBuildEnvironmentOptions(
raw.modulePreload = { polyfill: false }
}

const modulePreload = raw.modulePreload
const defaultModulePreload = {
polyfill: true,
const merged = mergeWithDefaults(
{
...buildEnvironmentOptionsDefaults,
cssCodeSplit: !raw.lib,
minify: consumer === 'server' ? false : 'esbuild',
ssr: consumer === 'server',
emitAssets: consumer === 'client',
createEnvironment: (name, config) => new BuildEnvironment(name, config),
} satisfies BuildEnvironmentOptions,
raw,
)

// handle special build targets
if (merged.target === 'modules') {
merged.target = ESBUILD_MODULES_TARGET
}

const defaultBuildEnvironmentOptions: BuildEnvironmentOptions = {
outDir: 'dist',
assetsDir: 'assets',
assetsInlineLimit: DEFAULT_ASSETS_INLINE_LIMIT,
cssCodeSplit: !raw.lib,
sourcemap: false,
rollupOptions: {},
minify: consumer === 'server' ? false : 'esbuild',
terserOptions: {},
write: true,
emptyOutDir: null,
copyPublicDir: true,
manifest: false,
lib: false,
ssr: consumer === 'server',
ssrManifest: false,
ssrEmitAssets: false,
emitAssets: consumer === 'client',
reportCompressedSize: true,
chunkSizeWarningLimit: 500,
watch: null,
createEnvironment: (name, config) => new BuildEnvironment(name, config),
// normalize false string into actual false
if ((merged.minify as string) === 'false') {
merged.minify = false
} else if (merged.minify === true) {
merged.minify = 'esbuild'
}

const userBuildEnvironmentOptions = raw
? mergeConfig(defaultBuildEnvironmentOptions, raw)
: defaultBuildEnvironmentOptions
const defaultModulePreload = {
polyfill: true,
}

// @ts-expect-error Fallback options instead of merging
const resolved: ResolvedBuildEnvironmentOptions = {
target: 'modules',
cssTarget: false,
...userBuildEnvironmentOptions,
commonjsOptions: {
include: [/node_modules/],
extensions: ['.js', '.cjs'],
...userBuildEnvironmentOptions.commonjsOptions,
},
dynamicImportVarsOptions: {
warnOnError: true,
exclude: [/node_modules/],
...userBuildEnvironmentOptions.dynamicImportVarsOptions,
},
...merged,
cssTarget: merged.cssTarget ?? merged.target,
cssMinify:
merged.cssMinify ?? (consumer === 'server' ? 'esbuild' : !!merged.minify),
// Resolve to false | object
modulePreload:
modulePreload === false
merged.modulePreload === false
? false
: typeof modulePreload === 'object'
? {
: merged.modulePreload === true
? defaultModulePreload
: {
...defaultModulePreload,
...modulePreload,
}
: defaultModulePreload,
}

// handle special build targets
if (resolved.target === 'modules') {
resolved.target = ESBUILD_MODULES_TARGET
}

if (!resolved.cssTarget) {
resolved.cssTarget = resolved.target
}

// normalize false string into actual false
if ((resolved.minify as string) === 'false') {
resolved.minify = false
} else if (resolved.minify === true) {
resolved.minify = 'esbuild'
}

if (resolved.cssMinify == null) {
resolved.cssMinify = consumer === 'server' ? 'esbuild' : !!resolved.minify
...merged.modulePreload,
},
}

if (isSsrTargetWebworkerEnvironment) {
Expand Down Expand Up @@ -1503,15 +1507,20 @@ async function defaultBuildApp(builder: ViteBuilder): Promise<void> {
}
}

export const builderOptionsDefaults = Object.freeze({
sharedConfigBuild: false,
sharedPlugins: false,
// buildApp
})

export function resolveBuilderOptions(
options: BuilderOptions | undefined,
): ResolvedBuilderOptions | undefined {
if (!options) return
return {
sharedConfigBuild: options.sharedConfigBuild ?? false,
sharedPlugins: options.sharedPlugins ?? false,
buildApp: options.buildApp ?? defaultBuildApp,
}
return mergeWithDefaults(
{ ...builderOptionsDefaults, buildApp: defaultBuildApp },
options,
)
}

export type ResolvedBuilderOptions = Required<BuilderOptions>
Expand Down
Loading