Skip to content

Commit

Permalink
chore: remove esbuild bunlding code
Browse files Browse the repository at this point in the history
  • Loading branch information
underfin committed Nov 7, 2023
1 parent 6a34be8 commit c49f6f6
Show file tree
Hide file tree
Showing 2 changed files with 7 additions and 425 deletions.
292 changes: 4 additions & 288 deletions packages/vite/src/node/optimizer/esbuildDepPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'node:path'
import type { ImportKind, Plugin } from 'esbuild'
import type * as Rollup from 'rollup'
import type { ImportKind } from 'esbuild'
import type { Plugin } from 'rollup'
import { KNOWN_ASSET_TYPES } from '../constants'
import type { PackageCache } from '../packages'
import { getDepOptimizationConfig } from '../config'
Expand Down Expand Up @@ -50,250 +50,12 @@ const externalTypes = [
...KNOWN_ASSET_TYPES,
]

export function esbuildDepPlugin(
qualified: Record<string, string>,
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<string | undefined> => {
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') {
// 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 http://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?\`)`,
}
}
},
)
},
}
}

export function rollupDepPlugin(
qualified: Record<string, string>,
external: string[],
config: ResolvedConfig,
ssr: boolean,
): Rollup.Plugin {
): Plugin {
const { extensions } = getDepOptimizationConfig(config, ssr)

// remove optimizable extensions from `externalTypes` list
Expand Down Expand Up @@ -514,58 +276,12 @@ export function rollupDepPlugin(

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;`,
}),
)
},
}
}

// esbuild doesn't transpile `require('foo')` into `import` statements if 'foo' is externalized
// https://github.com/evanw/esbuild/issues/566#issuecomment-735551834
export function rollupCjsExternalPlugin(
externals: string[],
platform: 'node' | 'browser',
): Rollup.Plugin {
): Plugin {
const filter = new RegExp(externals.map(matchesEntireLine).join('|'))

return {
Expand Down
Loading

0 comments on commit c49f6f6

Please sign in to comment.