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
|
@@ -0,0 +1,58 @@
|
|
|
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 { existsSync } from 'fs'
|
|
22
|
+
import { resolve } from 'path'
|
|
23
|
+
import { mergeConfig, preview as vitePreview } from 'vite'
|
|
24
|
+
import { state, cli } from './state.js'
|
|
25
|
+
import { resolveUserViteConfig } from './config.js'
|
|
26
|
+
import { methanolPreviewRoutingPlugin } from './vite-plugins.js'
|
|
27
|
+
|
|
28
|
+
export const runVitePreview = async () => {
|
|
29
|
+
const baseConfig = {
|
|
30
|
+
configFile: false,
|
|
31
|
+
root: state.PAGES_DIR,
|
|
32
|
+
base: './',
|
|
33
|
+
build: {
|
|
34
|
+
outDir: state.DIST_DIR
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const userConfig = await resolveUserViteConfig('preview')
|
|
38
|
+
const finalConfig = userConfig ? mergeConfig(baseConfig, userConfig) : baseConfig
|
|
39
|
+
if (cli.CLI_PORT != null) {
|
|
40
|
+
finalConfig.preview = { ...(finalConfig.preview || {}), port: cli.CLI_PORT }
|
|
41
|
+
}
|
|
42
|
+
if (cli.CLI_HOST !== null) {
|
|
43
|
+
finalConfig.preview = { ...(finalConfig.preview || {}), host: cli.CLI_HOST }
|
|
44
|
+
}
|
|
45
|
+
const outDir = finalConfig.build?.outDir || state.DIST_DIR
|
|
46
|
+
const distDir = resolve(state.ROOT_DIR, outDir)
|
|
47
|
+
const notFoundPath = resolve(distDir, '404.html')
|
|
48
|
+
const previewPlugins = Array.isArray(finalConfig.plugins) ? [...finalConfig.plugins] : []
|
|
49
|
+
previewPlugins.push(methanolPreviewRoutingPlugin(distDir, notFoundPath))
|
|
50
|
+
finalConfig.plugins = previewPlugins
|
|
51
|
+
if (!existsSync(distDir)) {
|
|
52
|
+
console.error(`Dist directory not found: ${distDir}`)
|
|
53
|
+
console.error('Run a production build before previewing.')
|
|
54
|
+
process.exit(1)
|
|
55
|
+
}
|
|
56
|
+
const server = await vitePreview(finalConfig)
|
|
57
|
+
server.printUrls()
|
|
58
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
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 { readdir, stat, copyFile, mkdir } from 'fs/promises'
|
|
22
|
+
import { existsSync } from 'fs'
|
|
23
|
+
import { resolve, dirname, relative } from 'path'
|
|
24
|
+
|
|
25
|
+
const ensureDir = async (dir) => {
|
|
26
|
+
await mkdir(dir, { recursive: true })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const copyDir = async (sourceDir, targetDir, onFile) => {
|
|
30
|
+
await ensureDir(targetDir)
|
|
31
|
+
const entries = await readdir(sourceDir)
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
if (entry.startsWith('.')) {
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
const sourcePath = resolve(sourceDir, entry)
|
|
37
|
+
const targetPath = resolve(targetDir, entry)
|
|
38
|
+
const stats = await stat(sourcePath)
|
|
39
|
+
if (stats.isDirectory()) {
|
|
40
|
+
await copyDir(sourcePath, targetPath, onFile)
|
|
41
|
+
} else {
|
|
42
|
+
if (existsSync(targetPath)) {
|
|
43
|
+
if (onFile) {
|
|
44
|
+
onFile(sourcePath, targetPath, { skipped: true })
|
|
45
|
+
}
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
await ensureDir(dirname(targetPath))
|
|
49
|
+
await copyFile(sourcePath, targetPath)
|
|
50
|
+
if (onFile) {
|
|
51
|
+
onFile(sourcePath, targetPath, { skipped: false })
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const copyPublicDir = async ({ sourceDir, targetDir, label = 'public' }) => {
|
|
58
|
+
if (!sourceDir || !targetDir) return
|
|
59
|
+
if (!existsSync(sourceDir)) return
|
|
60
|
+
const resolvedSource = resolve(sourceDir)
|
|
61
|
+
const resolvedTarget = resolve(targetDir)
|
|
62
|
+
if (resolvedSource === resolvedTarget) return
|
|
63
|
+
const created = !existsSync(resolvedTarget)
|
|
64
|
+
await ensureDir(resolvedTarget)
|
|
65
|
+
if (created) {
|
|
66
|
+
console.log(`Methanol: created ${label} directory`)
|
|
67
|
+
}
|
|
68
|
+
await copyDir(resolvedSource, resolvedTarget, (sourcePath, targetPath, info) => {
|
|
69
|
+
const rel = relative(resolvedSource, sourcePath).replace(/\\/g, '/')
|
|
70
|
+
if (info?.skipped) return
|
|
71
|
+
console.log(`Methanol: copied ${label}/${rel}`)
|
|
72
|
+
})
|
|
73
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
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 'node:url'
|
|
22
|
+
import { dirname, resolve } from 'node:path'
|
|
23
|
+
import { register } from 'node:module'
|
|
24
|
+
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
26
|
+
const __dirname = dirname(__filename)
|
|
27
|
+
const loaderPath = resolve(__dirname, '../src/node-loader.js')
|
|
28
|
+
|
|
29
|
+
register(loaderPath, import.meta.url)
|
|
@@ -0,0 +1,116 @@
|
|
|
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 { existsSync } from 'fs'
|
|
22
|
+
import { dirname, resolve, relative, isAbsolute } from 'path'
|
|
23
|
+
import { isElement } from 'hast-util-is-element'
|
|
24
|
+
import { visit } from 'unist-util-visit'
|
|
25
|
+
import { state } from '../state.js'
|
|
26
|
+
|
|
27
|
+
const extensionRegex = /\.(mdx?|html)$/i
|
|
28
|
+
|
|
29
|
+
const isRelativeHref = (href) => {
|
|
30
|
+
if (typeof href !== 'string') return false
|
|
31
|
+
if (!href) return false
|
|
32
|
+
if (href.startsWith('#') || href.startsWith('?') || href.startsWith('/')) return false
|
|
33
|
+
if (href.startsWith('//')) return false
|
|
34
|
+
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(href)) return false
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const splitHref = (href) => {
|
|
39
|
+
const cutIndex = href.search(/[?#]/)
|
|
40
|
+
if (cutIndex === -1) {
|
|
41
|
+
return { path: href, suffix: '' }
|
|
42
|
+
}
|
|
43
|
+
return { path: href.slice(0, cutIndex), suffix: href.slice(cutIndex) }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const resolveCandidate = (baseDir, targetPath) => resolve(baseDir, targetPath)
|
|
47
|
+
|
|
48
|
+
const isWithinRoot = (root, targetPath) => {
|
|
49
|
+
if (!root) return false
|
|
50
|
+
const relPath = relative(root, targetPath)
|
|
51
|
+
if (relPath === '') return true
|
|
52
|
+
if (relPath.startsWith('..') || relPath.startsWith('..\\')) return false
|
|
53
|
+
if (isAbsolute(relPath)) return false
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const hasExistingSource = (baseDir, pathWithoutSuffix, extension, root) => {
|
|
58
|
+
const targetPath = resolveCandidate(baseDir, `${pathWithoutSuffix}${extension}`)
|
|
59
|
+
if (root && !isWithinRoot(root, targetPath)) {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
return existsSync(targetPath)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const resolvePagesRoot = (filePath) => {
|
|
66
|
+
const roots = [state.PAGES_DIR, state.THEME_PAGES_DIR].filter(Boolean).map((dir) => resolve(dir))
|
|
67
|
+
if (!roots.length) return null
|
|
68
|
+
if (!filePath) return roots[0]
|
|
69
|
+
const resolvedFile = resolve(filePath)
|
|
70
|
+
for (const root of roots) {
|
|
71
|
+
if (isWithinRoot(root, resolvedFile)) {
|
|
72
|
+
return root
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return roots[0]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const linkResolve = () => {
|
|
79
|
+
return (tree, file) => {
|
|
80
|
+
const baseDir = file?.path ? dirname(file.path) : file?.cwd || process.cwd()
|
|
81
|
+
const pagesRoot = resolvePagesRoot(file?.path || null)
|
|
82
|
+
visit(tree, (node) => {
|
|
83
|
+
if (!isElement(node) || node.tagName !== 'a') {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
const href = node.properties?.href
|
|
87
|
+
if (!isRelativeHref(href)) {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const { path, suffix } = splitHref(href)
|
|
92
|
+
const match = path.match(extensionRegex)
|
|
93
|
+
if (!match) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
const extension = match[0]
|
|
97
|
+
const withoutExtension = path.replace(extensionRegex, '')
|
|
98
|
+
|
|
99
|
+
let shouldStrip = false
|
|
100
|
+
if (/\.mdx?$/i.test(extension)) {
|
|
101
|
+
shouldStrip = hasExistingSource(baseDir, withoutExtension, extension, pagesRoot)
|
|
102
|
+
} else if (/\.html$/i.test(extension)) {
|
|
103
|
+
shouldStrip =
|
|
104
|
+
hasExistingSource(baseDir, withoutExtension, extension, pagesRoot) ||
|
|
105
|
+
hasExistingSource(baseDir, withoutExtension, '.md', pagesRoot) ||
|
|
106
|
+
hasExistingSource(baseDir, withoutExtension, '.mdx', pagesRoot)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!shouldStrip) {
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
node.properties.href = withoutExtension + suffix
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
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 { extname } from 'path'
|
|
22
|
+
|
|
23
|
+
function createConst(name, init) {
|
|
24
|
+
return {
|
|
25
|
+
type: 'VariableDeclaration',
|
|
26
|
+
kind: 'const',
|
|
27
|
+
declarations: [
|
|
28
|
+
{
|
|
29
|
+
type: 'VariableDeclarator',
|
|
30
|
+
id: { type: 'Identifier', name },
|
|
31
|
+
init
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function argumentMember(name) {
|
|
38
|
+
return {
|
|
39
|
+
type: 'MemberExpression',
|
|
40
|
+
object: {
|
|
41
|
+
type: 'MemberExpression',
|
|
42
|
+
object: { type: 'Identifier', name: 'arguments' },
|
|
43
|
+
property: { type: 'Literal', value: 0 },
|
|
44
|
+
computed: true,
|
|
45
|
+
optional: false
|
|
46
|
+
},
|
|
47
|
+
property: { type: 'Identifier', name },
|
|
48
|
+
computed: false,
|
|
49
|
+
optional: false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const ctxFrontmatter = {
|
|
54
|
+
type: 'ChainExpression',
|
|
55
|
+
expression: {
|
|
56
|
+
type: 'MemberExpression',
|
|
57
|
+
object: {
|
|
58
|
+
type: 'MemberExpression',
|
|
59
|
+
object: { type: 'Identifier', name: 'ctx' },
|
|
60
|
+
property: { type: 'Identifier', name: 'page' },
|
|
61
|
+
computed: false,
|
|
62
|
+
optional: false
|
|
63
|
+
},
|
|
64
|
+
property: { type: 'Identifier', name: 'frontmatter' },
|
|
65
|
+
computed: false,
|
|
66
|
+
optional: true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function methanolCtx() {
|
|
71
|
+
return (tree, file) => {
|
|
72
|
+
// const filePath = file?.path || ''
|
|
73
|
+
|
|
74
|
+
tree.children.unshift({
|
|
75
|
+
type: 'mdxjsEsm',
|
|
76
|
+
data: {
|
|
77
|
+
estree: {
|
|
78
|
+
type: 'Program',
|
|
79
|
+
sourceType: 'module',
|
|
80
|
+
body: [
|
|
81
|
+
createConst('rawHTML', argumentMember('rawHTML')),
|
|
82
|
+
createConst('ctx', argumentMember('ctx')),
|
|
83
|
+
createConst('frontmatter', ctxFrontmatter)
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/renderer.js
ADDED
|
@@ -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
|
+
import { createHTMLRenderer } from 'refui/html'
|
|
22
|
+
|
|
23
|
+
const HTMLRenderer = createHTMLRenderer()
|
|
24
|
+
|
|
25
|
+
export { HTMLRenderer }
|
package/src/rewind.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
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 fnv1a from '@sindresorhus/fnv1a'
|
|
22
|
+
import JSON5 from 'json5'
|
|
23
|
+
|
|
24
|
+
const utf8Buffer = new Uint8Array(128)
|
|
25
|
+
|
|
26
|
+
const alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!?*:+-=._/[]{}()<>%#$^|~`'"
|
|
27
|
+
const base = alphabet.length
|
|
28
|
+
function compress(num) {
|
|
29
|
+
let result = ''
|
|
30
|
+
while (num > 0) {
|
|
31
|
+
result = alphabet[num % base] + result
|
|
32
|
+
num = Math.floor(num / base)
|
|
33
|
+
}
|
|
34
|
+
return result
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function hash(str, size = 32) {
|
|
38
|
+
return compress(Number(fnv1a(str, { size, utf8Buffer })))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function env(parentEnv) {
|
|
42
|
+
const registry = {}
|
|
43
|
+
const keyPathRegistry = {}
|
|
44
|
+
let parent = parentEnv || null
|
|
45
|
+
|
|
46
|
+
let renderCount = 0
|
|
47
|
+
|
|
48
|
+
function client(info) {
|
|
49
|
+
const { clientPath, staticComponent, exportName } = info
|
|
50
|
+
|
|
51
|
+
let key = null
|
|
52
|
+
let _clientPath = clientPath
|
|
53
|
+
do {
|
|
54
|
+
_clientPath += '\0'
|
|
55
|
+
key = hash(_clientPath)
|
|
56
|
+
} while (keyPathRegistry[key] && keyPathRegistry[key] !== clientPath)
|
|
57
|
+
|
|
58
|
+
const component = ({ children: childrenProp, ...props }, ...children) => {
|
|
59
|
+
const id = renderCount++
|
|
60
|
+
const idStr = id.toString(16)
|
|
61
|
+
const script = `$$rwnd(${JSON.stringify(key)},${id},${Object.keys(props).length ? JSON5.stringify(props) : '{}'})`
|
|
62
|
+
|
|
63
|
+
return (R) => {
|
|
64
|
+
return [
|
|
65
|
+
R.createAnchor(`{${idStr}}`, true),
|
|
66
|
+
R.c(
|
|
67
|
+
staticComponent,
|
|
68
|
+
props,
|
|
69
|
+
R.createAnchor(`[${idStr}[`, true),
|
|
70
|
+
...children,
|
|
71
|
+
R.createAnchor(`]${idStr}]`, true)
|
|
72
|
+
),
|
|
73
|
+
R.c('script', null, R.rawHTML(script))
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
registry[exportName] = [key, info.clientPath]
|
|
79
|
+
keyPathRegistry[key] = info.clientPath
|
|
80
|
+
|
|
81
|
+
return component
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function invalidate(exportName) {
|
|
85
|
+
if (!registry[exportName]) {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
const [key] = registry[exportName]
|
|
89
|
+
delete registry[exportName]
|
|
90
|
+
delete keyPathRegistry[key]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function setParent(nextParent) {
|
|
94
|
+
parent = nextParent || null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getMergedRegistry() {
|
|
98
|
+
return Object.assign({}, parent?.registry, registry)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function genRegistryScript() {
|
|
102
|
+
return `({
|
|
103
|
+
${Object.values(getMergedRegistry()).map(([key, path]) => `${JSON.stringify(key)}: () => import(${JSON.stringify(path)})`).join(`,
|
|
104
|
+
`)}
|
|
105
|
+
})`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
client,
|
|
110
|
+
invalidate,
|
|
111
|
+
genRegistryScript,
|
|
112
|
+
setParent,
|
|
113
|
+
get registry() {
|
|
114
|
+
return getMergedRegistry()
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
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 now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now())
|
|
22
|
+
|
|
23
|
+
export const createStageLogger = (enabled) => {
|
|
24
|
+
let lastLength = 0
|
|
25
|
+
const isTty = Boolean(process.stdout && process.stdout.isTTY)
|
|
26
|
+
const writeLine = (text, newline) => {
|
|
27
|
+
if (!process.stdout || !process.stdout.write) {
|
|
28
|
+
if (newline) {
|
|
29
|
+
console.log(text)
|
|
30
|
+
}
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
if (!isTty) {
|
|
34
|
+
if (newline) {
|
|
35
|
+
console.log(text)
|
|
36
|
+
}
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
const padding = lastLength > text.length ? ' '.repeat(lastLength - text.length) : ''
|
|
40
|
+
const clearLine = '\u001b[2K'
|
|
41
|
+
process.stdout.write(`${clearLine}\r${text}${padding}${newline ? '\n' : ''}`)
|
|
42
|
+
lastLength = text.length
|
|
43
|
+
}
|
|
44
|
+
const start = (label) => {
|
|
45
|
+
if (!enabled) return null
|
|
46
|
+
writeLine(`${label}...`, false)
|
|
47
|
+
return { label, start: now() }
|
|
48
|
+
}
|
|
49
|
+
const update = (token, message) => {
|
|
50
|
+
if (!enabled || !token || !message) return
|
|
51
|
+
writeLine(message, false)
|
|
52
|
+
}
|
|
53
|
+
const end = (token) => {
|
|
54
|
+
if (!enabled || !token) return
|
|
55
|
+
const duration = Math.round(now() - token.start)
|
|
56
|
+
writeLine(`${token.label}...\t${duration}ms`, true)
|
|
57
|
+
}
|
|
58
|
+
return { start, update, end }
|
|
59
|
+
}
|