Skip to content

Commit

Permalink
feat: support config.ts UserConfig
Browse files Browse the repository at this point in the history
  • Loading branch information
YunYouJun committed Aug 29, 2021
1 parent 68ebe5a commit 43ae7eb
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 16 deletions.
6 changes: 5 additions & 1 deletion docs/.vitepress/config.js → docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
module.exports = {
import { UserConfig } from 'vitepress'

const config: UserConfig = {
lang: 'en-US',
title: 'VitePress',
description: 'Vite & Vue powered static site generator.',
Expand Down Expand Up @@ -43,6 +45,8 @@ module.exports = {
}
}

export default config

function getGuideSidebar() {
return [
{
Expand Down
185 changes: 170 additions & 15 deletions src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from './shared'
import { resolveAliases, APP_PATH, DEFAULT_THEME_PATH } from './alias'
import { MarkdownOptions } from './markdown/markdown'
import { build } from 'esbuild'

export { resolveSiteDataByRoute } from './shared'

Expand Down Expand Up @@ -70,7 +71,7 @@ const resolve = (root: string, file: string) =>
export async function resolveConfig(
root: string = process.cwd()
): Promise<SiteConfig> {
const userConfig = await resolveUserConfig(root)
const { configPath, userConfig } = await resolveUserConfig(root)

if (userConfig.vueOptions) {
console.warn(
Expand Down Expand Up @@ -104,7 +105,7 @@ export async function resolveConfig(
cwd: srcDir,
ignore: ['**/node_modules', ...(userConfig.srcExclude || [])]
}),
configPath: resolve(root, 'config.js'),
configPath: configPath,
outDir: resolve(root, 'dist'),
tempDir: path.resolve(APP_PATH, 'temp'),
markdown: userConfig.markdown,
Expand All @@ -116,29 +117,100 @@ export async function resolveConfig(
return config
}

export async function resolveUserConfig(root: string): Promise<UserConfig> {
// load user config
const configPath = resolve(root, 'config.js')
const hasUserConfig = await fs.pathExists(configPath)
// always delete cache first before loading config
delete require.cache[configPath]
const userConfig: UserConfig | (() => UserConfig) = hasUserConfig
? require(configPath)
: {}
if (hasUserConfig) {
debug(`loaded config at ${chalk.yellow(configPath)}`)
/**
* load user config
* @param root
* @param configFile
* @returns
*/
export async function resolveUserConfig(
root: string,
configFile?: string
): Promise<{
configPath: string
userConfig: UserConfig
}> {
const start = Date.now()

let configPath: string | undefined
let isTS = false

if (configFile) {
configPath = resolve(root, configFile)
isTS = configFile.endsWith('.ts')
} else {
const jsConfigPath = resolve(root, 'config.js')
if (fs.existsSync(jsConfigPath)) {
configPath = jsConfigPath
}

if (!configPath) {
const tsConfigPath = resolve(root, 'config.ts')
if (fs.existsSync(tsConfigPath)) {
configPath = tsConfigPath
isTS = true
}
}
}

if (!configPath) {
debug(`no config file found.`)
return {
configPath: '',
userConfig: {}
}
}

return typeof userConfig === 'function' ? userConfig() : userConfig
try {
let userConfig: UserConfig | (() => UserConfig) | undefined

if (!userConfig && !isTS) {
try {
// always delete cache first before loading config
delete require.cache[configPath]
userConfig = configPath ? require(configPath) : {}
debug(`loaded config at ${chalk.yellow(configPath)}`)
} catch (e) {
const ignored = new RegExp(
[
`Cannot use import statement`,
`Must use import to load ES Module`,
// #1635, #2050 some Node 12.x versions don't have esm detection
// so it throws normal syntax errors when encountering esm syntax
`Unexpected token`,
`Unexpected identifier`
].join('|')
)
if (!ignored.test(e.message)) {
throw e
}
}
}

if (!userConfig) {
const bundled = await bundleConfigFile(configPath)
userConfig = await loadConfigFromBundledFile(configPath, bundled.code)
debug(`bundled config file loaded in ${Date.now() - start}ms`)
}

const config = typeof userConfig === 'function' ? userConfig() : userConfig
return {
configPath,
userConfig: config
}
} catch (e) {
console.error(chalk.red(`failed to load config from ${configPath}`), {
error: e
})
throw e
}
}

export async function resolveSiteData(
root: string,
userConfig?: UserConfig
): Promise<SiteData> {
userConfig = userConfig || (await resolveUserConfig(root))
userConfig = userConfig || (await (await resolveUserConfig(root)).userConfig)
return {
lang: userConfig.lang || 'en-US',
title: userConfig.title || 'VitePress',
Expand All @@ -151,3 +223,86 @@ export async function resolveSiteData(
customData: userConfig.customData || {}
}
}

interface NodeModuleWithCompile extends NodeModule {
_compile(code: string, filename: string): any
}

async function loadConfigFromBundledFile(
fileName: string,
bundledCode: string
): Promise<UserConfig> {
const extension = path.extname(fileName)
const defaultLoader = require.extensions[extension]!
require.extensions[extension] = (module: NodeModule, filename: string) => {
if (filename === fileName) {
;(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)
const config = raw.__esModule ? raw.default : raw
require.extensions[extension] = defaultLoader
return config
}

async function bundleConfigFile(
fileName: string,
mjs = false
): Promise<{ code: string; dependencies: string[] }> {
const result = await build({
absWorkingDir: process.cwd(),
entryPoints: [fileName],
outfile: 'out.js',
write: false,
platform: 'node',
bundle: true,
format: mjs ? 'esm' : 'cjs',
sourcemap: 'inline',
metafile: true,
plugins: [
{
name: 'externalize-deps',
setup(build) {
build.onResolve({ filter: /.*/ }, (args) => {
const id = args.path
if (id[0] !== '.' && !path.isAbsolute(id)) {
return {
external: true
}
}
})
}
},
{
name: 'replace-import-meta',
setup(build) {
build.onLoad({ filter: /\.[jt]s$/ }, async (args) => {
const contents = await fs.promises.readFile(args.path, 'utf8')
return {
loader: args.path.endsWith('.ts') ? 'ts' : 'js',
contents: contents
.replace(
/\bimport\.meta\.url\b/g,
JSON.stringify(`file://${args.path}`)
)
.replace(
/\b__dirname\b/g,
JSON.stringify(path.dirname(args.path))
)
.replace(/\b__filename\b/g, JSON.stringify(args.path))
}
})
}
}
]
})
const { text } = result.outputFiles[0]
return {
code: text,
dependencies: result.metafile ? Object.keys(result.metafile.inputs) : []
}
}

0 comments on commit 43ae7eb

Please sign in to comment.