Skip to content

Commit fa4e6dd

Browse files
committed
feat: use module runner to import the config
1 parent 67e3e95 commit fa4e6dd

File tree

2 files changed

+27
-213
lines changed

2 files changed

+27
-213
lines changed

packages/vite/src/node/config.ts

Lines changed: 23 additions & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
import fs from 'node:fs'
2-
import fsp from 'node:fs/promises'
32
import path from 'node:path'
4-
import { pathToFileURL } from 'node:url'
5-
import { promisify } from 'node:util'
63
import { performance } from 'node:perf_hooks'
7-
import { createRequire } from 'node:module'
84
import colors from 'picocolors'
95
import type { Alias, AliasOptions } from 'dep-types/alias'
10-
import { build } from 'esbuild'
116
import type { RollupOptions } from 'rollup'
127
import picomatch from 'picomatch'
138
import type { AnymatchFn } from '../types/anymatch'
@@ -52,11 +47,8 @@ import {
5247
asyncFlatten,
5348
createDebugger,
5449
createFilter,
55-
isBuiltin,
5650
isExternalUrl,
57-
isFilePathESM,
5851
isInNodeModules,
59-
isNodeBuiltin,
6052
isObject,
6153
isParentDirectory,
6254
mergeAlias,
@@ -76,7 +68,6 @@ import type {
7668
InternalResolveOptions,
7769
ResolveOptions,
7870
} from './plugins/resolve'
79-
import { tryNodeResolve } from './plugins/resolve'
8071
import type { LogLevel, Logger } from './logger'
8172
import { createLogger } from './logger'
8273
import type { DepOptimizationOptions } from './optimizer'
@@ -88,9 +79,9 @@ import type { ResolvedSSROptions, SSROptions } from './ssr'
8879
import { resolveSSROptions } from './ssr'
8980
import { PartialEnvironment } from './baseEnvironment'
9081
import { createIdResolver } from './idResolver'
82+
import { createServerModuleRunner } from './ssr/runtime/serverModuleRunner'
9183

9284
const debug = createDebugger('vite:config', { depth: 10 })
93-
const promisifiedRealpath = promisify(fs.realpath)
9485

9586
export interface ConfigEnv {
9687
/**
@@ -1502,17 +1493,27 @@ export async function loadConfigFromFile(
15021493
return null
15031494
}
15041495

1505-
const isESM =
1506-
typeof process.versions.deno === 'string' || isFilePathESM(resolvedPath)
1507-
15081496
try {
1509-
const bundled = await bundleConfigFile(resolvedPath, isESM)
1510-
const userConfig = await loadConfigFromBundledFile(
1511-
resolvedPath,
1512-
bundled.code,
1513-
isESM,
1497+
// console.time('config')
1498+
const environment = new DevEnvironment(
1499+
'config',
1500+
await resolveConfig({ configFile: false }, 'serve'),
1501+
{
1502+
options: {
1503+
consumer: 'server',
1504+
dev: {
1505+
moduleRunnerTransform: true,
1506+
},
1507+
},
1508+
hot: false,
1509+
},
15141510
)
1515-
debug?.(`bundled config file loaded in ${getTime()}`)
1511+
await environment.init()
1512+
const runner = createServerModuleRunner(environment)
1513+
const { default: userConfig } = (await runner.import(resolvedPath)) as {
1514+
default: UserConfigExport
1515+
}
1516+
debug?.(`config file loaded in ${getTime()}`)
15161517

15171518
const config = await (typeof userConfig === 'function'
15181519
? userConfig(configEnv)
@@ -1523,7 +1524,7 @@ export async function loadConfigFromFile(
15231524
return {
15241525
path: normalizePath(resolvedPath),
15251526
config,
1526-
dependencies: bundled.dependencies,
1527+
dependencies: [],
15271528
}
15281529
} catch (e) {
15291530
createLogger(logLevel, { customLogger }).error(
@@ -1533,197 +1534,8 @@ export async function loadConfigFromFile(
15331534
},
15341535
)
15351536
throw e
1536-
}
1537-
}
1538-
1539-
async function bundleConfigFile(
1540-
fileName: string,
1541-
isESM: boolean,
1542-
): Promise<{ code: string; dependencies: string[] }> {
1543-
const dirnameVarName = '__vite_injected_original_dirname'
1544-
const filenameVarName = '__vite_injected_original_filename'
1545-
const importMetaUrlVarName = '__vite_injected_original_import_meta_url'
1546-
const result = await build({
1547-
absWorkingDir: process.cwd(),
1548-
entryPoints: [fileName],
1549-
write: false,
1550-
target: [`node${process.versions.node}`],
1551-
platform: 'node',
1552-
bundle: true,
1553-
format: isESM ? 'esm' : 'cjs',
1554-
mainFields: ['main'],
1555-
sourcemap: 'inline',
1556-
metafile: true,
1557-
define: {
1558-
__dirname: dirnameVarName,
1559-
__filename: filenameVarName,
1560-
'import.meta.url': importMetaUrlVarName,
1561-
'import.meta.dirname': dirnameVarName,
1562-
'import.meta.filename': filenameVarName,
1563-
},
1564-
plugins: [
1565-
{
1566-
name: 'externalize-deps',
1567-
setup(build) {
1568-
const packageCache = new Map()
1569-
const resolveByViteResolver = (
1570-
id: string,
1571-
importer: string,
1572-
isRequire: boolean,
1573-
) => {
1574-
return tryNodeResolve(id, importer, {
1575-
root: path.dirname(fileName),
1576-
isBuild: true,
1577-
isProduction: true,
1578-
preferRelative: false,
1579-
tryIndex: true,
1580-
mainFields: [],
1581-
conditions: [],
1582-
externalConditions: [],
1583-
external: [],
1584-
noExternal: [],
1585-
overrideConditions: ['node'],
1586-
dedupe: [],
1587-
extensions: DEFAULT_EXTENSIONS,
1588-
preserveSymlinks: false,
1589-
packageCache,
1590-
isRequire,
1591-
webCompatible: false,
1592-
})?.id
1593-
}
1594-
1595-
// externalize bare imports
1596-
build.onResolve(
1597-
{ filter: /^[^.].*/ },
1598-
async ({ path: id, importer, kind }) => {
1599-
if (
1600-
kind === 'entry-point' ||
1601-
path.isAbsolute(id) ||
1602-
isNodeBuiltin(id)
1603-
) {
1604-
return
1605-
}
1606-
1607-
// With the `isNodeBuiltin` check above, this check captures if the builtin is a
1608-
// non-node built-in, which esbuild doesn't know how to handle. In that case, we
1609-
// externalize it so the non-node runtime handles it instead.
1610-
if (isBuiltin(id)) {
1611-
return { external: true }
1612-
}
1613-
1614-
const isImport = isESM || kind === 'dynamic-import'
1615-
let idFsPath: string | undefined
1616-
try {
1617-
idFsPath = resolveByViteResolver(id, importer, !isImport)
1618-
} catch (e) {
1619-
if (!isImport) {
1620-
let canResolveWithImport = false
1621-
try {
1622-
canResolveWithImport = !!resolveByViteResolver(
1623-
id,
1624-
importer,
1625-
false,
1626-
)
1627-
} catch {}
1628-
if (canResolveWithImport) {
1629-
throw new Error(
1630-
`Failed to resolve ${JSON.stringify(
1631-
id,
1632-
)}. This package is ESM only but it was tried to load by \`require\`. See https://vite.dev/guide/troubleshooting.html#this-package-is-esm-only for more details.`,
1633-
)
1634-
}
1635-
}
1636-
throw e
1637-
}
1638-
if (idFsPath && isImport) {
1639-
idFsPath = pathToFileURL(idFsPath).href
1640-
}
1641-
return {
1642-
path: idFsPath,
1643-
external: true,
1644-
}
1645-
},
1646-
)
1647-
},
1648-
},
1649-
{
1650-
name: 'inject-file-scope-variables',
1651-
setup(build) {
1652-
build.onLoad({ filter: /\.[cm]?[jt]s$/ }, async (args) => {
1653-
const contents = await fsp.readFile(args.path, 'utf-8')
1654-
const injectValues =
1655-
`const ${dirnameVarName} = ${JSON.stringify(
1656-
path.dirname(args.path),
1657-
)};` +
1658-
`const ${filenameVarName} = ${JSON.stringify(args.path)};` +
1659-
`const ${importMetaUrlVarName} = ${JSON.stringify(
1660-
pathToFileURL(args.path).href,
1661-
)};`
1662-
1663-
return {
1664-
loader: args.path.endsWith('ts') ? 'ts' : 'js',
1665-
contents: injectValues + contents,
1666-
}
1667-
})
1668-
},
1669-
},
1670-
],
1671-
})
1672-
const { text } = result.outputFiles[0]
1673-
return {
1674-
code: text,
1675-
dependencies: result.metafile ? Object.keys(result.metafile.inputs) : [],
1676-
}
1677-
}
1678-
1679-
interface NodeModuleWithCompile extends NodeModule {
1680-
_compile(code: string, filename: string): any
1681-
}
1682-
1683-
const _require = createRequire(import.meta.url)
1684-
async function loadConfigFromBundledFile(
1685-
fileName: string,
1686-
bundledCode: string,
1687-
isESM: boolean,
1688-
): Promise<UserConfigExport> {
1689-
// for esm, before we can register loaders without requiring users to run node
1690-
// with --experimental-loader themselves, we have to do a hack here:
1691-
// write it to disk, load it with native Node ESM, then delete the file.
1692-
if (isESM) {
1693-
const fileBase = `${fileName}.timestamp-${Date.now()}-${Math.random()
1694-
.toString(16)
1695-
.slice(2)}`
1696-
const fileNameTmp = `${fileBase}.mjs`
1697-
const fileUrl = `${pathToFileURL(fileBase)}.mjs`
1698-
await fsp.writeFile(fileNameTmp, bundledCode)
1699-
try {
1700-
return (await import(fileUrl)).default
1701-
} finally {
1702-
fs.unlink(fileNameTmp, () => {}) // Ignore errors
1703-
}
1704-
}
1705-
// for cjs, we can register a custom loader via `_require.extensions`
1706-
else {
1707-
const extension = path.extname(fileName)
1708-
// We don't use fsp.realpath() here because it has the same behaviour as
1709-
// fs.realpath.native. On some Windows systems, it returns uppercase volume
1710-
// letters (e.g. "C:\") while the Node.js loader uses lowercase volume letters.
1711-
// See https://github.com/vitejs/vite/issues/12923
1712-
const realFileName = await promisifiedRealpath(fileName)
1713-
const loaderExt = extension in _require.extensions ? extension : '.js'
1714-
const defaultLoader = _require.extensions[loaderExt]!
1715-
_require.extensions[loaderExt] = (module: NodeModule, filename: string) => {
1716-
if (filename === realFileName) {
1717-
;(module as NodeModuleWithCompile)._compile(bundledCode, filename)
1718-
} else {
1719-
defaultLoader(module, filename)
1720-
}
1721-
}
1722-
// clear cache in case of server restart
1723-
delete _require.cache[_require.resolve(fileName)]
1724-
const raw = _require(fileName)
1725-
_require.extensions[loaderExt] = defaultLoader
1726-
return raw.__esModule ? raw.default : raw
1537+
} finally {
1538+
// console.timeEnd('config')
17271539
}
17281540
}
17291541

packages/vite/src/node/ssr/runtime/serverModuleRunner.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,10 @@ export function createServerModuleRunner(
9292
...options,
9393
root: environment.config.root,
9494
transport: {
95-
fetchModule: (id, importer, options) =>
96-
environment.fetchModule(id, importer, options),
95+
fetchModule: async (id, importer, options) => {
96+
const result = await environment.fetchModule(id, importer, options)
97+
return result
98+
},
9799
},
98100
hmr,
99101
sourcemapInterceptor: resolveSourceMapOptions(options),

0 commit comments

Comments
 (0)