From 4d9152a0959f5c6214655d634592e92baf456438 Mon Sep 17 00:00:00 2001 From: mantou132 <709922234@qq.com> Date: Wed, 3 Jan 2024 04:14:13 +0800 Subject: [PATCH] [gem-book] Support esm theme, config --- .../gem-book/docs/zh/002-guide/003-cli.md | 5 +- .../gem-book/docs/zh/002-guide/005-theme.md | 4 +- packages/gem-book/src/bin/builder.ts | 6 +- packages/gem-book/src/bin/index.ts | 57 ++++++++++--------- packages/gem-book/src/bin/utils.ts | 47 ++++++++------- packages/gem-book/src/common/constant.ts | 2 +- packages/gem-book/src/element/index.ts | 4 +- packages/gem-book/src/index.ts | 4 ++ packages/gem-book/themes/cyberpunk.js | 9 +++ packages/gem-book/themes/cyberpunk.json | 6 -- packages/gem-book/themes/dark.json | 5 -- packages/gem-book/themes/dark.mjs | 8 +++ 12 files changed, 90 insertions(+), 67 deletions(-) create mode 100644 packages/gem-book/themes/cyberpunk.js delete mode 100644 packages/gem-book/themes/cyberpunk.json delete mode 100644 packages/gem-book/themes/dark.json create mode 100644 packages/gem-book/themes/dark.mjs diff --git a/packages/gem-book/docs/zh/002-guide/003-cli.md b/packages/gem-book/docs/zh/002-guide/003-cli.md index 1fc47ae0..41519e37 100644 --- a/packages/gem-book/docs/zh/002-guide/003-cli.md +++ b/packages/gem-book/docs/zh/002-guide/003-cli.md @@ -6,12 +6,13 @@ npx gem-book -h ## 配置文件 -`gem-book` 命令会自动从项目根目录查找配置文件 `gem-book.cli.json`,支持大部分命令行选项(同时提供时合并命令行选项),例如: +`gem-book` 命令会自动从项目根目录查找配置文件 `gem-book.cli.{js|json|mjs}`,支持大部分命令行选项(同时提供时合并命令行选项),例如: > [!TIP] -> 使用 json schema: `"$schema": "https://unpkg.com/gem-book/schema.json"` +> 如果用 `json` 格式,可以添加 `"$schema": "https://unpkg.com/gem-book/schema.json"` 以获取类型提示, +> 如果使用 `js` 格式,可以导入类型 `import('gem-book/common/config').CliConfig`。 ## 命令行选项 diff --git a/packages/gem-book/docs/zh/002-guide/005-theme.md b/packages/gem-book/docs/zh/002-guide/005-theme.md index 574e332b..7b9dd846 100644 --- a/packages/gem-book/docs/zh/002-guide/005-theme.md +++ b/packages/gem-book/docs/zh/002-guide/005-theme.md @@ -8,10 +8,10 @@ ## 自定义主题 -可以直接使用命令行参数提供 `json`/`CommonJs` 格式的主题文件路径或者[内置主题](https://github.com/mantou132/gem/tree/master/packages/gem-book/themes)名称: +可以直接使用命令行参数提供 `json`/`CommonJs/ESM` 格式的主题文件路径或者[内置主题](https://github.com/mantou132/gem/tree/master/packages/gem-book/themes)名称: ```bash -gem-book docs --theme path/my-theme +gem-book docs --theme path/my-theme.mjs gem-book docs --theme dark ``` diff --git a/packages/gem-book/src/bin/builder.ts b/packages/gem-book/src/bin/builder.ts index 7f07c3b1..211315df 100644 --- a/packages/gem-book/src/bin/builder.ts +++ b/packages/gem-book/src/bin/builder.ts @@ -11,14 +11,14 @@ import { GenerateSW } from 'workbox-webpack-plugin'; import { BookConfig, CliUniqueConfig } from '../common/config'; import { STATS_FILE } from '../common/constant'; -import { resolveLocalPlugin, resolveTheme, isURL, requireObject } from './utils'; +import { resolveLocalPlugin, resolveTheme, isURL, importObject } from './utils'; const publicDir = path.resolve(__dirname, '../public'); const entryDir = path.resolve(__dirname, process.env.GEM_BOOK_DEV ? '../src/website' : '../website'); const pluginDir = path.resolve(__dirname, process.env.GEM_BOOK_DEV ? '../src/plugins' : '../plugins'); // dev mode uses memory file system -export function startBuilder(dir: string, options: Required, bookConfig: Partial) { +export async function startBuilder(dir: string, options: Required, bookConfig: BookConfig) { const { debug, build, theme, template, output, icon, plugin, ga } = options; const plugins = [...plugin]; @@ -89,7 +89,7 @@ export function startBuilder(dir: string, options: Required, bo new webpack.DefinePlugin({ 'process.env.DEV_MODE': !build, 'process.env.BOOK_CONFIG': JSON.stringify(bookConfig), - 'process.env.THEME': JSON.stringify(requireObject(themePath)), + 'process.env.THEME': JSON.stringify(await importObject(themePath)), 'process.env.PLUGINS': JSON.stringify(plugins), 'process.env.GA_ID': JSON.stringify(ga), }), diff --git a/packages/gem-book/src/bin/index.ts b/packages/gem-book/src/bin/index.ts index f6e700f0..b9d29e64 100644 --- a/packages/gem-book/src/bin/index.ts +++ b/packages/gem-book/src/bin/index.ts @@ -35,12 +35,13 @@ import { getHash, getFile, resolveTheme, - requireObject, + importObject, + resolveModule, } from './utils'; import { startBuilder } from './builder'; import lang from './lang.json'; // https://developers.google.com/search/docs/advanced/crawling/localized-versions#language-codes -export const devServerEventTarget = new EventTarget(); +const devServerEventTarget = new EventTarget(); program.version(version, '-v, --version'); @@ -60,8 +61,9 @@ let cliConfig: Required = { config: '', }; -function readConfig(fullPath: string) { - const obj = requireObject(fullPath) || {}; +// 将配置文件和 cli 选项合并,并将 book 选项同步到 bookConfig +async function syncConfig(fullPath?: string) { + const obj: CliConfig = (await importObject(resolveModule(fullPath))) || {}; Object.keys(cliConfig).forEach((key: keyof CliUniqueConfig) => { if (key in obj) { const value = obj[key]; @@ -69,7 +71,6 @@ function readConfig(fullPath: string) { const cliConfigValue = cliConfig[key]; - // Overriding command line options is not allowed if (Array.isArray(cliConfigValue)) { Object.assign(cliConfig, { [key]: [...new Set([...cliConfigValue, ...(value as any[])])] }); } else if (!cliConfigValue) { @@ -233,6 +234,8 @@ async function generateBookConfig(dir: string) { fs.writeFileSync(configPath, configStr); } } + + if (cliConfig.debug) inspectObject(bookConfig, 2); // eslint-disable-next-line no-console console.log(`${new Date().toISOString()} book config updated! ${Date.now() - t}ms`); } @@ -316,9 +319,13 @@ program .option('--debug', 'enabled debug mode', () => { cliConfig.debug = true; }) - .option('--config ', `specify config file, default use \`${DEFAULT_CLI_FILE}\``, (configPath: string) => { - cliConfig.config = configPath; - }) + .option( + '--config ', + `specify config file, default use \`${DEFAULT_CLI_FILE}\`.{js|json|mjs}`, + (configPath: string) => { + cliConfig.config = configPath; + }, + ) .arguments('') .action(async (dir: string) => { const initCliOptions = structuredClone(cliConfig); @@ -326,8 +333,9 @@ program docsRootDir = path.resolve(process.cwd(), dir); - const configPath = path.resolve(process.cwd(), cliConfig.config || DEFAULT_CLI_FILE); - readConfig(configPath); + const configPath = resolveModule(cliConfig.config || DEFAULT_CLI_FILE); + await syncConfig(configPath); + await generateBookConfig(dir); const updateBookConfig = throttle( async () => { @@ -348,10 +356,10 @@ program return fs.watch( themePath, throttle( - () => { + async () => { devServerEventTarget.dispatchEvent( Object.assign(new Event(UPDATE_EVENT), { - detail: { theme: requireObject(themePath) }, + detail: { theme: await importObject(themePath) }, }), ); }, @@ -362,9 +370,7 @@ program } }; - await generateBookConfig(dir); - - let server = cliConfig.json ? undefined : startBuilder(dir, cliConfig, bookConfig); + let server = cliConfig.json ? undefined : await startBuilder(dir, cliConfig, bookConfig); if (!cliConfig.build) { devServerEventTarget.addEventListener(UPDATE_EVENT, ({ detail }: CustomEvent) => { @@ -379,18 +385,17 @@ program async () => { cliConfig = structuredClone(initCliOptions); bookConfig = structuredClone(initBookConfig); - readConfig(configPath); + await syncConfig(configPath); await generateBookConfig(dir); - server?.stopCallback(() => { - server = startBuilder(dir, cliConfig, bookConfig); - devServerEventTarget.dispatchEvent( - Object.assign(new Event(UPDATE_EVENT), { - detail: { config: bookConfig }, - }), - ); - themeWatcher?.close(); - themeWatcher = watchTheme(); - }); + await server?.stop(); + server = await startBuilder(dir, cliConfig, bookConfig); + devServerEventTarget.dispatchEvent( + Object.assign(new Event(UPDATE_EVENT), { + detail: { config: bookConfig }, + }), + ); + themeWatcher?.close(); + themeWatcher = watchTheme(); }, 300, { trailing: true }, diff --git a/packages/gem-book/src/bin/utils.ts b/packages/gem-book/src/bin/utils.ts index 005a1588..497894cf 100644 --- a/packages/gem-book/src/bin/utils.ts +++ b/packages/gem-book/src/bin/utils.ts @@ -49,6 +49,11 @@ export function getRepoTitle() { } } +// dir2 is in dir +export function inTheDir(dir: string, dir2: string) { + return !path.relative(dir, dir2).startsWith('.'); +} + // Prefer built-in export function resolveLocalPlugin(p: string) { try { @@ -65,23 +70,27 @@ export function resolveLocalPlugin(p: string) { } } -// Prefer built-in -export function resolveTheme(p: string) { +export function resolveModule(p?: string, builtInDirs: string[] = []) { if (!p) return; - try { - return require.resolve(path.resolve(__dirname, `../themes/${p}`)); - } catch { - try { - return require.resolve(path.resolve(process.cwd(), p)); - } catch {} - } + return [...builtInDirs.map((dir) => path.resolve(__dirname, `${dir}${p}`)), path.resolve(process.cwd(), p)] + .map((p) => ['', '.js', '.json', '.mjs'].map((ext) => p + ext)) + .flat() + .find((e) => existsSync(e)); } -export function requireObject(fullPath?: string) { +// Prefer built-in +export function resolveTheme(p: string) { + return resolveModule(p, ['../themes/']); +} + +export async function importObject(fullPath?: string) { if (!fullPath) return; - delete require.cache[fullPath]; try { + if (fullPath.endsWith('.mjs')) { + return (await import(`${fullPath}?v=${performance.now()}`)).default; + } + delete require.cache[fullPath]; return require(fullPath) as T; } catch (err) { // eslint-disable-next-line no-console @@ -200,21 +209,18 @@ export function isURL(s: string) { } } -// dir2 is in dir -export function inTheDir(dir: string, dir2: string) { - return !path.relative(dir, dir2).startsWith('.'); -} - export function isSomeContent(filePath: string, content: string) { return existsSync(filePath) && content === readFileSync(filePath, 'utf-8'); } -export function inspectObject(obj: any) { +export function inspectObject(obj: any, depth = 0) { // eslint-disable-next-line no-console - console.log(util.inspect(obj, { colors: true, depth: null })); + console.log(util.inspect(obj, { colors: true, depth: depth || null })); } export async function getIconDataUrl(filePath: string) { + if (isURL(filePath)) return filePath; + if (filePath.startsWith('data:')) return filePath; if (filePath.endsWith('.svg')) { return `data:image/svg+xml;base64,${btoa(readFileSync(path.resolve(process.cwd(), filePath), 'utf-8'))}`; } @@ -222,7 +228,8 @@ export async function getIconDataUrl(filePath: string) { const image = await Jimp.read(filePath); return await image.clone().resize(Jimp.AUTO, 100).getBase64Async(Jimp.MIME_PNG); } catch (err) { - if (isURL(filePath)) return filePath; - throw err; + // eslint-disable-next-line no-console + console.error(err); + return ''; } } diff --git a/packages/gem-book/src/common/constant.ts b/packages/gem-book/src/common/constant.ts index 10bfc10d..8cca905f 100644 --- a/packages/gem-book/src/common/constant.ts +++ b/packages/gem-book/src/common/constant.ts @@ -1,6 +1,6 @@ export const UPDATE_EVENT = 'gen-book-update'; +export const DEFAULT_CLI_FILE = 'gem-book.cli'; export const DEFAULT_FILE = 'gem-book.json'; -export const DEFAULT_CLI_FILE = 'gem-book.cli.json'; export const STATS_FILE = 'stats.json'; export const DEFAULT_SOURCE_BRANCH = 'main'; diff --git a/packages/gem-book/src/element/index.ts b/packages/gem-book/src/element/index.ts index 4e361d1a..f79f902f 100644 --- a/packages/gem-book/src/element/index.ts +++ b/packages/gem-book/src/element/index.ts @@ -87,6 +87,8 @@ export class GemBookElement extends GemElement { this.addEventListener('message', ({ detail }: CustomEvent) => { const event = JSON.parse(detail); if (typeof event.data !== 'object') return; + // eslint-disable-next-line no-console + if (this.dev) console.log('Event data', event.data); const { filePath, content, config, theme, reload } = event.data; if (event.type !== UPDATE_EVENT) return; const routeELement = this.routeRef.element!; @@ -323,5 +325,3 @@ export class GemBookElement extends GemElement { ); } } - -export { GemBookPluginElement } from './elements/plugin'; diff --git a/packages/gem-book/src/index.ts b/packages/gem-book/src/index.ts index ef9f88ec..589549df 100644 --- a/packages/gem-book/src/index.ts +++ b/packages/gem-book/src/index.ts @@ -1 +1,5 @@ export * from './element'; + +export * from './element/elements/plugin'; + +export * from './element/helper/default-theme'; diff --git a/packages/gem-book/themes/cyberpunk.js b/packages/gem-book/themes/cyberpunk.js new file mode 100644 index 00000000..ef843e72 --- /dev/null +++ b/packages/gem-book/themes/cyberpunk.js @@ -0,0 +1,9 @@ +/** + * @type {import('gem-book')['defaultTheme']} + */ +module.exports = { + backgroundColor: 'rgb(10, 23, 45)', + borderColor: 'rgb(19, 61, 201)', + textColor: 'rgb(11, 196, 207)', + primaryColor: 'rgb(240, 0, 219)', +}; diff --git a/packages/gem-book/themes/cyberpunk.json b/packages/gem-book/themes/cyberpunk.json deleted file mode 100644 index c38d7741..00000000 --- a/packages/gem-book/themes/cyberpunk.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "backgroundColor": "rgb(10, 23, 45)", - "borderColor": "rgb(19, 61, 201)", - "textColor": "rgb(11, 196, 207)", - "primaryColor": "rgb(240, 0, 219)" -} diff --git a/packages/gem-book/themes/dark.json b/packages/gem-book/themes/dark.json deleted file mode 100644 index 046e5f03..00000000 --- a/packages/gem-book/themes/dark.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "backgroundColor": "#1a1a1a", - "borderColor": "rgb(102, 102, 102)", - "textColor": "rgb(240, 240, 240)" -} diff --git a/packages/gem-book/themes/dark.mjs b/packages/gem-book/themes/dark.mjs new file mode 100644 index 00000000..0569eaf8 --- /dev/null +++ b/packages/gem-book/themes/dark.mjs @@ -0,0 +1,8 @@ +/** + * @type {import('gem-book')['defaultTheme']} + */ +export default { + backgroundColor: '#1a1a1a', + borderColor: 'rgb(102, 102, 102)', + textColor: 'rgb(240, 240, 240)', +};