methanol 0.0.10 → 0.0.12
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/README.md +4 -1
- package/package.json +1 -1
- package/src/build-system.js +116 -41
- package/src/config.js +13 -2
- package/src/dev-server.js +155 -24
- package/src/logger.js +15 -3
- package/src/mdx.js +68 -39
- package/src/pages.js +224 -57
- package/src/state.js +18 -14
- package/src/workers/build-pool.js +156 -0
- package/src/workers/build-worker.js +203 -0
- package/src/workers/mdx-compile-worker.js +60 -0
- package/themes/default/src/nav-tree.jsx +8 -7
|
@@ -0,0 +1,203 @@
|
|
|
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 '../register-loader.js'
|
|
22
|
+
import { parentPort, workerData } from 'worker_threads'
|
|
23
|
+
import { style } from '../logger.js'
|
|
24
|
+
|
|
25
|
+
const { mode = 'production', configPath = null, command = 'build', cli: cliOverrides = null } =
|
|
26
|
+
workerData || {}
|
|
27
|
+
let initPromise = null
|
|
28
|
+
let pages = []
|
|
29
|
+
let pagesContext = null
|
|
30
|
+
let components = null
|
|
31
|
+
|
|
32
|
+
const ensureInit = async () => {
|
|
33
|
+
if (initPromise) return initPromise
|
|
34
|
+
initPromise = (async () => {
|
|
35
|
+
const { loadUserConfig, applyConfig, resolveUserViteConfig } = await import('../config.js')
|
|
36
|
+
const { buildComponentRegistry } = await import('../components.js')
|
|
37
|
+
const { state, cli } = await import('../state.js')
|
|
38
|
+
if (cliOverrides) {
|
|
39
|
+
Object.assign(cli, cliOverrides)
|
|
40
|
+
}
|
|
41
|
+
const config = await loadUserConfig(mode, configPath)
|
|
42
|
+
await applyConfig(config, mode)
|
|
43
|
+
await resolveUserViteConfig(command)
|
|
44
|
+
const themeComponentsDir = state.THEME_COMPONENTS_DIR
|
|
45
|
+
const themeEnv = state.THEME_ENV
|
|
46
|
+
const themeRegistry = themeComponentsDir
|
|
47
|
+
? await buildComponentRegistry({
|
|
48
|
+
componentsDir: themeComponentsDir,
|
|
49
|
+
client: themeEnv.client
|
|
50
|
+
})
|
|
51
|
+
: { components: {} }
|
|
52
|
+
const themeComponents = {
|
|
53
|
+
...(themeRegistry.components || {}),
|
|
54
|
+
...(state.USER_THEME.components || {})
|
|
55
|
+
}
|
|
56
|
+
const registry = await buildComponentRegistry()
|
|
57
|
+
components = {
|
|
58
|
+
...themeComponents,
|
|
59
|
+
...(registry.components || {})
|
|
60
|
+
}
|
|
61
|
+
})()
|
|
62
|
+
return initPromise
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const rebuildPagesContext = async (excludedRoutes, excludedDirs) => {
|
|
66
|
+
const { createPagesContextFromPages } = await import('../pages.js')
|
|
67
|
+
pagesContext = createPagesContextFromPages({
|
|
68
|
+
pages,
|
|
69
|
+
excludedRoutes,
|
|
70
|
+
excludedDirs
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const serializeError = (error) => {
|
|
75
|
+
if (!error) return 'Unknown error'
|
|
76
|
+
if (error.stack) return error.stack
|
|
77
|
+
if (error.message) return error.message
|
|
78
|
+
return String(error)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const logPageError = (phase, page, error) => {
|
|
82
|
+
const target = page?.path || page?.routePath || 'unknown file'
|
|
83
|
+
console.error(style.red(`\n\n[methanol] ${phase} error in ${target}`))
|
|
84
|
+
// Error is thrown so wo don't need to print here
|
|
85
|
+
// console.error(error?.stack || error)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const handleSetPages = async (message) => {
|
|
89
|
+
const { pages: nextPages, excludedRoutes = [], excludedDirs = [] } = message || {}
|
|
90
|
+
pages = Array.isArray(nextPages) ? nextPages : []
|
|
91
|
+
await rebuildPagesContext(new Set(excludedRoutes), new Set(excludedDirs))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const handleSyncUpdates = async (message) => {
|
|
95
|
+
const { updates = [], titles = null, excludedRoutes = null, excludedDirs = null } = message || {}
|
|
96
|
+
if (Array.isArray(titles)) {
|
|
97
|
+
for (let i = 0; i < titles.length; i += 1) {
|
|
98
|
+
const page = pages[i]
|
|
99
|
+
if (!page) continue
|
|
100
|
+
if (titles[i] !== undefined) {
|
|
101
|
+
page.title = titles[i]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const update of updates) {
|
|
106
|
+
const page = pages[update.id]
|
|
107
|
+
if (!page) continue
|
|
108
|
+
if (update.title !== undefined) page.title = update.title
|
|
109
|
+
if (update.toc !== undefined) page.toc = update.toc
|
|
110
|
+
}
|
|
111
|
+
await rebuildPagesContext(
|
|
112
|
+
excludedRoutes ? new Set(excludedRoutes) : pagesContext?.excludedRoutes || new Set(),
|
|
113
|
+
excludedDirs ? new Set(excludedDirs) : pagesContext?.excludedDirs || new Set()
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const handleCompile = async (message) => {
|
|
118
|
+
const { ids = [], stage } = message || {}
|
|
119
|
+
const { compilePageMdx } = await import('../mdx.js')
|
|
120
|
+
const updates = []
|
|
121
|
+
let completed = 0
|
|
122
|
+
for (const id of ids) {
|
|
123
|
+
const page = pages[id]
|
|
124
|
+
if (!page || page.content == null || page.mdxComponent) {
|
|
125
|
+
completed += 1
|
|
126
|
+
parentPort?.postMessage({ type: 'progress', stage, completed })
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
await compilePageMdx(page, pagesContext, {
|
|
131
|
+
lazyPagesTree: true,
|
|
132
|
+
refreshPagesTree: false
|
|
133
|
+
})
|
|
134
|
+
updates.push({ id, title: page.title, toc: page.toc || null })
|
|
135
|
+
} catch (error) {
|
|
136
|
+
logPageError('MDX compile', page, error)
|
|
137
|
+
throw error
|
|
138
|
+
}
|
|
139
|
+
completed += 1
|
|
140
|
+
parentPort?.postMessage({ type: 'progress', stage, completed })
|
|
141
|
+
}
|
|
142
|
+
return updates
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const handleRender = async (message) => {
|
|
146
|
+
const { ids = [], stage } = message || {}
|
|
147
|
+
const { renderHtml } = await import('../mdx.js')
|
|
148
|
+
const results = []
|
|
149
|
+
let completed = 0
|
|
150
|
+
for (const id of ids) {
|
|
151
|
+
const page = pages[id]
|
|
152
|
+
if (!page) {
|
|
153
|
+
completed += 1
|
|
154
|
+
parentPort?.postMessage({ type: 'progress', stage, completed })
|
|
155
|
+
continue
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const html = await renderHtml({
|
|
159
|
+
routePath: page.routePath,
|
|
160
|
+
path: page.path,
|
|
161
|
+
components,
|
|
162
|
+
pagesContext,
|
|
163
|
+
pageMeta: page
|
|
164
|
+
})
|
|
165
|
+
results.push({ id, html })
|
|
166
|
+
} catch (error) {
|
|
167
|
+
logPageError('MDX render', page, error)
|
|
168
|
+
throw error
|
|
169
|
+
}
|
|
170
|
+
completed += 1
|
|
171
|
+
parentPort?.postMessage({ type: 'progress', stage, completed })
|
|
172
|
+
}
|
|
173
|
+
return results
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
parentPort?.on('message', async (message) => {
|
|
177
|
+
const { type, stage } = message || {}
|
|
178
|
+
try {
|
|
179
|
+
await ensureInit()
|
|
180
|
+
if (type === 'setPages') {
|
|
181
|
+
await handleSetPages(message)
|
|
182
|
+
parentPort?.postMessage({ type: 'done', stage: 'setPages' })
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
if (type === 'sync') {
|
|
186
|
+
await handleSyncUpdates(message)
|
|
187
|
+
parentPort?.postMessage({ type: 'done', stage: 'sync' })
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
if (type === 'compile') {
|
|
191
|
+
const updates = await handleCompile(message)
|
|
192
|
+
parentPort?.postMessage({ type: 'done', stage, updates })
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
if (type === 'render') {
|
|
196
|
+
const results = await handleRender(message)
|
|
197
|
+
parentPort?.postMessage({ type: 'done', stage, results })
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
parentPort?.postMessage({ type: 'error', stage, error: serializeError(error) })
|
|
202
|
+
}
|
|
203
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
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 '../register-loader.js'
|
|
22
|
+
import { parentPort, workerData } from 'worker_threads'
|
|
23
|
+
|
|
24
|
+
const { mode = 'production', configPath = null, cli: cliOverrides = null } = workerData || {}
|
|
25
|
+
let initPromise = null
|
|
26
|
+
let compileMdxSource = null
|
|
27
|
+
|
|
28
|
+
const ensureInit = async () => {
|
|
29
|
+
if (initPromise) return initPromise
|
|
30
|
+
initPromise = (async () => {
|
|
31
|
+
const { loadUserConfig, applyConfig } = await import('../config.js')
|
|
32
|
+
const { cli } = await import('../state.js')
|
|
33
|
+
if (cliOverrides) {
|
|
34
|
+
Object.assign(cli, cliOverrides)
|
|
35
|
+
}
|
|
36
|
+
const mdx = await import('../mdx.js')
|
|
37
|
+
compileMdxSource = mdx.compileMdxSource
|
|
38
|
+
const config = await loadUserConfig(mode, configPath)
|
|
39
|
+
await applyConfig(config, mode)
|
|
40
|
+
})()
|
|
41
|
+
return initPromise
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const serializeError = (error) => {
|
|
45
|
+
if (!error) return 'Unknown error'
|
|
46
|
+
if (error.stack) return error.stack
|
|
47
|
+
if (error.message) return error.message
|
|
48
|
+
return String(error)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
parentPort?.on('message', async (message) => {
|
|
52
|
+
const { id, path, content, frontmatter } = message || {}
|
|
53
|
+
try {
|
|
54
|
+
await ensureInit()
|
|
55
|
+
const result = await compileMdxSource({ content, path, frontmatter })
|
|
56
|
+
parentPort?.postMessage({ id, result })
|
|
57
|
+
} catch (error) {
|
|
58
|
+
parentPort?.postMessage({ id, error: serializeError(error) })
|
|
59
|
+
}
|
|
60
|
+
})
|
|
@@ -30,10 +30,10 @@ const matchCurrentPath = onCondition(currentPath)
|
|
|
30
30
|
const toSignal = (i) => {
|
|
31
31
|
const clone = Object.assign(new NullProtoObj(), i)
|
|
32
32
|
|
|
33
|
-
let sig = navEntryMap.get(clone.
|
|
33
|
+
let sig = navEntryMap.get(clone.routePath)
|
|
34
34
|
if (!sig) {
|
|
35
35
|
sig = signal(clone)
|
|
36
|
-
navEntryMap.set(clone.
|
|
36
|
+
navEntryMap.set(clone.routePath, sig)
|
|
37
37
|
} else {
|
|
38
38
|
sig.value = clone
|
|
39
39
|
}
|
|
@@ -79,11 +79,12 @@ const NavTree = ({ nodes, depth }) => (
|
|
|
79
79
|
<li class={isActive.choose('is-active', null)}>
|
|
80
80
|
<details class="sidebar-collapsible" open={isOpen.choose(true, null)}>
|
|
81
81
|
<summary class="sb-dir-header">{header}</summary>
|
|
82
|
-
<If condition={() => children.value.length}>
|
|
83
|
-
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
<If condition={() => children.value.length}>
|
|
83
|
+
{() => (
|
|
84
|
+
<ul data-depth={depth + 1}>
|
|
85
|
+
<NavTree nodes={children} depth={depth + 1} />
|
|
86
|
+
</ul>
|
|
87
|
+
)}
|
|
87
88
|
</If>
|
|
88
89
|
</details>
|
|
89
90
|
</li>
|