methanol 0.0.21 → 0.0.23
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 +319 -69
- package/src/client/sw.js +227 -180
- package/src/client/virtual-module/pwa-inject.js +25 -3
- package/src/config.js +16 -35
- package/src/dev-server.js +4 -2
- package/src/entry.js +22 -0
- package/src/html/build-html.js +221 -0
- package/src/html/utils.js +125 -0
- package/src/html/worker-html.js +594 -0
- package/src/main.js +97 -6
- package/src/mdx.js +33 -1
- package/src/pages-index.js +1 -2
- package/src/pages.js +26 -11
- package/src/pwa.js +240 -0
- package/src/state.js +6 -0
- 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 +165 -3
- 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/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,10 +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, feedIds = [] } = message || {}
|
|
182
|
+
const { ids = [], stage, feedIds = [], htmlStageDir = null, writeConcurrency = null } = message || {}
|
|
173
183
|
const { renderHtml, renderPageContent } = await import('../mdx.js')
|
|
174
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 = []
|
|
175
190
|
let completed = 0
|
|
176
191
|
for (const id of ids) {
|
|
177
192
|
const page = pages[id]
|
|
@@ -188,6 +203,8 @@ const handleRender = async (message) => {
|
|
|
188
203
|
pagesContext,
|
|
189
204
|
pageMeta: page
|
|
190
205
|
})
|
|
206
|
+
let outputHtml = html
|
|
207
|
+
let scan = null
|
|
191
208
|
let feedContent = null
|
|
192
209
|
if (feedSet.has(id)) {
|
|
193
210
|
feedContent = await renderPageContent({
|
|
@@ -198,7 +215,45 @@ const handleRender = async (message) => {
|
|
|
198
215
|
pageMeta: page
|
|
199
216
|
})
|
|
200
217
|
}
|
|
201
|
-
|
|
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
|
+
})
|
|
202
257
|
} catch (error) {
|
|
203
258
|
logPageError('MDX render', page, error)
|
|
204
259
|
throw error
|
|
@@ -206,6 +261,103 @@ const handleRender = async (message) => {
|
|
|
206
261
|
completed += 1
|
|
207
262
|
parentPort?.postMessage({ type: 'progress', stage, completed })
|
|
208
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
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
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 || [])
|
|
313
|
+
let completed = 0
|
|
314
|
+
for (const id of ids) {
|
|
315
|
+
const page = pages[id]
|
|
316
|
+
if (!page) {
|
|
317
|
+
completed += 1
|
|
318
|
+
parentPort?.postMessage({ type: 'progress', stage, completed })
|
|
319
|
+
continue
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
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 } })
|
|
354
|
+
} catch (error) {
|
|
355
|
+
logPageError('HTML rewrite', page, error)
|
|
356
|
+
throw error
|
|
357
|
+
}
|
|
358
|
+
completed += 1
|
|
359
|
+
parentPort?.postMessage({ type: 'progress', stage, completed })
|
|
360
|
+
}
|
|
209
361
|
}
|
|
210
362
|
|
|
211
363
|
parentPort?.on('message', async (message) => {
|
|
@@ -217,6 +369,11 @@ parentPort?.on('message', async (message) => {
|
|
|
217
369
|
parentPort?.postMessage({ type: 'done', stage: 'setPages' })
|
|
218
370
|
return
|
|
219
371
|
}
|
|
372
|
+
if (type === 'setPagesLite') {
|
|
373
|
+
await handleSetPagesLite(message)
|
|
374
|
+
parentPort?.postMessage({ type: 'done', stage: 'setPagesLite' })
|
|
375
|
+
return
|
|
376
|
+
}
|
|
220
377
|
if (type === 'sync') {
|
|
221
378
|
await handleSyncUpdates(message)
|
|
222
379
|
parentPort?.postMessage({ type: 'done', stage: 'sync' })
|
|
@@ -232,6 +389,11 @@ parentPort?.on('message', async (message) => {
|
|
|
232
389
|
parentPort?.postMessage({ type: 'done', stage })
|
|
233
390
|
return
|
|
234
391
|
}
|
|
392
|
+
if (type === 'rewrite') {
|
|
393
|
+
await handleRewrite(message)
|
|
394
|
+
parentPort?.postMessage({ type: 'done', stage })
|
|
395
|
+
return
|
|
396
|
+
}
|
|
235
397
|
} catch (error) {
|
|
236
398
|
parentPort?.postMessage({ type: 'error', stage, error: serializeError(error) })
|
|
237
399
|
}
|
|
@@ -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>
|
|
@@ -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"
|