Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: always load config with esbuild bundled code #9121

Merged
merged 1 commit into from Jul 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
95 changes: 40 additions & 55 deletions packages/vite/src/node/config.ts
Expand Up @@ -27,7 +27,6 @@ import {
dynamicImport,
isExternalUrl,
isObject,
isTS,
lookupFile,
mergeAlias,
mergeConfig,
Expand Down Expand Up @@ -816,7 +815,6 @@ export async function loadConfigFromFile(
const getTime = () => `${(performance.now() - start).toFixed(2)}ms`

let resolvedPath: string | undefined
let dependencies: string[] = []

if (configFile) {
// explicit config path is always resolved from cwd
Expand Down Expand Up @@ -852,42 +850,13 @@ export async function loadConfigFromFile(
}

try {
let userConfig: UserConfigExport | undefined

if (isESM) {
const fileUrl = pathToFileURL(resolvedPath)
const bundled = await bundleConfigFile(resolvedPath, true)
dependencies = bundled.dependencies

if (isTS(resolvedPath)) {
// before we can register loaders without requiring users to run node
// with --experimental-loader themselves, we have to do a hack here:
// bundle the config file w/ ts transforms first, write it to disk,
// load it with native Node ESM, then delete the file.
fs.writeFileSync(resolvedPath + '.mjs', bundled.code)
try {
userConfig = (await dynamicImport(`${fileUrl}.mjs?t=${Date.now()}`))
.default
} finally {
fs.unlinkSync(resolvedPath + '.mjs')
}
debug(`TS + native esm config loaded in ${getTime()}`, fileUrl)
} else {
// using Function to avoid this from being compiled away by TS/Rollup
// append a query so that we force reload fresh config in case of
// server restart
userConfig = (await dynamicImport(`${fileUrl}?t=${Date.now()}`)).default
debug(`native esm config loaded in ${getTime()}`, fileUrl)
}
}

if (!userConfig) {
// Bundle config file and transpile it to cjs using esbuild.
const bundled = await bundleConfigFile(resolvedPath)
dependencies = bundled.dependencies
userConfig = await loadConfigFromBundledFile(resolvedPath, bundled.code)
debug(`bundled config file loaded in ${getTime()}`)
}
const bundled = await bundleConfigFile(resolvedPath, isESM)
const userConfig = await loadConfigFromBundledFile(
resolvedPath,
bundled.code,
isESM
)
debug(`bundled config file loaded in ${getTime()}`)

const config = await (typeof userConfig === 'function'
? userConfig(configEnv)
Expand All @@ -898,7 +867,7 @@ export async function loadConfigFromFile(
return {
path: normalizePath(resolvedPath),
config,
dependencies
dependencies: bundled.dependencies
}
} catch (e) {
createLogger(logLevel).error(
Expand All @@ -911,7 +880,7 @@ export async function loadConfigFromFile(

async function bundleConfigFile(
fileName: string,
isESM = false
isESM: boolean
): Promise<{ code: string; dependencies: string[] }> {
const importMetaUrlVarName = '__vite_injected_original_import_meta_url'
const result = await build({
Expand Down Expand Up @@ -954,7 +923,7 @@ async function bundleConfigFile(
)};`

return {
loader: isTS(args.path) ? 'ts' : 'js',
loader: args.path.endsWith('ts') ? 'ts' : 'js',
contents: injectValues + contents
}
})
Expand All @@ -976,22 +945,38 @@ interface NodeModuleWithCompile extends NodeModule {
const _require = createRequire(import.meta.url)
async function loadConfigFromBundledFile(
fileName: string,
bundledCode: string
): Promise<UserConfig> {
const realFileName = fs.realpathSync(fileName)
const defaultLoader = _require.extensions['.js']
_require.extensions['.js'] = (module: NodeModule, filename: string) => {
if (filename === realFileName) {
;(module as NodeModuleWithCompile)._compile(bundledCode, filename)
} else {
defaultLoader(module, filename)
bundledCode: string,
isESM: boolean
): Promise<UserConfigExport> {
// for esm, before we can register loaders without requiring users to run node
// with --experimental-loader themselves, we have to do a hack here:
// write it to disk, load it with native Node ESM, then delete the file.
if (isESM) {
const fileUrl = pathToFileURL(fileName)
fs.writeFileSync(fileName + '.mjs', bundledCode)
try {
return (await dynamicImport(`${fileUrl}.mjs?t=${Date.now()}`)).default
} finally {
fs.unlinkSync(fileName + '.mjs')
}
}
// for cjs, we can register a custom loader via `_require.extensions`
else {
const realFileName = fs.realpathSync(fileName)
const defaultLoader = _require.extensions['.js']
_require.extensions['.js'] = (module: NodeModule, filename: string) => {
if (filename === realFileName) {
;(module as NodeModuleWithCompile)._compile(bundledCode, filename)
} else {
defaultLoader(module, filename)
}
}
// clear cache in case of server restart
delete _require.cache[_require.resolve(fileName)]
const raw = _require(fileName)
_require.extensions['.js'] = defaultLoader
return raw.__esModule ? raw.default : raw
}
// clear cache in case of server restart
delete _require.cache[_require.resolve(fileName)]
const raw = _require(fileName)
_require.extensions['.js'] = defaultLoader
return raw.__esModule ? raw.default : raw
}

export function getDepOptimizationConfig(
Expand Down
2 changes: 0 additions & 2 deletions packages/vite/src/node/utils.ts
Expand Up @@ -1130,8 +1130,6 @@ export function stripBomTag(content: string): string {
return content
}

export const isTS = (filename: string): boolean => /\.[cm]?ts$/.test(filename)

const windowsDrivePathPrefixRE = /^[A-Za-z]:[/\\]/

/**
Expand Down
5 changes: 5 additions & 0 deletions playground/ssr-vue/vite.config.js
Expand Up @@ -10,6 +10,11 @@ const nestedVirtualId = '\0' + nestedVirtualFile

const base = '/test/'

// preserve this to test loading __filename & __dirname in ESM as Vite polyfills them.
// if Vite incorrectly load this file, node.js would error out.
globalThis.__vite_test_filename = __filename
globalThis.__vite_test_dirname = __dirname

export default defineConfig(({ command, ssrBuild }) => ({
base,
plugins: [
Expand Down