feat: prototype blog & docs generation on dev

This commit is contained in:
endiliey 2018-08-05 18:59:23 +08:00
parent 187a46d9b6
commit 221023fd51
12 changed files with 181 additions and 37 deletions

11
lib/core/blog/index.js Normal file
View file

@ -0,0 +1,11 @@
import React from 'react';
import MarkdownBlock from '../markdown';
class Docs extends React.Component {
render() {
const {content, siteConfig} = this.props;
return <MarkdownBlock siteConfig={siteConfig}>{content}</MarkdownBlock>;
}
}
module.exports = Docs;

View file

@ -1,13 +0,0 @@
import React from 'react';
import blogDatas from '@generated/blogDatas';
// inner blog component for the article itself, without sidebar/header/footer
class BlogPost extends React.Component {
render() {
const {match} = this.props;
const post = blogDatas.find(blog => blog.path === match.path);
return <div className="post">{post && post.content}</div>;
}
}
module.exports = BlogPost;

View file

@ -1,8 +1,19 @@
import React from 'react';
import {render} from 'react-dom';
import {BrowserRouter, Route, Switch, Link} from 'react-router-dom';
import blogDatas from '@generated/blogDatas';
import BlogPost from './blogPost';
import blogMetadata from '@generated/blogMetadata';
import docsMetadata from '@generated/docsMetadata';
import Blog from './blog';
import Docs from './docs';
const renderBlog = props => {
const metadata = blogMetadata.find(blog => blog.path === props.match.path);
return <Blog content={metadata.content} {...props} />;
};
const renderDocs = props => {
const metadata = docsMetadata.find(doc => doc.path === props.match.path);
return <Docs content={metadata.content} {...props} />;
};
class App extends React.Component {
render() {
@ -10,17 +21,13 @@ class App extends React.Component {
<BrowserRouter>
<div>
<Switch>
{blogDatas.map(({path}) => (
<Route key={path} exact path={path} component={BlogPost} />
{blogMetadata.map(({path, content}) => (
<Route key={path} exact path={path} render={renderBlog} />
))}
{docsMetadata.map(({path}) => (
<Route key={path} exact path={path} render={renderDocs} />
))}
</Switch>
<div>
{blogDatas.map(({path}) => (
<div key={path}>
<Link to={path}>{path}</Link>
</div>
))}
</div>
</div>
</BrowserRouter>
);

11
lib/core/docs/index.js Normal file
View file

@ -0,0 +1,11 @@
import React from 'react';
import MarkdownBlock from '../markdown';
class Blog extends React.Component {
render() {
const {content, siteConfig} = this.props;
return <MarkdownBlock siteConfig={siteConfig}>{content}</MarkdownBlock>;
}
}
module.exports = Blog;

View file

@ -10,6 +10,7 @@ class MarkdownBlock extends React.Component {
const alias = {
js: 'jsx'
};
const {siteConfig} = this.props;
const md = new Markdown({
langPrefix: 'hljs css language-',
highlight(str, lang) {
@ -99,6 +100,9 @@ class MarkdownBlock extends React.Component {
render() {
const Container = this.props.container;
if (!Container) {
return <div>{this.content()}</div>;
}
return <Container>{this.content()}</Container>;
}
}

View file

@ -36,8 +36,7 @@ async function loadBlog(siteDir) {
return {
path: fileToPath(file),
content,
title: metadata.title,
date: metadata.date
...metadata
};
})
);

View file

@ -2,7 +2,7 @@ const fs = require('fs-extra');
const path = require('path');
module.exports = function loadConfig(siteDir, deleteCache = true) {
const configPath = path.resolve(siteDir, 'config.js');
const configPath = path.resolve(siteDir, 'siteConfig.js');
if (deleteCache) {
delete require.cache[configPath];
}

47
lib/loader/docs.js Normal file
View file

@ -0,0 +1,47 @@
const fs = require('fs-extra');
const path = require('path');
const fm = require('front-matter');
const globby = require('globby');
const indexRE = /(^|.*\/)index\.md$/i;
const mdRE = /\.md$/;
function fileToPath(file) {
if (indexRE.test(file)) {
return file.replace(indexRE, '/$1');
}
return `/${file.replace(mdRE, '').replace(/\\/g, '/')}.html`;
}
function parse(fileString) {
if (!fm.test(fileString)) {
return {metadata: null, content: fileString};
}
const {attributes: metadata, body: content} = fm(fileString);
return {metadata, content};
}
async function loadDocs(siteDir) {
const blogFiles = await globby(['**/*.md'], {
cwd: siteDir
});
const blogDatas = await Promise.all(
blogFiles.map(async file => {
const filepath = path.resolve(siteDir, file);
const fileString = await fs.readFile(filepath, 'utf-8');
const {metadata, content} = parse(fileString);
return {
path: fileToPath(file),
content,
...metadata
};
})
);
blogDatas.sort((a, b) => b.date - a.date);
return blogDatas;
}
module.exports = loadDocs;

View file

@ -2,19 +2,33 @@ const fs = require('fs-extra');
const path = require('path');
const loadConfig = require('./config');
const loadBlog = require('./blog');
const loadDocs = require('./docs');
const {generate} = require('../helpers');
module.exports = async function load(siteDir) {
// load siteConfig
const siteConfig = loadConfig(siteDir);
// extract data from all blog files
const blogDatas = await loadBlog(siteDir);
// docs
const docsRelativeDir = siteConfig.customDocsPath || 'docs';
const docsMetadata = await loadDocs(
path.resolve(siteDir, '..', docsRelativeDir)
);
await generate(
'blogDatas.js',
'docsMetadata.js',
`${'/**\n * @generated\n */\n' + 'module.exports = '}${JSON.stringify(
blogDatas,
docsMetadata,
null,
2
)};\n`
);
// blog
const blogMetadata = await loadBlog(path.resolve(siteDir, 'blog'));
await generate(
'blogMetadata.js',
`${'/**\n * @generated\n */\n' + 'module.exports = '}${JSON.stringify(
blogMetadata,
null,
2
)};\n`
@ -32,14 +46,13 @@ module.exports = async function load(siteDir) {
? path.resolve(__dirname, '../ui')
: siteConfig.uiPath;
const publicPath = siteConfig.base || '/';
const baseUrl = siteConfig.baseUrl || '/';
return {
siteConfig,
blogDatas,
siteDir,
outDir,
uiPath,
publicPath
baseUrl
};
};

View file

@ -2,7 +2,7 @@ const Config = require('webpack-chain');
const path = require('path');
module.exports = function createBaseConfig(props) {
const {outDir, uiPath, siteDir, publicPath} = props;
const {outDir, uiPath, siteDir, baseUrl} = props;
const config = new Config();
const isProd = process.env.NODE_ENV === 'production';
@ -13,7 +13,7 @@ module.exports = function createBaseConfig(props) {
.filename(
isProd ? 'static/js/[name].[chunkhash].js' : 'static/js/[name].js'
)
.publicPath(isProd ? publicPath : '/');
.publicPath(isProd ? baseUrl : '/');
config.resolve
.set('symlinks', true)

View file

@ -51,15 +51,18 @@
"front-matter": "^2.3.0",
"fs-extra": "^7.0.0",
"globby": "^8.0.1",
"highlight.js": "^9.12.0",
"html-webpack-plugin": "^3.2.0",
"koa-connect": "^2.0.1",
"koa-mount": "^3.0.0",
"koa-range": "^0.3.0",
"koa-static": "^5.0.0",
"portfinder": "^1.0.13",
"prismjs": "^1.15.0",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react-router-dom": "^4.3.1",
"remarkable": "^1.7.1",
"semver": "^5.5.0",
"webpack": "^4.16.3",
"webpack-chain": "^4.8.0",

View file

@ -379,6 +379,13 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
argparse@~0.1.15:
version "0.1.16"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-0.1.16.tgz#cfd01e0fbba3d6caed049fbd758d40f65196f57c"
dependencies:
underscore "~1.7.0"
underscore.string "~2.4.0"
aria-query@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc"
@ -517,6 +524,10 @@ atob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.1.tgz#ae2d5a729477f289d60dd7f96a6314a22dd6c22a"
autolinker@~0.15.0:
version "0.15.3"
resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.15.3.tgz#342417d8f2f3461b14cf09088d5edf8791dc9832"
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
@ -1492,6 +1503,14 @@ cli-width@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
clipboard@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.1.tgz#a12481e1c13d8a50f5f036b0560fe5d16d74e46a"
dependencies:
good-listener "^1.2.2"
select "^1.1.2"
tiny-emitter "^2.0.0"
clipboardy@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-1.2.3.tgz#0526361bf78724c1f20be248d428e365433c07ef"
@ -1895,6 +1914,10 @@ delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
delegate@^3.1.2:
version "3.2.0"
resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
@ -2846,6 +2869,12 @@ globby@^8.0.1:
pify "^3.0.0"
slash "^1.0.0"
good-listener@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
dependencies:
delegate "^3.1.2"
got@^6.7.1:
version "6.7.1"
resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
@ -2970,6 +2999,10 @@ he@1.1.x:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
highlight.js@^9.12.0:
version "9.12.0"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e"
history@^4.7.2:
version "4.7.2"
resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b"
@ -5132,6 +5165,12 @@ pretty-time@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e"
prismjs@^1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.15.0.tgz#8801d332e472091ba8def94976c8877ad60398d9"
optionalDependencies:
clipboard "^2.0.0"
private@^0.1.6, private@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
@ -5505,6 +5544,13 @@ relateurl@0.2.x:
version "0.2.7"
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
remarkable@^1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.1.tgz#aaca4972100b66a642a63a1021ca4bac1be3bff6"
dependencies:
argparse "~0.1.15"
autolinker "~0.15.0"
remove-array-items@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/remove-array-items/-/remove-array-items-1.0.0.tgz#07bf42cb332f4cf6e85ead83b5e4e896d2326b21"
@ -5737,6 +5783,10 @@ schema-utils@^0.4.4, schema-utils@^0.4.5:
ajv "^6.1.0"
ajv-keywords "^3.1.0"
select@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
semver-diff@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
@ -6211,6 +6261,10 @@ timers-browserify@^2.0.4:
dependencies:
setimmediate "^1.0.4"
tiny-emitter@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c"
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@ -6361,6 +6415,14 @@ uglifyjs-webpack-plugin@^1.2.4:
webpack-sources "^1.1.0"
worker-farm "^1.5.2"
underscore.string@~2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-2.4.0.tgz#8cdd8fbac4e2d2ea1e7e2e8097c42f442280f85b"
underscore@~1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209"
union-value@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"