Skip to content

Commit

Permalink
feat: introduce server option (#3725)
Browse files Browse the repository at this point in the history
Co-authored-by: Vladimir <sleuths.slews0s@icloud.com>
  • Loading branch information
fenghan34 and sheremet-va committed Jul 31, 2023
1 parent 8bd26b0 commit dc4faf6
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 48 deletions.
92 changes: 67 additions & 25 deletions docs/config/index.md
Expand Up @@ -101,46 +101,52 @@ Include globs for in-source test files.

When defined, Vitest will run all matched files with `import.meta.vitest` inside.

### deps
### server

- **Type:** `{ external?, inline?, ... }`
- **Type:** `{ sourcemap?, deps?, ... }`
- **Version:** Since Vitest 0.34.0

Handling for dependencies resolution.
Vite-Node server options.

#### deps.experimentalOptimizer
#### server.sourcemap

- **Type:** `{ ssr?, web? }`
- **Version:** Since Vitest 0.29.0
- **See also:** [Dep Optimization Options](https://vitejs.dev/config/dep-optimization-options.html)
- **Type:** `'inline' | boolean`
- **Default:** `'inline'`

Enable dependency optimization. If you have a lot of tests, this might improve their performance.
Inject inline sourcemap to modules.

When Vitest encounters the external library listed in `include`, it will be bundled into a single file using esbuild and imported as a whole module. This is good for several reasons:
#### server.debug

- Importing packages with a lot of imports is expensive. By bundling them into one file we can save a lot of time
- Importing UI libraries is expensive because they are not meant to run inside Node.js
- Your `alias` configuration is now respected inside bundled packages
- Code in your tests is running closer to how it's running in the browser
- **Type:** `{ dumpModules?, loadDumppedModules? }`

Be aware that only packages in `deps.experimentalOptimizer?.[mode].include` option are bundled (some plugins populate this automatically, like Svelte). You can read more about available options in [Vite](https://vitejs.dev/config/dep-optimization-options.html) docs. By default, Vitest uses `experimentalOptimizer.web` for `jsdom` and `happy-dom` environments, and `experimentalOptimizer.ssr` for `node` and `edge` environments, but it is configurable by [`transformMode`](#transformmode).
Vite-Node debugger options.

This options also inherits your `optimizeDeps` configuration (for web Vitest will extend `optimizeDeps`, for ssr - `ssr.optimizeDeps`). If you redefine `include`/`exclude` option in `deps.experimentalOptimizer` it will extend your `optimizeDeps` when running tests. Vitest automatically removes the same options from `include`, if they are listed in `exclude`.
#### server.debug.dumpModules

::: tip
You will not be able to edit your `node_modules` code for debugging, since the code is actually located in your `cacheDir` or `test.cache.dir` directory. If you want to debug with `console.log` statements, edit it directly or force rebundling with `deps.experimentalOptimizer?.[mode].force` option.
:::
- **Type:** `boolean | string`

Dump the transformed module to filesystem. Passing a string will dump to the specified path.

#### server.debug.loadDumppedModules

- **Type:** `boolean`

Read dumped module from filesystem whenever exists. Useful for debugging by modifying the dump result from the filesystem.

#### deps.external
#### server.deps

- **Type:** `{ external?, inline?, ... }`

Handling for dependencies resolution.

#### server.deps.external

- **Type:** `(string | RegExp)[]`
- **Default:** `[/\/node_modules\//]`

Externalize means that Vite will bypass the package to native Node. Externalized dependencies will not be applied Vite's transformers and resolvers, so they do not support HMR on reload. Typically, packages under `node_modules` are externalized.

When using strings they need to be paths inside your [`deps.moduleDirectories`](/config/#deps-moduledirectories). For example `external: ['module/folder']` with the default `moduleDirectories` option will externalize `node_modules/module/folder`.
Regular expressions on the other hand are matched against the whole path.
Externalize means that Vite will bypass the package to native Node. Externalized dependencies will not be applied Vite's transformers and resolvers, so they do not support HMR on reload. All packages under `node_modules` are externalized.

#### deps.inline
#### server.deps.inline

- **Type:** `(string | RegExp)[] | true`
- **Default:** `[]`
Expand All @@ -149,7 +155,7 @@ Vite will process inlined modules. This could be helpful to handle packages that

If `true`, every dependency will be inlined. All dependencies, specified in [`ssr.noExternal`](https://vitejs.dev/guide/ssr.html#ssr-externals) will be inlined by default.

#### deps.fallbackCJS
#### server.deps.fallbackCJS

- **Type** `boolean`
- **Default:** `false`
Expand All @@ -158,6 +164,42 @@ When a dependency is a valid ESM package, try to guess the cjs version based on

This might potentially cause some misalignment if a package has different logic in ESM and CJS mode.

#### server.deps.cacheDir

- **Type** `string`
- **Default**: `'node_modules/.vite'`

Directory to save cache files.

### deps

- **Type:** `{ experimentalOptimizer?, registerNodeLoader?, ... }`

Handling for dependencies resolution.

#### deps.experimentalOptimizer

- **Type:** `{ ssr?, web? }`
- **Version:** Since Vitest 0.29.0
- **See also:** [Dep Optimization Options](https://vitejs.dev/config/dep-optimization-options.html)

Enable dependency optimization. If you have a lot of tests, this might improve their performance.

When Vitest encounters the external library listed in `include`, it will be bundled into a single file using esbuild and imported as a whole module. This is good for several reasons:

- Importing packages with a lot of imports is expensive. By bundling them into one file we can save a lot of time
- Importing UI libraries is expensive because they are not meant to run inside Node.js
- Your `alias` configuration is now respected inside bundled packages
- Code in your tests is running closer to how it's running in the browser

Be aware that only packages in `deps.experimentalOptimizer?.[mode].include` option are bundled (some plugins populate this automatically, like Svelte). You can read more about available options in [Vite](https://vitejs.dev/config/dep-optimization-options.html) docs. By default, Vitest uses `experimentalOptimizer.web` for `jsdom` and `happy-dom` environments, and `experimentalOptimizer.ssr` for `node` and `edge` environments, but it is configurable by [`transformMode`](#transformmode).

This options also inherits your `optimizeDeps` configuration (for web Vitest will extend `optimizeDeps`, for ssr - `ssr.optimizeDeps`). If you redefine `include`/`exclude` option in `deps.experimentalOptimizer` it will extend your `optimizeDeps` when running tests. Vitest automatically removes the same options from `include`, if they are listed in `exclude`.

::: tip
You will not be able to edit your `node_modules` code for debugging, since the code is actually located in your `cacheDir` or `test.cache.dir` directory. If you want to debug with `console.log` statements, edit it directly or force rebundling with `deps.experimentalOptimizer?.[mode].force` option.
:::

#### deps.registerNodeLoader<NonProjectOption />

- **Type:** `boolean`
Expand Down
3 changes: 1 addition & 2 deletions packages/vite-node/src/server.ts
Expand Up @@ -39,8 +39,7 @@ export class ViteNodeServer {
const ssrOptions = server.config.ssr

options.deps ??= {}

options.deps.cacheDir = relative(server.config.root, server.config.cacheDir)
options.deps.cacheDir = relative(server.config.root, options.deps.cacheDir || server.config.cacheDir)

if (ssrOptions) {
// we don't externalize ssr, because it has different semantics in Vite
Expand Down
59 changes: 43 additions & 16 deletions packages/vitest/src/node/config.ts
Expand Up @@ -124,22 +124,7 @@ export function resolveConfig(
if (resolved.coverage.provider === 'v8' && resolved.coverage.enabled && isBrowserEnabled(resolved))
throw new Error('@vitest/coverage-v8 does not work with --browser. Use @vitest/coverage-istanbul instead')

resolved.deps = resolved.deps || {}
// vitenode will try to import such file with native node,
// but then our mocker will not work properly
if (resolved.deps.inline !== true) {
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
// @ts-ignore ssr is not typed in Vite 2, but defined in Vite 3, so we can't use expect-error
const ssrOptions = viteConfig.ssr

if (ssrOptions?.noExternal === true && resolved.deps.inline == null) {
resolved.deps.inline = true
}
else {
resolved.deps.inline ??= []
resolved.deps.inline.push(...extraInlineDeps)
}
}
resolved.deps ??= {}
resolved.deps.moduleDirectories ??= []
resolved.deps.moduleDirectories = resolved.deps.moduleDirectories.map((dir) => {
if (!dir.startsWith('/'))
Expand All @@ -151,6 +136,48 @@ export function resolveConfig(
if (!resolved.deps.moduleDirectories.includes('/node_modules/'))
resolved.deps.moduleDirectories.push('/node_modules/')

resolved.server ??= {}
resolved.server.deps ??= {}

const deprecatedDepsOptions = ['inline', 'external', 'fallbackCJS'] as const
deprecatedDepsOptions.forEach((option) => {
if (resolved.deps[option] === undefined)
return

if (option === 'fallbackCJS') {
console.warn(c.yellow(`${c.inverse(c.yellow(' Vitest '))} "deps.${option}" is deprecated. Use "server.deps.${option}" instead`))
}
else {
const transformMode = resolved.environment === 'happy-dom' || resolved.environment === 'jsdom' ? 'web' : 'ssr'
console.warn(
c.yellow(
`${c.inverse(c.yellow(' Vitest '))} "deps.${option}" is deprecated. If you rely on vite-node directly, use "server.deps.${option}" instead. Otherwise, consider using "deps.optimizer.${transformMode}.${option === 'external' ? 'exclude' : 'include'}"`,
),
)
}

if (resolved.server.deps![option] === undefined)
resolved.server.deps![option] = resolved.deps[option] as any
})

// vitenode will try to import such file with native node,
// but then our mocker will not work properly
if (resolved.server.deps.inline !== true) {
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
// @ts-ignore ssr is not typed in Vite 2, but defined in Vite 3, so we can't use expect-error
const ssrOptions = viteConfig.ssr
if (ssrOptions?.noExternal === true && resolved.server.deps.inline == null) {
resolved.server.deps.inline = true
}
else {
resolved.server.deps.inline ??= []
resolved.server.deps.inline.push(...extraInlineDeps)
}
}

resolved.server.deps.moduleDirectories ??= []
resolved.server.deps.moduleDirectories.push(...resolved.deps.moduleDirectories)

if (resolved.runner) {
resolved.runner = resolveModule(resolved.runner, { paths: [resolved.root] })
?? resolve(resolved.root, resolved.runner)
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/node/core.ts
Expand Up @@ -89,7 +89,7 @@ export class Vitest {
if (this.config.watch && this.mode !== 'typecheck')
this.registerWatcher()

this.vitenode = new ViteNodeServer(server, this.config)
this.vitenode = new ViteNodeServer(server, this.config.server)
const node = this.vitenode
this.runner = new ViteNodeRunner({
root: server.config.root,
Expand Down
15 changes: 15 additions & 0 deletions packages/vitest/src/types/config.ts
Expand Up @@ -2,6 +2,7 @@ import type { AliasOptions, CommonServerOptions, DepOptimizationConfig } from 'v
import type { PrettyFormatOptions } from 'pretty-format'
import type { FakeTimerInstallOpts } from '@sinonjs/fake-timers'
import type { SequenceHooks, SequenceSetupFiles } from '@vitest/runner'
import type { ViteNodeServerOptions } from 'vite-node'
import type { BuiltinReporters } from '../node/reporters'
import type { TestSequencerConstructor } from '../node/sequencers/types'
import type { ChaiConfig } from '../integrations/chai'
Expand Down Expand Up @@ -112,6 +113,8 @@ interface DepsOptions {
* And does not support HMR on reload.
*
* Typically, packages under `node_modules` are externalized.
*
* @deprecated If you rely on vite-node directly, use `server.deps.external` instead. Otherwise, consider using `deps.optimizer.{web,ssr}.exclude`.
*/
external?: (string | RegExp)[]
/**
Expand All @@ -120,6 +123,8 @@ interface DepsOptions {
* This could be helpful to handle packages that ship `.js` in ESM format (that Node can't handle).
*
* If `true`, every dependency will be inlined
*
* @deprecated If you rely on vite-node directly, use `server.deps.inline` instead. Otherwise, consider using `deps.optimizer.{web,ssr}.include`.
*/
inline?: (string | RegExp)[] | true

Expand All @@ -136,6 +141,9 @@ interface DepsOptions {
* cause some misalignment if a package have different logic in ESM and CJS mode.
*
* @default false
*
* @deprecated Use `server.deps.fallbackCJS` instead.
*
*/
fallbackCJS?: boolean

Expand All @@ -149,6 +157,7 @@ interface DepsOptions {
* A list of directories relative to the config file that should be treated as module directories.
*
* @default ['node_modules']
*
*/
moduleDirectories?: string[]
}
Expand Down Expand Up @@ -188,9 +197,15 @@ export interface InlineConfig {

/**
* Handling for dependencies inlining or externalizing
*
*/
deps?: DepsOptions

/**
* Vite-node server options
*/
server?: Omit<ViteNodeServerOptions, 'transformMode'>

/**
* Base directory to scan for the test files
*
Expand Down
8 changes: 6 additions & 2 deletions test/core/vitest.config.ts
Expand Up @@ -66,10 +66,14 @@ export default defineConfig({
seed: 101,
},
deps: {
external: ['tinyspy', /src\/external/, /esm\/esm/],
inline: ['inline-lib'],
moduleDirectories: ['node_modules', 'projects', 'packages'],
},
server: {
deps: {
external: ['tinyspy', /src\/external/, /esm\/esm/],
inline: ['inline-lib'],
},
},
alias: [
{
find: 'test-alias',
Expand Down
6 changes: 4 additions & 2 deletions test/resolve/vitest.config.ts
Expand Up @@ -6,8 +6,10 @@ export default defineConfig({
['**/web.test.ts', 'happy-dom'],
['**/ssr.test.ts', 'node'],
],
deps: {
external: [/pkg-/],
server: {
deps: {
external: [/pkg-/],
},
},
},
})

0 comments on commit dc4faf6

Please sign in to comment.