methanol 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +203 -0
- package/README.md +58 -0
- package/banner.txt +6 -0
- package/bin/methanol.js +24 -0
- package/index.js +22 -0
- package/package.json +51 -9
- package/src/assets.js +30 -0
- package/src/build-system.js +200 -0
- package/src/components.js +145 -0
- package/src/config.js +396 -0
- package/src/dev-server.js +632 -0
- package/src/main.js +133 -0
- package/src/mdx.js +406 -0
- package/src/node-loader.js +88 -0
- package/src/pagefind.js +107 -0
- package/src/pages.js +771 -0
- package/src/preview-server.js +58 -0
- package/src/public-assets.js +73 -0
- package/src/register-loader.js +29 -0
- package/src/rehype-plugins/link-resolve.js +116 -0
- package/src/rehype-plugins/methanol-ctx.js +89 -0
- package/src/renderer.js +25 -0
- package/src/rewind.js +117 -0
- package/src/stage-logger.js +59 -0
- package/src/state.js +179 -0
- package/src/virtual-module/inject.js +30 -0
- package/src/virtual-module/loader.js +116 -0
- package/src/virtual-module/pagefind.js +108 -0
- package/src/vite-plugins.js +173 -0
- package/themes/default/components/ThemeAccentSwitch.client.jsx +95 -0
- package/themes/default/components/ThemeAccentSwitch.static.jsx +23 -0
- package/themes/default/components/ThemeColorSwitch.client.jsx +95 -0
- package/themes/default/components/ThemeColorSwitch.static.jsx +23 -0
- package/themes/default/components/ThemeSearchBox.client.jsx +324 -0
- package/themes/default/components/ThemeSearchBox.static.jsx +40 -0
- package/themes/default/components/ThemeToCContainer.client.jsx +154 -0
- package/themes/default/components/ThemeToCContainer.static.jsx +61 -0
- package/themes/default/components/pre.client.jsx +84 -0
- package/themes/default/components/pre.static.jsx +27 -0
- package/themes/default/heading.jsx +35 -0
- package/themes/default/index.js +41 -0
- package/themes/default/page.jsx +303 -0
- package/themes/default/pages/404.mdx +8 -0
- package/themes/default/pages/index.mdx +31 -0
- package/themes/default/public/favicon.png +0 -0
- package/themes/default/public/logo.png +0 -0
- package/themes/default/sources/prefetch.js +49 -0
- package/themes/default/sources/style.css +1660 -0
package/src/main.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* Copyright Yukino Song, SudoMaker Ltd.
|
|
2
|
+
*
|
|
3
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
4
|
+
* or more contributor license agreements. See the NOTICE file
|
|
5
|
+
* distributed with this work for additional information
|
|
6
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
7
|
+
* to you under the Apache License, Version 2.0 (the
|
|
8
|
+
* "License"); you may not use this file except in compliance
|
|
9
|
+
* with the License. You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing,
|
|
14
|
+
* software distributed under the License is distributed on an
|
|
15
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
16
|
+
* KIND, either express or implied. See the License for the
|
|
17
|
+
* specific language governing permissions and limitations
|
|
18
|
+
* under the License.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { loadUserConfig, applyConfig } from './config.js'
|
|
22
|
+
import { runViteDev } from './dev-server.js'
|
|
23
|
+
import { buildHtmlEntries, runViteBuild } from './build-system.js'
|
|
24
|
+
import { runPagefind } from './pagefind.js'
|
|
25
|
+
import { runVitePreview } from './preview-server.js'
|
|
26
|
+
import { cli, state } from './state.js'
|
|
27
|
+
import { HTMLRenderer } from './renderer.js'
|
|
28
|
+
import { readFile } from 'fs/promises'
|
|
29
|
+
|
|
30
|
+
const printBanner = async () => {
|
|
31
|
+
try {
|
|
32
|
+
const pkgUrl = new URL('../package.json', import.meta.url)
|
|
33
|
+
const raw = await readFile(pkgUrl, 'utf-8')
|
|
34
|
+
const pkg = JSON.parse(raw)
|
|
35
|
+
const version = pkg?.version ? `v${pkg.version}` : ''
|
|
36
|
+
const isTty = Boolean(process.stdout && process.stdout.isTTY)
|
|
37
|
+
const label = `Methanol ${version}`.trim()
|
|
38
|
+
if (isTty) {
|
|
39
|
+
const bannerUrl = new URL('../banner.txt', import.meta.url)
|
|
40
|
+
const banner = await readFile(bannerUrl, 'utf-8')
|
|
41
|
+
console.log(banner.trimEnd())
|
|
42
|
+
console.log(`\n\t${label}\n`)
|
|
43
|
+
} else {
|
|
44
|
+
console.log(label)
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
console.log('Methanol')
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const main = async () => {
|
|
52
|
+
await printBanner()
|
|
53
|
+
const command = cli.command
|
|
54
|
+
if (!command) {
|
|
55
|
+
cli.showHelp()
|
|
56
|
+
process.exit(1)
|
|
57
|
+
}
|
|
58
|
+
const normalizedCommand = command === 'preview' ? 'serve' : command
|
|
59
|
+
const isDev = normalizedCommand === 'dev'
|
|
60
|
+
const isPreview = normalizedCommand === 'serve'
|
|
61
|
+
const isBuild = normalizedCommand === 'build'
|
|
62
|
+
const mode = isDev ? 'development' : 'production'
|
|
63
|
+
const config = await loadUserConfig(mode, cli.CLI_CONFIG_PATH)
|
|
64
|
+
await applyConfig(config, mode)
|
|
65
|
+
const hookContext = {
|
|
66
|
+
mode,
|
|
67
|
+
root: state.ROOT_DIR,
|
|
68
|
+
command: normalizedCommand,
|
|
69
|
+
isDev,
|
|
70
|
+
isBuild,
|
|
71
|
+
isPreview,
|
|
72
|
+
HTMLRenderer,
|
|
73
|
+
site: {
|
|
74
|
+
name: state.SITE_NAME,
|
|
75
|
+
root: state.ROOT_DIR,
|
|
76
|
+
pagesDir: state.PAGES_DIR,
|
|
77
|
+
componentsDir: state.COMPONENTS_DIR,
|
|
78
|
+
publicDir: state.STATIC_DIR,
|
|
79
|
+
distDir: state.DIST_DIR,
|
|
80
|
+
mode: state.CURRENT_MODE,
|
|
81
|
+
pagefind: {
|
|
82
|
+
enabled: state.PAGEFIND_ENABLED,
|
|
83
|
+
options: state.PAGEFIND_OPTIONS || null,
|
|
84
|
+
build: state.PAGEFIND_BUILD || null
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
data: {}
|
|
88
|
+
}
|
|
89
|
+
const runHooks = async (hooks = [], extra = null) => {
|
|
90
|
+
const context = extra ? { ...hookContext, ...extra } : hookContext
|
|
91
|
+
for (const hook of hooks) {
|
|
92
|
+
await hook(context)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (isDev) {
|
|
96
|
+
await runHooks(state.USER_PRE_BUILD_HOOKS)
|
|
97
|
+
await runHooks(state.THEME_PRE_BUILD_HOOKS)
|
|
98
|
+
await runViteDev()
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
if (isPreview) {
|
|
102
|
+
await runVitePreview()
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
if (isBuild) {
|
|
106
|
+
await runHooks(state.USER_PRE_BUILD_HOOKS)
|
|
107
|
+
await runHooks(state.THEME_PRE_BUILD_HOOKS)
|
|
108
|
+
const { entry, htmlCache, pagesContext } = await buildHtmlEntries()
|
|
109
|
+
await runViteBuild(entry, htmlCache)
|
|
110
|
+
if (state.PAGEFIND_ENABLED) {
|
|
111
|
+
await runPagefind()
|
|
112
|
+
}
|
|
113
|
+
const postBuildContext = pagesContext
|
|
114
|
+
? {
|
|
115
|
+
pagesContext,
|
|
116
|
+
pages: pagesContext.pages,
|
|
117
|
+
pagesTree: pagesContext.pagesTree,
|
|
118
|
+
pagesByRoute: pagesContext.pagesByRoute,
|
|
119
|
+
site: pagesContext.site
|
|
120
|
+
}
|
|
121
|
+
: null
|
|
122
|
+
await runHooks(state.THEME_POST_BUILD_HOOKS, postBuildContext)
|
|
123
|
+
await runHooks(state.USER_POST_BUILD_HOOKS, postBuildContext)
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
cli.showHelp()
|
|
127
|
+
process.exit(1)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
main().catch((err) => {
|
|
131
|
+
console.error(err)
|
|
132
|
+
process.exit(1)
|
|
133
|
+
})
|
package/src/mdx.js
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/* Copyright Yukino Song, SudoMaker Ltd.
|
|
2
|
+
*
|
|
3
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
4
|
+
* or more contributor license agreements. See the NOTICE file
|
|
5
|
+
* distributed with this work for additional information
|
|
6
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
7
|
+
* to you under the Apache License, Version 2.0 (the
|
|
8
|
+
* "License"); you may not use this file except in compliance
|
|
9
|
+
* with the License. You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing,
|
|
14
|
+
* software distributed under the License is distributed on an
|
|
15
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
16
|
+
* KIND, either express or implied. See the License for the
|
|
17
|
+
* specific language governing permissions and limitations
|
|
18
|
+
* under the License.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { compile, run } from '@mdx-js/mdx'
|
|
22
|
+
import * as JSXFactory from 'refui/jsx-runtime'
|
|
23
|
+
import * as JSXDevFactory from 'refui/jsx-dev-runtime'
|
|
24
|
+
import rehypeSlug from 'rehype-slug'
|
|
25
|
+
import extractToc from '@stefanprobst/rehype-extract-toc'
|
|
26
|
+
import withTocExport from '@stefanprobst/rehype-extract-toc/mdx'
|
|
27
|
+
import rehypeStarryNight from 'rehype-starry-night'
|
|
28
|
+
import { HTMLRenderer } from './renderer.js'
|
|
29
|
+
import { signal, computed, read, Suspense, nextTick } from 'refui'
|
|
30
|
+
import { createPortal } from 'refui/extras'
|
|
31
|
+
import { pathToFileURL } from 'url'
|
|
32
|
+
import { existsSync } from 'fs'
|
|
33
|
+
import { resolve, dirname, basename, relative } from 'path'
|
|
34
|
+
import { state } from './state.js'
|
|
35
|
+
import { resolveUserMdxConfig } from './config.js'
|
|
36
|
+
import { methanolCtx } from './rehype-plugins/methanol-ctx.js'
|
|
37
|
+
import { linkResolve } from './rehype-plugins/link-resolve.js'
|
|
38
|
+
|
|
39
|
+
// Workaround for Vite: it doesn't support resolving module/virtual modules in script src in dev mode
|
|
40
|
+
const RWND_INJECT = HTMLRenderer.rawHTML`<script type="module" src="/.methanol_virtual_module/inject.js"></script>`
|
|
41
|
+
const RWND_FALLBACK = HTMLRenderer.rawHTML`<script>
|
|
42
|
+
if (!window.$$rwnd) {
|
|
43
|
+
const l = []
|
|
44
|
+
const r = function(k,i,p) {
|
|
45
|
+
l.push([k,i,p,document.currentScript])
|
|
46
|
+
}
|
|
47
|
+
r.$$loaded = l
|
|
48
|
+
window.$$rwnd = r
|
|
49
|
+
}
|
|
50
|
+
</script>`
|
|
51
|
+
|
|
52
|
+
let cachedHeadAssets = null
|
|
53
|
+
|
|
54
|
+
const resolveUserHeadAssets = () => {
|
|
55
|
+
if (cachedHeadAssets) {
|
|
56
|
+
return cachedHeadAssets
|
|
57
|
+
}
|
|
58
|
+
const assets = []
|
|
59
|
+
const pagesDir = state.PAGES_DIR
|
|
60
|
+
if (!pagesDir) return assets
|
|
61
|
+
if (existsSync(resolve(pagesDir, 'style.css'))) {
|
|
62
|
+
assets.push(HTMLRenderer.c('link', { rel: 'stylesheet', href: '/style.css' }))
|
|
63
|
+
}
|
|
64
|
+
if (existsSync(resolve(pagesDir, 'index.js'))) {
|
|
65
|
+
assets.push(HTMLRenderer.c('script', { type: 'module', src: '/index.js' }))
|
|
66
|
+
} else if (existsSync(resolve(pagesDir, 'index.ts'))) {
|
|
67
|
+
assets.push(HTMLRenderer.c('script', { type: 'module', src: '/index.ts' }))
|
|
68
|
+
}
|
|
69
|
+
if (state.CURRENT_MODE === 'production') {
|
|
70
|
+
cachedHeadAssets = assets
|
|
71
|
+
}
|
|
72
|
+
return assets
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const resolvePageAssetUrl = (page, filePath) => {
|
|
76
|
+
const root = page?.source === 'theme' && state.THEME_PAGES_DIR
|
|
77
|
+
? state.THEME_PAGES_DIR
|
|
78
|
+
: state.PAGES_DIR
|
|
79
|
+
if (!root) return null
|
|
80
|
+
const relPath = relative(root, filePath).replace(/\\/g, '/')
|
|
81
|
+
if (!relPath || relPath.startsWith('..')) return null
|
|
82
|
+
return `/${relPath}`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const resolvePageHeadAssets = (page) => {
|
|
86
|
+
if (!page?.filePath) return []
|
|
87
|
+
const baseDir = dirname(page.filePath)
|
|
88
|
+
const baseName = basename(page.filePath).replace(/\.(mdx|md)$/, '')
|
|
89
|
+
const pagesRoot = state.PAGES_DIR ? resolve(state.PAGES_DIR) : null
|
|
90
|
+
const isRootIndex =
|
|
91
|
+
pagesRoot && baseName === 'index' && resolve(baseDir) === pagesRoot && page.source !== 'theme'
|
|
92
|
+
const isRootStylePage =
|
|
93
|
+
pagesRoot && baseName === 'style' && resolve(baseDir) === pagesRoot && page.source !== 'theme'
|
|
94
|
+
const assets = []
|
|
95
|
+
const cssPath = resolve(baseDir, `${baseName}.css`)
|
|
96
|
+
if (existsSync(cssPath)) {
|
|
97
|
+
if (isRootStylePage) {
|
|
98
|
+
const rootStyle = resolve(pagesRoot, 'style.css')
|
|
99
|
+
if (cssPath === rootStyle) {
|
|
100
|
+
return assets
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const href = resolvePageAssetUrl(page, cssPath)
|
|
104
|
+
if (href) {
|
|
105
|
+
assets.push(HTMLRenderer.c('link', { rel: 'stylesheet', href }))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const scriptExtensions = ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts']
|
|
109
|
+
let scriptPath = null
|
|
110
|
+
for (const ext of scriptExtensions) {
|
|
111
|
+
const candidate = resolve(baseDir, `${baseName}${ext}`)
|
|
112
|
+
if (existsSync(candidate)) {
|
|
113
|
+
scriptPath = candidate
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (scriptPath) {
|
|
118
|
+
if (isRootIndex) {
|
|
119
|
+
const rootIndexJs = resolve(pagesRoot, 'index.js')
|
|
120
|
+
const rootIndexTs = resolve(pagesRoot, 'index.ts')
|
|
121
|
+
if (scriptPath === rootIndexJs || scriptPath === rootIndexTs) {
|
|
122
|
+
return assets
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const src = resolvePageAssetUrl(page, scriptPath)
|
|
126
|
+
if (src) {
|
|
127
|
+
assets.push(HTMLRenderer.c('script', { type: 'module', src }))
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return assets
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const buildPageContext = ({
|
|
134
|
+
routePath,
|
|
135
|
+
filePath,
|
|
136
|
+
pageMeta,
|
|
137
|
+
pagesContext,
|
|
138
|
+
lazyPagesTree = false
|
|
139
|
+
}) => {
|
|
140
|
+
const page = pageMeta
|
|
141
|
+
const language = pagesContext?.getLanguageForRoute ? pagesContext.getLanguageForRoute(routePath) : null
|
|
142
|
+
const getSiblings = pagesContext?.getSiblings
|
|
143
|
+
? () => pagesContext.getSiblings(routePath, page?.filePath || filePath)
|
|
144
|
+
: null
|
|
145
|
+
if (page && getSiblings && page.getSiblings !== getSiblings) {
|
|
146
|
+
page.getSiblings = getSiblings
|
|
147
|
+
}
|
|
148
|
+
const ctx = {
|
|
149
|
+
routePath,
|
|
150
|
+
filePath,
|
|
151
|
+
page,
|
|
152
|
+
pages: pagesContext?.pages || [],
|
|
153
|
+
pagesByRoute: pagesContext?.pagesByRoute || new Map(),
|
|
154
|
+
languages: pagesContext?.languages || [],
|
|
155
|
+
language,
|
|
156
|
+
site: pagesContext?.site || null,
|
|
157
|
+
getSiblings
|
|
158
|
+
}
|
|
159
|
+
const resolvePagesTree = () =>
|
|
160
|
+
pagesContext?.getPagesTree ? pagesContext.getPagesTree(routePath) : pagesContext?.pagesTree || []
|
|
161
|
+
if (lazyPagesTree) {
|
|
162
|
+
let cachedTree = null
|
|
163
|
+
let hasTree = false
|
|
164
|
+
Object.defineProperty(ctx, 'pagesTree', {
|
|
165
|
+
enumerable: true,
|
|
166
|
+
get() {
|
|
167
|
+
if (!hasTree) {
|
|
168
|
+
cachedTree = resolvePagesTree()
|
|
169
|
+
hasTree = true
|
|
170
|
+
}
|
|
171
|
+
return cachedTree
|
|
172
|
+
},
|
|
173
|
+
set(value) {
|
|
174
|
+
cachedTree = value
|
|
175
|
+
hasTree = true
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
} else {
|
|
179
|
+
ctx.pagesTree = resolvePagesTree()
|
|
180
|
+
}
|
|
181
|
+
return ctx
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const findTitleFromToc = (toc = []) => {
|
|
185
|
+
let minDepth = Infinity
|
|
186
|
+
const scanDepth = (items) => {
|
|
187
|
+
for (const item of items) {
|
|
188
|
+
if (typeof item?.depth === 'number') {
|
|
189
|
+
minDepth = Math.min(minDepth, item.depth)
|
|
190
|
+
}
|
|
191
|
+
if (item?.children?.length) {
|
|
192
|
+
scanDepth(item.children)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
scanDepth(toc)
|
|
197
|
+
if (!Number.isFinite(minDepth)) return null
|
|
198
|
+
let result = null
|
|
199
|
+
const findFirst = (items) => {
|
|
200
|
+
for (const item of items) {
|
|
201
|
+
if (item?.depth === minDepth && item?.value) {
|
|
202
|
+
result = item.value
|
|
203
|
+
return true
|
|
204
|
+
}
|
|
205
|
+
if (item?.children?.length && findFirst(item.children)) {
|
|
206
|
+
return true
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
findFirst(toc)
|
|
212
|
+
return result
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let cachedMdxConfig = null
|
|
216
|
+
|
|
217
|
+
const normalizeStarryNightConfig = (value) => {
|
|
218
|
+
if (value == null) return null
|
|
219
|
+
if (typeof value === 'boolean') {
|
|
220
|
+
return { enabled: value, options: null }
|
|
221
|
+
}
|
|
222
|
+
if (typeof value !== 'object') return null
|
|
223
|
+
const { enabled, options, ...rest } = value
|
|
224
|
+
if (enabled === false) return { enabled: false, options: null }
|
|
225
|
+
if (options && typeof options === 'object') {
|
|
226
|
+
return { enabled: true, options: { ...options } }
|
|
227
|
+
}
|
|
228
|
+
if (Object.keys(rest).length) {
|
|
229
|
+
return { enabled: true, options: { ...rest } }
|
|
230
|
+
}
|
|
231
|
+
return { enabled: true, options: null }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const resolveStarryNightForPage = (frontmatter) => {
|
|
235
|
+
const base = {
|
|
236
|
+
enabled: state.STARRY_NIGHT_ENABLED === true,
|
|
237
|
+
options: state.STARRY_NIGHT_OPTIONS || null
|
|
238
|
+
}
|
|
239
|
+
if (!frontmatter || !Object.prototype.hasOwnProperty.call(frontmatter, 'starryNight')) {
|
|
240
|
+
return base
|
|
241
|
+
}
|
|
242
|
+
const override = normalizeStarryNightConfig(frontmatter.starryNight)
|
|
243
|
+
if (!override) return base
|
|
244
|
+
if (override.enabled === false) return { enabled: false, options: null }
|
|
245
|
+
const options = override.options != null ? override.options : base.options
|
|
246
|
+
return { enabled: true, options }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const resolveBaseMdxConfig = async () => {
|
|
250
|
+
const userMdxConfig = await resolveUserMdxConfig()
|
|
251
|
+
if (cachedMdxConfig) {
|
|
252
|
+
return cachedMdxConfig
|
|
253
|
+
}
|
|
254
|
+
const baseMdxConfig = {
|
|
255
|
+
outputFormat: 'function-body',
|
|
256
|
+
jsxRuntime: 'automatic',
|
|
257
|
+
jsxImportSource: 'refui',
|
|
258
|
+
development: state.CURRENT_MODE !== 'production',
|
|
259
|
+
elementAttributeNameCase: 'html',
|
|
260
|
+
rehypePlugins: [rehypeSlug, extractToc, [withTocExport, { name: 'toc' }]]
|
|
261
|
+
}
|
|
262
|
+
const mdxConfig = { ...baseMdxConfig, ...userMdxConfig }
|
|
263
|
+
const userRehypePlugins = Array.isArray(userMdxConfig.rehypePlugins) ? userMdxConfig.rehypePlugins : []
|
|
264
|
+
mdxConfig.rehypePlugins = [...baseMdxConfig.rehypePlugins, ...userRehypePlugins]
|
|
265
|
+
mdxConfig.rehypePlugins.push(linkResolve)
|
|
266
|
+
mdxConfig.rehypePlugins.push(methanolCtx)
|
|
267
|
+
return (cachedMdxConfig = mdxConfig)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const resolveMdxConfigForPage = async (frontmatter) => {
|
|
271
|
+
const baseConfig = await resolveBaseMdxConfig()
|
|
272
|
+
const mdxConfig = {
|
|
273
|
+
...baseConfig,
|
|
274
|
+
rehypePlugins: [...baseConfig.rehypePlugins]
|
|
275
|
+
}
|
|
276
|
+
const starryNightConfig = resolveStarryNightForPage(frontmatter)
|
|
277
|
+
if (!starryNightConfig.enabled) return mdxConfig
|
|
278
|
+
const plugin = starryNightConfig.options ? [rehypeStarryNight, starryNightConfig.options] : [rehypeStarryNight]
|
|
279
|
+
const insertIndex = mdxConfig.rehypePlugins.indexOf(linkResolve)
|
|
280
|
+
if (insertIndex >= 0) {
|
|
281
|
+
mdxConfig.rehypePlugins.splice(insertIndex, 0, plugin)
|
|
282
|
+
} else {
|
|
283
|
+
mdxConfig.rehypePlugins.push(plugin)
|
|
284
|
+
}
|
|
285
|
+
return mdxConfig
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export const compileMdx = async ({ content, filePath, ctx }) => {
|
|
289
|
+
const mdxConfig = await resolveMdxConfigForPage(ctx?.page?.frontmatter)
|
|
290
|
+
const runtimeFactory = mdxConfig.development ? JSXDevFactory : JSXFactory
|
|
291
|
+
const compiled = await compile({ value: content, path: filePath }, mdxConfig)
|
|
292
|
+
|
|
293
|
+
return await run(compiled, {
|
|
294
|
+
...runtimeFactory,
|
|
295
|
+
baseUrl: pathToFileURL(filePath).href,
|
|
296
|
+
ctx,
|
|
297
|
+
rawHTML: HTMLRenderer.rawHTML
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export const compilePageMdx = async (page, pagesContext, options = {}) => {
|
|
302
|
+
if (!page || page.content == null || page.mdxComponent) return
|
|
303
|
+
const { ctx = null, lazyPagesTree = false, refreshPagesTree = true } = options || {}
|
|
304
|
+
const activeCtx =
|
|
305
|
+
ctx ||
|
|
306
|
+
buildPageContext({
|
|
307
|
+
routePath: page.routePath,
|
|
308
|
+
filePath: page.filePath,
|
|
309
|
+
pageMeta: page,
|
|
310
|
+
pagesContext,
|
|
311
|
+
lazyPagesTree
|
|
312
|
+
})
|
|
313
|
+
const mdxModule = await compileMdx({
|
|
314
|
+
content: page.content,
|
|
315
|
+
filePath: page.filePath,
|
|
316
|
+
ctx: activeCtx
|
|
317
|
+
})
|
|
318
|
+
page.mdxComponent = mdxModule.default
|
|
319
|
+
page.toc = mdxModule.toc
|
|
320
|
+
const shouldUseTocTitle = page.frontmatter?.title == null
|
|
321
|
+
if (shouldUseTocTitle) {
|
|
322
|
+
const nextTitle = findTitleFromToc(page.toc) || page.title
|
|
323
|
+
if (nextTitle !== page.title) {
|
|
324
|
+
page.title = nextTitle
|
|
325
|
+
if (typeof pagesContext?.refreshPagesTree === 'function') {
|
|
326
|
+
pagesContext.refreshPagesTree()
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (typeof pagesContext?.setDerivedTitle === 'function') {
|
|
331
|
+
pagesContext.setDerivedTitle(page.filePath, shouldUseTocTitle ? page.title : null, page.toc)
|
|
332
|
+
}
|
|
333
|
+
if (ctx && refreshPagesTree && pagesContext?.getPagesTree) {
|
|
334
|
+
ctx.pagesTree = pagesContext.getPagesTree(activeCtx.routePath)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export const renderHtml = async ({
|
|
339
|
+
routePath,
|
|
340
|
+
filePath,
|
|
341
|
+
components,
|
|
342
|
+
pagesContext,
|
|
343
|
+
pageMeta: explicitPageMeta = null
|
|
344
|
+
}) => {
|
|
345
|
+
const pageMeta =
|
|
346
|
+
explicitPageMeta ||
|
|
347
|
+
(pagesContext.getPageByRoute
|
|
348
|
+
? pagesContext.getPageByRoute(routePath, { filePath })
|
|
349
|
+
: pagesContext.pagesByRoute.get(routePath))
|
|
350
|
+
|
|
351
|
+
const ctx = buildPageContext({
|
|
352
|
+
routePath,
|
|
353
|
+
filePath,
|
|
354
|
+
pageMeta,
|
|
355
|
+
pagesContext
|
|
356
|
+
})
|
|
357
|
+
await compilePageMdx(pageMeta, pagesContext, { ctx })
|
|
358
|
+
|
|
359
|
+
const [Head, Outlet] = createPortal()
|
|
360
|
+
const ExtraHead = () => {
|
|
361
|
+
return [
|
|
362
|
+
RWND_INJECT,
|
|
363
|
+
...resolveUserHeadAssets(),
|
|
364
|
+
...resolvePageHeadAssets(pageMeta),
|
|
365
|
+
Outlet(),
|
|
366
|
+
RWND_FALLBACK
|
|
367
|
+
]
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const mdxComponent = pageMeta?.mdxComponent
|
|
371
|
+
|
|
372
|
+
const Page = ({ components: extraComponents, ...props }, ...children) =>
|
|
373
|
+
mdxComponent({
|
|
374
|
+
children,
|
|
375
|
+
...props,
|
|
376
|
+
components: {
|
|
377
|
+
...components,
|
|
378
|
+
...extraComponents,
|
|
379
|
+
head: Head,
|
|
380
|
+
Head
|
|
381
|
+
}
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
const template = state.USER_THEME.template
|
|
385
|
+
|
|
386
|
+
const renderResult = await new Promise((r) => {
|
|
387
|
+
const result = HTMLRenderer.c(
|
|
388
|
+
Suspense,
|
|
389
|
+
{
|
|
390
|
+
onLoad() {
|
|
391
|
+
nextTick(() => r(result))
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
() =>
|
|
395
|
+
template({
|
|
396
|
+
ctx,
|
|
397
|
+
Page,
|
|
398
|
+
ExtraHead,
|
|
399
|
+
HTMLRenderer,
|
|
400
|
+
components
|
|
401
|
+
})
|
|
402
|
+
)
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
return HTMLRenderer.serialize(renderResult)
|
|
406
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* Copyright Yukino Song, SudoMaker Ltd.
|
|
2
|
+
*
|
|
3
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
4
|
+
* or more contributor license agreements. See the NOTICE file
|
|
5
|
+
* distributed with this work for additional information
|
|
6
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
7
|
+
* to you under the Apache License, Version 2.0 (the
|
|
8
|
+
* "License"); you may not use this file except in compliance
|
|
9
|
+
* with the License. You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing,
|
|
14
|
+
* software distributed under the License is distributed on an
|
|
15
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
16
|
+
* KIND, either express or implied. See the License for the
|
|
17
|
+
* specific language governing permissions and limitations
|
|
18
|
+
* under the License.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { readFile } from 'node:fs/promises'
|
|
22
|
+
import { dirname, extname, join, resolve as pathResolve, relative } from 'node:path'
|
|
23
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
24
|
+
import { createRequire } from 'node:module'
|
|
25
|
+
import { transform } from 'esbuild'
|
|
26
|
+
|
|
27
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
28
|
+
const __dirname = dirname(__filename)
|
|
29
|
+
|
|
30
|
+
const projectRoot = pathResolve('.', '__virtual__.js')
|
|
31
|
+
const projectRootURL = pathToFileURL(projectRoot)
|
|
32
|
+
export const projectRequire = createRequire(projectRootURL)
|
|
33
|
+
|
|
34
|
+
const require = createRequire(import.meta.url)
|
|
35
|
+
|
|
36
|
+
const EXTS = new Set(['.jsx', '.tsx', '.ts', '.mts', '.cts'])
|
|
37
|
+
|
|
38
|
+
export async function load(url, context, nextLoad) {
|
|
39
|
+
if (url.startsWith('node:') || url.startsWith('data:')) {
|
|
40
|
+
return nextLoad(url, context, nextLoad)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const pathname = new URL(url).pathname
|
|
44
|
+
const ext = extname(pathname).toLowerCase()
|
|
45
|
+
if (!EXTS.has(ext)) {
|
|
46
|
+
return nextLoad(url, context, nextLoad)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const source = await readFile(fileURLToPath(url), 'utf-8')
|
|
50
|
+
const loader = ext === '.tsx' ? 'tsx' : ext === '.jsx' ? 'jsx' : 'ts'
|
|
51
|
+
const result = await transform(source, {
|
|
52
|
+
loader,
|
|
53
|
+
format: 'esm',
|
|
54
|
+
jsx: 'automatic',
|
|
55
|
+
jsxImportSource: 'refui',
|
|
56
|
+
sourcemap: 'inline',
|
|
57
|
+
sourcefile: fileURLToPath(url)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
format: 'module',
|
|
62
|
+
shortCircuit: true,
|
|
63
|
+
source: result.code
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const startPos = 'methanol'.length
|
|
68
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
69
|
+
if (specifier === 'refui' || specifier.startsWith('refui/')) {
|
|
70
|
+
try {
|
|
71
|
+
// Use user installed rEFui when possible
|
|
72
|
+
return await nextResolve(specifier, { ...context, parentURL: projectRootURL })
|
|
73
|
+
} catch (e) {
|
|
74
|
+
return await nextResolve(specifier, { ...context, parentURL: import.meta.url })
|
|
75
|
+
}
|
|
76
|
+
} else if (specifier === 'methanol' || specifier.startsWith('methanol/')) {
|
|
77
|
+
// Force only one Metnanol instance
|
|
78
|
+
const filePath = require.resolve('..' + specifier.slice(startPos))
|
|
79
|
+
return {
|
|
80
|
+
__proto__: null,
|
|
81
|
+
shortCircuit: true,
|
|
82
|
+
format: 'module',
|
|
83
|
+
url: pathToFileURL(filePath).href
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
return await nextResolve(specifier, context)
|
|
87
|
+
}
|
|
88
|
+
}
|