Skip to content
This repository was archived by the owner on Aug 8, 2024. It is now read-only.

Commit 3db32a1

Browse files
authored
refactor: create routes from dir (#2)
1 parent b857028 commit 3db32a1

15 files changed

+289
-38
lines changed

README.md

+27-24
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ Markdown(\*.md) component plugin for umi.
44

55
*Create your website with umi and markdown only. Convenient and powerful for blog, documentation site and GitBook.*
66

7-
* **markdown-it:** powered by markdown-it
8-
* **highlight:** highlight render
9-
* **xss:** protect by [xss](https://www.npmjs.com/package/xss)
7+
* **Convert markdown into component:** loaded by `markdown-it` and translate into React component.
8+
* **Auto routes create:** auto create markdown routes from dir.
9+
* **XSS protect:** by [xss](https://www.npmjs.com/package/xss).
1010

1111
![Example](https://raw.githubusercontent.com/chiaweilee/umi-plugin-md/master/Screenshot%202019-07-08%20at%2021.15.41.png)
1212

@@ -21,41 +21,44 @@ npm install umi-plugin-md
2121
```js
2222
// .umirc.js
2323
export default {
24-
plugins: [
25-
[
26-
`umi-plugin-md`,
27-
{
28-
// option
29-
wrapper: 'section',
30-
className: 'markdown-body',
31-
},
32-
],
33-
],
24+
plugins: ['umi-plugin-md'],
3425
};
3526
```
3627

37-
That's it!
38-
Try create a markdown file under `pages`, and `npm start` then
28+
## Options
3929

40-
## Stylize
30+
option | intro | type | default
31+
-|-|-|-
32+
wrapper | HTMLElementTagName |string | section |
33+
className | React className | string | |
34+
style | React style | object | |
35+
html | markdown-it option | boolean | true |
36+
xhtmlOut | markdown-it option | boolean | true |
37+
breaks | markdown-it option | boolean | true |
38+
linkify | markdown-it option | boolean | true |
39+
typographer | markdown-it option | boolean | true |
40+
highlight | markdown-it option | function | highlight.js |
4141

42-
*wanna style for markdown?*
42+
## Stylize
4343

44-
Suggest, [github-markdown-css](https://www.npmjs.com/package/github-markdown-css)
44+
[github-markdown-css](https://www.npmjs.com/package/github-markdown-css)
4545

4646
```css
4747
// global.css
4848
@import "~github-markdown-css/github-markdown.css";
4949
```
5050

51-
and set plugin option `className: 'markdown-body'`
51+
```js
52+
// .umirc.js
53+
export default {
54+
plugins: [['umi-plugin-md', { className: 'markdown-body' }]],
55+
};
56+
```
5257

5358
## Layout
5459

55-
*Wanna layout for markdown?*
56-
57-
Read this, [umijs#different-global-layout](https://umijs.org/guide/router.html#different-global-layout)
60+
Try `_layout.js`.
5861

59-
## todo
62+
## Example
6063

61-
- abandon 'patch.js', gonna to be graceful. [addrouterimport](https://umijs.org/plugin/develop.html#addrouterimport) / [modifyroutes](https://umijs.org/plugin/develop.html#modifyroutes)
64+
See [pages](https://github.com/chiaweilee/umi-plugin-md/tree/master/src/pages)

package.json

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,31 @@
11
{
22
"name": "umi-plugin-md",
3-
"version": "0.0.0-beta.12",
3+
"version": "0.1.0-beta.1",
44
"description": "Markdown(*.md) component plugin for umi.",
55
"scripts": {
6-
"postinstall": "node ./scripts/patch.js",
76
"prepublishOnly": "npm run build",
87
"lint": "npm run lint:es && npm run lint:ts",
98
"lint:es": "eslint --ext .js --ext .jsx . --fix",
109
"lint:ts": "tslint \"src/**/*.ts\" \"src/**/*.tsx\" --fix",
1110
"prestart": "npm run build",
12-
"start": "DEBUG=loader umi dev",
13-
"test": "DEBUG=loader umi test --coverage",
11+
"start": "umi dev",
12+
"test": "umi test --coverage",
1413
"build": "npm run build:babel",
1514
"build:babel": "npx babel src --out-dir lib --extensions \".ts\"",
1615
"precommit": "lint-staged"
1716
},
1817
"main": "lib/index.js",
1918
"files": [
20-
"lib",
21-
"scripts"
19+
"lib"
2220
],
2321
"dependencies": {
2422
"@babel/core": "^7.3.4",
2523
"@babel/plugin-transform-react-jsx": "^7.3.0",
2624
"highlight.js": "^9.15.8",
2725
"loader-utils": "^1.2.3",
26+
"lodash": "^4.17.14",
2827
"markdown-it": "^8.4.2",
29-
"umi-build-dev": "^1.10.9",
28+
"umi-utils": "^1.5.1",
3029
"xss": "^1.0.6"
3130
},
3231
"peerDependencies": {

scripts/patch.js

-1
This file was deleted.

src/getRouteConfigFromDir.ts

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { readdirSync, statSync, readFileSync } from 'fs';
2+
import { join, extname, basename, relative } from 'path';
3+
import { winPath, findJS } from 'umi-utils';
4+
import assert from 'assert';
5+
6+
const JS_EXTNAMES = ['.md'];
7+
8+
export function sortRoutes(routes) {
9+
const paramsRoutes = [];
10+
const exactRoutes = [];
11+
const layoutRoutes = [];
12+
13+
routes.forEach(route => {
14+
const { _isParamsRoute, exact } = route;
15+
if (_isParamsRoute) {
16+
paramsRoutes.push(route);
17+
} else if (exact) {
18+
exactRoutes.push(route);
19+
} else {
20+
layoutRoutes.push(route);
21+
}
22+
});
23+
24+
assert(paramsRoutes.length <= 1, `We should not have multiple dynamic routes under a directory.`);
25+
26+
return [...exactRoutes, ...layoutRoutes, ...paramsRoutes].reduce((memo, route) => {
27+
if (route._toMerge) {
28+
memo = memo.concat(route.routes);
29+
} else {
30+
delete route._isParamsRoute;
31+
memo.push(route);
32+
}
33+
return memo;
34+
}, []);
35+
}
36+
37+
export default function getRouteConfigFromDir(paths) {
38+
const { cwd, absPagesPath, absSrcPath, dirPath = '' } = paths;
39+
const absPath = join(absPagesPath, dirPath);
40+
const files = readdirSync(absPath);
41+
42+
const absLayoutFile = findJS(absPagesPath, '_layout');
43+
if (absLayoutFile) {
44+
throw new Error('root _layout.js is not supported, use layouts/index.js instead');
45+
}
46+
47+
const routes = sortRoutes(
48+
files
49+
.filter(file => {
50+
if (
51+
file.charAt(0) === '.' ||
52+
file.charAt(0) === '_' ||
53+
/\.(test|spec)\.(j|t)sx?$/.test(file)
54+
) {
55+
return false;
56+
}
57+
return true;
58+
})
59+
.reduce(handleFile.bind(null, paths, absPath), []),
60+
);
61+
62+
if (dirPath === '' && absSrcPath) {
63+
const globalLayoutFile =
64+
findJS(absSrcPath, 'layouts/index') || findJS(absSrcPath, 'layout/index');
65+
if (globalLayoutFile) {
66+
const wrappedRoutes = [];
67+
addRoute(
68+
wrappedRoutes,
69+
{
70+
path: '/',
71+
component: `./${winPath(relative(cwd, globalLayoutFile))}`,
72+
routes,
73+
},
74+
{
75+
componentFile: globalLayoutFile,
76+
},
77+
);
78+
return wrappedRoutes;
79+
}
80+
}
81+
82+
return routes;
83+
}
84+
85+
function handleFile(paths, absPath, memo, file) {
86+
const { cwd, absPagesPath, dirPath = '' } = paths;
87+
const absFilePath = join(absPath, file);
88+
const stats = statSync(absFilePath);
89+
const isParamsRoute = file.charAt(0) === '$';
90+
91+
if (stats.isDirectory()) {
92+
const newDirPath = join(dirPath, file);
93+
// routes & _layout
94+
const routes = getRouteConfigFromDir({
95+
...paths,
96+
dirPath: newDirPath,
97+
});
98+
const absLayoutFile = findJS(join(absPagesPath, newDirPath), '_layout');
99+
if (absLayoutFile) {
100+
addRoute(
101+
memo,
102+
{
103+
path: normalizePath(newDirPath),
104+
exact: false,
105+
component: `./${winPath(relative(cwd, absLayoutFile))}`,
106+
routes,
107+
_isParamsRoute: isParamsRoute,
108+
},
109+
{
110+
componentFile: absLayoutFile,
111+
},
112+
);
113+
} else {
114+
memo.push({
115+
_toMerge: true,
116+
path: normalizePath(newDirPath),
117+
exact: true,
118+
_isParamsRoute: isParamsRoute,
119+
routes,
120+
});
121+
}
122+
} else if (stats.isFile() && isValidJS(file)) {
123+
const bName = basename(file, extname(file));
124+
const path = normalizePath(join(dirPath, bName));
125+
addRoute(
126+
memo,
127+
{
128+
path,
129+
exact: true,
130+
component: `./${winPath(relative(cwd, absFilePath))}`,
131+
_isParamsRoute: isParamsRoute,
132+
},
133+
{
134+
componentFile: absFilePath,
135+
},
136+
);
137+
}
138+
139+
return memo;
140+
}
141+
142+
function normalizePath(path) {
143+
let newPath = `/${winPath(path)
144+
.split('/')
145+
.map(_ => _.replace(/^\$/, ':').replace(/\$$/, '?'))
146+
.join('/')}`;
147+
148+
// /index/index -> /
149+
if (newPath === '/index/index') {
150+
newPath = '/';
151+
}
152+
// /xxxx/index -> /xxxx/
153+
newPath = newPath.replace(/\/index$/, '/');
154+
155+
// remove the last slash
156+
// e.g. /abc/ -> /abc
157+
if (newPath !== '/' && newPath.slice(-1) === '/') {
158+
newPath = newPath.slice(0, -1);
159+
}
160+
161+
return newPath;
162+
}
163+
164+
function addRoute(memo, route, { componentFile }) {
165+
memo.push({
166+
...route,
167+
});
168+
}
169+
170+
function isValidJS(file) {
171+
return JS_EXTNAMES.includes(extname(file));
172+
}

src/index.ts

+22-6
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,35 @@
11
import { IApi } from 'umi-plugin-types';
2-
import patch from './patch';
2+
import getRouteConfigFromDir from './getRouteConfigFromDir';
3+
import mergeRoute from './mergeRoute';
34

45
const path = require('path');
56
const loader = path.join(__dirname, './loader');
67

78
export interface IOption {
89
wrapper?: string;
910
className?: string;
10-
style?: string;
11+
style?: object;
1112
}
1213

13-
export default function(api: IApi, option = {} as IOption) {
14-
// patch getRouteConfigFromDir
15-
api.onStart(() => {
16-
patch(api.debug);
14+
interface ExIApi extends IApi {
15+
service: Service;
16+
}
17+
18+
interface Service {
19+
paths: Paths;
20+
}
21+
22+
interface Paths {
23+
cwd: string;
24+
absPagesPath: string;
25+
absSrcPath: string;
26+
dirPath?: string;
27+
}
28+
29+
export default function(api: ExIApi, option = {} as IOption) {
30+
api.modifyRoutes((routes: any[]) => {
31+
const mdRoutes = getRouteConfigFromDir(api.service.paths);
32+
return mergeRoute(mdRoutes, routes);
1733
});
1834

1935
// url-loader excludes

src/mergeRoute.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const forEach = require('lodash/forEach');
2+
const every = require('lodash/every');
3+
const sortBy = require('lodash/sortBy');
4+
5+
interface Routes {
6+
path: string;
7+
routes?: Routes;
8+
}
9+
10+
export default function mergeRoute(source: Routes[], target: Routes[]): Routes[] {
11+
if (!Array.isArray(source)) { source = []; }
12+
if (!Array.isArray(target)) { target = []; }
13+
14+
forEach(source, s => {
15+
const max = target.length;
16+
every(target, (t, i) => {
17+
if ((t.path === s.path)) {
18+
target[i] = s;
19+
if (Array.isArray(t.routes) || Array.isArray(s.routes)) {
20+
// @ts-ignore
21+
target[i].routes = mergeRoute(s.routes, t.routes);
22+
}
23+
return false;
24+
} else if (max === i + 1) {
25+
target.push(s);
26+
}
27+
return true;
28+
});
29+
});
30+
return sortBy(target, 'path');
31+
}

src/pages/demo.jsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function() {
2+
return <div>demo</div>;
3+
}

src/pages/doc/_layout.jsx

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default function(props) {
2+
return (
3+
<div>
4+
<h3>Docs</h3>
5+
{props.children}
6+
</div>
7+
);
8+
}

src/pages/doc/deep/_layout.jsx

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default function(props) {
2+
return (
3+
<div>
4+
<h3>Docs</h3>
5+
{props.children}
6+
</div>
7+
);
8+
}

src/pages/doc/deep/demo.jsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function() {
2+
return <div>demo</div>;
3+
}

src/pages/doc/deep/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*Hello World!*

src/pages/doc/demo.jsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function() {
2+
return <div>demo</div>;
3+
}

0 commit comments

Comments
 (0)