Skip to content

Commit d9850f0

Browse files
authored
feat: support .js extension and reduce sync fs operations (#2369)
1 parent 7bede89 commit d9850f0

File tree

10 files changed

+77
-71
lines changed

10 files changed

+77
-71
lines changed

packages/slidev/node/commands/shared.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import type { ResolvedSlidevOptions, SlidevData, SlidevServerOptions } from '@slidev/types'
22
import type { ConfigEnv, InlineConfig } from 'vite'
3-
import { existsSync } from 'node:fs'
4-
import { join } from 'node:path'
53
import MarkdownIt from 'markdown-it'
64
import { loadConfigFromFile, mergeConfig } from 'vite'
5+
import { resolveSourceFiles } from '../resolver'
76
import markdownItLink from '../syntax/markdown-it/markdown-it-link'
87
import { stringifyMarkdownTokens } from '../utils'
98
import { ViteSlidevPlugin } from '../vite'
@@ -25,22 +24,20 @@ export async function resolveViteConfigs(
2524
command: 'serve' | 'build',
2625
serverOptions?: SlidevServerOptions,
2726
) {
27+
// Merge theme & addon & user configs
2828
const configEnv: ConfigEnv = {
2929
mode: command === 'build' ? 'production' : 'development',
3030
command,
3131
}
32-
// Merge theme & addon & user configs
33-
const files = options.roots.map(i => join(i, 'vite.config.ts'))
34-
35-
for (const file of files) {
36-
if (!existsSync(file))
37-
continue
38-
const viteConfig = await loadConfigFromFile(configEnv, file)
39-
if (!viteConfig?.config)
32+
const files = resolveSourceFiles(options.roots, 'vite.config')
33+
const configs = await Promise.all(files.map(file => loadConfigFromFile(configEnv, file)))
34+
for (const config of configs) {
35+
if (!config?.config)
4036
continue
41-
baseConfig = mergeConfig(baseConfig, viteConfig.config)
37+
baseConfig = mergeConfig(baseConfig, config.config)
4238
}
4339

40+
// Merge command-specific overrides
4441
baseConfig = mergeConfig(baseConfig, overrideConfigs)
4542

4643
// Merge common overrides

packages/slidev/node/options.ts

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export async function createDataUtils(resolved: Omit<ResolvedSlidevOptions, 'uti
7979
.map(i => pm.makeRe(i))
8080

8181
let _layouts_cache_time = 0
82-
let _layouts_cache: Record<string, string> = {}
82+
let _layouts_cache: Promise<Record<string, string>> | null = null
8383

8484
return {
8585
...await setupShiki(resolved.roots),
@@ -90,28 +90,27 @@ export async function createDataUtils(resolved: Omit<ResolvedSlidevOptions, 'uti
9090
isMonacoTypesIgnored: pkg => monacoTypesIgnorePackagesMatches.some(i => i.test(pkg)),
9191
getLayouts: () => {
9292
const now = Date.now()
93-
if (now - _layouts_cache_time < 2000)
93+
if (_layouts_cache && now - _layouts_cache_time < 2000)
9494
return _layouts_cache
95-
96-
const layouts: Record<string, string> = {}
97-
98-
for (const root of [resolved.clientRoot, ...resolved.roots]) {
99-
const layoutPaths = fg.sync('layouts/**/*.{vue,ts}', {
100-
cwd: root,
101-
absolute: true,
102-
suppressErrors: true,
103-
})
104-
105-
for (const layoutPath of layoutPaths) {
95+
_layouts_cache_time = now
96+
return _layouts_cache = worker()
97+
98+
async function worker() {
99+
const layouts: Record<string, string> = {}
100+
const layoutPaths = await Promise.all(
101+
[resolved.clientRoot, ...resolved.roots]
102+
.map(root => fg('layouts/**/*.{vue,js,mjs,ts,mts}', {
103+
cwd: root,
104+
absolute: true,
105+
suppressErrors: true,
106+
})),
107+
)
108+
for (const layoutPath of layoutPaths.flat(1)) {
106109
const layoutName = path.basename(layoutPath).replace(/\.\w+$/, '')
107110
layouts[layoutName] = layoutPath
108111
}
112+
return layouts
109113
}
110-
111-
_layouts_cache_time = now
112-
_layouts_cache = layouts
113-
114-
return layouts
115114
},
116115
}
117116
}

packages/slidev/node/resolver.ts

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { RootsInfo } from '@slidev/types'
2-
import * as fs from 'node:fs'
2+
import { existsSync } from 'node:fs'
3+
import { copyFile, readFile } from 'node:fs/promises'
34
import { dirname, join, relative, resolve } from 'node:path'
45
import process from 'node:process'
56
import { fileURLToPath } from 'node:url'
@@ -68,28 +69,28 @@ export async function findGlobalPkgRoot(name: string, ensure: true): Promise<str
6869
export async function findGlobalPkgRoot(name: string, ensure?: boolean): Promise<string | undefined>
6970
export async function findGlobalPkgRoot(name: string, ensure = false) {
7071
const yarnPath = join(globalDirs.yarn.packages, name)
71-
if (fs.existsSync(`${yarnPath}/package.json`))
72+
if (existsSync(`${yarnPath}/package.json`))
7273
return yarnPath
7374
const npmPath = join(globalDirs.npm.packages, name)
74-
if (fs.existsSync(`${npmPath}/package.json`))
75+
if (existsSync(`${npmPath}/package.json`))
7576
return npmPath
7677
if (ensure)
7778
throw new Error(`Failed to resolve global package "${name}"`)
7879
}
7980

8081
export async function resolveEntry(entryRaw: string) {
81-
if (!fs.existsSync(entryRaw) && !entryRaw.endsWith('.md') && !/[/\\]/.test(entryRaw))
82+
if (!existsSync(entryRaw) && !entryRaw.endsWith('.md') && !/[/\\]/.test(entryRaw))
8283
entryRaw += '.md'
8384
const entry = resolve(entryRaw)
84-
if (!fs.existsSync(entry)) {
85+
if (!existsSync(entry)) {
8586
const { create } = await prompts({
8687
name: 'create',
8788
type: 'confirm',
8889
initial: 'Y',
8990
message: `Entry file ${yellow(`"${entry}"`)} does not exist, do you want to create it?`,
9091
})
9192
if (create)
92-
fs.copyFileSync(resolve(cliRoot, 'template.md'), entry)
93+
await copyFile(resolve(cliRoot, 'template.md'), entry)
9394
else
9495
process.exit(0)
9596
}
@@ -161,24 +162,20 @@ export function createResolver(type: 'theme' | 'addon', officials: Record<string
161162
}
162163
}
163164

164-
function getUserPkgJson(userRoot: string) {
165+
async function getUserPkgJson(userRoot: string) {
165166
const path = resolve(userRoot, 'package.json')
166-
if (fs.existsSync(path))
167-
return JSON.parse(fs.readFileSync(path, 'utf-8')) as Record<string, any>
167+
if (existsSync(path))
168+
return JSON.parse(await readFile(path, 'utf-8')) as Record<string, any>
168169
return {}
169170
}
170171

171172
// npm: https://docs.npmjs.com/cli/v7/using-npm/workspaces#installing-workspaces
172173
// yarn: https://classic.yarnpkg.com/en/docs/workspaces/#toc-how-to-use-it
173-
function hasWorkspacePackageJSON(root: string): boolean {
174+
async function hasWorkspacePackageJSON(root: string): Promise<boolean> {
174175
const path = join(root, 'package.json')
175-
try {
176-
fs.accessSync(path, fs.constants.R_OK)
177-
}
178-
catch {
176+
if (!existsSync(path))
179177
return false
180-
}
181-
const content = JSON.parse(fs.readFileSync(path, 'utf-8')) || {}
178+
const content = JSON.parse(await readFile(path, 'utf-8')) || {}
182179
return !!content.workspaces
183180
}
184181

@@ -198,19 +195,19 @@ function hasRootFile(root: string): boolean {
198195
// 'nx.json'
199196
]
200197

201-
return ROOT_FILES.some(file => fs.existsSync(join(root, file)))
198+
return ROOT_FILES.some(file => existsSync(join(root, file)))
202199
}
203200

204201
/**
205202
* Search up for the nearest workspace root
206203
*/
207-
function searchForWorkspaceRoot(
204+
async function searchForWorkspaceRoot(
208205
current: string,
209206
root = current,
210-
): string {
207+
): Promise<string> {
211208
if (hasRootFile(current))
212209
return current
213-
if (hasWorkspacePackageJSON(current))
210+
if (await hasWorkspacePackageJSON(current))
214211
return current
215212

216213
const dir = dirname(current)
@@ -234,8 +231,8 @@ export async function getRoots(entry?: string): Promise<RootsInfo> {
234231
|| (await import('is-installed-globally')).default
235232
const clientRoot = await findPkgRoot('@slidev/client', cliRoot, true)
236233
const closestPkgRoot = dirname(await findClosestPkgJsonPath(userRoot) || userRoot)
237-
const userPkgJson = getUserPkgJson(closestPkgRoot)
238-
const userWorkspaceRoot = searchForWorkspaceRoot(closestPkgRoot)
234+
const userPkgJson = await getUserPkgJson(closestPkgRoot)
235+
const userWorkspaceRoot = await searchForWorkspaceRoot(closestPkgRoot)
239236
rootsInfo = {
240237
cliRoot,
241238
clientRoot,
@@ -245,3 +242,21 @@ export async function getRoots(entry?: string): Promise<RootsInfo> {
245242
}
246243
return rootsInfo
247244
}
245+
246+
export function resolveSourceFiles(
247+
roots: string[],
248+
subpath: string,
249+
extensions = ['.mjs', '.js', '.mts', '.ts'], // The same order as https://vite.dev/config/shared-options#resolve-extensions
250+
) {
251+
const results: string[] = []
252+
for (const root of roots) {
253+
for (const ext of extensions) {
254+
const fullPath = join(root, subpath + ext)
255+
if (existsSync(fullPath)) {
256+
results.push(fullPath)
257+
break
258+
}
259+
}
260+
}
261+
return results
262+
}

packages/slidev/node/setups/indexHtml.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ResolvedSlidevOptions, SeoMeta } from '@slidev/types'
22
import type { ResolvableLink } from 'unhead/types'
3-
import { existsSync, readFileSync } from 'node:fs'
3+
import { existsSync } from 'node:fs'
4+
import { readFile } from 'node:fs/promises'
45
import { join } from 'node:path'
56
import { slash } from '@antfu/utils'
67
import { white, yellow } from 'ansis'
@@ -16,7 +17,7 @@ function toAttrValue(unsafe: unknown) {
1617
}
1718

1819
export default async function setupIndexHtml({ mode, entry, clientRoot, userRoot, roots, data, base }: Omit<ResolvedSlidevOptions, 'utils'>): Promise<string> {
19-
let main = readFileSync(join(clientRoot, 'index.html'), 'utf-8')
20+
let main = await readFile(join(clientRoot, 'index.html'), 'utf-8')
2021
let body = ''
2122

2223
const inputs: any[] = []
@@ -26,7 +27,7 @@ export default async function setupIndexHtml({ mode, entry, clientRoot, userRoot
2627
if (!existsSync(path))
2728
continue
2829

29-
const html = readFileSync(path, 'utf-8')
30+
const html = await readFile(path, 'utf-8')
3031

3132
if (root === userRoot && html.includes('<!DOCTYPE')) {
3233
console.error(yellow(`[Slidev] Ignored provided index.html with doctype declaration. (${white(path)})`))

packages/slidev/node/setups/unocss.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { ResolvedSlidevOptions, UnoSetup } from '@slidev/types'
22
import type { UserConfig } from '@unocss/core'
33
import type { Theme } from '@unocss/preset-uno'
4-
import { existsSync, readFileSync } from 'node:fs'
4+
import { existsSync } from 'node:fs'
5+
import { readFile } from 'node:fs/promises'
56
import { resolve } from 'node:path'
67
import { mergeConfigs, presetIcons } from 'unocss'
78
import { loadSetups } from '../setups/load'
@@ -34,7 +35,7 @@ export default async function setupUnocss(
3435
collectionsNodeResolvePath: utils.iconsResolvePath,
3536
collections: {
3637
slidev: {
37-
logo: () => readFileSync(resolve(clientRoot, 'assets/logo.svg'), 'utf-8'),
38+
logo: () => readFile(resolve(clientRoot, 'assets/logo.svg'), 'utf-8'),
3839
},
3940
},
4041
}),

packages/slidev/node/virtual/layouts.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { toAtFS } from '../resolver'
44

55
export const templateLayouts: VirtualModuleTemplate = {
66
id: '/@slidev/layouts',
7-
getContent({ utils }) {
7+
async getContent({ utils }) {
88
const imports: string[] = []
99
const layouts = objectMap(
10-
utils.getLayouts(),
10+
await utils.getLayouts(),
1111
(k, v) => {
1212
imports.push(`import __layout_${k} from "${toAtFS(v)}"`)
1313
return [k, `__layout_${k}`]

packages/slidev/node/virtual/setups.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
11
import type { VirtualModuleTemplate } from './types'
2-
import { existsSync } from 'node:fs'
3-
import { join } from 'node:path'
4-
import { toAtFS } from '../resolver'
2+
import { resolveSourceFiles, toAtFS } from '../resolver'
53

64
function createSetupTemplate(name: string): VirtualModuleTemplate {
75
return {
86
id: `/@slidev/setups/${name}`,
97
getContent({ roots }) {
10-
const setups = roots
11-
.flatMap((i) => {
12-
const path = join(i, 'setup', name)
13-
return ['.ts', '.mts', '.js', '.mjs'].map(ext => path + ext)
14-
})
15-
.filter(i => existsSync(i))
8+
const setups = resolveSourceFiles(roots, `setup/${name}`)
169

1710
const imports: string[] = []
1811

packages/slidev/node/virtual/slides.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ export const VIRTUAL_SLIDE_PREFIX = '/@slidev/slides/'
44

55
export const templateSlides: VirtualModuleTemplate = {
66
id: '/@slidev/slides',
7-
getContent({ data, utils }) {
8-
const layouts = utils.getLayouts()
7+
async getContent({ data, utils }) {
8+
const layouts = await utils.getLayouts()
99
const statements = [
1010
`import { defineAsyncComponent, shallowRef } from 'vue'`,
1111
`import SlideError from '${layouts.error}'`,

packages/slidev/node/vite/layoutWrapper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function createLayoutWrapperPlugin(
2323
if (type !== 'md')
2424
return
2525
const index = +no - 1
26-
const layouts = utils.getLayouts()
26+
const layouts = await utils.getLayouts()
2727
const rawLayoutName = data.slides[index]?.frontmatter?.layout ?? data.slides[0]?.frontmatter?.defaults?.layout
2828
let layoutName = rawLayoutName || (index === 0 ? 'cover' : 'default')
2929
if (!layouts[layoutName]) {

packages/types/src/options.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export interface ResolvedSlidevUtils {
6464
define: Record<string, string>
6565
iconsResolvePath: string[]
6666
isMonacoTypesIgnored: (pkg: string) => boolean
67-
getLayouts: () => Record<string, string>
67+
getLayouts: () => Promise<Record<string, string>>
6868
}
6969

7070
export interface SlidevServerOptions {

0 commit comments

Comments
 (0)