first commit

This commit is contained in:
mozzie 2023-02-01 17:52:53 +08:00
commit 197cae9a54
52 changed files with 20993 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

31
bolt.config.ts Normal file
View File

@ -0,0 +1,31 @@
const pkg = require("./package.json");
export default {
/**
*
*/
version: pkg.version,
/**
* <title>
*/
title: "API文档 • Bolt Design",
/**
* logo
*/
subTitle: "Bolt Design",
/**
*
*/
customPages: [
{
path: "/",
src: "Welcome",
title: "首页",
},
{
path: "/intro",
src: "Intro",
title: "文档",
},
],
};

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

12290
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

49
package.json Normal file
View File

@ -0,0 +1,49 @@
{
"name": "bolt",
"private": true,
"description": "基于 mdx-react 的自动化组件、文档脚手架",
"version": "1.2.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "pnpm run build:lib && pnpm run build:doc",
"build:lib": "tsc -p ./tsconfig.lib.json && vite build --mode lib && pnpm build:less",
"build:doc": "tsc -p ./tsconfig.prod.json && vite build --mode doc",
"build:less": "esno plugin/build/autocss.ts",
"preview": "vite preview"
},
"dependencies": {
"@mapbox/rehype-prism": "^0.8.0",
"@mdx-js/react": "^2.1.3",
"@mdx-js/rollup": "^2.1.3",
"antd": "^4.23.5",
"less": "^4.1.3",
"markdown-toc": "^1.2.0",
"object-hash": "3.0.0",
"prismjs": "^1.29.0",
"rc-tooltip": "^5.2.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-loadable": "^5.5.0",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"react-scroll": "^1.8.7",
"react-tooltip": "^4.2.21"
},
"devDependencies": {
"@ricons/fluent": "^0.12.0",
"@types/node": "^18.7.9",
"@types/object-hash": "2.2.1",
"@types/prismjs": "^1.26.0",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
"@types/react-scroll": "1.8.4",
"@vitejs/plugin-react": "^2.0.1",
"cpy": "^9.0.1",
"fast-glob": "^3.2.12",
"terser": "^5.15.1",
"typescript": "^4.7.4",
"vite": "^3.0.7",
"vite-plugin-dts": "^1.6.5"
}
}

49
plugin/build/autocss.ts Normal file
View File

@ -0,0 +1,49 @@
/**
* css rollup index.js
* e.g Buttom/index.js
* before import './index.less'
* after import './index.css'
*/
import cpy from "cpy";
import { resolve, dirname } from "path";
import { promises as fs } from "fs";
import less from "less";
import glob from "fast-glob";
const rootPath = process.cwd();
const sourceDir = resolve(rootPath, "src");
//lib文件目录
const targetLib = resolve(rootPath, "dist/ui");
//src目录
const srcDir = resolve(rootPath, "src");
const buildLess = async () => {
//直接将less文件复制到打包后目录
await cpy([`${sourceDir}/**/*.less`], targetLib);
//获取打包后.less文件目录(lib和es一样)
const lessFiles = await glob("**/*.less", { cwd: srcDir, onlyFiles: true });
//遍历含有less的目录
for (let path in lessFiles) {
const filePath = `${srcDir}/${lessFiles[path]}`;
//获取less文件字符串
const lessCode = await fs.readFile(filePath, "utf-8");
//将less解析成css
const code = await less.render(lessCode, {
//指定src下对应less文件的文件夹为目录
paths: [srcDir, dirname(filePath)],
});
//拿到.css后缀path
const cssPath = lessFiles[path].replace(".less", ".css");
//将css写入对应目录
await fs.writeFile(resolve(targetLib, cssPath), code.css);
}
};
buildLess();

4
plugin/build/autopkg.ts Normal file
View File

@ -0,0 +1,4 @@
/**
* dist package.json
*/
const buildPkg = () => {};

48
plugin/build/vite.conf.ts Normal file
View File

@ -0,0 +1,48 @@
import path from "path";
/**
* doc vite构建配置
*/
const docPage = { outDir: "dist/doc" };
/**
* lib vite构建配置
*/
const componentLib = {
minify: true, // boolean | 'terser' | 'esbuild'
sourcemap: true, // 输出单独 source文件
brotliSize: true, // 生成压缩大小报告
cssCodeSplit: false, // css 从 js 组件中分离出去
rollupOptions: {
external: ["react", "react-dom", /\.less/], // 忽略js 中的 less 文件
output: [
{
format: "es",
entryFileNames: "[name].js",
preserveModules: true,
dir: "dist/ui",
preserveModulesRoot: "src",
// assetFileNames: (assetInfo) => processAssets(assetInfo),
},
],
},
lib: {
entry: path.resolve(process.cwd(), "src/index.ts"),
name: "Bolt", // 暴露的全局变量名称
},
};
export const build = (mode: string, config = {}) => {
switch (mode) {
case "doc":
config = docPage;
break;
case "lib":
config = componentLib;
break;
default:
config = {};
break;
}
return config;
};

View File

@ -0,0 +1,78 @@
import { useEffect, useState } from "react";
import { IPropMeta } from "../types";
interface IProps {
propList: IPropMeta[];
}
type IHeader = {
[key: string]: string;
};
/**
* annotation tag
*/
const tableHeader: IHeader = {
name: "参数名",
description: "说明",
type: "类型",
default: "默认值",
required: "必须",
version: "版本",
};
export default function ApiTable(props: IProps) {
const [propList, setPropList] = useState<IPropMeta[]>([]);
useEffect(() => {
props.propList?.length > 0 ? setPropList(props.propList) : setPropList([]);
}, [props.propList]);
const format = (prop: string, v: any) => {
if (prop === "version") return !v ? "" : "v" + v;
if (typeof v === "boolean") return !v ? "" : "" + v;
return v;
};
return (
<div className="anchor-api-table api-table-container">
<h1>API</h1>
<table className="api-table">
<thead>
<tr>
{Object.keys(tableHeader).map((prop) => {
return (
<th key={prop}>
<div>{tableHeader[prop]}</div>
{/* <div className="annotation-prop">@{prop}</div> */}
</th>
);
})}
</tr>
</thead>
<tbody>
{propList?.length > 0 ? (
propList.map((m) => {
return (
<tr key={m.name}>
{Object.keys(tableHeader).map((prop) => {
return <td key={prop}>{format(prop, (m as any)[prop])}</td>;
})}
</tr>
);
})
) : (
<tr key={"without-data"}>
<td
style={{ textAlign: "center", fontWeight: "normal" }}
colSpan={Object.keys(tableHeader).length}
>
props.ts , interface
</td>
</tr>
)}
</tbody>
</table>
</div>
);
}

59
plugin/common/Aside.tsx Normal file
View File

@ -0,0 +1,59 @@
import { useEffect, useState } from "react";
import { useLocation, useNavigate } from "react-router";
import { IRoute } from "../types";
interface IProps {
menus: IRoute[];
subTitle: string;
version: string;
}
export default function Aside(props: IProps) {
const navigate = useNavigate();
const location = useLocation();
const version = props.version;
const [menuList, setMenuList] = useState<IRoute[]>([]);
useEffect(() => {
if (props.menus?.length > 0) setMenuList(props.menus);
}, [props.menus]);
useEffect(() => {
const path = location.pathname.replace(/\//, "");
const isCompoPath = menuList.map((i) => i.path).includes(path);
if (!isCompoPath)
setMenuList((p) => p.map((i) => ({ ...i, active: false })));
}, [location.pathname]);
const toggle = (route: IRoute) => {
setMenuList((prev) => {
return prev.map((r: IRoute) => {
r.active = r.path === route.path;
return r;
});
});
navigate(route.path);
};
return (
<aside>
<div className="logo">
<img src="logo.png" />
<span data-version={version}>{props.subTitle}</span>
</div>
<h4>({menuList.length})</h4>
<ul className="nav-list">
{menuList.map((route: IRoute) => (
<li key={route.path}>
<a
className={route.active ? "active" : ""}
onClick={() => toggle(route)}
>
{route.name}
</a>
</li>
))}
</ul>
</aside>
);
}

53
plugin/common/Code.tsx Normal file
View File

@ -0,0 +1,53 @@
import { useEffect, useRef, useState } from "react";
import Code20Regular from "@ricons/fluent/Code20Regular";
import Prism from "prismjs";
import "prismjs/plugins/line-numbers/prism-line-numbers";
import "prismjs/plugins/line-numbers/prism-line-numbers.css";
// import "prismjs/components/prism";
import "prismjs/components/prism-javascript";
import "prismjs/components/prism-typescript";
import "prismjs/components/prism-bash";
import "prismjs/components/prism-css";
interface IProps {
/**
* @description prism
*/
raw: string;
/**
*
*/
lang: string;
}
export default function Code(props: IProps) {
const [visible, setVisible] = useState(false);
const codeRef = useRef<any>();
useEffect(() => {
Prism.highlightElement(codeRef.current);
}, [props.raw]);
return (
<div className="code-container">
<div style={{ paddingTop: "10px", textAlign: "right" }}>
<span
data-placement="示例代码"
className={`btn ${visible ? "active" : ""}`}
onClick={() => setVisible((v) => !v)}
>
<Code20Regular />
</span>
</div>
<pre
className="line-numbers"
data-lang={props.lang}
style={{ display: visible ? "block" : "none" }}
>
<code ref={codeRef} className={`language-${props.lang}`}>
{props.raw}
</code>
</pre>
</div>
);
}

65
plugin/common/MdToc.tsx Normal file
View File

@ -0,0 +1,65 @@
import { useEffect, useState } from "react";
import { TToc } from "../types";
import { scroller } from "react-scroll";
interface IProps {
children?: any;
toc: TToc[];
}
const tocEndItem: TToc = {
content: "API",
slug: "API",
lvl: 1,
anchor: "anchor-api-table",
};
export default function Toc(props: IProps) {
const [contentTable, setContentTable] = useState<TToc[]>([]);
useEffect(() => {
const tocWithApiItem = [...props.toc, tocEndItem];
props.toc?.length > 0
? setContentTable(tocWithApiItem.map((i) => ({ ...i, active: false })))
: setContentTable([]);
}, [props.toc]);
/**
* toc
*/
const onClickTocItem = (anchor: string) => {
setContentTable((p) =>
p?.map((i) => ({ ...i, active: i.anchor === anchor }))
);
scroller.scrollTo(anchor, {
duration: 800,
delay: 0,
smooth: "easeInOutQuart",
offset: -28,
});
};
return (
<div className="toc curtain-bottom">
<h4></h4>
{contentTable && (
<ul className="hidden-scrollbar">
{contentTable.map((item: TToc) => {
return (
<li key={item.content}>
<div
onClick={() => onClickTocItem(item.anchor)}
className={`toc-lvl toc-lvl-${item.lvl} ${
item.active ? "active" : ""
}`}
>
{item.content}
</div>
</li>
);
})}
</ul>
)}
</div>
);
}

65
plugin/common/Nav.tsx Normal file
View File

@ -0,0 +1,65 @@
import { useState, useEffect } from "react";
import Search20Regular from "@ricons/fluent/Search20Regular";
import { useLocation, useNavigate } from "react-router";
import { ICustomPage } from "../types";
interface IProps {
customPages: ICustomPage[];
}
interface INavItem extends ICustomPage {
active: boolean;
key: string;
}
export default function Nav(props: IProps) {
const navigate = useNavigate();
const location = useLocation();
const [navList, setNavList] = useState<INavItem[]>([]);
useEffect(() => {
const navList = props.customPages.map((i: ICustomPage) => ({
...i,
key: i.path,
active: false,
}));
setNavList(navList);
}, [props.customPages]);
/**
* active
*/
useEffect(() => {
const path = location.pathname;
setNavList((p) => p.map((i) => ({ ...i, active: path === i.path })));
}, [location.pathname]);
/**
*
*/
const onClickNavItem = (key: string) => {
const nav = navList.find((i: INavItem) => i.key === key);
setNavList((p: INavItem[]) =>
p.map((i: INavItem) => ({ ...i, active: key === i.key }))
);
navigate(nav!.path);
};
return (
<nav>
<div className="start"></div>
<div className="end">
<div className="search-container">
<Search20Regular />
<input type="text" placeholder="搜索 / 待开发" />
</div>
<ul>
{navList.map((item) => (
<li key={item.key} onClick={() => onClickNavItem(item.key)}>
<span className={item.active ? "active" : ""}>{item.title}</span>
</li>
))}
</ul>
</div>
</nav>
);
}

View File

@ -0,0 +1,534 @@
* {
box-sizing: border-box;
}
:root {
--color-grey: #f2f3f5;
--color-grey-lighter: #f7f8fa;
--color-text-1: #1d2129;
--color-text-2: #4e5969;
--color-text-3: rgb(118, 124, 130);
--color-primary: rgba(22, 93, 255, 1);
--color-primary-lighter: rgba(106, 161, 255, 1);
--color-primary-lighter-5: rgba(106, 161, 255, 0.1);
--color-primary-hover: rgba(22, 93, 255, 0.05);
}
@import "./prism-laserwave.less";
html,
body {
position: relative;
margin: 0;
padding: 0;
line-height: 1;
font-family: Inter, "-apple-system", BlinkMacSystemFont, "PingFang SC",
"Hiragino Sans GB", "noto sans", "Microsoft YaHei", "Helvetica Neue",
Helvetica, Arial, sans-serif;
font-size: 13px;
color: var(--color-text-2);
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: var(--color-text-1);
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
li {
padding: 0;
}
a {
text-decoration: none;
color: var(--color-primary-lighter);
&:hover {
color: var(--color-primary);
}
}
.curtain-top {
position: relative;
&::before {
position: absolute;
content: "";
top: 20px;
z-index: 10;
left: 0;
right: 0;
height: 32px;
background: linear-gradient(to top, transparent, #fff 70%);
}
}
.curtain-bottom {
position: relative;
&::after {
position: absolute;
content: "";
bottom: 0;
z-index: 10;
left: 0;
right: 0;
height: 32px;
background: linear-gradient(to bottom, transparent, #fff 70%);
}
}
.hidden-scrollbar {
position: relative;
overflow-y: auto;
&::-webkit-scrollbar {
display: none;
}
}
.box {
margin-top: 24px;
padding: 48px;
color: #282f38;
background-color: #fff;
border: 1px solid var(--color-grey);
border-radius: 2px 2px 0 0;
}
.root-container {
nav {
padding: 0 40px;
display: flex;
align-items: center;
justify-content: space-between;
position: fixed;
left: 260px;
right: 0;
height: 60px;
// border-bottom: 1px solid var(--color-grey);
backdrop-filter: saturate(50%) blur(8px);
background: rgba(255, 255, 255, 0.7);
z-index: 199401;
.end {
display: flex;
align-items: center;
.search-container {
position: relative;
margin-right: 40px;
height: 32px;
width: 180px;
svg {
position: absolute;
left: 5px;
top: 50%;
margin-top: -10px;
width: 20px;
height: 20px;
z-index: 10;
}
input {
transition: all 0.25s ease;
padding-left: 25px;
padding-right: 10px;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
border: 0;
outline: 0;
border-radius: 4px;
background-color: var(--color-grey-lighter);
&:focus {
background-color: var(--color-grey);
}
}
}
ul {
display: flex;
margin: 0;
li {
margin-right: 30px;
&:last-of-type {
margin-right: 0;
}
span {
user-select: none;
font-size: 14px;
cursor: pointer;
&:hover {
color: var(--color-primary);
}
&.active {
color: var(--color-primary);
}
}
}
}
}
}
aside {
transition: all 0.15s ease-in-out;
position: fixed;
top: 0;
bottom: 0;
width: 260px;
border-right: 1px solid var(--color-grey);
padding: 0 20px;
overflow: auto;
.logo {
display: flex;
align-items: center;
margin-top: 10px;
height: 40px;
img {
height: 100%;
}
span {
position: relative;
padding-left: 10px;
font-size: 16px;
&::after {
margin-left: 10px;
padding: 0 2px;
line-height: 1.3;
border-radius: 3px;
position: absolute;
top: -10px;
content: attr(data-version);
font-size: 12px;
color: var(--color-primary);
border-radius: 4px;
border: 1px solid var(--color-primary);
background: var(--color-primary-lighter-5);
}
}
}
> h4 {
margin: 0;
padding: 20px 0;
font-weight: 400;
font-size: 12px;
color: var(--color-text-3);
}
> header {
display: flex;
align-items: center;
padding: 10px 10px 10px 30px;
img {
width: 32px;
}
}
.nav-list {
li {
margin-bottom: 5px;
&:last-of-type {
margin-bottom: 0;
}
&:hover {
border-radius: 4px;
background-color: var(--color-grey-lighter);
}
a {
border-radius: 4px;
cursor: pointer;
user-select: none;
display: inline-block;
display: block;
padding-left: 20px;
color: var(--color-text-1);
line-height: 40px;
text-decoration: none;
&.active {
color: var(--color-primary);
background-color: var(--color-primary-hover);
}
}
}
}
}
> main {
padding: 20px 40px 0 40px;
margin-left: 260px;
margin-right: 180px;
}
}
.page-loading {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: #fff;
z-index: 1023;
&::after {
position: absolute;
content: "";
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
border: 2px solid transparent;
border-top-color: var(--color-primary-lighter);
border-bottom-color: var(--color-primary-lighter);
border-radius: 50%;
animation: rotation 1s ease infinite;
}
}
[data-lang] {
position: relative;
&::after {
position: absolute;
top: 1em;
right: 1em;
content: attr(data-lang);
}
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.btn {
position: relative;
transition: all 0.25s ease-in-out;
display: inline-flex;
justify-content: center;
align-items: center;
padding: 5px;
box-sizing: border-box;
width: 28px;
height: 28px;
border: 1px solid #e5e6eb;
background: #fff;
border-radius: 50%;
z-index: 2;
cursor: pointer;
&:hover {
box-shadow: 0 4px 10px #0000001a;
&::before {
display: inline-block;
position: absolute;
content: attr(data-placement);
top: 50%;
transform: translateY(-50%);
left: -65px;
width: 60px;
text-align: center;
padding: 4px 0;
color: var(--color-text-2);
border-radius: 2px;
}
}
}
.markdown-container {
> h1,
> h2,
> h3,
> h4,
> h5,
> h6 {
margin: 0;
padding: 48px 0 12px 0;
font-weight: 500;
font-size: 20px;
}
> ul {
list-style-position: inside;
list-style-type: square;
> li {
line-height: 2;
}
}
li {
list-style-type: circle;
}
> blockquote {
margin: 20px 0;
padding: 16px;
color: var(--color-primary);
border-radius: 8px;
border: 1px solid var(--color-primary);
background: var(--color-primary-lighter-5);
> p {
margin: 0;
}
}
> p,
li {
margin: 0;
line-height: 2;
code {
padding: 2px 4px;
margin: 0 2px;
font-size: 85%;
color: #3c81a6;
background-color: #d4eefc;
border-radius: 3px;
font-family: Inter, "-apple-system", BlinkMacSystemFont, "PingFang SC",
"Hiragino Sans GB", "noto sans", "Microsoft YaHei", "Helvetica Neue",
Helvetica, Arial, sans-serif;
}
}
}
.api-table-container {
> h1 {
margin: 0;
padding: 48px 0 12px 0;
font-weight: 500;
font-size: 20px;
}
.api-table {
border-collapse: collapse;
table-layout: fixed;
border: 0;
width: 100%;
td,
th {
padding: 12px;
font-size: 13px;
text-align: left;
}
thead {
tr {
th {
background: #f2f3f5;
&:nth-of-type(1) {
width: 10%;
border-radius: 8px 0 0 8px;
}
&:nth-of-type(2) {
width: 45%;
}
&:nth-of-type(3) {
width: 21%;
}
&:nth-of-type(4) {
width: 8%;
}
&:nth-of-type(5) {
width: 8%;
}
&:nth-of-type(6) {
width: 8%;
border-radius: 0 8px 8px 0;
}
.annotation-prop {
padding-top: 3px;
font-weight: 300;
font-size: 12px;
color: #989898;
}
}
}
}
tbody {
tr {
&:hover {
background: rgba(60, 90, 100, 0.04);
}
}
td {
color: var(--color-text-1);
line-height: 20px;
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
&:nth-of-type(1) {
font-weight: 600;
}
&:nth-of-type(2) {
color: var(--color-text-3);
word-break: break-all;
}
&:nth-of-type(3),
&:nth-of-type(4) {
color: #40b4c4;
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
word-break: break-all;
}
&:nth-of-type(5) {
color: #eb64b9;
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
word-break: break-all;
}
}
}
}
}
.toc {
position: fixed;
width: 180px;
top: 120px;
bottom: 0;
right: 0;
background: #fff;
h4 {
display: flex;
align-items: center;
font-weight: 400;
color: var(--color-text-3);
font-size: 12px;
}
ul {
height: 100%;
border-left: 1px solid var(--color-grey);
li {
.toc-lvl {
color: var(--color-text-2);
display: block;
padding-right: 20px;
height: 28px;
line-height: 28px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
cursor: pointer;
background-color: var(--color-grey-lighter);
}
&.active {
color: var(--color-primary);
border-left-color: var(--color-primary);
background-color: var(--color-primary-hover);
}
&-1 {
padding-left: 15px;
}
&-2 {
padding-left: 25px;
}
}
}
}
}
.code-container {
.btn {
&.active {
background: #27212e;
color: #fff;
}
}
}

View File

@ -0,0 +1,133 @@
/*
* Laserwave Theme originally by Jared Jones for Visual Studio Code
* https://github.com/Jaredk3nt/laserwave
*
* Ported for PrismJS by Simon Jespersen [https://github.com/simjes]
*/
code[class*="language-"],
pre[class*="language-"] {
background: #27212e;
color: #ffffff;
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; /* this is the default */
/* The following properties are standard, please leave them as they are */
font-size: 1em;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
line-height: 1.5;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
/* The following properties are also standard */
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection,
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection {
background: #eb64b927;
color: inherit;
}
code[class*="language-"]::selection,
code[class*="language-"] ::selection,
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection {
background: #eb64b927;
color: inherit;
}
/* Properties specific to code blocks */
pre[class*="language-"] {
padding: 1em; /* this is standard */
margin: 0.5em 0; /* this is the default */
overflow: auto; /* this is standard */
border-radius: 0.5em;
}
/* Properties specific to inline code */
:not(pre) > code[class*="language-"] {
padding: 0.2em 0.3em;
border-radius: 0.5rem;
white-space: normal; /* this is standard */
}
.token.comment,
.token.prolog,
.token.cdata {
color: #91889b;
}
.token.punctuation {
color: #7b6995;
}
.token.builtin,
.token.constant,
.token.boolean {
color: #ffe261;
}
.token.number {
color: #b381c5;
}
.token.important,
.token.atrule,
.token.property,
.token.keyword {
color: #40b4c4;
}
.token.doctype,
.token.operator,
.token.inserted,
.token.tag,
.token.class-name,
.token.symbol {
color: #74dfc4;
}
.token.attr-name,
.token.function,
.token.deleted,
.token.selector {
color: #eb64b9;
}
.token.attr-value,
.token.regex,
.token.char,
.token.string {
color: #b4dce7;
}
.token.entity,
.token.url,
.token.variable {
color: #ffffff;
}
/* The following rules are pretty similar across themes, but feel free to adjust them */
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.token.namespace {
opacity: 0.7;
}

View File

@ -0,0 +1,210 @@
code[class*="language-"],
pre[class*="language-"] {
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
color: #c3cee3;
background: #2a2d3e;
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
font-size: 1em;
line-height: 1.5em;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
code[class*="language-"]::-moz-selection,
pre[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection,
pre[class*="language-"] ::-moz-selection {
background: #363636;
}
code[class*="language-"]::selection,
pre[class*="language-"]::selection,
code[class*="language-"] ::selection,
pre[class*="language-"] ::selection {
background: #363636;
}
:not(pre) > code[class*="language-"] {
white-space: normal;
border-radius: 0.2em;
padding: 0.1em;
}
pre[class*="language-"] {
overflow: auto;
position: relative;
margin: 0.5em 0;
padding: 1.25em 1em;
}
.language-css > code,
.language-sass > code,
.language-scss > code {
color: #fd9170;
}
[class*="language-"] .namespace {
opacity: 0.7;
}
.token.atrule {
color: #c792ea;
}
.token.attr-name {
color: #ffcb6b;
}
.token.attr-value {
color: #c3e88d;
}
.token.attribute {
color: #c3e88d;
}
.token.boolean {
color: #c792ea;
}
.token.builtin {
color: #ffcb6b;
}
.token.cdata {
color: #80cbc4;
}
.token.char {
color: #80cbc4;
}
.token.class {
color: #ffcb6b;
}
.token.class-name {
color: #f2ff00;
}
.token.color {
color: #f2ff00;
}
.token.comment {
color: #546e7a;
}
.token.constant {
color: #c792ea;
}
.token.deleted {
color: #f07178;
}
.token.doctype {
color: #546e7a;
}
.token.entity {
color: #f07178;
}
.token.function {
color: #c792ea;
}
.token.hexcode {
color: #f2ff00;
}
.token.id {
color: #c792ea;
font-weight: bold;
}
.token.important {
color: #c792ea;
font-weight: bold;
}
.token.inserted {
color: #80cbc4;
}
.token.keyword {
color: #c792ea;
font-style: italic;
}
.token.number {
color: #fd9170;
}
.token.operator {
color: #89ddff;
}
.token.prolog {
color: #546e7a;
}
.token.property {
color: #80cbc4;
}
.token.pseudo-class {
color: #c3e88d;
}
.token.pseudo-element {
color: #c3e88d;
}
.token.punctuation {
color: #89ddff;
}
.token.regex {
color: #f2ff00;
}
.token.selector {
color: #f07178;
}
.token.string {
color: #c3e88d;
}
.token.symbol {
color: #c792ea;
}
.token.tag {
color: #f07178;
}
.token.unit {
color: #f07178;
}
.token.url {
color: #fd9170;
}
.token.variable {
color: #f07178;
}

View File

@ -0,0 +1,158 @@
/**
* MIT License
* Copyright (c) 2018 Sarah Drasner
* Sarah Drasner's[@sdras] Night Owl
* Ported by Sara vieria [@SaraVieira]
* Added by Souvik Mandal [@SimpleIndian]
*/
code[class*="language-"],
pre[class*="language-"] {
color: #d6deeb;
font-family: Menlo, Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
font-size: 1em;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: rgba(29, 59, 83, 0.99);
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: rgba(29, 59, 83, 0.99);
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
color: white;
background: #011627;
}
:not(pre) > code[class*="language-"] {
padding: 0.1em;
border-radius: 0.3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.cdata {
color: rgb(99, 119, 119);
font-style: italic;
}
.token.punctuation {
color: rgb(199, 146, 234);
}
.namespace {
color: rgb(178, 204, 214);
}
.token.deleted {
color: rgba(239, 83, 80, 0.56);
font-style: italic;
}
.token.symbol,
.token.property {
color: rgb(128, 203, 196);
}
.token.tag,
.token.operator,
.token.keyword {
color: rgb(127, 219, 202);
}
.token.boolean {
color: rgb(255, 88, 116);
}
.token.number {
color: rgb(247, 140, 108);
}
.token.constant,
.token.function,
.token.builtin,
.token.char {
color: rgb(130, 170, 255);
}
.token.selector,
.token.doctype {
color: rgb(199, 146, 234);
font-style: italic;
}
.token.attr-name,
.token.inserted {
color: rgb(173, 219, 103);
font-style: italic;
}
.token.string,
.token.url,
.token.entity,
.language-css .token.string,
.style .token.string {
color: rgb(173, 219, 103);
}
.token.class-name,
.token.atrule,
.token.attr-value {
color: rgb(255, 203, 139);
}
.token.regex,
.token.important,
.token.variable {
color: rgb(214, 222, 235);
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}

View File

@ -0,0 +1,428 @@
/**
* One Light theme for prism.js
* Based on Atom's One Light theme: https://github.com/atom/atom/tree/master/packages/one-light-syntax
*/
/**
* One Light colours (accurate as of commit eb064bf on 19 Feb 2021)
* From colors.less
* --mono-1: hsl(230, 8%, 24%);
* --mono-2: hsl(230, 6%, 44%);
* --mono-3: hsl(230, 4%, 64%)
* --hue-1: hsl(198, 99%, 37%);
* --hue-2: hsl(221, 87%, 60%);
* --hue-3: hsl(301, 63%, 40%);
* --hue-4: hsl(119, 34%, 47%);
* --hue-5: hsl(5, 74%, 59%);
* --hue-5-2: hsl(344, 84%, 43%);
* --hue-6: hsl(35, 99%, 36%);
* --hue-6-2: hsl(35, 99%, 40%);
* --syntax-fg: hsl(230, 8%, 24%);
* --syntax-bg: hsl(230, 1%, 98%);
* --syntax-gutter: hsl(230, 1%, 62%);
* --syntax-guide: hsla(230, 8%, 24%, 0.2);
* --syntax-accent: hsl(230, 100%, 66%);
* From syntax-variables.less
* --syntax-selection-color: hsl(230, 1%, 90%);
* --syntax-gutter-background-color-selected: hsl(230, 1%, 90%);
* --syntax-cursor-line: hsla(230, 8%, 24%, 0.05);
*/
code[class*="language-"],
pre[class*="language-"] {
background: hsl(230, 1%, 98%);
color: hsl(230, 8%, 24%);
font-family: Menlo, Consolas, "DejaVu Sans Mono", monospace;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
line-height: 1.4;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Selection */
code[class*="language-"]::-moz-selection,
code[class*="language-"] *::-moz-selection,
pre[class*="language-"] *::-moz-selection {
background: hsl(230, 1%, 90%);
color: inherit;
}
code[class*="language-"]::selection,
code[class*="language-"] *::selection,
pre[class*="language-"] *::selection {
background: hsl(230, 1%, 90%);
color: inherit;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
border-radius: 0.3em;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: 0.2em 0.3em;
border-radius: 0.3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.cdata {
color: hsl(230, 4%, 64%);
}
.token.doctype,
.token.punctuation,
.token.entity {
color: hsl(230, 8%, 24%);
}
.token.attr-name,
.token.class-name,
.token.boolean,
.token.constant,
.token.number,
.token.atrule {
color: hsl(35, 99%, 36%);
}
.token.keyword {
color: hsl(301, 63%, 40%);
}
.token.property,
.token.tag,
.token.symbol,
.token.deleted,
.token.important {
color: hsl(5, 74%, 59%);
}
.token.selector,
.token.string,
.token.char,
.token.builtin,
.token.inserted,
.token.regex,
.token.attr-value,
.token.attr-value > .token.punctuation {
color: hsl(119, 34%, 47%);
}
.token.variable,
.token.operator,
.token.function {
color: hsl(221, 87%, 60%);
}
.token.url {
color: hsl(198, 99%, 37%);
}
/* HTML overrides */
.token.attr-value > .token.punctuation.attr-equals,
.token.special-attr > .token.attr-value > .token.value.css {
color: hsl(230, 8%, 24%);
}
/* CSS overrides */
.language-css .token.selector {
color: hsl(5, 74%, 59%);
}
.language-css .token.property {
color: hsl(230, 8%, 24%);
}
.language-css .token.function,
.language-css .token.url > .token.function {
color: hsl(198, 99%, 37%);
}
.language-css .token.url > .token.string.url {
color: hsl(119, 34%, 47%);
}
.language-css .token.important,
.language-css .token.atrule .token.rule {
color: hsl(301, 63%, 40%);
}
/* JS overrides */
.language-javascript .token.operator {
color: hsl(301, 63%, 40%);
}
.language-javascript .token.template-string > .token.interpolation > .token.interpolation-punctuation.punctuation {
color: hsl(344, 84%, 43%);
}
/* JSON overrides */
.language-json .token.operator {
color: hsl(230, 8%, 24%);
}
.language-json .token.null.keyword {
color: hsl(35, 99%, 36%);
}
/* MD overrides */
.language-markdown .token.url,
.language-markdown .token.url > .token.operator,
.language-markdown .token.url-reference.url > .token.string {
color: hsl(230, 8%, 24%);
}
.language-markdown .token.url > .token.content {
color: hsl(221, 87%, 60%);
}
.language-markdown .token.url > .token.url,
.language-markdown .token.url-reference.url {
color: hsl(198, 99%, 37%);
}
.language-markdown .token.blockquote.punctuation,
.language-markdown .token.hr.punctuation {
color: hsl(230, 4%, 64%);
font-style: italic;
}
.language-markdown .token.code-snippet {
color: hsl(119, 34%, 47%);
}
.language-markdown .token.bold .token.content {
color: hsl(35, 99%, 36%);
}
.language-markdown .token.italic .token.content {
color: hsl(301, 63%, 40%);
}
.language-markdown .token.strike .token.content,
.language-markdown .token.strike .token.punctuation,
.language-markdown .token.list.punctuation,
.language-markdown .token.title.important > .token.punctuation {
color: hsl(5, 74%, 59%);
}
/* General */
.token.bold {
font-weight: bold;
}
.token.comment,
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.token.namespace {
opacity: 0.8;
}
/* Plugin overrides */
/* Selectors should have higher specificity than those in the plugins' default stylesheets */
/* Show Invisibles plugin overrides */
.token.token.tab:not(:empty):before,
.token.token.cr:before,
.token.token.lf:before,
.token.token.space:before {
color: hsla(230, 8%, 24%, 0.2);
}
/* Toolbar plugin overrides */
/* Space out all buttons and move them away from the right edge of the code block */
div.code-toolbar > .toolbar.toolbar > .toolbar-item {
margin-right: 0.4em;
}
/* Styling the buttons */
div.code-toolbar > .toolbar.toolbar > .toolbar-item > button,
div.code-toolbar > .toolbar.toolbar > .toolbar-item > a,
div.code-toolbar > .toolbar.toolbar > .toolbar-item > span {
background: hsl(230, 1%, 90%);
color: hsl(230, 6%, 44%);
padding: 0.1em 0.4em;
border-radius: 0.3em;
}
div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover,
div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus,
div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover,
div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus,
div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover,
div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus {
background: hsl(230, 1%, 78%); /* custom: darken(--syntax-bg, 20%) */
color: hsl(230, 8%, 24%);
}
/* Line Highlight plugin overrides */
/* The highlighted line itself */
.line-highlight.line-highlight {
background: hsla(230, 8%, 24%, 0.05);
}
/* Default line numbers in Line Highlight plugin */
.line-highlight.line-highlight:before,
.line-highlight.line-highlight[data-end]:after {
background: hsl(230, 1%, 90%);
color: hsl(230, 8%, 24%);
padding: 0.1em 0.6em;
border-radius: 0.3em;
box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.2); /* same as Toolbar plugin default */
}
/* Hovering over a linkable line number (in the gutter area) */
/* Requires Line Numbers plugin as well */
pre[id].linkable-line-numbers.linkable-line-numbers span.line-numbers-rows > span:hover:before {
background-color: hsla(230, 8%, 24%, 0.05);
}
/* Line Numbers and Command Line plugins overrides */
/* Line separating gutter from coding area */
.line-numbers.line-numbers .line-numbers-rows,
.command-line .command-line-prompt {
border-right-color: hsla(230, 8%, 24%, 0.2);
}
/* Stuff in the gutter */
.line-numbers .line-numbers-rows > span:before,
.command-line .command-line-prompt > span:before {
color: hsl(230, 1%, 62%);
}
/* Match Braces plugin overrides */
/* Note: Outline colour is inherited from the braces */
.rainbow-braces .token.token.punctuation.brace-level-1,
.rainbow-braces .token.token.punctuation.brace-level-5,
.rainbow-braces .token.token.punctuation.brace-level-9 {
color: hsl(5, 74%, 59%);
}
.rainbow-braces .token.token.punctuation.brace-level-2,
.rainbow-braces .token.token.punctuation.brace-level-6,
.rainbow-braces .token.token.punctuation.brace-level-10 {
color: hsl(119, 34%, 47%);
}
.rainbow-braces .token.token.punctuation.brace-level-3,
.rainbow-braces .token.token.punctuation.brace-level-7,
.rainbow-braces .token.token.punctuation.brace-level-11 {
color: hsl(221, 87%, 60%);
}
.rainbow-braces .token.token.punctuation.brace-level-4,
.rainbow-braces .token.token.punctuation.brace-level-8,
.rainbow-braces .token.token.punctuation.brace-level-12 {
color: hsl(301, 63%, 40%);
}
/* Diff Highlight plugin overrides */
/* Taken from https://github.com/atom/github/blob/master/styles/variables.less */
pre.diff-highlight > code .token.token.deleted:not(.prefix),
pre > code.diff-highlight .token.token.deleted:not(.prefix) {
background-color: hsla(353, 100%, 66%, 0.15);
}
pre.diff-highlight > code .token.token.deleted:not(.prefix)::-moz-selection,
pre.diff-highlight > code .token.token.deleted:not(.prefix) *::-moz-selection,
pre > code.diff-highlight .token.token.deleted:not(.prefix)::-moz-selection,
pre > code.diff-highlight .token.token.deleted:not(.prefix) *::-moz-selection {
background-color: hsla(353, 95%, 66%, 0.25);
}
pre.diff-highlight > code .token.token.deleted:not(.prefix)::selection,
pre.diff-highlight > code .token.token.deleted:not(.prefix) *::selection,
pre > code.diff-highlight .token.token.deleted:not(.prefix)::selection,
pre > code.diff-highlight .token.token.deleted:not(.prefix) *::selection {
background-color: hsla(353, 95%, 66%, 0.25);
}
pre.diff-highlight > code .token.token.inserted:not(.prefix),
pre > code.diff-highlight .token.token.inserted:not(.prefix) {
background-color: hsla(137, 100%, 55%, 0.15);
}
pre.diff-highlight > code .token.token.inserted:not(.prefix)::-moz-selection,
pre.diff-highlight > code .token.token.inserted:not(.prefix) *::-moz-selection,
pre > code.diff-highlight .token.token.inserted:not(.prefix)::-moz-selection,
pre > code.diff-highlight .token.token.inserted:not(.prefix) *::-moz-selection {
background-color: hsla(135, 73%, 55%, 0.25);
}
pre.diff-highlight > code .token.token.inserted:not(.prefix)::selection,
pre.diff-highlight > code .token.token.inserted:not(.prefix) *::selection,
pre > code.diff-highlight .token.token.inserted:not(.prefix)::selection,
pre > code.diff-highlight .token.token.inserted:not(.prefix) *::selection {
background-color: hsla(135, 73%, 55%, 0.25);
}
/* Previewers plugin overrides */
/* Based on https://github.com/atom-community/atom-ide-datatip/blob/master/styles/atom-ide-datatips.less and https://github.com/atom/atom/blob/master/packages/one-light-ui */
/* Border around popup */
.prism-previewer.prism-previewer:before,
.prism-previewer-gradient.prism-previewer-gradient div {
border-color: hsl(0, 0, 95%);
}
/* Angle and time should remain as circles and are hence not included */
.prism-previewer-color.prism-previewer-color:before,
.prism-previewer-gradient.prism-previewer-gradient div,
.prism-previewer-easing.prism-previewer-easing:before {
border-radius: 0.3em;
}
/* Triangles pointing to the code */
.prism-previewer.prism-previewer:after {
border-top-color: hsl(0, 0, 95%);
}
.prism-previewer-flipped.prism-previewer-flipped.after {
border-bottom-color: hsl(0, 0, 95%);
}
/* Background colour within the popup */
.prism-previewer-angle.prism-previewer-angle:before,
.prism-previewer-time.prism-previewer-time:before,
.prism-previewer-easing.prism-previewer-easing {
background: hsl(0, 0%, 100%);
}
/* For angle, this is the positive area (eg. 90deg will display one quadrant in this colour) */
/* For time, this is the alternate colour */
.prism-previewer-angle.prism-previewer-angle circle,
.prism-previewer-time.prism-previewer-time circle {
stroke: hsl(230, 8%, 24%);
stroke-opacity: 1;
}
/* Stroke colours of the handle, direction point, and vector itself */
.prism-previewer-easing.prism-previewer-easing circle,
.prism-previewer-easing.prism-previewer-easing path,
.prism-previewer-easing.prism-previewer-easing line {
stroke: hsl(230, 8%, 24%);
}
/* Fill colour of the handle */
.prism-previewer-easing.prism-previewer-easing circle {
fill: transparent;
}

193
plugin/initialize.ts Normal file
View File

@ -0,0 +1,193 @@
import path from "path";
import fs from "fs";
import TsToJson from "./ts2json";
import toc from "markdown-toc";
import objHash from "object-hash";
const hash = (str: string) => "" + objHash(str).substring(0, 6);
/**
* props
*/
interface IPropMeta {
/**
* ts ?
*/
required?: boolean;
/**
* @name
*/
name?: string;
/**
* @type
*/
type?: string;
/**
* @description
*/
description?: string;
/**
* @default
*/
default?: string;
/**
* @version
*/
version?: string | number;
}
interface IProp2jsonItem {
[key: string]: {
props: IPropMeta[];
};
}
interface IRoute {
active: false;
path: string;
name: string;
}
export interface ITree {
/**
* 🎄
*/
compoTree: ICompoTree;
/**
* 🎄
*/
fileTree?: {};
/**
* 🎄 menuTree
*/
routerList: IRoute[];
/**
*
*/
config?: {};
}
interface ITocItem {
/**
* hash
*/
anchor?: string;
/**
* mardkown h标签 innerHTML
*/
content: string;
i: 0;
lvl: 1;
seen: 0;
/**
* innerHTML
*/
slug: "hi";
}
/**
* ts interface json
*/
const tsParser = new TsToJson();
/**
*
*/
const root = path.resolve(process.cwd(), "src/components");
/**
* For HMR
*/
const fileTree = {};
/**
* ICompoTree Item
*/
interface ICompoMetadata {
/**
* props
*/
props: IPropMeta[] | [];
/**
* markdown table of content
*/
toc: ITocItem[];
}
/**
* 🎄
*/
interface ICompoTree {
[key: string]: ICompoMetadata;
}
/**
* markdown table of content
* @param {string} content markdown
*/
const md2toc = (content: string) => {
if (!content) return [];
return toc(content).json;
};
/**
*
* @param {ICompoTree} compoTree
*/
const processRouter = (compoTree: ICompoTree): IRoute[] => {
return Object.keys(compoTree).map((t) => ({
active: false,
path: t.toLowerCase(),
name: t,
}));
};
/**
* props.ts json
* @param {string} path props.ts
* @returns
*/
const parsePropsTs = (path: string) => {
const prop2json: IProp2jsonItem = fs.existsSync(path)
? tsParser.parse(path)
: {};
return Object.values(prop2json)?.[0]?.props ?? [];
};
/**
* mdx
* @param {string} path mdx
*/
const parseToc = (path: string) => {
const isFile = fs.statSync(path).isFile();
const mdxContent = isFile ? fs.readFileSync(path).toString() : "";
// 注入锚点 hash
const toc = md2toc(mdxContent).map((i) => ({
...i,
anchor: `anchor-${hash(i.content)}`,
}));
return toc;
};
const walkCompoMetadata = (dirs: string[]): ICompoTree => {
const compoTree: ICompoTree = {};
dirs.forEach((dir) => {
const mdxFile = path.resolve(root, dir, "index.mdx");
const propFile = path.resolve(root, dir, "props.ts");
fileTree[mdxFile] = dir;
fileTree[propFile] = dir;
const toc = parseToc(mdxFile);
const props = parsePropsTs(propFile);
compoTree[dir] = { toc, props };
});
return compoTree;
};
/**
*
* @returns menuTree: 目录树, markdownHeadings | propsTree: 组件 api
*/
export const initialize = (): ITree => {
const dirs = fs.existsSync(root) ? fs.readdirSync(root) : [];
const compoTree = walkCompoMetadata(dirs);
const routerList = processRouter(compoTree);
return { compoTree, fileTree, routerList };
};

213
plugin/ts2json.ts Normal file
View File

@ -0,0 +1,213 @@
import ts from "typescript";
export interface DocsType {
name?: string;
type?: string;
description?: string;
default?: string;
required?: boolean;
version?: number | string;
}
export interface Interfaces {
name?: {
description?: string;
/** 是否不渲染 markdown */
ignore?: string;
props?: DocsType[];
};
}
/**
* Ts Interface json
*/
export default class TsToJson {
checker: any;
interfaces: any;
fileNames: any;
types: any;
_createProgram(fileNames: string[], options: ts.CompilerOptions) {
this.interfaces = {};
this.types = {};
// 使用filename中的根文件名构建一个程序;
const program = ts.createProgram(fileNames, options);
// 获取检查器,我们将使用它来查找更多关于 ast 的信息
this.checker = program.getTypeChecker();
this.fileNames = fileNames;
// 访问程序中的每个sourceFile
for (const sourceFile of program.getSourceFiles()) {
if (!sourceFile.isDeclarationFile) {
// 遍历树以搜索 interface 和 type
ts.forEachChild(sourceFile, (node) => this._visitAst(node));
}
}
}
/**
* 访
* @param node
* @returns
*/
_visitAst(node: ts.Node) {
// 只考虑导出的节点
if (!this._isNodeExported(node)) {
return;
}
switch (ts.SyntaxKind[node.kind]) {
case "InterfaceDeclaration":
this.generateInterfaceDeclaration(node);
break;
case "TypeAliasDeclaration":
this.generateTypeAliasDeclaration(node);
break;
default:
break;
}
}
/**
* Interface json
* @param node
*/
generateInterfaceDeclaration(node: ts.Node) {
if (
// @ts-ignore
node.parent?.fileName.replace(/\\/g, "/") !==
this.fileNames[0].replace(/\\/g, "/")
)
return;
const newNode = node as ts.InterfaceDeclaration;
const symbol = this.checker.getSymbolAtLocation(newNode.name);
const { escapedName } = symbol;
const { name = escapedName as string, ...rest } = this._getDocs(symbol);
this.interfaces[name] = {
...rest,
props: [],
};
if (newNode.heritageClauses) {
// 是否有extends
const firstHeritageClause = newNode.heritageClauses[0];
const firstHeritageClauseType = firstHeritageClause.types![0];
const extendsType = this.checker.getTypeAtLocation(
firstHeritageClauseType.expression
);
if (extendsType?.symbol?.members && extendsType.symbol.members.size > 0) {
// 接口 extends 接口Interface
extendsType.symbol.members.forEach((member: any) => {
this.interfaces[name].props.push(this._serializeSymbol(member));
});
} else if (firstHeritageClauseType) {
console.log("暂时不支持接口继承Type类型");
// 接口 extends 类型Type
// const type = this.checker.typeToTypeNode(
// this.checker.getTypeAtLocation(firstHeritageClauseType),
// firstHeritageClauseType,
// ts.NodeBuilderFlags.InTypeAlias
// // ts.TypeFormatFlags.InTypeAlias
// );
// console.log(firstHeritageClauseType.getFullText());
}
}
symbol.members.forEach((member: any) => {
this.interfaces[name].props.push(this._serializeSymbol(member));
});
}
/**
* Type json
* @param node
*/
generateTypeAliasDeclaration(node: ts.Node) {
const type = this.checker.typeToString(
this.checker.getTypeAtLocation(node),
node,
ts.TypeFormatFlags.InTypeAlias
);
const name = (node as any).name.escapedText;
this.types[name] = type;
}
/**
* symbol序列化为json对象
* @param symbol
* @returns
*/
_serializeSymbol(symbol: ts.Symbol): DocsType | undefined {
if (!symbol) return;
const docs = this._getDocs(symbol);
const questionToken = (symbol as any).valueDeclaration?.questionToken;
const type = this.checker.typeToString(
this.checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration)
);
let newType = type;
if (type.indexOf("|") > 0) {
newType = type
.split("|")
.map((item: any) => {
const hasType = this.types[item.trim()];
if (hasType) {
return ` ${hasType} `;
}
return item;
})
.join("|");
} else if (this.types[type]) {
newType = this.types[type];
}
return {
name: symbol.getName(),
required: !questionToken,
type: newType,
...docs,
};
}
/**
* jsDoc信息
* @param symbol
* @returns
*/
_getDocs(symbol: ts.Symbol): DocsType {
const docs: DocsType = {};
if (
symbol.getJsDocTags &&
Array.isArray(symbol.getJsDocTags(this.checker))
) {
symbol.getJsDocTags(this.checker).forEach((tag) => {
(docs as any)[tag.name] = tag.text && tag.text[0].text;
});
}
return docs;
}
/**
* Truefalse
* @param node
* @returns boolean
*/
_isNodeExported(node: ts.Node): boolean {
return (
(ts.getCombinedModifierFlags(node as ts.Declaration) &
ts.ModifierFlags.Export) !==
0 ||
(!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile)
);
}
parse(
fileNames: string,
options: ts.CompilerOptions = {
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.CommonJS,
}
) {
this._createProgram([fileNames], options);
return this.interfaces;
}
}

116
plugin/types/index.ts Normal file
View File

@ -0,0 +1,116 @@
export interface ITocItem {
/**
* hash
*/
anchor: string;
/**
* mardkown h标签 innerHTML
*/
content: string;
i: 0;
lvl: 1;
seen: 0;
/**
* innerHTML
*/
slug: "hi";
/**
*
*/
active?: boolean;
}
/**
* props
*/
export interface IPropMeta {
/**
* ts ?
*/
required?: boolean;
/**
* @name
*/
name?: string;
/**
* @type
*/
type?: string;
/**
* @description
*/
description?: string;
/**
* @default
*/
default?: string;
/**
* @version
*/
version?: string | number;
}
/**
* 🎄
*/
export interface ICompoTree {
[key: string]: ICompoMetadata;
}
/**
* ICompoTree Item
*/
export interface ICompoMetadata {
/**
* props
*/
props: IPropMeta[] | [];
/**
* markdown table of content
*/
toc: [];
}
export interface ITree {
/**
* 🎄
*/
compoTree: ICompoTree;
/**
* 🎄
*/
fileTree?: {};
/**
* 🎄 menuTree
*/
routerList: IRoute[];
/**
*
*/
config?: {
title: string;
subTitle: string;
};
}
export interface IRoute {
name: string;
path: string;
active: boolean;
}
export type TToc = {
content: string;
slug: string;
lvl: 1 | 2;
i?: number;
seen?: number;
active?: boolean;
anchor: string;
};
export interface ICustomPage {
path: string;
src: string;
title: string;
}

View File

@ -0,0 +1,77 @@
import { initialize, ITree } from "./initialize";
interface IMozzieOptions {
virtualModuleName?: string;
config?: any;
}
export interface IMeta {
name: string;
required: boolean;
type: string;
[key: string]: any;
}
export interface IRoute {
name: string;
path: string;
active: boolean;
}
export interface IConfigs {
config: {
title: string; // document title
subTitle: string; // 文档标题
};
}
export default function Bolt(options: IMozzieOptions) {
const { config = {} } = options;
const virtualModuleId = "virtual:@vite/plugin-bolt";
const resolvedVirtualModuleId = "\0" + virtualModuleId;
const TREE: ITree = {
compoTree: {},
fileTree: {},
routerList: [],
config: {},
};
return {
name: "@vite/plugin-bolt",
enforce: "pre",
resolveId(id: string) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId;
}
},
load(id: string) {
if (id === resolvedVirtualModuleId) {
const { compoTree, fileTree, routerList } = initialize();
TREE.fileTree = fileTree;
TREE.compoTree = compoTree;
TREE.routerList = routerList;
TREE.config = config;
// build 后也存在 config
return `export const TREE = ${JSON.stringify(TREE)}`;
}
},
handleHotUpdate(ctx: any) {
const { file, server } = ctx;
//! 开发阶段:
//* 由于 上面load方法 只会触发一次,导致写到前端的 TREE 对象,无法更改
//* 当动态增减 components 组件,默认会先使用 TREE然后触发 client:refresh 方法,获取到最新的 compoTree
//* 导致左侧菜单会变化,目前采用 loading 遮住这个跳变的过程
server.ws.send("server:fullUpdate", initialize());
//! return [] 会让 mdx-plugin 不更新
},
configureServer(server) {
server.ws.on("client:refresh", (data, client) => {
client.send("client:refresh:response", initialize());
});
},
transformIndexHtml(html) {
const { title } = config;
return html.replace(/<title>(.*?)<\/title>/, `<title>${title}</title>`);
},
};
}

5386
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

24
readme.md Normal file
View File

@ -0,0 +1,24 @@
# quick start
```bash
pnpm install
```
# 构建产物
```bash
pnpm build
```
- dist 文件夹 `doc` 是文档站,直接部署到 `web`容器。路由 `history`模式,配合 nginx 开启 `nginx 伪静态`
- dist 文件夹 `ui` 为 组件库 ,直接`ESModule`引入项目
- `src/pages` 文件夹自定义页面,`bolt.config.ts` 配置引入
- `src/components` 为组件开发目录,基本组成:
- index.tsx 组件的代码
- index.less 组件的样式
- props.ts 组件 api 接口
- index.mdx 组件文档说明

174
src/App.tsx Normal file
View File

@ -0,0 +1,174 @@
import { MDXProvider } from "@mdx-js/react";
import { TREE } from "@bolt/config";
import { lazy, Suspense, useEffect, useState } from "react";
import { Route, Routes, useLocation } from "react-router";
import {
ICompoTree,
ICustomPage,
IPropMeta,
IRoute,
ITocItem,
ITree,
} from "../plugin/types";
import ApiTable from "../plugin/common/ApiTable";
import Aside from "../plugin/common/Aside";
import objHash from "object-hash";
import Toc from "../plugin/common/MdToc";
import Nav from "../plugin/common/Nav";
const hash = (str: string) => "" + objHash(str).substring(0, 6);
function App() {
const location = useLocation();
const [customPages, setCustomPages] = useState<ICustomPage[]>(
TREE.config.customPages
);
const [compoTree, setCompoTree] = useState<ICompoTree>(TREE.compoTree);
const [routerList, setRouterList] = useState<IRoute[]>(TREE.routerList);
const [toc, setToc] = useState<ITocItem[]>([]);
const [propsTable, setPropsTable] = useState<IPropMeta[]>([]);
const [isPage, setIsPage] = useState(true);
/**
* vite-server
*/
useEffect(() => {
if (import.meta.hot) import.meta.hot.send("client:refresh");
}, []);
/**
* vite-server TREE
*/
useEffect(() => {
if (import.meta.hot) {
import.meta.hot.on("client:refresh:response", (response: ITree) => {
console.log("refresh", response);
const { routerList, compoTree } = response;
setRouterList(routerList);
setCompoTree(compoTree);
});
}
}, []);
/**
* vite-server
*/
useEffect(() => {
if (import.meta.hot) {
import.meta.hot.on("server:fullUpdate", (response: ITree) => {
console.log("fullUpdate", response);
const { routerList, compoTree } = response;
setRouterList(routerList);
setCompoTree(compoTree);
});
}
}, []);
const getCurrent = () => {
const path = location.pathname.replace(/\//, "");
const route = routerList.find((i) => i.path === path);
const compoName = route?.name ?? "";
setIsPage(compoName === "");
return { path, route, compoName };
};
useEffect(() => {
const { compoName } = getCurrent();
if (!!compoName) {
const { toc, props } = compoTree[compoName];
setPropsTable(props);
setToc(toc);
} else {
setToc([]);
}
}, [compoTree, location.pathname]);
useEffect(() => {
setRouterList((p) =>
p.map((i) => ({ ...i, active: location.pathname.indexOf(i.path) > -1 }))
);
}, [compoTree]);
const H1 = (args: any) => {
const anchorHash = "anchor-" + hash(args.children);
const attr = { name: anchorHash };
return <h1 {...attr}>{args.children}</h1>;
};
const H2 = (args: any) => {
const anchorHash = "anchor-" + hash(args.children);
const attr = { name: anchorHash };
return <h2 {...attr}>{args.children}</h2>;
};
const lazyLoad = (modulePath: string) => {
const Module = lazy(() =>
new Promise((resolve) => setTimeout(resolve, 150)).then(() =>
import(`./components/${modulePath}/index.mdx`).then((m) => m)
)
);
return (
<Suspense fallback={<div className="page-loading" />}>
<MDXProvider components={{ h1: H1, h2: H2 }}>
<Module />
</MDXProvider>
</Suspense>
);
};
const lazyLoadPage = (src: string) => {
const Module = lazy(() =>
new Promise((resolve) => setTimeout(resolve, 150)).then(() =>
import(`./pages/${src}.mdx`).then((m) => m)
)
);
return (
<Suspense fallback={<div className="page-loading" />}>
<MDXProvider components={{ h1: H1, h2: H2 }}>
<Module />
</MDXProvider>
</Suspense>
);
};
return (
<div className="root-container">
{/* nav */}
<Nav customPages={customPages} />
{/* 左侧导航栏 */}
<Aside
menus={routerList}
subTitle={TREE.config.subTitle}
version={TREE.config.version}
/>
<main>
<article className="markdown-container">
<Routes>
{/* 自定义页面 */}
{customPages.map((page: ICustomPage) => (
<Route
key={page.path}
path={page.path}
element={lazyLoadPage(page.src)}
/>
))}
{/* 组件 */}
{routerList.map((route: IRoute) => (
<Route
key={route.name}
path={route.path}
element={lazyLoad(route.name)}
/>
))}
</Routes>
</article>
{/* 组件 props 表 */}
{!isPage && <ApiTable propList={propsTable} />}
{/* table of content */}
{toc.length > 0 && <Toc toc={toc} />}
</main>
</div>
);
}
export default App;

1
src/assets/common.less Normal file
View File

@ -0,0 +1 @@
@import "./var.less";

13
src/assets/var.less Normal file
View File

@ -0,0 +1,13 @@
:root {
--bolt-primary-color: #1890ff;
--bolt-primary-color-hover: #40a9ff;
--bolt-primary-color-active: #096dd9;
--bolt-primary-color-1: #e6f7ff;
--bolt-primary-color-2: #bae7ff;
--bolt-primary-color-3: #91d5ff;
--bolt-primary-color-4: #69c0ff;
--bolt-primary-color-5: #40a9ff;
--bolt-primary-color-6: #1890ff;
--bolt-primary-color-7: #096dd9;
--bolt-border-radius: 2px;
}

View File

@ -0,0 +1,14 @@
import Button from "..";
export default function test() {
return (
<div className="box">
<p>
<Button size={"small"}></Button>
</p>
<p>
<Button size={"large"}></Button>
</p>
</div>
);
}

View File

@ -0,0 +1,42 @@
@import "../../assets/common.less";
button {
transition: all .25s ease-in-out;
position: relative;
font-size: 13px;
height: 32px;
padding: 4px 15px;
background: var(--bolt-primary-color);
border: 0;
color: #fff;
border-radius: var(--bolt-border-radius);
cursor: pointer;
&:hover {
background: var(--bolt-primary-color-hover);
}
&:active {
background: var(--bolt-primary-color-active);
}
&.halo {
&::after {
position: absolute;
content: "";
left: 0;
right: 0;
top: 0;
bottom: 0;
border-color: var(--bolt-primary-color-hover);
border-radius: 2px;
animation: ani-halo 0.25s 1;
}
}
}
@keyframes ani-halo {
0% {
box-shadow: 0 0 0 0px rgb(24 144 255 / 60%);
}
100% {
box-shadow: 0 0 0 4px rgb(24 144 255 / 0%);
}
}

View File

@ -0,0 +1,114 @@
import Code from "../../../plugin/common/Code.tsx";
import Demo1 from "./demo/demo1.tsx";
import Demo1Code from "./demo/demo1.tsx?raw";
# Hi
按钮组件测试
<Demo1 />
<Code raw={Demo1Code} lang="ts" />
## 使用说明
间距预设大、中、小三种大小。[链接](/input)
通过设置 `size`、`large`、`middle` 分别把间距设为大、中间距。若不设置 `size`, 则间距为小。
## 使用 useage
```bash
# npm install pnpm -g
pnpm install
```
```css
html,
body {
margin: 0;
}
```
## 测试 toc
是的吗,好厉害
## JJ
```ts
const a = 1;
const a = 3;
```
## Emphasis
**This is bold text**
**This is bold text**
_This is italic text_
_This is italic text_
~~Strikethrough~~
## Blockquotes
> Blockquotes can also be nested...
>
> > by using additional greater-than signs right next to each other...
> >
> > > ..or with spaces between arrows.
## Lists
Unordered
- Create a list by starting a line with `+`, `-`, or `*`
- Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
- Ac tristique libero volutpat at
* Facilisis in pretium nisl aliquet
- Nulla volutpat aliquam velit
- Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
4. You can use sequential numbers...
5. ...or keep all the numbers as `1.`
Start numbering with offset:
57. foo
1. bar
## Code
Inline `code`
Indented code
// Some comments
line 1 of code
line 2 of code
line 3 of code
Block code "fences"
```
Sample text here...
```
Syntax highlighting
```js
var foo = function (bar) {
return bar++;
};
console.log(foo(5));
```

View File

@ -0,0 +1,35 @@
import { useRef, useState } from "react";
import "./index.less";
import { IButton } from "./props";
/**
* Button组件
*/
export default function Button(props: IButton) {
const sizeTable = {
small: "12px",
large: "16px",
};
const [animation, setAnimation] = useState(false);
const onClick = () => {
setAnimation(true);
setTimeout(() => {
setAnimation(false);
}, 250);
console.log("click event handler");
};
return (
<>
{
<button
onClick={onClick}
className={animation ? "halo" : ""}
style={{ fontSize: sizeTable[props.size] }}
>
{props.children}
</button>
}
</>
);
}

View File

@ -0,0 +1,29 @@
type TSize = "small" | "large";
export interface IButton {
children: string;
/**
* @description san
* @default small
*/
size: TSize;
type?: "primary" | "danger";
omg?: 1 | 2 | 3;
and?: Array<number> | Array<string>;
/**
* @description
* @default false
*/
disabled?: boolean;
/**
* @description
* @param {Event} e dom对象
* @version 3.0
*/
onClick?: (e: Event, callback: () => {}) => {};
}

View File

@ -0,0 +1,8 @@
import Input from "..";
export default function demo1() {
return (
<div>
<Input size="small" />
</div>
);
}

View File

@ -0,0 +1,3 @@
.bolt-input {
border: 1px solid red;
}

View File

@ -0,0 +1,15 @@
import Code from "../../../plugin/common/Code.tsx";
import Demo1 from "./demo/demo1.tsx";
import Demo1Code from "./demo/demo1.tsx?raw";
## Input
<Demo1 />
<Code raw={Demo1Code} lang="ts" />
开动小脑静,冷静的思考下,这个问题一定可以不被解决掉
- 开发阶段 HMR 会重置路由的状态
- 切换路由懒加载, markdown 文字区域目录生成的问题
> 冲啊Fighting 渣渣,我到河北省来

View File

@ -0,0 +1,6 @@
import { IInput } from "./props";
import "./index.less";
export default function Input(props: IInput) {
return <input className="bolt-input" type="text" placeholder="待封装" />;
}

View File

@ -0,0 +1,7 @@
export interface IInput {
/**
* @description大小的属性
* @default small
*/
size: "small" | "large";
}

View File

View File

@ -0,0 +1,3 @@
# switch
1112222

View File

@ -0,0 +1,5 @@
import { ISwitch } from "./props";
export default function Switch(props: ISwitch) {
return <div>Switch</div>;
}

View File

@ -0,0 +1,11 @@
export interface ISwitch {
/**
* @description
*/
checked: boolean;
/**
* @description 2
*/
test: 1 | 2 | 3
}

5
src/index.ts Normal file
View File

@ -0,0 +1,5 @@
import Button from "./components/Button";
import Input from "./components/Input";
import Switch from "./components/Switch";
export { Button, Input, Switch };

13
src/main.tsx Normal file
View File

@ -0,0 +1,13 @@
import ReactDOM from "react-dom/client";
import App from "./App";
import "../plugin/common/style/index.less";
import { BrowserRouter as Router } from "react-router-dom";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
// 会执行两次
// <React.StrictMode>
<Router>
<App />
</Router>
// </React.StrictMode>
);

3
src/pages/Intro.mdx Normal file
View File

@ -0,0 +1,3 @@
# 介绍
滴滴滴滴

46
src/pages/Welcome.mdx Normal file
View File

@ -0,0 +1,46 @@
# Bolt
Bolt 是快速简单的组件库开发脚手架,用于构建快速组件库、文档。你可以在开发`react组件库`的同时, 无缝地编写`组件文档`, 实时热更新
# ✨ 特性
- 🌈 使用 `markdown` 语法编写组件文档
- ⚙️ 使用 `mdx` 扩展了 `markdown` 的能力, 你可以将 `Jsx.Element` 引入 `markdown`
- 🛡 摒弃 `PropTypes`, 使用 `typescript` 约束组件 `props`, 组件文档自动化生成相应 `API`
- 📦 借助 `vite`、`rollup`能力, `文档站`,`组件库`一并构建产出
# MDX
`MDX` 允许在降价内容中使用 JSX。您可以导入组件, 例如交互式图表或警报, 并将其嵌入到内容中。
```md
import Code from "./Code.tsx";
# Test
mdx 文件引入 react 组件示例
<Code raw={Demo1Code} lang="ts" />
```
# 项目结构
```bash
├── bolt.config.ts # bolt 基本配置
├── dist
│ ├── doc # 文档静态资源构建产物
│ └── ui # 组件库构建产物
├── src
│ ├── pages # 自定义页面
│ │ ├── Intro.mdx
│ │ └── Welcome.mdx
│ ├── assets # 静态资源
│ │ └── common.less
│ ├── components # 组件开发目录
│ │ ├── Button
│ │ │ ├── index.less # 样式
│ │ │ ├── index.mdx # 组件文档
│ │ │ ├── index.tsx # 组件
│ │ │ └── props.ts # 组件 props
│ ├── index.ts # 组件是否打包进组件库
```

9
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
/// <reference types="vite/client" />
declare module "@bolt/config" {
export const TREE;
}
declare module "*.mdx" {
let MDXComponent: (props: any) => JSX.Element;
export default MDXComponent;
}

39
tsconfig.json Normal file
View File

@ -0,0 +1,39 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": [
"DOM",
"DOM.Iterable",
"ESNext"
],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
},
"include": [
"src",
"lib",
"common",
"plugin/vite-plugin-mozzie",
"plugin/ui",
"plugin/types",
],
"references": [
{
"path": "./tsconfig.node.json"
}
],
"path": {
"@bolt/config": "virtual:@vite/plugin-bolt",
"@": "src"
}
}

13
tsconfig.lib.json Normal file
View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"declaration": true, // d.ts
"noEmit": true, //
"removeComments": true,
"jsx": "react-jsx",
"outDir": "./dist/BoltUI"
},
"include": [
"src/components/**/*.tsx",
"src/components/**/*.ts",
],
}

14
tsconfig.node.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": [
"vite.config.ts",
"bolt.config.ts",
"plugin",
"plugin/build",
]
}

9
tsconfig.prod.json Normal file
View File

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": false, // d.ts
"noEmit": true, //
"removeComments": true,
"outDir": "./dist/doc"
},
}

62
vite.config.ts Normal file
View File

@ -0,0 +1,62 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import Bolt from "./plugin/vite-plugin-bolt";
import rehypePrism from "@mapbox/rehype-prism";
import { build } from "./plugin/build/vite.conf";
import dts from "vite-plugin-dts";
import BoltConfig from "./bolt.config";
// https://vitejs.dev/config/
export default defineConfig(async ({ mode }): Promise<{}> => {
const mdx = await import("@mdx-js/rollup");
return {
server: {
watch: {
ignored: ["!**/dist/**"],
},
},
plugins: [
process.env.MODE !== "production"
? react({
jsxRuntime: "classic",
})
: react(),
mdx.default({
jsxRuntime: "automatic",
providerImportSource: "@mdx-js/react",
rehypePlugins: [rehypePrism], // syntax highlight会compile pre > code
remarkPlugins: [],
}),
Bolt({ config: BoltConfig }),
mode === "lib" &&
dts({
entryRoot: "src",
outputDir: "dist/ui",
}),
mode !== "doc" && {
name: "style",
generateBundle(config, bundle) {
//这里可以获取打包后的文件目录以及代码code
const keys = Object.keys(bundle);
for (const key of keys) {
const bundler: any = bundle[key as any];
//rollup内置方法,将所有输出文件code中的.less换成.css
//TODO 导致sourcemap 文件丢失,待解决
this.emitFile({
type: "asset",
fileName: key, //文件名名不变
source: bundler.code.replace(/\.less/g, ".css"),
});
}
},
},
].filter(Boolean),
resolve: {
alias: {
"@bolt/config": "virtual:@vite/plugin-bolt",
"@": "src",
},
},
build: build(mode),
};
});