Skip to content

Commit b10d4c0

Browse files
authored
fix: package and path resolving (#1308)
1 parent be807d4 commit b10d4c0

File tree

21 files changed

+470
-527
lines changed

21 files changed

+470
-527
lines changed

packages/parser/src/fs.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { promises as fs } from 'node:fs'
22
import { dirname, resolve } from 'node:path'
33
import YAML from 'js-yaml'
44
import { slash } from '@antfu/utils'
5-
import type { PreparserExtensionLoader, SlideInfo, SlidevData, SlidevMarkdown, SlidevPreparserExtension, SlidevThemeMeta, SourceSlideInfo } from '@slidev/types'
6-
import { detectFeatures, parse, parseRangeString, resolveConfig, stringify } from './core'
5+
import type { PreparserExtensionLoader, SlideInfo, SlidevData, SlidevMarkdown, SlidevPreparserExtension, SourceSlideInfo } from '@slidev/types'
6+
import { detectFeatures, parse, parseRangeString, stringify } from './core'
77

88
export * from './core'
99

@@ -13,7 +13,13 @@ export function injectPreparserExtensionLoader(fn: PreparserExtensionLoader) {
1313
preparserExtensionLoader = fn
1414
}
1515

16-
export async function load(userRoot: string, filepath: string, themeMeta?: SlidevThemeMeta, content?: string): Promise<SlidevData> {
16+
/**
17+
* Slidev data without config and themeMeta,
18+
* because config and themeMeta depends on the theme to be loaded.
19+
*/
20+
export type LoadedSlidevData = Omit<SlidevData, 'config' | 'themeMeta'>
21+
22+
export async function load(userRoot: string, filepath: string, content?: string): Promise<LoadedSlidevData> {
1723
const markdown = content ?? await fs.readFile(filepath, 'utf-8')
1824

1925
let extensions: SlidevPreparserExtension[] | undefined
@@ -90,10 +96,8 @@ export async function load(userRoot: string, filepath: string, themeMeta?: Slide
9096
return {
9197
slides,
9298
entry,
93-
config: resolveConfig(headmatter, themeMeta, filepath),
9499
headmatter,
95100
features: detectFeatures(slides.map(s => s.source.raw).join('')),
96-
themeMeta,
97101
markdownFiles,
98102
watchFiles: Object.keys(markdownFiles).map(slash),
99103
}

packages/slidev/node/addons.ts

Lines changed: 22 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,30 @@
1+
import { resolve } from 'node:path'
12
import fs from 'fs-extra'
2-
import { satisfies } from 'semver'
3-
import type { SlidevConfig } from '@slidev/types'
4-
import { version } from '../package.json'
5-
import { packageExists, resolveImportPath } from './utils'
6-
import { isPath } from './options'
3+
import { createResolver, getRoots } from './resolver'
4+
import { checkEngine } from './utils'
75

8-
export async function getPackageJson(root: string): Promise<Record<string, any>> {
9-
try {
10-
const file = await resolveImportPath(`${root}/package.json`, true)
11-
if (file && fs.existsSync(file))
12-
return await fs.readJSON(file)
13-
return {}
14-
}
15-
catch (e) {
16-
return {}
17-
}
18-
}
19-
20-
export async function getAddons(userRoot: string, config: SlidevConfig): Promise<string[]> {
21-
const { slidev = {} } = await getPackageJson(userRoot)
22-
const configAddons = Array.isArray(config.addons) ? config.addons : []
23-
const addons = configAddons.concat(Array.isArray(slidev?.addons) ? slidev.addons : [])
24-
return (await getRecursivePlugins(await Promise.all(addons.map(resolvePluginName)), 3)).filter(Boolean)
25-
}
6+
export async function resolveAddons(addonsInConfig: string[]) {
7+
const { userRoot, userPkgJson } = await getRoots()
8+
const resolved: string[] = []
269

27-
export async function getRecursivePlugins(addons: string[], depth: number): Promise<string[]> {
28-
const addonsArray = await Promise.all(addons.map(async (addon) => {
29-
const { slidev = {}, engines = {} } = await getPackageJson(addon)
30-
checkEngine(addon, engines)
10+
const resolveAddonNameAndRoot = createResolver('addon', {})
3111

32-
let addons = Array.isArray(slidev?.addons) ? slidev.addons : []
33-
if (addons.length > 0 && depth)
34-
addons = await getRecursivePlugins(addons.map(resolvePluginName), depth - 1)
35-
addons.push(addon)
12+
async function resolveAddon(name: string, parent: string) {
13+
const [, pkgRoot] = await resolveAddonNameAndRoot(name, parent)
14+
if (!pkgRoot)
15+
return
16+
resolved.push(pkgRoot)
17+
const { slidev, engines } = await fs.readJSON(resolve(pkgRoot, 'package.json'))
18+
checkEngine(name, engines)
3619

37-
return addons
38-
}))
39-
return addonsArray.flat()
40-
}
20+
if (Array.isArray(slidev?.addons))
21+
await Promise.all(slidev.addons.map((addon: string) => resolveAddon(addon, pkgRoot)))
22+
}
4123

42-
export async function checkEngine(name: string, engines: { slidev?: string }) {
43-
if (engines.slidev && !satisfies(version, engines.slidev, { includePrerelease: true }))
44-
throw new Error(`[slidev] addon "${name}" requires Slidev version range "${engines.slidev}" but found "${version}"`)
45-
}
24+
if (Array.isArray(addonsInConfig))
25+
await Promise.all(addonsInConfig.map((addon: string) => resolveAddon(addon, userRoot)))
26+
if (Array.isArray(userPkgJson.slidev?.addons))
27+
await Promise.all(userPkgJson.slidev.addons.map((addon: string) => resolveAddon(addon, userRoot)))
4628

47-
export async function resolvePluginName(name: string) {
48-
if (!name)
49-
return ''
50-
if (isPath(name))
51-
return name
52-
if (await packageExists(`slidev-addon-${name}`))
53-
return `slidev-addon-${name}`
54-
return name
29+
return resolved
5530
}

packages/slidev/node/build.ts

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type { BuildArgs } from '@slidev/types'
1111
import { ViteSlidevPlugin } from './plugins/preset'
1212
import { getIndexHtml, mergeViteConfigs } from './common'
1313
import type { ResolvedSlidevOptions } from './options'
14+
import { findPkgRoot } from './resolver'
1415

1516
export async function build(
1617
options: ResolvedSlidevOptions,
@@ -64,34 +65,31 @@ export async function build(
6465
}
6566
else {
6667
console.log(blue(' building for Monaco...\n'))
67-
68-
await viteBuild(
69-
await mergeViteConfigs(
70-
options,
71-
inlineConfig,
72-
<InlineConfig>({
73-
root: join(options.clientRoot, 'iframes/monaco'),
74-
base: `${config.base}iframes/monaco/`,
75-
build: {
76-
outDir: resolve(config.build.outDir, 'iframes/monaco'),
77-
// fix for monaco workers
78-
// https://github.com/vitejs/vite/issues/1927#issuecomment-805803918
79-
rollupOptions: {
80-
output: {
81-
manualChunks: {
82-
jsonWorker: ['monaco-editor/esm/vs/language/json/json.worker'],
83-
cssWorker: ['monaco-editor/esm/vs/language/css/css.worker'],
84-
htmlWorker: ['monaco-editor/esm/vs/language/html/html.worker'],
85-
tsWorker: ['monaco-editor/esm/vs/language/typescript/ts.worker'],
86-
editorWorker: ['monaco-editor/esm/vs/editor/editor.worker'],
87-
},
88-
},
68+
const monacoRoot = await findPkgRoot('monaco-editor', options.clientRoot, true)
69+
const getWorkerPath = (path: string) => [join(monacoRoot, 'esm/vs', path)]
70+
await viteBuild({
71+
root: join(options.clientRoot, 'iframes/monaco'),
72+
base: `${config.base}iframes/monaco/`,
73+
plugins: [
74+
await ViteSlidevPlugin(options, inlineConfig.slidev || {}),
75+
],
76+
build: {
77+
outDir: resolve(options.userRoot, config.build.outDir, 'iframes/monaco'),
78+
// fix for monaco workers
79+
// https://github.com/vitejs/vite/issues/1927#issuecomment-805803918
80+
rollupOptions: {
81+
output: {
82+
manualChunks: {
83+
jsonWorker: getWorkerPath('language/json/json.worker'),
84+
cssWorker: getWorkerPath('language/css/css.worker'),
85+
htmlWorker: getWorkerPath('language/html/html.worker'),
86+
tsWorker: getWorkerPath('language/typescript/ts.worker'),
87+
editorWorker: getWorkerPath('editor/editor.worker'),
8988
},
9089
},
91-
}),
92-
'build',
93-
),
94-
)
90+
},
91+
},
92+
})
9593
}
9694
}
9795
}

packages/slidev/node/cli.ts

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,24 @@ import fs from 'fs-extra'
88
import openBrowser from 'open'
99
import type { Argv } from 'yargs'
1010
import yargs from 'yargs'
11-
import prompts from 'prompts'
1211
import { blue, bold, cyan, dim, gray, green, underline, yellow } from 'kolorist'
1312
import type { LogLevel, ViteDevServer } from 'vite'
14-
import type { SlidevConfig, SlidevPreparserExtension } from '@slidev/types'
13+
import type { SlidevConfig, SlidevData, SlidevPreparserExtension } from '@slidev/types'
1514
import isInstalledGlobally from 'is-installed-globally'
1615
import equal from 'fast-deep-equal'
1716
import { verifyConfig } from '@slidev/parser'
1817
import { injectPreparserExtensionLoader } from '@slidev/parser/fs'
18+
import { uniq } from '@antfu/utils'
1919
import { checkPort } from 'get-port-please'
2020
import { version } from '../package.json'
2121
import { createServer } from './server'
2222
import type { ResolvedSlidevOptions } from './options'
23-
import { getAddonRoots, getClientRoot, getThemeRoots, getUserRoot, isPath, resolveOptions } from './options'
24-
import { resolveThemeName } from './themes'
23+
import { resolveOptions } from './options'
24+
import { getThemeMeta, resolveTheme } from './themes'
2525
import { parser } from './parser'
2626
import { loadSetups } from './plugins/setupNode'
27+
import { getRoots } from './resolver'
28+
import { resolveAddons } from './addons'
2729

2830
const CONFIG_RESTART_FIELDS: (keyof SlidevConfig)[] = [
2931
'highlighter',
@@ -33,17 +35,19 @@ const CONFIG_RESTART_FIELDS: (keyof SlidevConfig)[] = [
3335
'css',
3436
'mdc',
3537
'editor',
38+
'theme',
3639
]
3740

3841
injectPreparserExtensionLoader(async (headmatter?: Record<string, unknown>, filepath?: string) => {
3942
const addons = headmatter?.addons as string[] ?? []
40-
const roots = /* uniq */([
41-
getUserRoot({}).userRoot,
42-
...await getAddonRoots(addons, ''),
43-
await getClientRoot(),
43+
const { clientRoot, userRoot } = await getRoots()
44+
const roots = uniq([
45+
clientRoot,
46+
userRoot,
47+
...await resolveAddons(addons),
4448
])
4549
const mergeArrays = (a: SlidevPreparserExtension[], b: SlidevPreparserExtension[]) => a.concat(b)
46-
return await loadSetups(roots, 'preparser.ts', { filepath, headmatter }, [], mergeArrays)
50+
return await loadSetups(clientRoot, roots, 'preparser.ts', { filepath, headmatter }, [], mergeArrays)
4751
})
4852

4953
const cli = yargs(process.argv.slice(2))
@@ -104,22 +108,6 @@ cli.command(
104108
.strict()
105109
.help(),
106110
async ({ entry, theme, port: userPort, open, log, remote, tunnel, force, inspect, bind }) => {
107-
if (!fs.existsSync(entry) && !entry.endsWith('.md'))
108-
entry = `${entry}.md`
109-
110-
if (!fs.existsSync(entry)) {
111-
const { create } = await prompts({
112-
name: 'create',
113-
type: 'confirm',
114-
initial: 'Y',
115-
message: `Entry file ${yellow(`"${entry}"`)} does not exist, do you want to create it?`,
116-
})
117-
if (create)
118-
await fs.copyFile(new URL('../template.md', import.meta.url), entry)
119-
else
120-
process.exit(0)
121-
}
122-
123111
let server: ViteDevServer | undefined
124112
let port = 3030
125113

@@ -148,15 +136,30 @@ cli.command(
148136
logLevel: log as LogLevel,
149137
},
150138
{
151-
async onDataReload(newData, data) {
152-
if (!theme && await resolveThemeName(newData.config.theme) !== await resolveThemeName(data.config.theme)) {
139+
async loadData() {
140+
const { data: oldData, entry } = options
141+
const loaded = await parser.load(options.userRoot, entry)
142+
143+
const themeRaw = theme || loaded.headmatter.theme as string || 'default'
144+
if (options.themeRaw !== themeRaw) {
153145
console.log(yellow('\n restarting on theme change\n'))
154146
initServer()
147+
return false
155148
}
156-
else if (CONFIG_RESTART_FIELDS.some(i => !equal(newData.config[i], data.config[i]))) {
149+
// Because themeRaw is not changed, we don't resolve it again
150+
const themeMeta = options.themeRoots[0] ? await getThemeMeta(themeRaw, options.themeRoots[0]) : undefined
151+
const newData: SlidevData = {
152+
...loaded,
153+
themeMeta,
154+
config: parser.resolveConfig(loaded.headmatter, themeMeta, entry),
155+
}
156+
157+
if (CONFIG_RESTART_FIELDS.some(i => !equal(newData.config[i], oldData.config[i]))) {
157158
console.log(yellow('\n restarting on config change\n'))
158159
initServer()
160+
return false
159161
}
162+
return newData
160163
},
161164
},
162165
))
@@ -351,23 +354,18 @@ cli.command(
351354
default: 'theme',
352355
}),
353356
async ({ entry, dir, theme: themeInput }) => {
354-
const { userRoot } = getUserRoot({ entry })
355-
const data = await parser.load(userRoot, entry)
356-
const theme = await resolveThemeName(themeInput || data.config.theme)
357-
if (theme === 'none') {
357+
const roots = await getRoots()
358+
const data = await parser.load(roots.userRoot, entry)
359+
const themeRaw = themeInput || (data.headmatter.theme as string) || 'default'
360+
if (themeRaw === 'none') {
358361
console.error('Cannot eject theme "none"')
359362
process.exit(1)
360363
}
361-
if (isPath(theme)) {
364+
if ('/.'.includes(themeRaw[0]) || (themeRaw[0] !== '@' && themeRaw.includes('/'))) {
362365
console.error('Theme is already ejected')
363366
process.exit(1)
364367
}
365-
const roots = await getThemeRoots(theme, entry)
366-
if (!roots.length) {
367-
console.error(`Could not find theme "${theme}"`)
368-
process.exit(1)
369-
}
370-
const root = roots[0]
368+
const [name, root] = (await resolveTheme(themeRaw, entry)) as [string, string]
371369

372370
await fs.copy(root, path.resolve(dir), {
373371
filter: i => !/node_modules|.git/.test(path.relative(root, i)),
@@ -379,7 +377,7 @@ cli.command(
379377
parser.prettifySlide(firstSlide)
380378
await parser.save(data.entry)
381379

382-
console.log(`Theme "${theme}" ejected successfully to "${dirPath}"`)
380+
console.log(`Theme "${name}" ejected successfully to "${dirPath}"`)
383381
},
384382
)
385383
},

packages/slidev/node/common.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
11
import { existsSync, promises as fs } from 'node:fs'
22
import { join } from 'node:path'
3-
import { uniq } from '@antfu/utils'
43
import { loadConfigFromFile, mergeConfig, resolveConfig } from 'vite'
54
import type { ConfigEnv, InlineConfig } from 'vite'
65
import type { ResolvedSlidevOptions } from './options'
7-
import { generateGoogleFontsUrl, toAtFS } from './utils'
6+
import { generateGoogleFontsUrl } from './utils'
7+
import { toAtFS } from './resolver'
88

9-
export async function getIndexHtml({ clientRoot, themeRoots, addonRoots, data, userRoot }: ResolvedSlidevOptions): Promise<string> {
9+
export async function getIndexHtml({ clientRoot, roots, data }: ResolvedSlidevOptions): Promise<string> {
1010
let main = await fs.readFile(join(clientRoot, 'index.html'), 'utf-8')
1111
let head = ''
1212
let body = ''
1313

1414
head += `<link rel="icon" href="${data.config.favicon}">`
1515

16-
const roots = uniq([
17-
...themeRoots,
18-
...addonRoots,
19-
userRoot,
20-
])
21-
2216
for (const root of roots) {
2317
const path = join(root, 'index.html')
2418
if (!existsSync(path))
@@ -45,7 +39,7 @@ export async function getIndexHtml({ clientRoot, themeRoots, addonRoots, data, u
4539
}
4640

4741
export async function mergeViteConfigs(
48-
{ addonRoots, themeRoots, entry }: ResolvedSlidevOptions,
42+
{ roots, entry }: ResolvedSlidevOptions,
4943
viteConfig: InlineConfig,
5044
config: InlineConfig,
5145
command: 'serve' | 'build',
@@ -55,10 +49,7 @@ export async function mergeViteConfigs(
5549
command,
5650
}
5751
// Merge theme & addon configs
58-
const files = uniq([
59-
...themeRoots,
60-
...addonRoots,
61-
]).map(i => join(i, 'vite.config.ts'))
52+
const files = roots.map(i => join(i, 'vite.config.ts'))
6253

6354
for await (const file of files) {
6455
if (!existsSync(file))

0 commit comments

Comments
 (0)