[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

refactor: seperate vite runner to `vite-node` package (#495)

authored by

Anthony Fu and committed by
GitHub
(Jan 11, 2022, 2:45 AM +0800) 4498ae96 25eb95d2

+796 -418
+29 -3
pnpm-lock.yaml
··· 389 389 vue: 3.2.26 390 390 vue-router: 4.0.12_vue@3.2.26 391 391 392 + packages/vite-node: 393 + specifiers: 394 + '@types/minimist': ^1.2.2 395 + kolorist: ^1.5.1 396 + minimist: ^1.2.5 397 + mlly: ^0.3.17 398 + pathe: ^0.2.0 399 + rollup: ^2.63.0 400 + vite: ^2.7.10 401 + dependencies: 402 + kolorist: 1.5.1 403 + minimist: 1.2.5 404 + mlly: 0.3.17 405 + pathe: 0.2.0 406 + vite: 2.7.10 407 + devDependencies: 408 + '@types/minimist': 1.2.2 409 + rollup: 2.63.0 410 + 392 411 packages/vitest: 393 412 specifiers: 394 413 '@antfu/install-pkg': ^0.1.0 ··· 434 453 tinyspy: ^0.2.8 435 454 typescript: ^4.5.4 436 455 vite: '>=2.7.10' 456 + vite-node: workspace:* 437 457 ws: ^8.4.0 438 458 dependencies: 439 459 '@types/chai': 4.3.0 ··· 480 500 source-map-js: 1.0.1 481 501 strip-ansi: 7.0.1 482 502 typescript: 4.5.4 503 + vite-node: link:../vite-node 483 504 ws: 8.4.0 484 505 485 506 packages/ws-client: ··· 2062 2083 resolution: {integrity: sha512-oqXqVb0ci19GtH0vOA/U2TmHTcRY9kuZl4mqUxe0QmJAlIW13kzhuK5pi1i9+ngav8FjpSb9FVS/GE00GLX1VA==} 2063 2084 dependencies: 2064 2085 '@types/braces': 3.0.1 2086 + dev: true 2087 + 2088 + /@types/minimist/1.2.2: 2089 + resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} 2065 2090 dev: true 2066 2091 2067 2092 /@types/natural-compare/1.4.1: ··· 5759 5784 engines: {node: '>=6'} 5760 5785 dev: true 5761 5786 5787 + /kolorist/1.5.1: 5788 + resolution: {integrity: sha512-lxpCM3HTvquGxKGzHeknB/sUjuVoUElLlfYnXZT73K8geR9jQbroGlSCFBax9/0mpGoD3kzcMLnOlGQPJJNyqQ==} 5789 + dev: false 5790 + 5762 5791 /levn/0.3.0: 5763 5792 resolution: {integrity: sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=} 5764 5793 engines: {node: '>= 0.8.0'} ··· 5984 6013 5985 6014 /minimist/1.2.5: 5986 6015 resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} 5987 - dev: true 5988 6016 5989 6017 /mkdirp-classic/0.5.3: 5990 6018 resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} ··· 6004 6032 6005 6033 /mlly/0.3.17: 6006 6034 resolution: {integrity: sha512-C3v8eHB9KqmS1ewOB5DUgljX13C3xuoaXZd5bOLtwpxk9pBZhA+wyVgYXPuP4aukQ9bKYWjy+YQVC+DmniIsgA==} 6007 - dev: true 6008 6035 6009 6036 /mockdate/3.0.5: 6010 6037 resolution: {integrity: sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==} ··· 6493 6520 6494 6521 /pathe/0.2.0: 6495 6522 resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==} 6496 - dev: true 6497 6523 6498 6524 /pathval/1.1.1: 6499 6525 resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
+5 -1
tsconfig.json
··· 20 20 "~/*": ["./packages/ui/client/*"], 21 21 "vitest": ["./packages/vitest/src/index.ts"], 22 22 "vitest/global": ["./packages/vitest/global.d.ts"], 23 - "vitest/node": ["./packages/vitest/src/node/index.ts"] 23 + "vitest/node": ["./packages/vitest/src/node/index.ts"], 24 + "vite-node": ["./packages/vite-node/src/index.ts"], 25 + "vite-node/client": ["./packages/vite-node/src/client.ts"], 26 + "vite-node/server": ["./packages/vite-node/src/server.ts"], 27 + "vite-node/utils": ["./packages/vite-node/src/utils.ts"] 24 28 } 25 29 }, 26 30 "exclude": [
+82
packages/vite-node/README.md
··· 1 + # vite-node 2 + 3 + [![NPM version](https://img.shields.io/npm/v/vite-node?color=a1b858&label=)](https://www.npmjs.com/package/vite-node) 4 + 5 + Vite as Node runtime. The engine powers [Vitest](https://github.com/vitest-dev/vitest). 6 + 7 + ## Features 8 + 9 + - Out-of-box ESM & TypeScript support (possible for more with plugins) 10 + - Top-level await 11 + - Vite plugins, resolve, aliasing 12 + - Respect `vite.config.ts` 13 + - Shims for `__dirname` and `__filename` in ESM 14 + - Access to native node modules like `fs`, `path`, etc. 15 + 16 + ## CLI Usage 17 + 18 + Run JS/TS file on Node.js using Vite's resolvers and transformers. 19 + 20 + ```bash 21 + npx vite-node index.ts 22 + ``` 23 + 24 + Options: 25 + 26 + ```bash 27 + npx vite-node -h 28 + ``` 29 + 30 + ## Programmatic Usage 31 + 32 + In Vite Node, the server and runner (client) are separated, so you can integrate them in different contexts (workers, cross-process, or remote) if needed. The demo below shows a simple example of having the server and running in the same context 33 + 34 + ```ts 35 + import { createServer } from 'vite' 36 + import { ViteNodeServer } from 'vite-node/server' 37 + import { ViteNodeRunner } from 'vite-node/client' 38 + 39 + // create vite server 40 + const server = await createServer() 41 + // this is need to initialize the plugins 42 + await server.pluginContainer.buildStart({}) 43 + 44 + // create vite-node server 45 + const node = new ViteNodeServer(server) 46 + 47 + // create vite-node runner 48 + const runner = new ViteNodeRunner({ 49 + root: server.config.root, 50 + base: server.config.base, 51 + // when having the server and runner in a different context, 52 + // you will need to handle the communication between them 53 + // and pass to this function 54 + fetchModule(id) { 55 + return node.fetchModule(id) 56 + }, 57 + }) 58 + 59 + // execute the file 60 + await runner.run('./example.ts') 61 + 62 + // close the vite server 63 + await server.close() 64 + ``` 65 + 66 + ## Credits 67 + 68 + Based on [@pi0](https://github.com/pi0)'s brilliant idea of having a Vite server as the on-demand transforming service for [Nuxt's Vite SSR](https://github.com/nuxt/vite/pull/201). 69 + 70 + Thanks [@brillout](https://github.com/brillout) for kindly sharing this package name. 71 + 72 + ## Sponsors 73 + 74 + <p align="center"> 75 + <a href="https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg"> 76 + <img src='https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg'/> 77 + </a> 78 + </p> 79 + 80 + ## License 81 + 82 + [MIT](./LICENSE) License © 2021 [Anthony Fu](https://github.com/antfu)
+67
packages/vite-node/package.json
··· 1 + { 2 + "name": "vite-node", 3 + "version": "0.0.139", 4 + "description": "Vite as Node.js runtime", 5 + "homepage": "https://github.com/vitest-dev/vitest#readme", 6 + "bugs": { 7 + "url": "https://github.com/vitest-dev/vitest/issues" 8 + }, 9 + "repository": { 10 + "type": "git", 11 + "url": "git+https://github.com/vitest-dev/vitest.git", 12 + "directory": "packages/vite-node" 13 + }, 14 + "funding": "https://github.com/sponsors/antfu", 15 + "license": "MIT", 16 + "author": "Anthony Fu <anthonyfu117@hotmail.com>", 17 + "type": "module", 18 + "exports": { 19 + ".": { 20 + "import": "./dist/index.js", 21 + "types": "./dist/index.d.ts" 22 + }, 23 + "./client": { 24 + "import": "./dist/client.js", 25 + "types": "./dist/client.d.ts" 26 + }, 27 + "./server": { 28 + "import": "./dist/server.js", 29 + "types": "./dist/server.d.ts" 30 + }, 31 + "./utils": { 32 + "import": "./dist/utils.js", 33 + "types": "./dist/utils.d.ts" 34 + } 35 + }, 36 + "main": "./dist/index.js", 37 + "module": "./dist/index.js", 38 + "types": "./dist/index.d.ts", 39 + "bin": { 40 + "vite-node": "./vite-node.js" 41 + }, 42 + "files": [ 43 + "dist", 44 + "*.d.ts", 45 + "*.mjs" 46 + ], 47 + "scripts": { 48 + "build": "rimraf dist && rollup -c", 49 + "dev": "rollup -c --watch --watch.include=src/**", 50 + "prepublishOnly": "nr build", 51 + "typecheck": "tsc --noEmit" 52 + }, 53 + "dependencies": { 54 + "kolorist": "^1.5.1", 55 + "minimist": "^1.2.5", 56 + "mlly": "^0.3.17", 57 + "pathe": "^0.2.0", 58 + "vite": "^2.7.10" 59 + }, 60 + "devDependencies": { 61 + "@types/minimist": "^1.2.2", 62 + "rollup": "^2.63.0" 63 + }, 64 + "engines": { 65 + "node": ">=14.14.0" 66 + } 67 + }
+59
packages/vite-node/rollup.config.js
··· 1 + import esbuild from 'rollup-plugin-esbuild' 2 + import dts from 'rollup-plugin-dts' 3 + import resolve from '@rollup/plugin-node-resolve' 4 + import commonjs from '@rollup/plugin-commonjs' 5 + import json from '@rollup/plugin-json' 6 + import alias from '@rollup/plugin-alias' 7 + import pkg from './package.json' 8 + 9 + const entry = [ 10 + 'src/index.ts', 11 + 'src/server.ts', 12 + 'src/client.ts', 13 + 'src/utils.ts', 14 + 'src/cli.ts', 15 + ] 16 + 17 + const external = [ 18 + ...Object.keys(pkg.dependencies || {}), 19 + ...Object.keys(pkg.peerDependencies || {}), 20 + 'birpc', 21 + 'vite', 22 + ] 23 + 24 + export default () => [ 25 + { 26 + input: entry, 27 + output: { 28 + dir: 'dist', 29 + format: 'esm', 30 + }, 31 + external, 32 + plugins: [ 33 + alias({ 34 + entries: [ 35 + { find: /^node:(.+)$/, replacement: '$1' }, 36 + ], 37 + }), 38 + resolve({ 39 + preferBuiltins: true, 40 + }), 41 + json(), 42 + commonjs(), 43 + esbuild({ 44 + target: 'node14', 45 + }), 46 + ], 47 + }, 48 + ...entry.map(input => ({ 49 + input, 50 + output: { 51 + file: input.replace('src/', 'dist/').replace('.ts', '.d.ts'), 52 + format: 'esm', 53 + }, 54 + external, 55 + plugins: [ 56 + dts({ respectExternal: true }), 57 + ], 58 + })), 59 + ]
+4
packages/vite-node/tsconfig.json
··· 1 + { 2 + "extends": "../../tsconfig.json", 3 + "exclude": ["./dist"] 4 + }
+2
packages/vite-node/vite-node.mjs
··· 1 + #!/usr/bin/env node 2 + import('./dist/cli.js')
+2 -1
packages/vitest/package.json
··· 97 97 "source-map-js": "^1.0.1", 98 98 "strip-ansi": "^7.0.1", 99 99 "typescript": "^4.5.4", 100 - "ws": "^8.4.0" 100 + "ws": "^8.4.0", 101 + "vite-node": "workspace:*" 101 102 }, 102 103 "peerDependencies": { 103 104 "@vitest/ui": "*",
-1
packages/vitest/tsconfig.json
··· 1 1 { 2 2 "extends": "../../tsconfig.json", 3 - "include": ["./src/**/*.ts"], 4 3 "exclude": ["./dist"] 5 4 }
+91
packages/vite-node/src/cli.ts
··· 1 + import minimist from 'minimist' 2 + import { dim, red } from 'kolorist' 3 + import { createServer } from 'vite' 4 + import { ViteNodeServer } from './server' 5 + import { ViteNodeRunner } from './client' 6 + 7 + const argv = minimist(process.argv.slice(2), { 8 + 'alias': { 9 + r: 'root', 10 + c: 'config', 11 + h: 'help', 12 + w: 'watch', 13 + s: 'silent', 14 + }, 15 + '--': true, 16 + 'string': ['root', 'config'], 17 + 'boolean': ['help', 'watch', 'silent'], 18 + unknown(name: string) { 19 + if (name[0] === '-') { 20 + console.error(red(`Unknown argument: ${name}`)) 21 + help() 22 + process.exit(1) 23 + } 24 + return true 25 + }, 26 + }) 27 + 28 + if (argv.help) { 29 + help() 30 + process.exit(0) 31 + } 32 + 33 + if (!argv._.length) { 34 + console.error(red('No files specified.')) 35 + help() 36 + process.exit(1) 37 + } 38 + 39 + // forward argv 40 + process.argv = [...process.argv.slice(0, 2), ...(argv['--'] || [])] 41 + 42 + run(argv) 43 + 44 + function help() { 45 + // eslint-disable-next-line no-console 46 + console.log(` 47 + Usage: 48 + $ vite-node [options] [files] 49 + 50 + Options: 51 + -r, --root <path> ${dim('[string]')} use specified root directory 52 + -c, --config <file> ${dim('[string]')} use specified config file 53 + -w, --watch ${dim('[boolean]')} restart on file changes, similar to "nodemon" 54 + -s, --silent ${dim('[boolean]')} do not emit errors and logs 55 + --vue ${dim('[boolean]')} support for importing Vue component 56 + `) 57 + } 58 + 59 + export interface CliOptions { 60 + files?: string[] 61 + _?: string[] 62 + root?: string 63 + config?: string 64 + } 65 + 66 + async function run(options: CliOptions = {}) { 67 + const files = options.files || options._ || [] 68 + 69 + const server = await createServer({ 70 + logLevel: 'error', 71 + clearScreen: false, 72 + configFile: options.config, 73 + root: options.root, 74 + }) 75 + await server.pluginContainer.buildStart({}) 76 + 77 + const node = new ViteNodeServer(server) 78 + 79 + const runner = new ViteNodeRunner({ 80 + root: server.config.root, 81 + base: server.config.base, 82 + fetchModule(id) { 83 + return node.fetchModule(id) 84 + }, 85 + }) 86 + 87 + for (const file of files) 88 + await runner.run(file) 89 + 90 + await server.close() 91 + }
+163
packages/vite-node/src/client.ts
··· 1 + import { builtinModules, createRequire } from 'module' 2 + import { fileURLToPath, pathToFileURL } from 'url' 3 + import vm from 'vm' 4 + import { dirname, resolve } from 'pathe' 5 + import { normalizeId, slash, toFilePath } from './utils' 6 + import type { ModuleCache, ViteNodeRunnerOptions } from './types' 7 + 8 + export class ViteNodeRunner { 9 + root: string 10 + 11 + externalCache: Map<string, string | Promise<false | string>> 12 + moduleCache: Map<string, ModuleCache> 13 + 14 + constructor(public options: ViteNodeRunnerOptions) { 15 + this.root = options.root || process.cwd() 16 + this.moduleCache = options.moduleCache || new Map() 17 + this.externalCache = new Map<string, string | Promise<false | string>>() 18 + builtinModules.forEach(m => this.externalCache.set(m, m)) 19 + } 20 + 21 + async run(file: string) { 22 + return await this.cachedRequest(`/@fs/${slash(resolve(file))}`, []) 23 + } 24 + 25 + async cachedRequest(rawId: string, callstack: string[]) { 26 + const id = normalizeId(rawId, this.options.base) 27 + const fsPath = toFilePath(id, this.root) 28 + 29 + if (this.moduleCache.get(fsPath)?.promise) 30 + return this.moduleCache.get(fsPath)?.promise 31 + 32 + const promise = this.directRequest(id, fsPath, callstack) 33 + this.setCache(fsPath, { promise }) 34 + 35 + return await promise 36 + } 37 + 38 + async directRequest(id: string, fsPath: string, callstack: string[]) { 39 + callstack = [...callstack, id] 40 + const request = async(dep: string) => { 41 + if (callstack.includes(dep)) { 42 + const cacheKey = toFilePath(dep, this.root) 43 + if (!this.moduleCache.get(cacheKey)?.exports) 44 + throw new Error(`Circular dependency detected\nStack:\n${[...callstack, dep].reverse().map(p => `- ${p}`).join('\n')}`) 45 + return this.moduleCache.get(cacheKey)!.exports 46 + } 47 + return this.cachedRequest(dep, callstack) 48 + } 49 + 50 + if (this.options.requestStubs && id in this.options.requestStubs) 51 + return this.options.requestStubs[id] 52 + 53 + const { code: transformed, externalize } = await this.options.fetchModule(id) 54 + if (externalize) { 55 + const mod = await interpretedImport(externalize, this.options.interpretDefault ?? true) 56 + this.setCache(fsPath, { exports: mod }) 57 + return mod 58 + } 59 + 60 + if (transformed == null) 61 + throw new Error(`failed to load ${id}`) 62 + 63 + // disambiguate the `<UNIT>:/` on windows: see nodejs/node#31710 64 + const url = pathToFileURL(fsPath).href 65 + const exports: any = {} 66 + 67 + this.setCache(fsPath, { code: transformed, exports }) 68 + 69 + const __filename = fileURLToPath(url) 70 + const moduleProxy = { 71 + set exports(value) { 72 + exportAll(exports, value) 73 + exports.default = value 74 + }, 75 + get exports() { 76 + return exports.default 77 + }, 78 + } 79 + 80 + const context = this.prepareContext({ 81 + // esm transformed by Vite 82 + __vite_ssr_import__: request, 83 + __vite_ssr_dynamic_import__: request, 84 + __vite_ssr_exports__: exports, 85 + __vite_ssr_exportAll__: (obj: any) => exportAll(exports, obj), 86 + __vite_ssr_import_meta__: { url }, 87 + 88 + // cjs compact 89 + require: createRequire(url), 90 + exports, 91 + module: moduleProxy, 92 + __filename, 93 + __dirname: dirname(__filename), 94 + }) 95 + 96 + const fn = vm.runInThisContext(`async (${Object.keys(context).join(',')})=>{{${transformed}\n}}`, { 97 + filename: fsPath, 98 + lineOffset: 0, 99 + }) 100 + 101 + await fn(...Object.values(context)) 102 + 103 + return exports 104 + } 105 + 106 + prepareContext(context: Record<string, any>) { 107 + return context 108 + } 109 + 110 + setCache(id: string, mod: Partial<ModuleCache>) { 111 + if (!this.moduleCache.has(id)) 112 + this.moduleCache.set(id, mod) 113 + else 114 + Object.assign(this.moduleCache.get(id), mod) 115 + } 116 + } 117 + 118 + function hasNestedDefault(target: any) { 119 + return '__esModule' in target && target.__esModule && 'default' in target.default 120 + } 121 + 122 + function proxyMethod(name: 'get' | 'set' | 'has' | 'deleteProperty', tryDefault: boolean) { 123 + return function(target: any, key: string | symbol, ...args: [any?, any?]) { 124 + const result = Reflect[name](target, key, ...args) 125 + if (typeof target.default !== 'object') 126 + return result 127 + if ((tryDefault && key === 'default') || typeof result === 'undefined') 128 + return Reflect[name](target.default, key, ...args) 129 + return result 130 + } 131 + } 132 + 133 + async function interpretedImport(path: string, interpretDefault: boolean) { 134 + const mod = await import(path) 135 + 136 + if (interpretDefault && 'default' in mod) { 137 + const tryDefault = hasNestedDefault(mod) 138 + return new Proxy(mod, { 139 + get: proxyMethod('get', tryDefault), 140 + set: proxyMethod('set', tryDefault), 141 + has: proxyMethod('has', tryDefault), 142 + deleteProperty: proxyMethod('deleteProperty', tryDefault), 143 + }) 144 + } 145 + 146 + return mod 147 + } 148 + 149 + function exportAll(exports: any, sourceModule: any) { 150 + // eslint-disable-next-line no-restricted-syntax 151 + for (const key in sourceModule) { 152 + if (key !== 'default') { 153 + try { 154 + Object.defineProperty(exports, key, { 155 + enumerable: true, 156 + configurable: true, 157 + get() { return sourceModule[key] }, 158 + }) 159 + } 160 + catch (_err) { } 161 + } 162 + } 163 + }
+110
packages/vite-node/src/externalize.ts
··· 1 + import { existsSync } from 'fs' 2 + import { isNodeBuiltin, isValidNodeImport } from 'mlly' 3 + import type { ExternalizeOptions } from './types' 4 + import { slash } from './utils' 5 + 6 + const ESM_EXT_RE = /\.(es|esm|esm-browser|esm-bundler|es6|module)\.js$/ 7 + const ESM_FOLDER_RE = /\/esm\/(.*\.js)$/ 8 + 9 + const defaultInline = [ 10 + /\/vitest\/dist\//, 11 + // yarn's .store folder 12 + /vitest-virtual-\w+\/dist/, 13 + /virtual:/, 14 + /\.ts$/, 15 + ESM_EXT_RE, 16 + ESM_FOLDER_RE, 17 + ] 18 + 19 + const depsExternal = [ 20 + /\.cjs.js$/, 21 + /\.mjs$/, 22 + ] 23 + 24 + export function guessCJSversion(id: string): string | undefined { 25 + if (id.match(ESM_EXT_RE)) { 26 + for (const i of [ 27 + id.replace(ESM_EXT_RE, '.mjs'), 28 + id.replace(ESM_EXT_RE, '.umd.js'), 29 + id.replace(ESM_EXT_RE, '.cjs.js'), 30 + id.replace(ESM_EXT_RE, '.js'), 31 + ]) { 32 + if (existsSync(i)) 33 + return i 34 + } 35 + } 36 + if (id.match(ESM_FOLDER_RE)) { 37 + for (const i of [ 38 + id.replace(ESM_FOLDER_RE, '/umd/$1'), 39 + id.replace(ESM_FOLDER_RE, '/cjs/$1'), 40 + id.replace(ESM_FOLDER_RE, '/$1'), 41 + ]) { 42 + if (existsSync(i)) 43 + return i 44 + } 45 + } 46 + } 47 + 48 + export async function shouldExternalize( 49 + id: string, 50 + config?: ExternalizeOptions, 51 + cache = new Map<string, Promise<string | false>>(), 52 + ) { 53 + if (!cache.has(id)) 54 + cache.set(id, _shouldExternalize(id, config)) 55 + return cache.get(id)! 56 + } 57 + 58 + async function _shouldExternalize( 59 + id: string, 60 + config?: ExternalizeOptions, 61 + ): Promise<string | false> { 62 + if (isNodeBuiltin(id)) 63 + return id 64 + 65 + id = patchWindowsImportPath(id) 66 + 67 + if (matchExternalizePattern(id, config?.inline)) 68 + return false 69 + if (matchExternalizePattern(id, config?.external)) 70 + return id 71 + 72 + const isNodeModule = id.includes('/node_modules/') 73 + 74 + id = isNodeModule ? guessCJSversion(id) || id : id 75 + 76 + if (matchExternalizePattern(id, defaultInline)) 77 + return false 78 + if (matchExternalizePattern(id, depsExternal)) 79 + return id 80 + 81 + if (isNodeModule && await isValidNodeImport(id)) 82 + return id 83 + 84 + return false 85 + } 86 + 87 + function matchExternalizePattern(id: string, patterns?: (string | RegExp)[]) { 88 + if (!patterns) 89 + return false 90 + for (const ex of patterns) { 91 + if (typeof ex === 'string') { 92 + if (id.includes(`/node_modules/${ex}/`)) 93 + return true 94 + } 95 + else { 96 + if (ex.test(id)) 97 + return true 98 + } 99 + } 100 + return false 101 + } 102 + 103 + function patchWindowsImportPath(path: string) { 104 + if (path.match(/^\w:\\/)) 105 + return `file:///${slash(path)}` 106 + else if (path.match(/^\w:\//)) 107 + return `file:///${path}` 108 + else 109 + return path 110 + }
+1
packages/vite-node/src/index.ts
··· 1 + export * from './types'
+92
packages/vite-node/src/server.ts
··· 1 + import type { TransformResult, ViteDevServer } from 'vite' 2 + import { shouldExternalize } from './externalize' 3 + import type { ViteNodeServerOptions } from './types' 4 + import { toFilePath } from './utils' 5 + 6 + export * from './externalize' 7 + 8 + let SOURCEMAPPING_URL = 'sourceMa' 9 + SOURCEMAPPING_URL += 'ppingURL' 10 + 11 + export class ViteNodeServer { 12 + promiseMap = new Map<string, Promise<TransformResult | null | undefined>>() 13 + 14 + constructor( 15 + public server: ViteDevServer, 16 + public options: ViteNodeServerOptions = {}, 17 + ) {} 18 + 19 + shouldExternalize(id: string) { 20 + return shouldExternalize(id, this.options.deps) 21 + } 22 + 23 + async fetchModule(id: string) { 24 + const externalize = await this.shouldExternalize(toFilePath(id, this.server.config.root)) 25 + if (externalize) 26 + return { externalize } 27 + const r = await this.transformRequest(id) 28 + return { code: r?.code } 29 + } 30 + 31 + async transformRequest(id: string) { 32 + // reuse transform for concurrent requests 33 + if (!this.promiseMap.has(id)) { 34 + this.promiseMap.set(id, 35 + this._transformRequest(id) 36 + .then((r) => { 37 + this.promiseMap.delete(id) 38 + return r 39 + }), 40 + ) 41 + } 42 + return this.promiseMap.get(id) 43 + } 44 + 45 + private getTransformMode(id: string) { 46 + const withoutQuery = id.split('?')[0] 47 + 48 + if (this.options.transformMode?.web?.some(r => withoutQuery.match(r))) 49 + return 'web' 50 + if (this.options.transformMode?.ssr?.some(r => withoutQuery.match(r))) 51 + return 'ssr' 52 + 53 + if (withoutQuery.match(/\.([cm]?[jt]sx?|json)$/)) 54 + return 'ssr' 55 + return 'web' 56 + } 57 + 58 + private async _transformRequest(id: string) { 59 + let result: TransformResult | null = null 60 + 61 + const mode = this.getTransformMode(id) 62 + if (mode === 'web') { 63 + // for components like Vue, we want to use the client side 64 + // plugins but then covert the code to be consumed by the server 65 + result = await this.server.transformRequest(id) 66 + if (result) 67 + result = await this.server.ssrTransform(result.code, result.map, id) 68 + } 69 + else { 70 + result = await this.server.transformRequest(id, { ssr: true }) 71 + } 72 + 73 + if (result && !id.includes('node_modules')) 74 + withInlineSourcemap(result) 75 + 76 + // if (result?.map && process.env.NODE_V8_COVERAGE) 77 + // visitedFilesMap.set(toFilePath(id, config.root), result.map as any) 78 + 79 + return result 80 + } 81 + } 82 + 83 + export async function withInlineSourcemap(result: TransformResult) { 84 + const { code, map } = result 85 + 86 + if (code.includes(`${SOURCEMAPPING_URL}=`)) 87 + return result 88 + if (map) 89 + result.code = `${code}\n\n//# ${SOURCEMAPPING_URL}=data:application/json;charset=utf-8;base64,${Buffer.from(JSON.stringify(map), 'utf-8').toString('base64')}\n` 90 + 91 + return result 92 + }
+30
packages/vite-node/src/types.ts
··· 1 + export interface ExternalizeOptions { 2 + external?: (string | RegExp)[] 3 + inline?: (string | RegExp)[] 4 + fallbackCJS?: boolean 5 + } 6 + 7 + export type FetchFunction = (id: string) => Promise<{ code?: string; externalize?: string }> 8 + 9 + export interface ModuleCache { 10 + promise?: Promise<any> 11 + exports?: any 12 + code?: string 13 + } 14 + 15 + export interface ViteNodeRunnerOptions { 16 + fetchModule: FetchFunction 17 + root: string 18 + base?: string 19 + moduleCache?: Map<string, ModuleCache> 20 + interpretDefault?: boolean 21 + requestStubs?: Record<string, any> 22 + } 23 + 24 + export interface ViteNodeServerOptions { 25 + deps?: ExternalizeOptions 26 + transformMode?: { 27 + ssr?: RegExp[] 28 + web?: RegExp[] 29 + } 30 + }
+39
packages/vite-node/src/utils.ts
··· 1 + import { fileURLToPath, pathToFileURL } from 'url' 2 + import { dirname, resolve } from 'pathe' 3 + 4 + export const isWindows = process.platform === 'win32' 5 + 6 + export function slash(str: string) { 7 + return str.replace(/\\/g, '/') 8 + } 9 + 10 + export function normalizeId(id: string, base?: string): string { 11 + if (base && id.startsWith(base)) 12 + id = `/${id.slice(base.length)}` 13 + 14 + return id 15 + .replace(/^\/@id\/__x00__/, '\0') // virtual modules start with `\0` 16 + .replace(/^\/@id\//, '') 17 + .replace(/^__vite-browser-external:/, '') 18 + .replace(/^node:/, '') 19 + .replace(/[?&]v=\w+/, '?') // remove ?v= query 20 + .replace(/\?$/, '') // remove end query mark 21 + } 22 + 23 + export function toFilePath(id: string, root: string): string { 24 + let absolute = slash(id).startsWith('/@fs/') 25 + ? id.slice(4) 26 + : id.startsWith(dirname(root)) 27 + ? id 28 + : id.startsWith('/') 29 + ? slash(resolve(root, id.slice(1))) 30 + : id 31 + 32 + if (absolute.startsWith('//')) 33 + absolute = absolute.slice(1) 34 + 35 + // disambiguate the `<UNIT>:/` on windows: see nodejs/node#31710 36 + return isWindows && absolute.startsWith('/') 37 + ? fileURLToPath(pathToFileURL(absolute.slice(1)).href) 38 + : absolute 39 + }
+2 -3
packages/vitest/src/api/setup.ts
··· 9 9 import type { Vitest } from '../node' 10 10 import type { File, ModuleGraphData, Reporter, TaskResultPack } from '../types' 11 11 import { interpretSourcePos, parseStacktrace } from '../utils/source-map' 12 - import { transformRequest } from '../node/transform' 13 12 import type { TransformResultWithSource, WebSocketEvents, WebSocketHandlers } from './types' 14 13 15 14 export function setup(ctx: Vitest) { ··· 52 51 return ctx.config 53 52 }, 54 53 async getTransformResult(id) { 55 - const result: TransformResultWithSource | null | undefined = await transformRequest(ctx, id) 54 + const result: TransformResultWithSource | null | undefined = await ctx.vitenode.transformRequest(id) 56 55 if (result) { 57 56 try { 58 57 result.source = result.source || (await fs.readFile(id, 'utf-8')) ··· 76 75 return seen.get(mod) 77 76 let id = clearId(mod.id) 78 77 seen.set(mod, id) 79 - const rewrote = await ctx.shouldExternalize(id) 78 + const rewrote = await ctx.vitenode.shouldExternalize(id) 80 79 if (rewrote) { 81 80 id = rewrote 82 81 externalized.add(id)
+1 -4
packages/vitest/src/node/config.ts
··· 63 63 64 64 resolved.coverage = resolveC8Options(resolved.coverage, resolved.root) 65 65 66 - resolved.depsInline = [...resolved.deps?.inline || []] 67 - resolved.depsExternal = [...resolved.deps?.external || []] 68 - resolved.fallbackCJS = resolved.deps?.fallbackCJS ?? true 69 - resolved.interpretDefault = resolved.deps?.interpretDefault ?? true 66 + resolved.deps = resolved.deps || {} 70 67 71 68 resolved.environment = resolved.environment || 'node' 72 69 resolved.threads = resolved.threads ?? true
+4 -176
packages/vitest/src/node/execute.ts
··· 1 - import { builtinModules, createRequire } from 'module' 2 - import { fileURLToPath, pathToFileURL } from 'url' 3 - import vm from 'vm' 4 - import { dirname, resolve } from 'pathe' 5 - import type { FetchFunction, ModuleCache } from '../types' 6 - import { normalizeId, slash, toFilePath } from '../utils' 1 + import { ViteNodeRunner } from 'vite-node/client' 2 + import { toFilePath } from 'vite-node/utils' 3 + import type { ViteNodeRunnerOptions } from 'vite-node' 7 4 import type { SuiteMocks } from './mocker' 8 5 import { createMocker } from './mocker' 9 6 10 - export interface ViteNodeOptions { 11 - root: string 12 - base?: string 13 - fetch: FetchFunction 14 - moduleCache: Map<string, ModuleCache> 15 - depsInline: (string | RegExp)[] 16 - depsExternal: (string | RegExp)[] 17 - fallbackCJS: boolean 18 - interpretDefault: boolean 19 - requestStubs?: Record<string, any> 20 - } 21 - 22 - export interface ExecuteOptions extends ViteNodeOptions { 7 + export interface ExecuteOptions extends ViteNodeRunnerOptions { 23 8 files: string[] 24 9 mockMap: SuiteMocks 25 - } 26 - 27 - function hasNestedDefault(target: any) { 28 - return '__esModule' in target && target.__esModule && 'default' in target.default 29 - } 30 - 31 - function proxyMethod(name: 'get' | 'set' | 'has' | 'deleteProperty', tryDefault: boolean) { 32 - return function(target: any, key: string | symbol, ...args: [any?, any?]) { 33 - const result = Reflect[name](target, key, ...args) 34 - if (typeof target.default !== 'object') 35 - return result 36 - if ((tryDefault && key === 'default') || typeof result === 'undefined') 37 - return Reflect[name](target.default, key, ...args) 38 - return result 39 - } 40 - } 41 - 42 - export async function interpretedImport(path: string, interpretDefault: boolean) { 43 - const mod = await import(path) 44 - 45 - if (interpretDefault && 'default' in mod) { 46 - const tryDefault = hasNestedDefault(mod) 47 - return new Proxy(mod, { 48 - get: proxyMethod('get', tryDefault), 49 - set: proxyMethod('set', tryDefault), 50 - has: proxyMethod('has', tryDefault), 51 - deleteProperty: proxyMethod('deleteProperty', tryDefault), 52 - }) 53 - } 54 - 55 - return mod 56 - } 57 - 58 - function exportAll(exports: any, sourceModule: any) { 59 - // eslint-disable-next-line no-restricted-syntax 60 - for (const key in sourceModule) { 61 - if (key !== 'default') { 62 - try { 63 - Object.defineProperty(exports, key, { 64 - enumerable: true, 65 - configurable: true, 66 - get() { return sourceModule[key] }, 67 - }) 68 - } 69 - catch (_err) { } 70 - } 71 - } 72 10 } 73 11 74 12 export async function executeInViteNode(options: ExecuteOptions) { ··· 79 17 result.push(await runner.run(file)) 80 18 81 19 return result 82 - } 83 - 84 - export class ViteNodeRunner { 85 - root: string 86 - 87 - externalCache: Map<string, string | Promise<false | string>> 88 - moduleCache: Map<string, ModuleCache> 89 - 90 - constructor(public options: ViteNodeOptions) { 91 - this.root = options.root || process.cwd() 92 - 93 - this.moduleCache = options.moduleCache || new Map() 94 - this.externalCache = new Map<string, string | Promise<false | string>>() 95 - builtinModules.forEach(m => this.externalCache.set(m, m)) 96 - } 97 - 98 - async run(file: string) { 99 - return await this.cachedRequest(`/@fs/${slash(resolve(file))}`, []) 100 - } 101 - 102 - async cachedRequest(rawId: string, callstack: string[]) { 103 - const id = normalizeId(rawId, this.options.base) 104 - const fsPath = toFilePath(id, this.root) 105 - 106 - if (this.moduleCache.get(fsPath)?.promise) 107 - return this.moduleCache.get(fsPath)?.promise 108 - 109 - const promise = this.directRequest(id, fsPath, callstack) 110 - this.setCache(fsPath, { promise }) 111 - 112 - return await promise 113 - } 114 - 115 - async directRequest(id: string, fsPath: string, callstack: string[]) { 116 - callstack = [...callstack, id] 117 - const request = async(dep: string) => { 118 - if (callstack.includes(dep)) { 119 - const cacheKey = toFilePath(dep, this.root) 120 - if (!this.moduleCache.get(cacheKey)?.exports) 121 - throw new Error(`Circular dependency detected\nStack:\n${[...callstack, dep].reverse().map(p => `- ${p}`).join('\n')}`) 122 - return this.moduleCache.get(cacheKey)!.exports 123 - } 124 - return this.cachedRequest(dep, callstack) 125 - } 126 - 127 - if (this.options.requestStubs && id in this.options.requestStubs) 128 - return this.options.requestStubs[id] 129 - 130 - const { code: transformed, externalize } = await this.options.fetch(id) 131 - if (externalize) { 132 - const mod = await interpretedImport(externalize, this.options.interpretDefault) 133 - this.setCache(fsPath, { exports: mod }) 134 - return mod 135 - } 136 - 137 - if (transformed == null) 138 - throw new Error(`failed to load ${id}`) 139 - 140 - // disambiguate the `<UNIT>:/` on windows: see nodejs/node#31710 141 - const url = pathToFileURL(fsPath).href 142 - const exports: any = {} 143 - 144 - this.setCache(fsPath, { code: transformed, exports }) 145 - 146 - const __filename = fileURLToPath(url) 147 - const moduleProxy = { 148 - set exports(value) { 149 - exportAll(exports, value) 150 - exports.default = value 151 - }, 152 - get exports() { 153 - return exports.default 154 - }, 155 - } 156 - 157 - const context = this.prepareContext({ 158 - // esm transformed by Vite 159 - __vite_ssr_import__: request, 160 - __vite_ssr_dynamic_import__: request, 161 - __vite_ssr_exports__: exports, 162 - __vite_ssr_exportAll__: (obj: any) => exportAll(exports, obj), 163 - __vite_ssr_import_meta__: { url }, 164 - 165 - // cjs compact 166 - require: createRequire(url), 167 - exports, 168 - module: moduleProxy, 169 - __filename, 170 - __dirname: dirname(__filename), 171 - }) 172 - 173 - const fn = vm.runInThisContext(`async (${Object.keys(context).join(',')})=>{{${transformed}\n}}`, { 174 - filename: fsPath, 175 - lineOffset: 0, 176 - }) 177 - await fn(...Object.values(context)) 178 - 179 - return exports 180 - } 181 - 182 - prepareContext(context: Record<string, any>) { 183 - return context 184 - } 185 - 186 - setCache(id: string, mod: Partial<ModuleCache>) { 187 - if (!this.moduleCache.has(id)) 188 - this.moduleCache.set(id, mod) 189 - else 190 - Object.assign(this.moduleCache.get(id), mod) 191 - } 192 20 } 193 21 194 22 export class VitestRunner extends ViteNodeRunner {
+6 -11
packages/vitest/src/node/index.ts
··· 7 7 import mm from 'micromatch' 8 8 import c from 'picocolors' 9 9 import type { RawSourceMap } from 'source-map-js' 10 + import { ViteNodeServer } from 'vite-node/server' 10 11 import type { ArgumentsType, Reporter, ResolvedConfig, UserConfig } from '../types' 11 12 import { SnapshotManager } from '../integrations/snapshot/manager' 12 13 import { configFiles } from '../constants' ··· 14 15 import { GlobalSetupPlugin } from '../plugins/globalSetup' 15 16 import { MocksPlugin } from '../plugins/mock' 16 17 import { DefaultReporter, ReportersMap } from '../reporters' 17 - 18 18 import { cleanCoverage, reportCoverage } from '../coverage' 19 - import { shouldExternalize } from '../utils/externalize' 20 19 import type { WorkerPool } from './pool' 21 20 import { StateManager } from './state' 22 21 import { resolveApiConfig, resolveConfig } from './config' 23 22 import { createPool } from './pool' 24 - import { transformRequest } from './transform' 25 23 26 24 const WATCHER_DEBOUNCE = 100 27 25 ··· 37 35 outputStream = process.stdout 38 36 errorStream = process.stderr 39 37 38 + vitenode: ViteNodeServer = undefined! 39 + 40 40 invalidates: Set<string> = new Set() 41 41 changedTests: Set<string> = new Set() 42 42 visitedFilesMap: Map<string, RawSourceMap> = new Map() 43 43 runningPromise?: Promise<void> 44 44 closingPromise?: Promise<void> 45 - externalizeCache = new Map<string, Promise<string | false>>() 46 45 47 46 isFirstRun = true 48 - 49 47 restartsCount = 0 50 48 51 49 private _onRestartListeners: Array<() => void> = [] ··· 60 58 this.restartsCount += 1 61 59 this.pool?.close() 62 60 this.pool = undefined 63 - this.externalizeCache.clear() 64 61 65 62 const resolved = resolveConfig(options, server.config) 66 63 ··· 85 82 86 83 if (this.config.watch) 87 84 this.registerWatcher() 85 + 86 + this.vitenode = new ViteNodeServer(server, this.config) 88 87 89 88 this.runningPromise = undefined 90 89 ··· 122 121 const deps = new Set<string>() 123 122 124 123 const addImports = async(filepath: string) => { 125 - const transformed = await transformRequest(this, filepath) 124 + const transformed = await this.vitenode.transformRequest(filepath) 126 125 if (!transformed) return 127 126 const dependencies = [...transformed.deps || [], ...transformed.dynamicDeps || []] 128 127 for (const dep of dependencies) { ··· 347 346 if (mm.isMatch(id, this.config.exclude)) 348 347 return false 349 348 return mm.isMatch(id, this.config.include) 350 - } 351 - 352 - shouldExternalize(id: string) { 353 - return shouldExternalize(id, this.config, this.externalizeCache) 354 349 } 355 350 356 351 onServerRestarted(fn: () => void) {
+3 -9
packages/vitest/src/node/pool.ts
··· 7 7 import { createBirpc } from 'birpc' 8 8 import { distDir } from '../constants' 9 9 import type { WorkerContext, WorkerRPC } from '../types' 10 - import { toFilePath } from '../utils' 11 - import { transformRequest } from './transform' 12 10 import type { Vitest } from './index' 13 11 14 12 export type RunWithFiles = (files: string[], invalidates?: string[]) => Promise<void> ··· 120 118 if (mod) 121 119 ctx.server.moduleGraph.invalidateModule(mod) 122 120 } 123 - const r = await transformRequest(ctx, id) 121 + const r = await ctx.vitenode.transformRequest(id) 124 122 return r?.map as RawSourceMap | undefined 125 123 }, 126 - async fetch(id) { 127 - const externalize = await ctx.shouldExternalize(toFilePath(id, ctx.config.root)) 128 - if (externalize) 129 - return { externalize } 130 - const r = await transformRequest(ctx, id) 131 - return { code: r?.code } 124 + fetch(id) { 125 + return ctx.vitenode.fetchModule(id) 132 126 }, 133 127 onCollected(files) { 134 128 ctx.state.collectFiles(files)
-70
packages/vitest/src/node/transform.ts
··· 1 - import type { TransformResult } from 'vite' 2 - import { toFilePath } from '../utils' 3 - import type { Vitest } from './index' 4 - 5 - const promiseMap = new Map<string, Promise<TransformResult | null | undefined>>() 6 - 7 - export async function transformRequest(ctx: Vitest, id: string) { 8 - // reuse transform for concurrent requests 9 - if (!promiseMap.has(id)) { 10 - promiseMap.set(id, 11 - _transformRequest(ctx, id) 12 - .then((r) => { 13 - promiseMap.delete(id) 14 - return r 15 - }), 16 - ) 17 - } 18 - return promiseMap.get(id) 19 - } 20 - 21 - function getTransformMode(ctx: Vitest, id: string) { 22 - const withoutQuery = id.split('?')[0] 23 - 24 - if (ctx.config.transformMode?.web?.some(r => withoutQuery.match(r))) 25 - return 'web' 26 - if (ctx.config.transformMode?.ssr?.some(r => withoutQuery.match(r))) 27 - return 'ssr' 28 - 29 - if (withoutQuery.match(/\.([cm]?[jt]sx?|json)$/)) 30 - return 'ssr' 31 - return 'web' 32 - } 33 - 34 - async function _transformRequest(ctx: Vitest, id: string) { 35 - let result: TransformResult | null = null 36 - 37 - const mode = getTransformMode(ctx, id) 38 - if (mode === 'web') { 39 - // for components like Vue, we want to use the client side 40 - // plugins but then covert the code to be consumed by the server 41 - result = await ctx.server.transformRequest(id) 42 - if (result) 43 - result = await ctx.server.ssrTransform(result.code, result.map, id) 44 - } 45 - else { 46 - result = await ctx.server.transformRequest(id, { ssr: true }) 47 - } 48 - 49 - if (result && !id.includes('node_modules')) 50 - withInlineSourcemap(result) 51 - 52 - if (result?.map && process.env.NODE_V8_COVERAGE) 53 - ctx.visitedFilesMap.set(toFilePath(id, ctx.config.root), result.map as any) 54 - 55 - return result 56 - } 57 - 58 - let SOURCEMAPPING_URL = 'sourceMa' 59 - SOURCEMAPPING_URL += 'ppingURL' 60 - 61 - export async function withInlineSourcemap(result: TransformResult) { 62 - const { code, map } = result 63 - 64 - if (code.includes(`${SOURCEMAPPING_URL}=`)) 65 - return result 66 - if (map) 67 - result.code = `${code}\n\n//# ${SOURCEMAPPING_URL}=data:application/json;charset=utf-8;base64,${Buffer.from(JSON.stringify(map), 'utf-8').toString('base64')}\n` 68 - 69 - return result 70 - }
+2 -5
packages/vitest/src/runtime/worker.ts
··· 34 34 files: [ 35 35 resolve(distDir, 'entry.js'), 36 36 ], 37 - fetch(id) { 37 + fetchModule(id) { 38 38 return rpc().fetch(id) 39 39 }, 40 40 moduleCache, 41 41 mockMap, 42 + interpretDefault: config.deps.interpretDefault ?? true, 42 43 root: config.root, 43 - depsInline: config.depsInline, 44 - depsExternal: config.depsExternal, 45 - fallbackCJS: config.fallbackCJS, 46 - interpretDefault: config.interpretDefault, 47 44 base: config.base, 48 45 }))[0] 49 46
-5
packages/vitest/src/types/config.ts
··· 296 296 testNamePattern?: RegExp 297 297 related?: string[] 298 298 299 - depsInline: (string | RegExp)[] 300 - depsExternal: (string | RegExp)[] 301 - fallbackCJS: boolean 302 - interpretDefault: boolean 303 - 304 299 coverage: ResolvedC8Options 305 300 snapshotOptions: SnapshotStateOptions 306 301
-108
packages/vitest/src/utils/externalize.ts
··· 1 - import { existsSync } from 'fs' 2 - import { isNodeBuiltin, isValidNodeImport } from 'mlly' 3 - import type { ResolvedConfig } from '../types' 4 - import { slash } from '../utils' 5 - 6 - const ESM_EXT_RE = /\.(es|esm|esm-browser|esm-bundler|es6|module)\.js$/ 7 - const ESM_FOLDER_RE = /\/esm\/(.*\.js)$/ 8 - 9 - const defaultInline = [ 10 - /\/vitest\/dist\//, 11 - // yarn's .store folder 12 - /vitest-virtual-\w+\/dist/, 13 - /virtual:/, 14 - /\.ts$/, 15 - ESM_EXT_RE, 16 - ESM_FOLDER_RE, 17 - ] 18 - 19 - const depsExternal = [ 20 - /\.cjs.js$/, 21 - /\.mjs$/, 22 - ] 23 - 24 - export function guessCJSversion(id: string): string | undefined { 25 - if (id.match(ESM_EXT_RE)) { 26 - for (const i of [ 27 - id.replace(ESM_EXT_RE, '.mjs'), 28 - id.replace(ESM_EXT_RE, '.umd.js'), 29 - id.replace(ESM_EXT_RE, '.cjs.js'), 30 - id.replace(ESM_EXT_RE, '.js'), 31 - ]) { 32 - if (existsSync(i)) 33 - return i 34 - } 35 - } 36 - if (id.match(ESM_FOLDER_RE)) { 37 - for (const i of [ 38 - id.replace(ESM_FOLDER_RE, '/umd/$1'), 39 - id.replace(ESM_FOLDER_RE, '/cjs/$1'), 40 - id.replace(ESM_FOLDER_RE, '/$1'), 41 - ]) { 42 - if (existsSync(i)) 43 - return i 44 - } 45 - } 46 - } 47 - 48 - export async function shouldExternalize( 49 - id: string, 50 - config: Pick<ResolvedConfig, 'depsInline' | 'depsExternal' | 'fallbackCJS'>, 51 - cache = new Map<string, Promise<string | false>>(), 52 - ) { 53 - if (!cache.has(id)) 54 - cache.set(id, _shouldExternalize(id, config)) 55 - return cache.get(id)! 56 - } 57 - 58 - async function _shouldExternalize( 59 - id: string, 60 - config: Pick<ResolvedConfig, 'depsInline' | 'depsExternal' | 'fallbackCJS'>, 61 - ): Promise<string | false> { 62 - if (isNodeBuiltin(id)) 63 - return id 64 - 65 - id = patchWindowsImportPath(id) 66 - 67 - if (matchExternalizePattern(id, config.depsInline)) 68 - return false 69 - if (matchExternalizePattern(id, config.depsExternal)) 70 - return id 71 - 72 - const isNodeModule = id.includes('/node_modules/') 73 - 74 - id = isNodeModule ? guessCJSversion(id) || id : id 75 - 76 - if (matchExternalizePattern(id, defaultInline)) 77 - return false 78 - if (matchExternalizePattern(id, depsExternal)) 79 - return id 80 - 81 - if (isNodeModule && await isValidNodeImport(id)) 82 - return id 83 - 84 - return false 85 - } 86 - 87 - function matchExternalizePattern(id: string, patterns: (string | RegExp)[]) { 88 - for (const ex of patterns) { 89 - if (typeof ex === 'string') { 90 - if (id.includes(`/node_modules/${ex}/`)) 91 - return true 92 - } 93 - else { 94 - if (ex.test(id)) 95 - return true 96 - } 97 - } 98 - return false 99 - } 100 - 101 - export function patchWindowsImportPath(path: string) { 102 - if (path.match(/^\w:\\/)) 103 - return `file:///${slash(path)}` 104 - else if (path.match(/^\w:\//)) 105 - return `file:///${path}` 106 - else 107 - return path 108 - }
+2 -21
packages/vitest/src/utils/index.ts
··· 1 - import { fileURLToPath, pathToFileURL } from 'url' 2 1 import c from 'picocolors' 3 2 import { isPackageExists } from 'local-pkg' 4 - import { dirname, resolve } from 'pathe' 3 + import { resolve } from 'pathe' 5 4 import type { Suite, Task } from '../types' 6 - import { getNames, slash } from './tasks' 5 + import { getNames } from './tasks' 7 6 8 7 export * from './tasks' 9 8 export * from './path' ··· 109 108 return deepMergeArray(target, source) 110 109 } 111 110 return target 112 - } 113 - 114 - export function toFilePath(id: string, root: string): string { 115 - let absolute = slash(id).startsWith('/@fs/') 116 - ? id.slice(4) 117 - : id.startsWith(dirname(root)) 118 - ? id 119 - : id.startsWith('/') 120 - ? slash(resolve(root, id.slice(1))) 121 - : id 122 - 123 - if (absolute.startsWith('//')) 124 - absolute = absolute.slice(1) 125 - 126 - // disambiguate the `<UNIT>:/` on windows: see nodejs/node#31710 127 - return isWindows && absolute.startsWith('/') 128 - ? fileURLToPath(pathToFileURL(absolute.slice(1)).href) 129 - : absolute 130 111 } 131 112 132 113 export { resolve as resolvePath }