diff --git a/package.json b/package.json
index 1a3b2566830736..663c18e83c7525 100644
--- a/package.json
+++ b/package.json
@@ -106,7 +106,8 @@
"acorn@8.12.0": "patches/acorn@8.12.0.patch",
"chokidar@3.6.0": "patches/chokidar@3.6.0.patch",
"http-proxy@1.18.1": "patches/http-proxy@1.18.1.patch",
- "sirv@2.0.4": "patches/sirv@2.0.4.patch"
+ "sirv@2.0.4": "patches/sirv@2.0.4.patch",
+ "rolldown@0.12.1": "patches/rolldown@0.12.1.patch"
},
"peerDependencyRules": {
"allowedVersions": {
diff --git a/packages/vite/LICENSE.md b/packages/vite/LICENSE.md
index ddd5a6209512dc..912e980504e45a 100644
--- a/packages/vite/LICENSE.md
+++ b/packages/vite/LICENSE.md
@@ -1006,27 +1006,6 @@ License: MIT
By: Mathias Bynens
Repository: https://github.com/mathiasbynens/cssesc.git
-> Copyright Mathias Bynens
->
-> Permission is hereby granted, free of charge, to any person obtaining
-> a copy of this software and associated documentation files (the
-> "Software"), to deal in the Software without restriction, including
-> without limitation the rights to use, copy, modify, merge, publish,
-> distribute, sublicense, and/or sell copies of the Software, and to
-> permit persons to whom the Software is furnished to do so, subject to
-> the following conditions:
->
-> The above copyright notice and this permission notice shall be
-> included in all copies or substantial portions of the Software.
->
-> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
---------------------------------------
## debug
@@ -2681,29 +2660,6 @@ License: MIT
By: Ben Briggs, Chris Eppstein
Repository: postcss/postcss-selector-parser
-> Copyright (c) Ben Briggs (http://beneb.info)
->
-> Permission is hereby granted, free of charge, to any person
-> obtaining a copy of this software and associated documentation
-> files (the "Software"), to deal in the Software without
-> restriction, including without limitation the rights to use,
-> copy, modify, merge, publish, distribute, sublicense, and/or sell
-> copies of the Software, and to permit persons to whom the
-> Software is furnished to do so, subject to the following
-> conditions:
->
-> The above copyright notice and this permission notice shall be
-> included in all copies or substantial portions of the Software.
->
-> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-> OTHER DEALINGS IN THE SOFTWARE.
-
---------------------------------------
## postcss-value-parser
diff --git a/packages/vite/package.json b/packages/vite/package.json
index 1b032821464ef7..0c12728ac31245 100644
--- a/packages/vite/package.json
+++ b/packages/vite/package.json
@@ -87,7 +87,8 @@
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.39",
- "rollup": "^4.13.0"
+ "rollup": "^4.13.0",
+ "rolldown": "^0.12.1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
diff --git a/packages/vite/rollup.dts.config.ts b/packages/vite/rollup.dts.config.ts
index 41b1da1458a31e..be9a04625c7be4 100644
--- a/packages/vite/rollup.dts.config.ts
+++ b/packages/vite/rollup.dts.config.ts
@@ -97,7 +97,7 @@ function patchTypes(): Plugin {
validateRuntimeChunk.call(this, chunk)
} else {
validateChunkImports.call(this, chunk)
- code = replaceConfusingTypeNames.call(this, code, chunk)
+ if (0) code = replaceConfusingTypeNames.call(this, code, chunk)
code = stripInternalTypes.call(this, code, chunk)
code = cleanUnnecessaryComments(code)
}
diff --git a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts
deleted file mode 100644
index 1f4c4dab16748d..00000000000000
--- a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts
+++ /dev/null
@@ -1,342 +0,0 @@
-import path from 'node:path'
-import type { ImportKind, Plugin } from 'esbuild'
-import { KNOWN_ASSET_TYPES } from '../constants'
-import type { PackageCache } from '../packages'
-import { getDepOptimizationConfig } from '../config'
-import type { ResolvedConfig } from '../config'
-import {
- escapeRegex,
- flattenId,
- isBuiltin,
- isExternalUrl,
- moduleListContains,
- normalizePath,
-} from '../utils'
-import { browserExternalId, optionalPeerDepId } from '../plugins/resolve'
-import { isCSSRequest, isModuleCSSRequest } from '../plugins/css'
-
-const externalWithConversionNamespace =
- 'vite:dep-pre-bundle:external-conversion'
-const convertedExternalPrefix = 'vite-dep-pre-bundle-external:'
-
-const cjsExternalFacadeNamespace = 'vite:cjs-external-facade'
-const nonFacadePrefix = 'vite-cjs-external-facade:'
-
-const externalTypes = [
- 'css',
- // supported pre-processor types
- 'less',
- 'sass',
- 'scss',
- 'styl',
- 'stylus',
- 'pcss',
- 'postcss',
- // wasm
- 'wasm',
- // known SFC types
- 'vue',
- 'svelte',
- 'marko',
- 'astro',
- 'imba',
- // JSX/TSX may be configured to be compiled differently from how esbuild
- // handles it by default, so exclude them as well
- 'jsx',
- 'tsx',
- ...KNOWN_ASSET_TYPES,
-]
-
-export function esbuildDepPlugin(
- qualified: Record,
- external: string[],
- config: ResolvedConfig,
- ssr: boolean,
-): Plugin {
- const { extensions } = getDepOptimizationConfig(config, ssr)
-
- // remove optimizable extensions from `externalTypes` list
- const allExternalTypes = extensions
- ? externalTypes.filter((type) => !extensions?.includes('.' + type))
- : externalTypes
-
- // use separate package cache for optimizer as it caches paths around node_modules
- // and it's unlikely for the core Vite process to traverse into node_modules again
- const esmPackageCache: PackageCache = new Map()
- const cjsPackageCache: PackageCache = new Map()
-
- // default resolver which prefers ESM
- const _resolve = config.createResolver({
- asSrc: false,
- scan: true,
- packageCache: esmPackageCache,
- })
-
- // cjs resolver that prefers Node
- const _resolveRequire = config.createResolver({
- asSrc: false,
- isRequire: true,
- scan: true,
- packageCache: cjsPackageCache,
- })
-
- const resolve = (
- id: string,
- importer: string,
- kind: ImportKind,
- resolveDir?: string,
- ): Promise => {
- let _importer: string
- // explicit resolveDir - this is passed only during yarn pnp resolve for
- // entries
- if (resolveDir) {
- _importer = normalizePath(path.join(resolveDir, '*'))
- } else {
- // map importer ids to file paths for correct resolution
- _importer = importer in qualified ? qualified[importer] : importer
- }
- const resolver = kind.startsWith('require') ? _resolveRequire : _resolve
- return resolver(id, _importer, undefined, ssr)
- }
-
- const resolveResult = (id: string, resolved: string) => {
- if (resolved.startsWith(browserExternalId)) {
- return {
- path: id,
- namespace: 'browser-external',
- }
- }
- if (resolved.startsWith(optionalPeerDepId)) {
- return {
- path: resolved,
- namespace: 'optional-peer-dep',
- }
- }
- if (ssr && isBuiltin(resolved)) {
- return
- }
- if (isExternalUrl(resolved)) {
- return {
- path: resolved,
- external: true,
- }
- }
- return {
- path: path.resolve(resolved),
- }
- }
-
- return {
- name: 'vite:dep-pre-bundle',
- setup(build) {
- // clear package cache when esbuild is finished
- build.onEnd(() => {
- esmPackageCache.clear()
- cjsPackageCache.clear()
- })
-
- // externalize assets and commonly known non-js file types
- // See #8459 for more details about this require-import conversion
- build.onResolve(
- {
- filter: new RegExp(
- `\\.(` + allExternalTypes.join('|') + `)(\\?.*)?$`,
- ),
- },
- async ({ path: id, importer, kind }) => {
- // if the prefix exist, it is already converted to `import`, so set `external: true`
- if (id.startsWith(convertedExternalPrefix)) {
- return {
- path: id.slice(convertedExternalPrefix.length),
- external: true,
- }
- }
-
- const resolved = await resolve(id, importer, kind)
- if (resolved) {
- if (kind === 'require-call') {
- // #16116 fix: Import the module.scss path, which is actually module.scss.js
- if (resolved.endsWith('.js')) {
- return {
- path: resolved,
- external: false,
- }
- }
-
- // here it is not set to `external: true` to convert `require` to `import`
- return {
- path: resolved,
- namespace: externalWithConversionNamespace,
- }
- }
- return {
- path: resolved,
- external: true,
- }
- }
- },
- )
- build.onLoad(
- { filter: /./, namespace: externalWithConversionNamespace },
- (args) => {
- // import itself with prefix (this is the actual part of require-import conversion)
- const modulePath = `"${convertedExternalPrefix}${args.path}"`
- return {
- contents:
- isCSSRequest(args.path) && !isModuleCSSRequest(args.path)
- ? `import ${modulePath};`
- : `export { default } from ${modulePath};` +
- `export * from ${modulePath};`,
- loader: 'js',
- }
- },
- )
-
- function resolveEntry(id: string) {
- const flatId = flattenId(id)
- if (flatId in qualified) {
- return {
- path: qualified[flatId],
- }
- }
- }
-
- build.onResolve(
- { filter: /^[\w@][^:]/ },
- async ({ path: id, importer, kind }) => {
- if (moduleListContains(external, id)) {
- return {
- path: id,
- external: true,
- }
- }
-
- // ensure esbuild uses our resolved entries
- let entry: { path: string } | undefined
- // if this is an entry, return entry namespace resolve result
- if (!importer) {
- if ((entry = resolveEntry(id))) return entry
- // check if this is aliased to an entry - also return entry namespace
- const aliased = await _resolve(id, undefined, true)
- if (aliased && (entry = resolveEntry(aliased))) {
- return entry
- }
- }
-
- // use vite's own resolver
- const resolved = await resolve(id, importer, kind)
- if (resolved) {
- return resolveResult(id, resolved)
- }
- },
- )
-
- build.onLoad(
- { filter: /.*/, namespace: 'browser-external' },
- ({ path }) => {
- if (config.isProduction) {
- return {
- contents: 'module.exports = {}',
- }
- } else {
- return {
- // Return in CJS to intercept named imports. Use `Object.create` to
- // create the Proxy in the prototype to workaround esbuild issue. Why?
- //
- // In short, esbuild cjs->esm flow:
- // 1. Create empty object using `Object.create(Object.getPrototypeOf(module.exports))`.
- // 2. Assign props of `module.exports` to the object.
- // 3. Return object for ESM use.
- //
- // If we do `module.exports = new Proxy({}, {})`, step 1 returns empty object,
- // step 2 does nothing as there's no props for `module.exports`. The final object
- // is just an empty object.
- //
- // Creating the Proxy in the prototype satisfies step 1 immediately, which means
- // the returned object is a Proxy that we can intercept.
- //
- // Note: Skip keys that are accessed by esbuild and browser devtools.
- contents: `\
-module.exports = Object.create(new Proxy({}, {
- get(_, key) {
- if (
- key !== '__esModule' &&
- key !== '__proto__' &&
- key !== 'constructor' &&
- key !== 'splice'
- ) {
- console.warn(\`Module "${path}" has been externalized for browser compatibility. Cannot access "${path}.\${key}" in client code. See https://vitejs.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.\`)
- }
- }
-}))`,
- }
- }
- },
- )
-
- build.onLoad(
- { filter: /.*/, namespace: 'optional-peer-dep' },
- ({ path }) => {
- if (config.isProduction) {
- return {
- contents: 'module.exports = {}',
- }
- } else {
- const [, peerDep, parentDep] = path.split(':')
- return {
- contents: `throw new Error(\`Could not resolve "${peerDep}" imported by "${parentDep}". Is it installed?\`)`,
- }
- }
- },
- )
- },
- }
-}
-
-const matchesEntireLine = (text: string) => `^${escapeRegex(text)}$`
-
-// 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[],
- platform: 'node' | 'browser',
-): Plugin {
- return {
- name: 'cjs-external',
- setup(build) {
- const filter = new RegExp(externals.map(matchesEntireLine).join('|'))
-
- build.onResolve({ filter: new RegExp(`^${nonFacadePrefix}`) }, (args) => {
- return {
- path: args.path.slice(nonFacadePrefix.length),
- external: true,
- }
- })
-
- build.onResolve({ filter }, (args) => {
- // preserve `require` for node because it's more accurate than converting it to import
- if (args.kind === 'require-call' && platform !== 'node') {
- return {
- path: args.path,
- namespace: cjsExternalFacadeNamespace,
- }
- }
-
- return {
- path: args.path,
- external: true,
- }
- })
-
- build.onLoad(
- { filter: /.*/, namespace: cjsExternalFacadeNamespace },
- (args) => ({
- contents:
- `import * as m from ${JSON.stringify(
- nonFacadePrefix + args.path,
- )};` + `module.exports = m;`,
- }),
- )
- },
- }
-}
diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts
index e62d78fdf1b956..ad54c7c83c30c7 100644
--- a/packages/vite/src/node/optimizer/index.ts
+++ b/packages/vite/src/node/optimizer/index.ts
@@ -4,13 +4,15 @@ import path from 'node:path'
import { promisify } from 'node:util'
import { performance } from 'node:perf_hooks'
import colors from 'picocolors'
-import type { BuildContext, BuildOptions as EsbuildBuildOptions } from 'esbuild'
-import esbuild, { build } from 'esbuild'
+import type { BuildOptions as EsbuildBuildOptions } from 'esbuild'
import { init, parse } from 'es-module-lexer'
import glob from 'fast-glob'
+import type { RollupOptions } from 'rolldown'
+import * as rolldown from 'rolldown'
import { getDepOptimizationConfig } from '../config'
import type { ResolvedConfig } from '../config'
import {
+ asyncFlatten,
createDebugger,
flattenId,
getHash,
@@ -22,13 +24,13 @@ import {
tryStatSync,
unique,
} from '../utils'
-import {
- defaultEsbuildSupported,
- transformWithEsbuild,
-} from '../plugins/esbuild'
+import { transformWithEsbuild } from '../plugins/esbuild'
import { ESBUILD_MODULES_TARGET, METADATA_FILENAME } from '../constants'
import { isWindows } from '../../shared/utils'
-import { esbuildCjsExternalPlugin, esbuildDepPlugin } from './esbuildDepPlugin'
+import {
+ rolldownCjsExternalPlugin,
+ rolldownDepPlugin,
+} from './rolldownDepPlugin'
import { scanImports } from './scan'
import { createOptimizeDepsIncludeResolver, expandGlobIds } from './resolve'
export {
@@ -40,7 +42,7 @@ export {
const debug = createDebugger('vite:deps')
const jsExtensionRE = /\.js$/i
-const jsMapExtensionRE = /\.js\.map$/i
+// const jsMapExtensionRE = /\.js\.map$/i
export type ExportsData = {
hasModuleSyntax: boolean
@@ -106,6 +108,8 @@ export interface DepOptimizationConfig {
| 'outExtension'
| 'metafile'
>
+
+ rollupOptions?: RollupOptions
/**
* List of file extensions that can be optimized. A corresponding esbuild
* plugin must exist to handle the specific extension.
@@ -194,6 +198,7 @@ export interface OptimizedDepInfo {
* data used both to define if interop is needed and when pre-bundling
*/
exportsData?: Promise
+ isDynamicEntry?: boolean
}
export interface DepOptimizationMetadata {
@@ -593,7 +598,7 @@ export function runOptimizeDeps(
const start = performance.now()
- const preparedRun = prepareEsbuildOptimizerRun(
+ const preparedRun = prepareRolldownOptimizerRun(
resolvedConfig,
depsInfo,
ssr,
@@ -601,76 +606,59 @@ export function runOptimizeDeps(
optimizerContext,
)
- const runResult = preparedRun.then(({ context, idToExports }) => {
- function disposeContext() {
- return context?.dispose().catch((e) => {
- config.logger.error('Failed to dispose esbuild context', { error: e })
- })
- }
- if (!context || optimizerContext.cancelled) {
- disposeContext()
+ const runResult = preparedRun.then(({ build, idToExports }) => {
+ if (!build || optimizerContext.cancelled) {
return cancelledResult
}
- return context
- .rebuild()
+ return build()
.then((result) => {
- const meta = result.metafile!
-
- // the paths in `meta.outputs` are relative to `process.cwd()`
- const processingCacheDirOutputPath = path.relative(
- process.cwd(),
- processingCacheDir,
- )
-
- for (const id in depsInfo) {
- const output = esbuildOutputFromId(
- meta.outputs,
- id,
- processingCacheDir,
- )
-
- const { exportsData, ...info } = depsInfo[id]
- addOptimizedDepInfo(metadata, 'optimized', {
- ...info,
- // We only need to hash the output.imports in to check for stability, but adding the hash
- // and file path gives us a unique hash that may be useful for other things in the future
- fileHash: getHash(
- metadata.hash +
- depsInfo[id].file +
- JSON.stringify(output.imports),
- ),
- browserHash: metadata.browserHash,
- // After bundling we have more information and can warn the user about legacy packages
- // that require manual configuration
- needsInterop: needsInterop(
- config,
- ssr,
- id,
- idToExports[id],
- output,
- ),
- })
- }
-
- for (const o of Object.keys(meta.outputs)) {
- if (!jsMapExtensionRE.test(o)) {
- const id = path
- .relative(processingCacheDirOutputPath, o)
- .replace(jsExtensionRE, '')
- const file = getOptimizedDepPath(id, resolvedConfig, ssr)
- if (
- !findOptimizedDepInfoInRecord(
- metadata.optimized,
- (depInfo) => depInfo.file === file,
+ for (const chunk of result.output) {
+ if (chunk.type === 'chunk') {
+ if (chunk.isEntry) {
+ // One chunk maybe corresponding multiply entry
+ const deps = Object.values(depsInfo).filter(
+ (d) => d.src === chunk.facadeModuleId!,
)
- ) {
- addOptimizedDepInfo(metadata, 'chunks', {
- id,
- file,
- needsInterop: false,
- browserHash: metadata.browserHash,
- })
+ for (const { exportsData, file, id, ...info } of deps) {
+ addOptimizedDepInfo(metadata, 'optimized', {
+ id,
+ file,
+ ...info,
+ // We only need to hash the output.imports in to check for stability, but adding the hash
+ // and file path gives us a unique hash that may be useful for other things in the future
+ fileHash: getHash(
+ metadata.hash + file + JSON.stringify(chunk.modules),
+ ),
+ browserHash: metadata.browserHash,
+ // After bundling we have more information and can warn the user about legacy packages
+ // that require manual configuration
+ needsInterop: needsInterop(
+ config,
+ ssr,
+ id,
+ idToExports[id],
+ chunk,
+ ),
+ })
+ }
+ } else {
+ const id = chunk.fileName.replace(jsExtensionRE, '')
+ const file = getOptimizedDepPath(id, resolvedConfig, ssr)
+ if (
+ !findOptimizedDepInfoInRecord(
+ metadata.optimized,
+ (depInfo) => depInfo.file === file,
+ )
+ ) {
+ addOptimizedDepInfo(metadata, 'chunks', {
+ id,
+ file,
+ needsInterop: false,
+ browserHash: metadata.browserHash,
+ isDynamicEntry: chunk.isDynamicEntry,
+ })
+ }
}
}
}
@@ -683,16 +671,8 @@ export function runOptimizeDeps(
})
.catch((e) => {
- if (e.errors && e.message.includes('The build was canceled')) {
- // esbuild logs an error when cancelling, but this is expected so
- // return an empty result instead
- return cancelledResult
- }
throw e
})
- .finally(() => {
- return disposeContext()
- })
})
runResult.catch(() => {
@@ -702,24 +682,23 @@ export function runOptimizeDeps(
return {
async cancel() {
optimizerContext.cancelled = true
- const { context } = await preparedRun
- await context?.cancel()
cleanUp()
},
result: runResult,
}
}
-async function prepareEsbuildOptimizerRun(
+async function prepareRolldownOptimizerRun(
resolvedConfig: ResolvedConfig,
depsInfo: Record,
ssr: boolean,
processingCacheDir: string,
optimizerContext: { cancelled: boolean },
): Promise<{
- context?: BuildContext
+ build?: () => Promise
idToExports: Record
}> {
+ const isBuild = resolvedConfig.command === 'build'
const config: ResolvedConfig = {
...resolvedConfig,
command: 'build',
@@ -736,21 +715,19 @@ async function prepareEsbuildOptimizerRun(
const optimizeDeps = getDepOptimizationConfig(config, ssr)
- const { plugins: pluginsFromConfig = [], ...esbuildOptions } =
- optimizeDeps?.esbuildOptions ?? {}
+ const { plugins: pluginsFromConfig = [], ...rollupOptions } =
+ optimizeDeps?.rollupOptions ?? {}
+ let jsxLoader = false
await Promise.all(
Object.keys(depsInfo).map(async (id) => {
const src = depsInfo[id].src!
const exportsData = await (depsInfo[id].exportsData ??
extractExportsData(src, config, ssr))
- if (exportsData.jsxLoader && !esbuildOptions.loader?.['.js']) {
+ if (exportsData.jsxLoader) {
// Ensure that optimization won't fail by defaulting '.js' to the JSX parser.
// This is useful for packages such as Gatsby.
- esbuildOptions.loader = {
- '.js': 'jsx',
- ...esbuildOptions.loader,
- }
+ jsxLoader = true
}
const flatId = flattenId(id)
flatIdDeps[flatId] = src
@@ -758,10 +735,14 @@ async function prepareEsbuildOptimizerRun(
}),
)
- if (optimizerContext.cancelled) return { context: undefined, idToExports }
+ if (optimizerContext.cancelled) return { build: undefined, idToExports }
+ // In lib mode, we need to keep process.env.NODE_ENV untouched
const define = {
- 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || config.mode),
+ 'process.env.NODE_ENV':
+ isBuild && config.build.lib
+ ? 'process.env.NODE_ENV'
+ : JSON.stringify(process.env.NODE_ENV || config.mode),
}
const platform =
@@ -769,46 +750,87 @@ async function prepareEsbuildOptimizerRun(
const external = [...(optimizeDeps?.exclude ?? [])]
- const plugins = [...pluginsFromConfig]
+ if (isBuild) {
+ let rollupOptionsExternal = config?.build?.rollupOptions?.external
+ if (rollupOptionsExternal) {
+ if (typeof rollupOptionsExternal === 'string') {
+ rollupOptionsExternal = [rollupOptionsExternal]
+ }
+ // TODO: decide whether to support RegExp and function options
+ // They're not supported yet because `optimizeDeps.exclude` currently only accepts strings
+ if (
+ !Array.isArray(rollupOptionsExternal) ||
+ rollupOptionsExternal.some((ext) => typeof ext !== 'string')
+ ) {
+ throw new Error(
+ `[vite] 'build.rollupOptions.external' can only be an array of strings or a string when using esbuild optimization at build time.`,
+ )
+ }
+ external.push(...(rollupOptionsExternal as string[]))
+ }
+ }
+
+ const plugins = await asyncFlatten(
+ Array.isArray(pluginsFromConfig) ? pluginsFromConfig : [pluginsFromConfig],
+ )
if (external.length) {
- plugins.push(esbuildCjsExternalPlugin(external, platform))
+ plugins.push(rolldownCjsExternalPlugin(external, platform))
}
- plugins.push(esbuildDepPlugin(flatIdDeps, external, config, ssr))
-
- const context = await esbuild.context({
- absWorkingDir: process.cwd(),
- entryPoints: Object.keys(flatIdDeps),
- bundle: true,
- // We can't use platform 'neutral', as esbuild has custom handling
- // when the platform is 'node' or 'browser' that can't be emulated
- // by using mainFields and conditions
- platform,
- define,
- format: 'esm',
- // See https://github.com/evanw/esbuild/issues/1921#issuecomment-1152991694
- banner:
- platform === 'node'
- ? {
- js: `import { createRequire } from 'module';const require = createRequire(import.meta.url);`,
- }
- : undefined,
- target: ESBUILD_MODULES_TARGET,
- external,
- logLevel: 'error',
- splitting: true,
- sourcemap: true,
- outdir: processingCacheDir,
- ignoreAnnotations: true,
- metafile: true,
- plugins,
- charset: 'utf8',
- ...esbuildOptions,
- supported: {
- ...defaultEsbuildSupported,
- ...esbuildOptions.supported,
+ plugins.push(rolldownDepPlugin(flatIdDeps, external, config, ssr))
+ plugins.push({
+ name: 'optimizer-transform',
+ async transform(code, id) {
+ if (/\.(?:m?[jt]s|[jt]sx)$/.test(id)) {
+ const result = await transformWithEsbuild(code, id, {
+ sourcemap: true,
+ sourcefile: id,
+ loader: jsxLoader && /\.js$/.test(id) ? 'jsx' : undefined,
+ define,
+ target: isBuild
+ ? config.build.target || undefined
+ : ESBUILD_MODULES_TARGET,
+ })
+ // result.warnings.forEach((m) => {
+ // this.warn(prettifyMessage(m, code))
+ // })
+ return {
+ code: result.code,
+ map: result.map,
+ }
+ }
},
})
- return { context, idToExports }
+
+ async function build() {
+ const bundle = await rolldown.rolldown({
+ input: Object.keys(flatIdDeps),
+ // external,
+ logLevel: 'warn',
+ plugins,
+ resolve: {
+ mainFields: ['module', 'main'],
+ aliasFields: [['browser']],
+ extensions: ['.js', '.css'],
+ },
+ ...rollupOptions,
+ })
+ return await bundle.write({
+ format: 'esm',
+ sourcemap: true,
+ dir: processingCacheDir,
+ banner:
+ platform === 'node'
+ ? // TODO: use async to workaround https://github.com/rolldown/rolldown/issues/1655
+ async (chunk) =>
+ chunk.fileName.endsWith('.js')
+ ? `import { createRequire } from 'module';const require = createRequire(import.meta.url);`
+ : ''
+ : undefined,
+ ...rollupOptions.output,
+ })
+ }
+
+ return { build, idToExports }
}
export async function addManuallyIncludedOptimizeDeps(
@@ -1009,19 +1031,23 @@ function stringifyDepsOptimizerMetadata(
browserHash,
optimized: Object.fromEntries(
Object.values(optimized).map(
- ({ id, src, file, fileHash, needsInterop }) => [
+ ({ id, src, file, fileHash, needsInterop, isDynamicEntry }) => [
id,
{
src,
file,
fileHash,
needsInterop,
+ isDynamicEntry,
},
],
),
),
chunks: Object.fromEntries(
- Object.values(chunks).map(({ id, file }) => [id, { file }]),
+ Object.values(chunks).map(({ id, file, isDynamicEntry }) => [
+ id,
+ { file, isDynamicEntry },
+ ]),
),
},
(key: string, value: string) => {
@@ -1036,29 +1062,6 @@ function stringifyDepsOptimizerMetadata(
)
}
-function esbuildOutputFromId(
- outputs: Record,
- id: string,
- cacheDirOutputPath: string,
-): any {
- const cwd = process.cwd()
- const flatId = flattenId(id) + '.js'
- const normalizedOutputPath = normalizePath(
- path.relative(cwd, path.join(cacheDirOutputPath, flatId)),
- )
- const output = outputs[normalizedOutputPath]
- if (output) {
- return output
- }
- // If the root dir was symlinked, esbuild could return output keys as `../cwd/`
- // Normalize keys to support this case too
- for (const [key, value] of Object.entries(outputs)) {
- if (normalizePath(path.relative(cwd, key)) === normalizedOutputPath) {
- return value
- }
- }
-}
-
export async function extractExportsData(
filePath: string,
config: ResolvedConfig,
@@ -1068,18 +1071,20 @@ export async function extractExportsData(
const optimizeDeps = getDepOptimizationConfig(config, ssr)
- const esbuildOptions = optimizeDeps?.esbuildOptions ?? {}
+ const rollupOptions = optimizeDeps?.rollupOptions ?? {}
if (optimizeDeps.extensions?.some((ext) => filePath.endsWith(ext))) {
// For custom supported extensions, build the entry file to transform it into JS,
// and then parse with es-module-lexer. Note that the `bundle` option is not `true`,
// so only the entry file is being transformed.
- const result = await build({
- ...esbuildOptions,
- entryPoints: [filePath],
- write: false,
+ const rolldownBuild = await rolldown.rolldown({
+ ...rollupOptions,
+ input: [filePath],
+ })
+ const result = await rolldownBuild.generate({
+ ...rollupOptions.output,
format: 'esm',
})
- const [, exports, , hasModuleSyntax] = parse(result.outputFiles[0].text)
+ const [, exports, , hasModuleSyntax] = parse(result.output[0].code)
return {
hasModuleSyntax,
exports: exports.map((e) => e.n),
@@ -1093,7 +1098,7 @@ export async function extractExportsData(
try {
parseResult = parse(entryContent)
} catch {
- const loader = esbuildOptions.loader?.[path.extname(filePath)] || 'jsx'
+ const loader = rollupOptions.moduleTypes?.[path.extname(filePath)] || 'jsx'
debug?.(
`Unable to parse: ${filePath}.\n Trying again with a ${loader} transform.`,
)
diff --git a/packages/vite/src/node/optimizer/rolldownDepPlugin.ts b/packages/vite/src/node/optimizer/rolldownDepPlugin.ts
new file mode 100644
index 00000000000000..02f03198f80a2a
--- /dev/null
+++ b/packages/vite/src/node/optimizer/rolldownDepPlugin.ts
@@ -0,0 +1,331 @@
+import path from 'node:path'
+import type { Plugin, ImportKind } from 'rolldown'
+import { KNOWN_ASSET_TYPES } from '../constants'
+import type { PackageCache } from '../packages'
+import { getDepOptimizationConfig } from '../config'
+import type { ResolvedConfig } from '../config'
+import {
+ escapeRegex,
+ flattenId,
+ isBuiltin,
+ isExternalUrl,
+ moduleListContains,
+ normalizePath,
+} from '../utils'
+import { browserExternalId, optionalPeerDepId } from '../plugins/resolve'
+import { isCSSRequest, isModuleCSSRequest } from '../plugins/css'
+
+const externalWithConversionNamespace =
+ 'vite:dep-pre-bundle:external-conversion'
+const convertedExternalPrefix = 'vite-dep-pre-bundle-external:'
+
+const cjsExternalFacadeNamespace = 'vite:cjs-external-facade'
+const nonFacadePrefix = 'vite-cjs-external-facade:'
+
+const externalTypes = [
+ 'css',
+ // supported pre-processor types
+ 'less',
+ 'sass',
+ 'scss',
+ 'styl',
+ 'stylus',
+ 'pcss',
+ 'postcss',
+ // wasm
+ 'wasm',
+ // known SFC types
+ 'vue',
+ 'svelte',
+ 'marko',
+ 'astro',
+ 'imba',
+ // JSX/TSX may be configured to be compiled differently from how esbuild
+ // handles it by default, so exclude them as well
+ 'jsx',
+ 'tsx',
+ ...KNOWN_ASSET_TYPES,
+]
+
+
+const optionalPeerDepNamespace = 'optional-peer-dep:'
+const browserExternalNamespace = 'browser-external:'
+
+export function rolldownDepPlugin(
+ qualified: Record,
+ external: string[],
+ config: ResolvedConfig,
+ ssr: boolean,
+): Plugin {
+ const { extensions } = getDepOptimizationConfig(config, ssr)
+
+ // remove optimizable extensions from `externalTypes` list
+ const allExternalTypes = extensions
+ ? externalTypes.filter((type) => !extensions?.includes('.' + type))
+ : externalTypes
+
+ // use separate package cache for optimizer as it caches paths around node_modules
+ // and it's unlikely for the core Vite process to traverse into node_modules again
+ const esmPackageCache: PackageCache = new Map()
+ const cjsPackageCache: PackageCache = new Map()
+
+ // default resolver which prefers ESM
+ const _resolve = config.createResolver({
+ asSrc: false,
+ scan: true,
+ packageCache: esmPackageCache,
+ })
+
+ // cjs resolver that prefers Node
+ const _resolveRequire = config.createResolver({
+ asSrc: false,
+ isRequire: true,
+ scan: true,
+ packageCache: cjsPackageCache,
+ })
+
+ const resolve = (
+ id: string,
+ importer: string | undefined,
+ kind: ImportKind,
+ resolveDir?: string,
+ ): Promise => {
+ let _importer: string | undefined = undefined
+ // explicit resolveDir - this is passed only during yarn pnp resolve for
+ // entries
+ if (resolveDir) {
+ _importer = normalizePath(path.join(resolveDir, '*'))
+ } else if (importer) {
+ // map importer ids to file paths for correct resolution
+ _importer = importer in qualified ? qualified[importer] : importer
+ }
+ const resolver = kind.startsWith('require') ? _resolveRequire : _resolve
+ return resolver(id, _importer, undefined, ssr)
+ }
+
+ const resolveResult = (id: string, resolved: string) => {
+ if (resolved.startsWith(browserExternalId)) {
+ return {
+ id: browserExternalNamespace + id,
+ }
+ }
+ if (resolved.startsWith(optionalPeerDepId)) {
+ return {
+ id: optionalPeerDepNamespace + resolved,
+ }
+ }
+ if (ssr && isBuiltin(resolved)) {
+ return
+ }
+ if (isExternalUrl(resolved)) {
+ return {
+ id: resolved,
+ external: true,
+ }
+ }
+ return {
+ id: path.resolve(resolved),
+ }
+ }
+
+ const allExternalTypesReg = new RegExp(
+ `\\.(` + allExternalTypes.join('|') + `)(\\?.*)?$`,
+ )
+
+ function resolveEntry(id: string) {
+ const flatId = flattenId(id)
+ if (flatId in qualified) {
+ return {
+ id: qualified[flatId],
+ }
+ }
+ }
+
+ return {
+ name: 'vite:dep-pre-bundle',
+ // clear package cache when build is finished
+ buildEnd() {
+ esmPackageCache.clear()
+ cjsPackageCache.clear()
+ },
+ resolveId: async function (id, importer, options) {
+ const kind = options.kind
+ // externalize assets and commonly known non-js file types
+ // See #8459 for more details about this require-import conversion
+ if (allExternalTypesReg.test(id)) {
+ // if the prefix exist, it is already converted to `import`, so set `external: true`
+ if (id.startsWith(convertedExternalPrefix)) {
+ return {
+ id: id.slice(convertedExternalPrefix.length),
+ external: true,
+ }
+ }
+
+ const resolved = await resolve(id, importer, kind)
+ if (resolved) {
+ if (kind === 'require-call') {
+ // #16116 fix: Import the module.scss path, which is actually module.scss.js
+ if (resolved.endsWith('.js')) {
+ return {
+ id: resolved,
+ external: false,
+ }
+ }
+
+ // here it is not set to `external: true` to convert `require` to `import`
+ return {
+ id: externalWithConversionNamespace + resolved,
+ }
+ }
+ return {
+ id: resolved,
+ external: true,
+ }
+ }
+ }
+
+ if (/^[\w@][^:]/.test(id)) {
+ if (moduleListContains(external, id)) {
+ return {
+ id: id,
+ external: true,
+ }
+ }
+
+ // ensure esbuild uses our resolved entries
+ let entry: { id: string } | undefined
+ // if this is an entry, return entry namespace resolve result
+ if (!importer) {
+ if ((entry = resolveEntry(id))) return entry
+ // check if this is aliased to an entry - also return entry namespace
+ const aliased = await _resolve(id, undefined, true)
+ if (aliased && (entry = resolveEntry(aliased))) {
+ return entry
+ }
+ }
+
+ // use vite's own resolver
+ const resolved = await resolve(id, importer, kind)
+ if (resolved) {
+ return resolveResult(id, resolved)
+ }
+ }
+ },
+ load(id) {
+ if (id.startsWith(externalWithConversionNamespace)) {
+ const path = id.slice(externalWithConversionNamespace.length)
+ // import itself with prefix (this is the actual part of require-import conversion)
+ const modulePath = `"${convertedExternalPrefix}${path}"`
+ return {
+ code:
+ isCSSRequest(path) && !isModuleCSSRequest(path)
+ ? `import ${modulePath};`
+ : `export { default } from ${modulePath};` +
+ `export * from ${modulePath};`,
+ }
+ }
+
+ if (id.startsWith(browserExternalNamespace)) {
+ const path = id.slice(browserExternalNamespace.length)
+ if (config.isProduction) {
+ return {
+ code: 'module.exports = {}',
+ }
+ } else {
+ // Return in CJS to intercept named imports. Use `Object.create` to
+ // create the Proxy in the prototype to workaround esbuild issue. Why?
+ //
+ // In short, esbuild cjs->esm flow:
+ // 1. Create empty object using `Object.create(Object.getPrototypeOf(module.exports))`.
+ // 2. Assign props of `module.exports` to the object.
+ // 3. Return object for ESM use.
+ //
+ // If we do `module.exports = new Proxy({}, {})`, step 1 returns empty object,
+ // step 2 does nothing as there's no props for `module.exports`. The final object
+ // is just an empty object.
+ //
+ // Creating the Proxy in the prototype satisfies step 1 immediately, which means
+ // the returned object is a Proxy that we can intercept.
+ //
+ // Note: Skip keys that are accessed by esbuild and browser devtools.
+ return {
+ code: `\
+ module.exports = Object.create(new Proxy({}, {
+ get(_, key) {
+ if (
+ key !== '__esModule' &&
+ key !== '__proto__' &&
+ key !== 'constructor' &&
+ key !== 'splice'
+ ) {
+ console.warn(\`Module "${path}" has been externalized for browser compatibility. Cannot access "${path}.\${key}" in client code. See http://vitejs.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.\`)
+ }
+ }
+ }))`,
+ }
+ }
+ }
+
+ if (id.startsWith(optionalPeerDepNamespace)) {
+ if (config.isProduction) {
+ return {
+ code: 'module.exports = {}',
+ }
+ } else {
+ const path = id.slice(externalWithConversionNamespace.length)
+ const [, peerDep, parentDep] = path.split(':')
+ return {
+ code: `throw new Error(\`Could not resolve "${peerDep}" imported by "${parentDep}". Is it installed?\`)`,
+ }
+ }
+ }
+ },
+ }
+}
+
+const matchesEntireLine = (text: string) => `^${escapeRegex(text)}$`
+
+// esbuild doesn't transpile `require('foo')` into `import` statements if 'foo' is externalized
+// https://github.com/evanw/esbuild/issues/566#issuecomment-735551834
+export function rolldownCjsExternalPlugin(
+ externals: string[],
+ platform: 'node' | 'browser',
+): Plugin {
+ const filter = new RegExp(externals.map(matchesEntireLine).join('|'))
+
+ return {
+ name: 'cjs-external',
+ resolveId(id, importer, options) {
+ if (id.startsWith(nonFacadePrefix)) {
+ return {
+ id: id.slice(nonFacadePrefix.length),
+ external: true,
+ }
+ }
+
+ if (filter.test(id)) {
+ const kind = options.kind
+ if (kind === 'require-call' && platform !== 'node') {
+ return {
+ id: cjsExternalFacadeNamespace + id,
+ }
+ }
+
+ return {
+ id,
+ external: true,
+ }
+ }
+ },
+ load(id) {
+ if (id.startsWith(cjsExternalFacadeNamespace)) {
+ return {
+ code:
+ `import * as m from ${JSON.stringify(
+ nonFacadePrefix + id.slice(cjsExternalFacadeNamespace.length),
+ )};` + `module.exports = m;`,
+ }
+ }
+ },
+ }
+}
\ No newline at end of file
diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts
index 1cdef6c339c103..2d2f3705899c21 100644
--- a/packages/vite/src/node/optimizer/scan.ts
+++ b/packages/vite/src/node/optimizer/scan.ts
@@ -3,14 +3,10 @@ import fsp from 'node:fs/promises'
import path from 'node:path'
import { performance } from 'node:perf_hooks'
import glob from 'fast-glob'
-import type {
- BuildContext,
- Loader,
- OnLoadArgs,
- OnLoadResult,
- Plugin,
-} from 'esbuild'
-import esbuild, { formatMessages, transform } from 'esbuild'
+import type { Plugin } from 'rolldown'
+import * as rolldown from 'rolldown'
+import type { Loader } from 'esbuild'
+import { transform } from 'esbuild'
import colors from 'picocolors'
import type { ResolvedConfig } from '..'
import {
@@ -21,6 +17,7 @@ import {
} from '../constants'
import {
arraify,
+ asyncFlatten,
createDebugger,
dataUrlRE,
externalRE,
@@ -30,6 +27,7 @@ import {
moduleListContains,
multilineCommentsRE,
normalizePath,
+ parseRequest,
singlelineCommentsRE,
virtualModulePrefix,
virtualModuleRE,
@@ -38,7 +36,6 @@ import type { PluginContainer } from '../server/pluginContainer'
import { createPluginContainer } from '../server/pluginContainer'
import { transformGlobImport } from '../plugins/importMetaGlob'
import { cleanUrl } from '../../shared/utils'
-import { loadTsconfigJsonForFile } from '../plugins/esbuild'
type ResolveIdOptions = Parameters[2]
@@ -73,9 +70,7 @@ export function scanImports(config: ResolvedConfig): {
const scanContext = { cancelled: false }
- const esbuildContext: Promise = computeEntries(
- config,
- ).then((computedEntries) => {
+ const context = computeEntries(config).then((computedEntries) => {
entries = computedEntries
if (!entries.length) {
@@ -97,22 +92,16 @@ export function scanImports(config: ResolvedConfig): {
.map((entry) => `\n ${colors.dim(entry)}`)
.join('')}`,
)
- return prepareEsbuildScanner(config, entries, deps, missing, scanContext)
+ return prepareRolldownScanner(config, entries, deps, missing, scanContext)
})
- const result = esbuildContext
+ const result = context
.then((context) => {
- function disposeContext() {
- return context?.dispose().catch((e) => {
- config.logger.error('Failed to dispose esbuild context', { error: e })
- })
- }
if (!context || scanContext?.cancelled) {
- disposeContext()
return { deps: {}, missing: {} }
}
return context
- .rebuild()
+ .build()
.then(() => {
return {
// Ensure a fixed order so hashes are stable and improve logs
@@ -120,31 +109,28 @@ export function scanImports(config: ResolvedConfig): {
missing,
}
})
- .finally(() => {
- return disposeContext()
- })
})
.catch(async (e) => {
- if (e.errors && e.message.includes('The build was canceled')) {
- // esbuild logs an error when cancelling, but this is expected so
- // return an empty result instead
- return { deps: {}, missing: {} }
- }
+ // if (e.errors && e.message.includes('The build was canceled')) {
+ // // esbuild logs an error when cancelling, but this is expected so
+ // // return an empty result instead
+ // return { deps: {}, missing: {} }
+ // }
const prependMessage = colors.red(`\
Failed to scan for dependencies from entries:
${entries.join('\n')}
`)
- if (e.errors) {
- const msgs = await formatMessages(e.errors, {
- kind: 'error',
- color: true,
- })
- e.message = prependMessage + msgs.join('\n')
- } else {
+ // if (e.errors) {
+ // const msgs = await formatMessages(e.errors, {
+ // kind: 'error',
+ // color: true,
+ // })
+ // e.message = prependMessage + msgs.join('\n')
+ // } else {
e.message = prependMessage + e.message
- }
+ // }
throw e
})
.finally(() => {
@@ -162,7 +148,7 @@ export function scanImports(config: ResolvedConfig): {
return {
cancel: async () => {
scanContext.cancelled = true
- return esbuildContext.then((context) => context?.cancel())
+ // return esbuildContext.then((context) => context?.cancel())
},
result,
}
@@ -202,51 +188,96 @@ async function computeEntries(config: ResolvedConfig) {
return entries
}
-async function prepareEsbuildScanner(
+// async function prepareEsbuildScanner(
+// config: ResolvedConfig,
+// entries: string[],
+// deps: Record,
+// missing: Record,
+// scanContext?: { cancelled: boolean },
+// ): Promise {
+// const container = await createPluginContainer(config)
+
+// if (scanContext?.cancelled) return
+
+// const plugin = esbuildScanPlugin(config, container, deps, missing, entries)
+
+// const { plugins = [], ...esbuildOptions } =
+// config.optimizeDeps?.esbuildOptions ?? {}
+
+// // The plugin pipeline automatically loads the closest tsconfig.json.
+// // But esbuild doesn't support reading tsconfig.json if the plugin has resolved the path (https://github.com/evanw/esbuild/issues/2265).
+// // Due to syntax incompatibilities between the experimental decorators in TypeScript and TC39 decorators,
+// // we cannot simply set `"experimentalDecorators": true` or `false`. (https://github.com/vitejs/vite/pull/15206#discussion_r1417414715)
+// // Therefore, we use the closest tsconfig.json from the root to make it work in most cases.
+// let tsconfigRaw = esbuildOptions.tsconfigRaw
+// if (!tsconfigRaw && !esbuildOptions.tsconfig) {
+// const tsconfigResult = await loadTsconfigJsonForFile(
+// path.join(config.root, '_dummy.js'),
+// )
+// if (tsconfigResult.compilerOptions?.experimentalDecorators) {
+// tsconfigRaw = { compilerOptions: { experimentalDecorators: true } }
+// }
+// }
+
+// return await esbuild.context({
+// absWorkingDir: process.cwd(),
+// write: false,
+// stdin: {
+// contents: entries.map((e) => `import ${JSON.stringify(e)}`).join('\n'),
+// loader: 'js',
+// },
+// bundle: true,
+// format: 'esm',
+// logLevel: 'silent',
+// plugins: [...plugins, plugin],
+// ...esbuildOptions,
+// tsconfigRaw,
+// })
+// }
+
+async function prepareRolldownScanner(
config: ResolvedConfig,
entries: string[],
deps: Record,
missing: Record,
scanContext?: { cancelled: boolean },
-): Promise {
+): Promise<
+ | {
+ build: () => Promise
+ }
+ | undefined
+> {
const container = await createPluginContainer(config)
if (scanContext?.cancelled) return
- const plugin = esbuildScanPlugin(config, container, deps, missing, entries)
+ if (config.optimizeDeps.esbuildOptions) {
+ config.logger.error(
+ `You've set "optimizeDeps.esbuildOptions" in your config. ` +
+ `This is deprecated and vite already use rollup to optimize packages. ` +
+ `Please use "optimizeDeps.rollupOptions" instead.`,
+ )
+ }
- const { plugins = [], ...esbuildOptions } =
- config.optimizeDeps?.esbuildOptions ?? {}
+ const { plugins: pluginsFromConfig = [], ...rollupOptions } =
+ config.optimizeDeps.rollupOptions ?? {}
- // The plugin pipeline automatically loads the closest tsconfig.json.
- // But esbuild doesn't support reading tsconfig.json if the plugin has resolved the path (https://github.com/evanw/esbuild/issues/2265).
- // Due to syntax incompatibilities between the experimental decorators in TypeScript and TC39 decorators,
- // we cannot simply set `"experimentalDecorators": true` or `false`. (https://github.com/vitejs/vite/pull/15206#discussion_r1417414715)
- // Therefore, we use the closest tsconfig.json from the root to make it work in most cases.
- let tsconfigRaw = esbuildOptions.tsconfigRaw
- if (!tsconfigRaw && !esbuildOptions.tsconfig) {
- const tsconfigResult = await loadTsconfigJsonForFile(
- path.join(config.root, '_dummy.js'),
- )
- if (tsconfigResult.compilerOptions?.experimentalDecorators) {
- tsconfigRaw = { compilerOptions: { experimentalDecorators: true } }
- }
+ const plugins = await asyncFlatten(
+ Array.isArray(pluginsFromConfig) ? pluginsFromConfig : [pluginsFromConfig],
+ )
+
+ plugins.push(rolldownScanPlugin(config, container, deps, missing, entries))
+
+ async function build() {
+ await rolldown.experimental_scan({
+ input: entries,
+ logLevel: 'silent',
+ plugins,
+ ...rollupOptions,
+ })
}
- return await esbuild.context({
- absWorkingDir: process.cwd(),
- write: false,
- stdin: {
- contents: entries.map((e) => `import ${JSON.stringify(e)}`).join('\n'),
- loader: 'js',
- },
- bundle: true,
- format: 'esm',
- logLevel: 'silent',
- plugins: [...plugins, plugin],
- ...esbuildOptions,
- tsconfigRaw,
- })
+ return { build }
}
function orderedDependencies(deps: Record) {
@@ -286,7 +317,393 @@ const typeRE = /\btype\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/i
const langRE = /\blang\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/i
const contextRE = /\bcontext\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/i
-function esbuildScanPlugin(
+// function esbuildScanPlugin(
+// config: ResolvedConfig,
+// container: PluginContainer,
+// depImports: Record,
+// missing: Record,
+// entries: string[],
+// ): Plugin {
+// const seen = new Map()
+
+// const resolve = async (
+// id: string,
+// importer?: string,
+// options?: ResolveIdOptions,
+// ) => {
+// const key = id + (importer && path.dirname(importer))
+// if (seen.has(key)) {
+// return seen.get(key)
+// }
+// const resolved = await container.resolveId(
+// id,
+// importer && normalizePath(importer),
+// {
+// ...options,
+// scan: true,
+// },
+// )
+// const res = resolved?.id
+// seen.set(key, res)
+// return res
+// }
+
+// const include = config.optimizeDeps?.include
+// const exclude = [
+// ...(config.optimizeDeps?.exclude || []),
+// '@vite/client',
+// '@vite/env',
+// ]
+
+// const isUnlessEntry = (path: string) => !entries.includes(path)
+
+// const externalUnlessEntry = ({ path }: { path: string }) => ({
+// path,
+// external: isUnlessEntry(path),
+// })
+
+// const doTransformGlobImport = async (
+// contents: string,
+// id: string,
+// loader: Loader,
+// ) => {
+// let transpiledContents
+// // transpile because `transformGlobImport` only expects js
+// if (loader !== 'js') {
+// transpiledContents = (await transform(contents, { loader })).code
+// } else {
+// transpiledContents = contents
+// }
+
+// const result = await transformGlobImport(
+// transpiledContents,
+// id,
+// config.root,
+// resolve,
+// )
+
+// return result?.s.toString() || transpiledContents
+// }
+
+// return {
+// name: 'vite:dep-scan',
+// setup(build) {
+// const scripts: Record = {}
+
+// // external urls
+// build.onResolve({ filter: externalRE }, ({ path }) => ({
+// path,
+// external: true,
+// }))
+
+// // data urls
+// build.onResolve({ filter: dataUrlRE }, ({ path }) => ({
+// path,
+// external: true,
+// }))
+
+// // local scripts (`