methanol 0.0.20 → 0.0.22
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/bin/methanol.js +1 -2
- package/package.json +6 -4
- package/src/base.js +36 -0
- package/src/build-system.js +337 -113
- package/src/client/sw.js +227 -180
- package/src/client/virtual-module/pwa-inject.js +25 -3
- package/src/config.js +19 -35
- package/src/dev-server.js +4 -2
- package/src/entry.js +22 -0
- package/src/feed.js +3 -12
- package/src/html/build-html.js +221 -0
- package/src/html/utils.js +125 -0
- package/src/html/worker-html.js +591 -0
- package/src/main.js +97 -6
- package/src/mdx.js +35 -2
- package/src/pages-index.js +11 -3
- package/src/pages.js +26 -11
- package/src/pwa.js +240 -0
- package/src/state.js +15 -3
- package/src/utils.js +1 -1
- package/src/vite-plugins.js +6 -2
- package/src/workers/build-pool.js +1 -1
- package/src/workers/build-worker.js +157 -18
- package/src/workers/entry-build-worker.js +22 -0
- package/src/workers/entry-mdx-compile-worker.js +22 -0
- package/src/workers/mdx-compile-worker.js +0 -1
- package/themes/benchmark/README.md +5 -0
- package/themes/benchmark/index.js +33 -0
- package/themes/benchmark/src/page.jsx +25 -0
- package/themes/blog/src/page.jsx +0 -2
- package/themes/default/src/nav-tree.jsx +3 -2
- package/themes/default/src/page.jsx +0 -2
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
* under the License.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import '../register-loader.js'
|
|
22
21
|
import { parentPort, workerData } from 'worker_threads'
|
|
22
|
+
import { mkdir, writeFile, readFile, copyFile } from 'fs/promises'
|
|
23
|
+
import { resolve, join, dirname } from 'path'
|
|
23
24
|
import { style } from '../logger.js'
|
|
25
|
+
import { scanRenderedHtml, rewriteHtmlContent, resolveManifestEntry } from '../html/worker-html.js'
|
|
24
26
|
|
|
25
27
|
const { mode = 'production', configPath = null, command = 'build', cli: cliOverrides = null } =
|
|
26
28
|
workerData || {}
|
|
@@ -29,6 +31,7 @@ let pages = []
|
|
|
29
31
|
let pagesContext = null
|
|
30
32
|
let components = null
|
|
31
33
|
let mdxPageIds = new Set()
|
|
34
|
+
const parsedHtmlCache = new Map()
|
|
32
35
|
|
|
33
36
|
const ensureInit = async () => {
|
|
34
37
|
if (initPromise) return initPromise
|
|
@@ -117,6 +120,11 @@ const handleSetPages = async (message) => {
|
|
|
117
120
|
await rebuildPagesContext(new Set(excludedRoutes), new Set(excludedDirs))
|
|
118
121
|
}
|
|
119
122
|
|
|
123
|
+
const handleSetPagesLite = async (message) => {
|
|
124
|
+
const { pages: nextPages } = message || {}
|
|
125
|
+
pages = Array.isArray(nextPages) ? nextPages : []
|
|
126
|
+
}
|
|
127
|
+
|
|
120
128
|
const handleSyncUpdates = async (message) => {
|
|
121
129
|
const { updates = [], excludedRoutes = null, excludedDirs = null } = message || {}
|
|
122
130
|
for (const update of updates) {
|
|
@@ -168,9 +176,17 @@ const handleCompile = async (message) => {
|
|
|
168
176
|
return updates
|
|
169
177
|
}
|
|
170
178
|
|
|
179
|
+
const MAX_PENDING_WRITES = 32
|
|
180
|
+
|
|
171
181
|
const handleRender = async (message) => {
|
|
172
|
-
const { ids = [], stage } = message || {}
|
|
173
|
-
const { renderHtml } = await import('../mdx.js')
|
|
182
|
+
const { ids = [], stage, feedIds = [], htmlStageDir = null, writeConcurrency = null } = message || {}
|
|
183
|
+
const { renderHtml, renderPageContent } = await import('../mdx.js')
|
|
184
|
+
const feedSet = new Set(Array.isArray(feedIds) ? feedIds : [])
|
|
185
|
+
const writeLimit =
|
|
186
|
+
typeof writeConcurrency === 'number' && Number.isFinite(writeConcurrency)
|
|
187
|
+
? Math.max(1, Math.floor(writeConcurrency))
|
|
188
|
+
: MAX_PENDING_WRITES
|
|
189
|
+
const pendingWrites = []
|
|
174
190
|
let completed = 0
|
|
175
191
|
for (const id of ids) {
|
|
176
192
|
const page = pages[id]
|
|
@@ -187,7 +203,57 @@ const handleRender = async (message) => {
|
|
|
187
203
|
pagesContext,
|
|
188
204
|
pageMeta: page
|
|
189
205
|
})
|
|
190
|
-
|
|
206
|
+
let outputHtml = html
|
|
207
|
+
let scan = null
|
|
208
|
+
let feedContent = null
|
|
209
|
+
if (feedSet.has(id)) {
|
|
210
|
+
feedContent = await renderPageContent({
|
|
211
|
+
routePath: page.routePath,
|
|
212
|
+
path: page.path,
|
|
213
|
+
components,
|
|
214
|
+
pagesContext,
|
|
215
|
+
pageMeta: page
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
let stagePath = null
|
|
219
|
+
if (htmlStageDir) {
|
|
220
|
+
const scanned = await scanRenderedHtml(outputHtml, page.routePath)
|
|
221
|
+
outputHtml = scanned.html
|
|
222
|
+
scan = scanned.scan
|
|
223
|
+
const hasResources =
|
|
224
|
+
scan.scripts.length > 0 || scan.styles.length > 0 || scan.assets.length > 0
|
|
225
|
+
if (hasResources) {
|
|
226
|
+
parsedHtmlCache.set(id, scanned.plan)
|
|
227
|
+
}
|
|
228
|
+
const name = resolveOutputName(page)
|
|
229
|
+
stagePath = resolve(htmlStageDir, `${name}.html`)
|
|
230
|
+
pendingWrites.push(
|
|
231
|
+
(async () => {
|
|
232
|
+
await mkdir(dirname(stagePath), { recursive: true })
|
|
233
|
+
await writeFile(stagePath, outputHtml)
|
|
234
|
+
})()
|
|
235
|
+
)
|
|
236
|
+
if (pendingWrites.length >= writeLimit) {
|
|
237
|
+
const results = await Promise.allSettled(pendingWrites)
|
|
238
|
+
pendingWrites.length = 0
|
|
239
|
+
const failed = results.find((result) => result.status === 'rejected')
|
|
240
|
+
if (failed) {
|
|
241
|
+
throw failed.reason
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
page.mdxComponent = null
|
|
246
|
+
parentPort?.postMessage({
|
|
247
|
+
type: 'result',
|
|
248
|
+
stage,
|
|
249
|
+
result: {
|
|
250
|
+
id,
|
|
251
|
+
html: htmlStageDir ? null : outputHtml,
|
|
252
|
+
stagePath,
|
|
253
|
+
feedContent,
|
|
254
|
+
scan
|
|
255
|
+
}
|
|
256
|
+
})
|
|
191
257
|
} catch (error) {
|
|
192
258
|
logPageError('MDX render', page, error)
|
|
193
259
|
throw error
|
|
@@ -195,11 +261,55 @@ const handleRender = async (message) => {
|
|
|
195
261
|
completed += 1
|
|
196
262
|
parentPort?.postMessage({ type: 'progress', stage, completed })
|
|
197
263
|
}
|
|
264
|
+
if (pendingWrites.length) {
|
|
265
|
+
const results = await Promise.allSettled(pendingWrites)
|
|
266
|
+
const failed = results.find((result) => result.status === 'rejected')
|
|
267
|
+
if (failed) {
|
|
268
|
+
throw failed.reason
|
|
269
|
+
}
|
|
270
|
+
}
|
|
198
271
|
}
|
|
199
272
|
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
273
|
+
const resolveOutputName = (page) => {
|
|
274
|
+
if (!page) return 'index'
|
|
275
|
+
if (page.routePath === '/') return 'index'
|
|
276
|
+
if (page.isIndex && page.dir) {
|
|
277
|
+
return join(page.dir, 'index').replace(/\\/g, '/')
|
|
278
|
+
}
|
|
279
|
+
return page.routePath.slice(1)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const handleRewrite = async (message) => {
|
|
283
|
+
const {
|
|
284
|
+
ids = [],
|
|
285
|
+
stage,
|
|
286
|
+
htmlStageDir,
|
|
287
|
+
manifest,
|
|
288
|
+
entryModules = [],
|
|
289
|
+
commonScripts = [],
|
|
290
|
+
commonEntry = null,
|
|
291
|
+
scans = {}
|
|
292
|
+
} = message || {}
|
|
293
|
+
const { state } = await import('../state.js')
|
|
294
|
+
const { resolveBasePrefix } = await import('../config.js')
|
|
295
|
+
const basePrefix = resolveBasePrefix()
|
|
296
|
+
const scriptMap = new Map()
|
|
297
|
+
const styleMap = new Map()
|
|
298
|
+
for (const entry of entryModules) {
|
|
299
|
+
if (!entry?.publicPath || !entry?.manifestKey) continue
|
|
300
|
+
const manifestEntry = resolveManifestEntry(manifest, entry.manifestKey)
|
|
301
|
+
if (!manifestEntry?.file) continue
|
|
302
|
+
if (entry.kind === 'script') {
|
|
303
|
+
scriptMap.set(entry.publicPath, { file: manifestEntry.file, css: manifestEntry.css || null })
|
|
304
|
+
}
|
|
305
|
+
if (entry.kind === 'style') {
|
|
306
|
+
const cssFile = manifestEntry.css?.[0] || (manifestEntry.file.endsWith('.css') ? manifestEntry.file : null)
|
|
307
|
+
if (cssFile) {
|
|
308
|
+
styleMap.set(entry.publicPath, { file: cssFile, css: manifestEntry.css || null })
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const commonSet = new Set(commonScripts || [])
|
|
203
313
|
let completed = 0
|
|
204
314
|
for (const id of ids) {
|
|
205
315
|
const page = pages[id]
|
|
@@ -209,16 +319,40 @@ const handleRss = async (message) => {
|
|
|
209
319
|
continue
|
|
210
320
|
}
|
|
211
321
|
try {
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
322
|
+
const name = resolveOutputName(page)
|
|
323
|
+
const stagePath = htmlStageDir ? resolve(htmlStageDir, `${name}.html`) : null
|
|
324
|
+
const distPath = resolve(state.DIST_DIR, `${name}.html`)
|
|
325
|
+
await mkdir(dirname(distPath), { recursive: true })
|
|
326
|
+
const scan = scans?.[id] || null
|
|
327
|
+
if (scan && Array.isArray(scan.scripts) && Array.isArray(scan.styles) && Array.isArray(scan.assets)) {
|
|
328
|
+
if (scan.scripts.length === 0 && scan.styles.length === 0 && scan.assets.length === 0) {
|
|
329
|
+
await copyFile(stagePath, distPath)
|
|
330
|
+
completed += 1
|
|
331
|
+
parentPort?.postMessage({ type: 'progress', stage, completed })
|
|
332
|
+
continue
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const plan = parsedHtmlCache.get(id)
|
|
336
|
+
const html = await readFile(stagePath, 'utf-8')
|
|
337
|
+
if (html == null) {
|
|
338
|
+
throw new Error('HTML content not available for rewrite')
|
|
339
|
+
}
|
|
340
|
+
const output = rewriteHtmlContent(
|
|
341
|
+
html,
|
|
342
|
+
plan,
|
|
343
|
+
page.routePath,
|
|
344
|
+
basePrefix,
|
|
345
|
+
manifest,
|
|
346
|
+
scriptMap,
|
|
347
|
+
styleMap,
|
|
348
|
+
commonSet,
|
|
349
|
+
commonEntry
|
|
350
|
+
)
|
|
351
|
+
parsedHtmlCache.delete(id)
|
|
352
|
+
await writeFile(distPath, output)
|
|
353
|
+
parentPort?.postMessage({ type: 'result', stage, result: { id } })
|
|
220
354
|
} catch (error) {
|
|
221
|
-
logPageError('
|
|
355
|
+
logPageError('HTML rewrite', page, error)
|
|
222
356
|
throw error
|
|
223
357
|
}
|
|
224
358
|
completed += 1
|
|
@@ -235,6 +369,11 @@ parentPort?.on('message', async (message) => {
|
|
|
235
369
|
parentPort?.postMessage({ type: 'done', stage: 'setPages' })
|
|
236
370
|
return
|
|
237
371
|
}
|
|
372
|
+
if (type === 'setPagesLite') {
|
|
373
|
+
await handleSetPagesLite(message)
|
|
374
|
+
parentPort?.postMessage({ type: 'done', stage: 'setPagesLite' })
|
|
375
|
+
return
|
|
376
|
+
}
|
|
238
377
|
if (type === 'sync') {
|
|
239
378
|
await handleSyncUpdates(message)
|
|
240
379
|
parentPort?.postMessage({ type: 'done', stage: 'sync' })
|
|
@@ -250,8 +389,8 @@ parentPort?.on('message', async (message) => {
|
|
|
250
389
|
parentPort?.postMessage({ type: 'done', stage })
|
|
251
390
|
return
|
|
252
391
|
}
|
|
253
|
-
if (type === '
|
|
254
|
-
await
|
|
392
|
+
if (type === 'rewrite') {
|
|
393
|
+
await handleRewrite(message)
|
|
255
394
|
parentPort?.postMessage({ type: 'done', stage })
|
|
256
395
|
return
|
|
257
396
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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('./build-worker.js')
|
|
@@ -0,0 +1,22 @@
|
|
|
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('./mdx-compile-worker.js')
|
|
@@ -0,0 +1,33 @@
|
|
|
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 { fileURLToPath } from 'url'
|
|
22
|
+
import { dirname } from 'path'
|
|
23
|
+
import PAGE_TEMPLATE from './src/page.jsx'
|
|
24
|
+
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
26
|
+
const __dirname = dirname(__filename)
|
|
27
|
+
|
|
28
|
+
export default () => {
|
|
29
|
+
return {
|
|
30
|
+
root: __dirname,
|
|
31
|
+
template: PAGE_TEMPLATE
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
const PAGE_TEMPLATE = ({ PageContent }) => {
|
|
22
|
+
return <PageContent />
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default PAGE_TEMPLATE
|
package/themes/blog/src/page.jsx
CHANGED
|
@@ -128,7 +128,6 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx, withBase }) =>
|
|
|
128
128
|
const pagefindOptions = ctx.site.pagefind?.options || null
|
|
129
129
|
const feedInfo = ctx.site.feed
|
|
130
130
|
const rssHref = feedInfo?.enabled ? feedInfo.href : null
|
|
131
|
-
const feedType = feedInfo?.atom ? 'application/atom+xml' : 'application/rss+xml'
|
|
132
131
|
const feedLabel = feedInfo?.atom ? 'Atom' : 'RSS'
|
|
133
132
|
|
|
134
133
|
return (
|
|
@@ -142,7 +141,6 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx, withBase }) =>
|
|
|
142
141
|
{title} | {siteName}
|
|
143
142
|
</title>
|
|
144
143
|
<link rel="stylesheet" href="/.methanol_theme_blog/style.css" />
|
|
145
|
-
{rssHref ? <link rel="alternate" type={feedType} title={`${siteName} ${feedLabel}`} href={rssHref} /> : null}
|
|
146
144
|
<ExtraHead />
|
|
147
145
|
</head>
|
|
148
146
|
<body>
|
|
@@ -118,11 +118,12 @@ const NavTree = ({ nodes, depth }) => {
|
|
|
118
118
|
)
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
const
|
|
121
|
+
const _rootNodes = signal()
|
|
122
|
+
const rootNodes = signal(_rootNodes, (nodes) => nodes?.map(toSignal))
|
|
122
123
|
const rootTree = HTMLRenderer.createElement(NavTree, { nodes: rootNodes, depth: 0 })
|
|
123
124
|
|
|
124
125
|
export const renderNavTree = (nodes, path) => {
|
|
125
126
|
currentPath.value = path
|
|
126
|
-
|
|
127
|
+
_rootNodes.value = nodes
|
|
127
128
|
return rootTree
|
|
128
129
|
}
|
|
@@ -63,7 +63,6 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx }) => {
|
|
|
63
63
|
const pagefindOptions = ctx.site.pagefind?.options || null
|
|
64
64
|
const feedInfo = ctx.site.feed
|
|
65
65
|
const rssHref = feedInfo?.enabled ? feedInfo.href : null
|
|
66
|
-
const feedType = feedInfo?.atom ? 'application/atom+xml' : 'application/rss+xml'
|
|
67
66
|
const feedLabel = feedInfo?.atom ? 'Atom' : 'RSS'
|
|
68
67
|
const repoBase = ctx.site.repoBase
|
|
69
68
|
const sourceUrl = pageFrontmatter.sourceURL
|
|
@@ -128,7 +127,6 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx }) => {
|
|
|
128
127
|
{twitterTitle ? <meta name="twitter:title" content={twitterTitle} /> : null}
|
|
129
128
|
{twitterDescription ? <meta name="twitter:description" content={twitterDescription} /> : null}
|
|
130
129
|
{twitterImage ? <meta name="twitter:image" content={twitterImage} /> : null}
|
|
131
|
-
{rssHref ? <link rel="alternate" type={feedType} title={`${siteName} ${feedLabel}`} href={rssHref} /> : null}
|
|
132
130
|
<link
|
|
133
131
|
rel="preload stylesheet"
|
|
134
132
|
as="style"
|