Skip to content

Commit

Permalink
fix: package and path resolving (#1308)
Browse files Browse the repository at this point in the history
  • Loading branch information
KermanX committed Feb 21, 2024
1 parent be807d4 commit b10d4c0
Show file tree
Hide file tree
Showing 21 changed files with 470 additions and 527 deletions.
14 changes: 9 additions & 5 deletions packages/parser/src/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { promises as fs } from 'node:fs'
import { dirname, resolve } from 'node:path'
import YAML from 'js-yaml'
import { slash } from '@antfu/utils'
import type { PreparserExtensionLoader, SlideInfo, SlidevData, SlidevMarkdown, SlidevPreparserExtension, SlidevThemeMeta, SourceSlideInfo } from '@slidev/types'
import { detectFeatures, parse, parseRangeString, resolveConfig, stringify } from './core'
import type { PreparserExtensionLoader, SlideInfo, SlidevData, SlidevMarkdown, SlidevPreparserExtension, SourceSlideInfo } from '@slidev/types'
import { detectFeatures, parse, parseRangeString, stringify } from './core'

export * from './core'

Expand All @@ -13,7 +13,13 @@ export function injectPreparserExtensionLoader(fn: PreparserExtensionLoader) {
preparserExtensionLoader = fn
}

export async function load(userRoot: string, filepath: string, themeMeta?: SlidevThemeMeta, content?: string): Promise<SlidevData> {
/**
* Slidev data without config and themeMeta,
* because config and themeMeta depends on the theme to be loaded.
*/
export type LoadedSlidevData = Omit<SlidevData, 'config' | 'themeMeta'>

export async function load(userRoot: string, filepath: string, content?: string): Promise<LoadedSlidevData> {
const markdown = content ?? await fs.readFile(filepath, 'utf-8')

let extensions: SlidevPreparserExtension[] | undefined
Expand Down Expand Up @@ -90,10 +96,8 @@ export async function load(userRoot: string, filepath: string, themeMeta?: Slide
return {
slides,
entry,
config: resolveConfig(headmatter, themeMeta, filepath),
headmatter,
features: detectFeatures(slides.map(s => s.source.raw).join('')),
themeMeta,
markdownFiles,
watchFiles: Object.keys(markdownFiles).map(slash),
}
Expand Down
69 changes: 22 additions & 47 deletions packages/slidev/node/addons.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,30 @@
import { resolve } from 'node:path'
import fs from 'fs-extra'
import { satisfies } from 'semver'
import type { SlidevConfig } from '@slidev/types'
import { version } from '../package.json'
import { packageExists, resolveImportPath } from './utils'
import { isPath } from './options'
import { createResolver, getRoots } from './resolver'
import { checkEngine } from './utils'

export async function getPackageJson(root: string): Promise<Record<string, any>> {
try {
const file = await resolveImportPath(`${root}/package.json`, true)
if (file && fs.existsSync(file))
return await fs.readJSON(file)
return {}
}
catch (e) {
return {}
}
}

export async function getAddons(userRoot: string, config: SlidevConfig): Promise<string[]> {
const { slidev = {} } = await getPackageJson(userRoot)
const configAddons = Array.isArray(config.addons) ? config.addons : []
const addons = configAddons.concat(Array.isArray(slidev?.addons) ? slidev.addons : [])
return (await getRecursivePlugins(await Promise.all(addons.map(resolvePluginName)), 3)).filter(Boolean)
}
export async function resolveAddons(addonsInConfig: string[]) {
const { userRoot, userPkgJson } = await getRoots()
const resolved: string[] = []

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

let addons = Array.isArray(slidev?.addons) ? slidev.addons : []
if (addons.length > 0 && depth)
addons = await getRecursivePlugins(addons.map(resolvePluginName), depth - 1)
addons.push(addon)
async function resolveAddon(name: string, parent: string) {
const [, pkgRoot] = await resolveAddonNameAndRoot(name, parent)
if (!pkgRoot)
return
resolved.push(pkgRoot)
const { slidev, engines } = await fs.readJSON(resolve(pkgRoot, 'package.json'))
checkEngine(name, engines)

return addons
}))
return addonsArray.flat()
}
if (Array.isArray(slidev?.addons))
await Promise.all(slidev.addons.map((addon: string) => resolveAddon(addon, pkgRoot)))
}

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

export async function resolvePluginName(name: string) {
if (!name)
return ''
if (isPath(name))
return name
if (await packageExists(`slidev-addon-${name}`))
return `slidev-addon-${name}`
return name
return resolved
}
50 changes: 24 additions & 26 deletions packages/slidev/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { BuildArgs } from '@slidev/types'
import { ViteSlidevPlugin } from './plugins/preset'
import { getIndexHtml, mergeViteConfigs } from './common'
import type { ResolvedSlidevOptions } from './options'
import { findPkgRoot } from './resolver'

export async function build(
options: ResolvedSlidevOptions,
Expand Down Expand Up @@ -64,34 +65,31 @@ export async function build(
}
else {
console.log(blue(' building for Monaco...\n'))

await viteBuild(
await mergeViteConfigs(
options,
inlineConfig,
<InlineConfig>({
root: join(options.clientRoot, 'iframes/monaco'),
base: `${config.base}iframes/monaco/`,
build: {
outDir: resolve(config.build.outDir, 'iframes/monaco'),
// fix for monaco workers
// https://github.com/vitejs/vite/issues/1927#issuecomment-805803918
rollupOptions: {
output: {
manualChunks: {
jsonWorker: ['monaco-editor/esm/vs/language/json/json.worker'],
cssWorker: ['monaco-editor/esm/vs/language/css/css.worker'],
htmlWorker: ['monaco-editor/esm/vs/language/html/html.worker'],
tsWorker: ['monaco-editor/esm/vs/language/typescript/ts.worker'],
editorWorker: ['monaco-editor/esm/vs/editor/editor.worker'],
},
},
const monacoRoot = await findPkgRoot('monaco-editor', options.clientRoot, true)
const getWorkerPath = (path: string) => [join(monacoRoot, 'esm/vs', path)]
await viteBuild({
root: join(options.clientRoot, 'iframes/monaco'),
base: `${config.base}iframes/monaco/`,
plugins: [
await ViteSlidevPlugin(options, inlineConfig.slidev || {}),
],
build: {
outDir: resolve(options.userRoot, config.build.outDir, 'iframes/monaco'),
// fix for monaco workers
// https://github.com/vitejs/vite/issues/1927#issuecomment-805803918
rollupOptions: {
output: {
manualChunks: {
jsonWorker: getWorkerPath('language/json/json.worker'),
cssWorker: getWorkerPath('language/css/css.worker'),
htmlWorker: getWorkerPath('language/html/html.worker'),
tsWorker: getWorkerPath('language/typescript/ts.worker'),
editorWorker: getWorkerPath('editor/editor.worker'),
},
},
}),
'build',
),
)
},
},
})
}
}
}
Expand Down
78 changes: 38 additions & 40 deletions packages/slidev/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,24 @@ import fs from 'fs-extra'
import openBrowser from 'open'
import type { Argv } from 'yargs'
import yargs from 'yargs'
import prompts from 'prompts'
import { blue, bold, cyan, dim, gray, green, underline, yellow } from 'kolorist'
import type { LogLevel, ViteDevServer } from 'vite'
import type { SlidevConfig, SlidevPreparserExtension } from '@slidev/types'
import type { SlidevConfig, SlidevData, SlidevPreparserExtension } from '@slidev/types'
import isInstalledGlobally from 'is-installed-globally'
import equal from 'fast-deep-equal'
import { verifyConfig } from '@slidev/parser'
import { injectPreparserExtensionLoader } from '@slidev/parser/fs'
import { uniq } from '@antfu/utils'
import { checkPort } from 'get-port-please'
import { version } from '../package.json'
import { createServer } from './server'
import type { ResolvedSlidevOptions } from './options'
import { getAddonRoots, getClientRoot, getThemeRoots, getUserRoot, isPath, resolveOptions } from './options'
import { resolveThemeName } from './themes'
import { resolveOptions } from './options'
import { getThemeMeta, resolveTheme } from './themes'
import { parser } from './parser'
import { loadSetups } from './plugins/setupNode'
import { getRoots } from './resolver'
import { resolveAddons } from './addons'

const CONFIG_RESTART_FIELDS: (keyof SlidevConfig)[] = [
'highlighter',
Expand All @@ -33,17 +35,19 @@ const CONFIG_RESTART_FIELDS: (keyof SlidevConfig)[] = [
'css',
'mdc',
'editor',
'theme',
]

injectPreparserExtensionLoader(async (headmatter?: Record<string, unknown>, filepath?: string) => {
const addons = headmatter?.addons as string[] ?? []
const roots = /* uniq */([
getUserRoot({}).userRoot,
...await getAddonRoots(addons, ''),
await getClientRoot(),
const { clientRoot, userRoot } = await getRoots()
const roots = uniq([
clientRoot,
userRoot,
...await resolveAddons(addons),
])
const mergeArrays = (a: SlidevPreparserExtension[], b: SlidevPreparserExtension[]) => a.concat(b)
return await loadSetups(roots, 'preparser.ts', { filepath, headmatter }, [], mergeArrays)
return await loadSetups(clientRoot, roots, 'preparser.ts', { filepath, headmatter }, [], mergeArrays)
})

const cli = yargs(process.argv.slice(2))
Expand Down Expand Up @@ -104,22 +108,6 @@ cli.command(
.strict()
.help(),
async ({ entry, theme, port: userPort, open, log, remote, tunnel, force, inspect, bind }) => {
if (!fs.existsSync(entry) && !entry.endsWith('.md'))
entry = `${entry}.md`

if (!fs.existsSync(entry)) {
const { create } = await prompts({
name: 'create',
type: 'confirm',
initial: 'Y',
message: `Entry file ${yellow(`"${entry}"`)} does not exist, do you want to create it?`,
})
if (create)
await fs.copyFile(new URL('../template.md', import.meta.url), entry)
else
process.exit(0)
}

let server: ViteDevServer | undefined
let port = 3030

Expand Down Expand Up @@ -148,15 +136,30 @@ cli.command(
logLevel: log as LogLevel,
},
{
async onDataReload(newData, data) {
if (!theme && await resolveThemeName(newData.config.theme) !== await resolveThemeName(data.config.theme)) {
async loadData() {
const { data: oldData, entry } = options
const loaded = await parser.load(options.userRoot, entry)

const themeRaw = theme || loaded.headmatter.theme as string || 'default'
if (options.themeRaw !== themeRaw) {
console.log(yellow('\n restarting on theme change\n'))
initServer()
return false
}
else if (CONFIG_RESTART_FIELDS.some(i => !equal(newData.config[i], data.config[i]))) {
// Because themeRaw is not changed, we don't resolve it again
const themeMeta = options.themeRoots[0] ? await getThemeMeta(themeRaw, options.themeRoots[0]) : undefined
const newData: SlidevData = {
...loaded,
themeMeta,
config: parser.resolveConfig(loaded.headmatter, themeMeta, entry),
}

if (CONFIG_RESTART_FIELDS.some(i => !equal(newData.config[i], oldData.config[i]))) {
console.log(yellow('\n restarting on config change\n'))
initServer()
return false
}
return newData
},
},
))
Expand Down Expand Up @@ -351,23 +354,18 @@ cli.command(
default: 'theme',
}),
async ({ entry, dir, theme: themeInput }) => {
const { userRoot } = getUserRoot({ entry })
const data = await parser.load(userRoot, entry)
const theme = await resolveThemeName(themeInput || data.config.theme)
if (theme === 'none') {
const roots = await getRoots()
const data = await parser.load(roots.userRoot, entry)
const themeRaw = themeInput || (data.headmatter.theme as string) || 'default'
if (themeRaw === 'none') {
console.error('Cannot eject theme "none"')
process.exit(1)
}
if (isPath(theme)) {
if ('/.'.includes(themeRaw[0]) || (themeRaw[0] !== '@' && themeRaw.includes('/'))) {
console.error('Theme is already ejected')
process.exit(1)
}
const roots = await getThemeRoots(theme, entry)
if (!roots.length) {
console.error(`Could not find theme "${theme}"`)
process.exit(1)
}
const root = roots[0]
const [name, root] = (await resolveTheme(themeRaw, entry)) as [string, string]

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

console.log(`Theme "${theme}" ejected successfully to "${dirPath}"`)
console.log(`Theme "${name}" ejected successfully to "${dirPath}"`)
},
)
},
Expand Down
19 changes: 5 additions & 14 deletions packages/slidev/node/common.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import { existsSync, promises as fs } from 'node:fs'
import { join } from 'node:path'
import { uniq } from '@antfu/utils'
import { loadConfigFromFile, mergeConfig, resolveConfig } from 'vite'
import type { ConfigEnv, InlineConfig } from 'vite'
import type { ResolvedSlidevOptions } from './options'
import { generateGoogleFontsUrl, toAtFS } from './utils'
import { generateGoogleFontsUrl } from './utils'
import { toAtFS } from './resolver'

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

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

const roots = uniq([
...themeRoots,
...addonRoots,
userRoot,
])

for (const root of roots) {
const path = join(root, 'index.html')
if (!existsSync(path))
Expand All @@ -45,7 +39,7 @@ export async function getIndexHtml({ clientRoot, themeRoots, addonRoots, data, u
}

export async function mergeViteConfigs(
{ addonRoots, themeRoots, entry }: ResolvedSlidevOptions,
{ roots, entry }: ResolvedSlidevOptions,
viteConfig: InlineConfig,
config: InlineConfig,
command: 'serve' | 'build',
Expand All @@ -55,10 +49,7 @@ export async function mergeViteConfigs(
command,
}
// Merge theme & addon configs
const files = uniq([
...themeRoots,
...addonRoots,
]).map(i => join(i, 'vite.config.ts'))
const files = roots.map(i => join(i, 'vite.config.ts'))

for await (const file of files) {
if (!existsSync(file))
Expand Down

0 comments on commit b10d4c0

Please sign in to comment.