├── packages ├── bisheng │ ├── test │ │ ├── fixtures │ │ │ ├── posts │ │ │ │ ├── b.md │ │ │ │ ├── a.en-US.md │ │ │ │ └── a.zh-CN.md │ │ │ ├── _theme │ │ │ │ └── index.js │ │ │ ├── theme.index.js │ │ │ └── bisheng.config.js │ │ └── utils │ │ │ ├── generate-files-path.test.js │ │ │ ├── get-config.test.js │ │ │ ├── strinigfy.test.js │ │ │ └── source-data.test.js │ ├── src │ │ ├── utils │ │ │ ├── data.js │ │ │ ├── ssr-data.js │ │ │ ├── create-element.jsx │ │ │ ├── escape-win-path.js │ │ │ ├── get-theme-config.js │ │ │ ├── stringify.js │ │ │ ├── resolve-plugins.js │ │ │ ├── get-bisheng-config.js │ │ │ ├── generate-files-path.js │ │ │ └── source-data.js │ │ ├── config │ │ │ ├── getTSCommonConfig.js │ │ │ ├── getBabelCommonConfig.js │ │ │ ├── updateWebpackConfig.js │ │ │ ├── getStyleLoadersConfig.js │ │ │ └── getWebpackCommonConfig.js │ │ ├── transformers │ │ │ └── markdown.js │ │ ├── context.js │ │ ├── loaders │ │ │ ├── common │ │ │ │ ├── worker.js │ │ │ │ └── boss.js │ │ │ ├── source-loader.js │ │ │ └── bisheng-data-loader.js │ │ ├── bisheng-plugin-highlight │ │ │ └── lib │ │ │ │ ├── browser.js │ │ │ │ └── node.js │ │ ├── template.html │ │ ├── ssr.nunjucks.jsx │ │ ├── entry.nunjucks.jsx │ │ ├── routes.nunjucks.jsx │ │ └── index.js │ ├── router.js │ ├── collect.js │ ├── bin │ │ ├── bisheng-start │ │ ├── bisheng-build │ │ ├── bisheng │ │ └── bisheng-gh-pages │ └── package.json ├── bisheng-example │ ├── posts │ │ ├── should-be-ignore.md │ │ ├── good-bye.md │ │ └── hello-world.md │ ├── package.json │ └── bisheng.config.js ├── bisheng-theme-one │ ├── src │ │ ├── static │ │ │ ├── style.js │ │ │ ├── syntax.css │ │ │ ├── site.css │ │ │ └── yue.css │ │ ├── template │ │ │ ├── NotFound.jsx │ │ │ ├── Layout.jsx │ │ │ ├── Post.jsx │ │ │ ├── Archive.jsx │ │ │ └── TagCloud.jsx │ │ └── index.js │ ├── README.md │ └── package.json ├── bisheng-plugin-react │ ├── screenshot.png │ ├── lib │ │ ├── browser.js │ │ ├── node.js │ │ └── transformer.js │ ├── package.json │ ├── test │ │ └── node.test.js │ └── README.md ├── bisheng-plugin-toc │ ├── test │ │ ├── fixtures.md │ │ └── node.test.js │ ├── package.json │ ├── README.md │ └── lib │ │ └── node.js └── bisheng-plugin-description │ ├── package.json │ ├── lib │ └── node.js │ ├── README.md │ └── test │ └── node.test.js ├── big-picture.jpg ├── .gitignore ├── .travis.yml ├── lerna.json ├── .eslintrc.js ├── appveyor.yml ├── package.json ├── docs ├── pick.md ├── plugin.md ├── lazy-load.md └── theme.md ├── LICENSE ├── .vscode └── launch.json └── README.md /packages/bisheng/test/fixtures/posts/b.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/bisheng/test/fixtures/posts/a.en-US.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/bisheng/test/fixtures/posts/a.zh-CN.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/bisheng-example/posts/should-be-ignore.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/bisheng/src/utils/data.js: -------------------------------------------------------------------------------- 1 | // placeholder 2 | -------------------------------------------------------------------------------- /packages/bisheng/src/utils/ssr-data.js: -------------------------------------------------------------------------------- 1 | // placeholder 2 | -------------------------------------------------------------------------------- /packages/bisheng/test/fixtures/_theme/index.js: -------------------------------------------------------------------------------- 1 | // placeholder 2 | -------------------------------------------------------------------------------- /big-picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisestCoder/bisheng/master/big-picture.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | _site 3 | node_modules 4 | packages/*/lib 5 | tmp 6 | package-lock.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "7" 5 | before_install: npm install lerna@2.0.0-rc.3 -g 6 | -------------------------------------------------------------------------------- /packages/bisheng-theme-one/src/static/style.js: -------------------------------------------------------------------------------- 1 | require('./yue.css'); 2 | require('./syntax.css'); 3 | require('./site.css'); 4 | -------------------------------------------------------------------------------- /packages/bisheng/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var router = require('react-router'); 4 | 5 | module.exports = router; 6 | -------------------------------------------------------------------------------- /packages/bisheng/test/fixtures/theme.index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | plugins: ['bisheng-plugin-description'], 5 | }; 6 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-react/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WisestCoder/bisheng/master/packages/bisheng-plugin-react/screenshot.png -------------------------------------------------------------------------------- /packages/bisheng-example/posts/good-bye.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Good bye! 3 | publishDate: 2015-05-05 4 | --- 5 | 6 | The second article which is posted by BiSheng. 7 | 8 | --- 9 | 10 | Good bye! 11 | -------------------------------------------------------------------------------- /packages/bisheng/collect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(collector) { 4 | return function(Component) { 5 | Component.collector = collector; 6 | return Component; 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/bisheng/src/config/getTSCommonConfig.js: -------------------------------------------------------------------------------- 1 | export default function ts() { 2 | return { 3 | target: 'es6', 4 | jsx: 'preserve', 5 | moduleResolution: 'node', 6 | declaration: false, 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.5.1", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "commands": { 7 | "exec": { 8 | "ignore": "bisheng-{theme-one,example}" 9 | } 10 | }, 11 | "version": "independent" 12 | } 13 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-toc/test/fixtures.md: -------------------------------------------------------------------------------- 1 | # Heading1 2 | 3 | ## Heading2 4 | 5 | ### Heading3 6 | 7 | #### Heading4 8 | 9 | ##### Heading5 10 | 11 | ###### Heading6 12 | 13 | ### hello world 14 | 15 | ### hello [world](./world) 16 | -------------------------------------------------------------------------------- /packages/bisheng/bin/bisheng-start: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const program = require('commander'); 4 | const BiSheng = require('../lib'); 5 | 6 | program 7 | .option('-c, --config ', 'set config path. defaults to ./bisheng.config.js') 8 | .parse(process.argv); 9 | 10 | BiSheng.start(program); 11 | -------------------------------------------------------------------------------- /packages/bisheng-example/posts/hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hello world! 3 | publishDate: 2016-05-05 4 | tags: 5 | - test 6 | --- 7 | 8 | The first article which is posted by BiSheng. 9 | 10 | --- 11 | 12 | Hello world! 13 | 14 | ```js 15 | (function () { 16 | console.log('Hello world!'); 17 | })(); 18 | ``` 19 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'eslint-config-airbnb', 3 | env: { 4 | browser: true, 5 | mocha: true, 6 | }, 7 | rules: { 8 | 'consistent-return': 0, 9 | 'global-require': 0, 10 | 'import/no-dynamic-require': 0, 11 | 'no-underscore-dangle': 0, 12 | 'max-len': 0, 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /packages/bisheng/src/transformers/markdown.js: -------------------------------------------------------------------------------- 1 | const markTwain = require('mark-twain'); 2 | const { toUriPath } = require('../utils/escape-win-path'); 3 | 4 | module.exports = function (filename, fileContent) { 5 | const markdown = markTwain(fileContent); 6 | markdown.meta.filename = toUriPath(filename); 7 | return markdown; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/bisheng/src/context.js: -------------------------------------------------------------------------------- 1 | let isInitialized = false; 2 | exports.initialize = function (context) { 3 | if (isInitialized) { 4 | console.error('`context` had been initialized'); 5 | return; 6 | } 7 | Object.assign(exports, context); 8 | isInitialized = true; 9 | }; 10 | 11 | exports.turnOnSSRFlag = function () { 12 | exports.isSSR = true; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/bisheng/bin/bisheng-build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const program = require('commander'); 4 | const BiSheng = require('../lib'); 5 | 6 | program 7 | .option('-c, --config ', 'set config path. defaults to ./bisheng.config.js') 8 | .option('--ssr', 'whether to enable ssr while building pages.') 9 | .parse(process.argv); 10 | 11 | BiSheng.build(program); 12 | -------------------------------------------------------------------------------- /packages/bisheng-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bisheng-example", 3 | "scripts": { 4 | "start": "bisheng start", 5 | "build": "bisheng build --ssr", 6 | "deploy": "bisheng gh-pages --ssr && rm -rf _site" 7 | }, 8 | "dependencies": { 9 | "bisheng": "^0.28.0", 10 | "bisheng-theme-one": "^0.2.31" 11 | }, 12 | "version": "1.0.31", 13 | "private": true 14 | } 15 | -------------------------------------------------------------------------------- /packages/bisheng/src/utils/create-element.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | const React = require('react'); 3 | /* eslint-enable no-unused-vars */ 4 | const NProgress = require('nprogress'); 5 | 6 | module.exports = function createElement(Component, props) { 7 | NProgress.done(); 8 | const dynamicPropsKey = props.location.pathname; 9 | return ; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/bisheng-theme-one/src/template/NotFound.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DocumentTitle from 'react-document-title'; 3 | import Layout from './Layout'; 4 | 5 | export default (props) => { 6 | return ( 7 | 8 | 9 |

404 Not Found!

10 |
11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-react/lib/browser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable no-var */ 4 | var React = require('react'); 5 | 6 | module.exports = function() { 7 | return { 8 | converters: [ 9 | [ 10 | function(node) { return typeof node === 'function'; }, 11 | function(node, index) { 12 | return React.cloneElement(node(), { key: index }); 13 | }, 14 | ], 15 | ], 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /packages/bisheng/src/utils/escape-win-path.js: -------------------------------------------------------------------------------- 1 | // require('D:\ant-design') will throw a module not found error need to escape `\` to `\\`. 2 | // Note that this is only required when you persist code to file 3 | function escapeWinPath(path) { 4 | return path.replace(/\\/g, '\\\\'); 5 | } 6 | 7 | function toUriPath(path) { 8 | return path.replace(/\\/g, '/'); 9 | } 10 | 11 | module.exports = { 12 | escapeWinPath, 13 | toUriPath, 14 | }; 15 | -------------------------------------------------------------------------------- /packages/bisheng-example/bisheng.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | exclude: /should-be-ignore/, 3 | theme: 'bisheng-theme-one', 4 | themeConfig: { 5 | home: '/', 6 | sitename: 'One', 7 | tagline: 'The one theme for bisheng', 8 | // navigation: [{ 9 | // title: 'BiSheng', 10 | // link: 'https://github.com/benjycui/bisheng', 11 | // }], 12 | // footer: 'Copyright and so on...', 13 | // hideBisheng: true, 14 | github: 'https://github.com/benjycui/bisheng', 15 | }, 16 | root: '/bisheng/', 17 | }; 18 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Test against the latest version of this Node.js version 2 | environment: 3 | nodejs_version: "6" 4 | 5 | # Install scripts. (runs after repo cloning) 6 | install: 7 | # Get the latest stable version of Node.js or io.js 8 | - ps: Install-Product node $env:nodejs_version 9 | # install modules 10 | - npm install 11 | 12 | # Post-install test scripts. 13 | test_script: 14 | # Output useful info for debugging. 15 | - node --version 16 | - npm --version 17 | # run tests 18 | - npm test 19 | 20 | # Don't actually build. 21 | build: off -------------------------------------------------------------------------------- /packages/bisheng/src/loaders/common/worker.js: -------------------------------------------------------------------------------- 1 | const sourceData = require('../../utils/source-data'); 2 | const stringify = require('../../utils/stringify'); 3 | 4 | process.on('message', (task) => { 5 | const { 6 | filename, 7 | content, 8 | plugins, 9 | transformers, 10 | isBuild, 11 | } = task; 12 | const parsedMarkdown = sourceData.process( 13 | filename, 14 | content, 15 | plugins, 16 | transformers, 17 | isBuild, 18 | ); 19 | const result = stringify(parsedMarkdown); 20 | process.send(result); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/bisheng/test/fixtures/bisheng.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const rucksack = require('rucksack-css'); 3 | const autoprefixer = require('autoprefixer'); 4 | const svgo = require('postcss-svgo'); 5 | 6 | module.exports = { 7 | source: './content', 8 | theme: path.join(__dirname, './_theme'), 9 | postcssConfig: { 10 | plugins: [ 11 | rucksack(), 12 | autoprefixer({ 13 | browsers: ['last 2 versions', 'Firefox ESR', '> 1%', 'ie >= 8', 'iOS >= 8', 'Android >= 4'], 14 | }), 15 | svgo(), 16 | ] 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /packages/bisheng/bin/bisheng: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const program = require('commander'); 4 | const package = require('../package.json'); 5 | 6 | program 7 | .version(package.version) 8 | .usage('[command] [options]') 9 | .command('start [options]', 'to start a server') 10 | .command('build [options]', 'to build and write static files to `config.output`') 11 | .command('gh-pages [options]', 'to deploy website to gh-pages') 12 | .parse(process.argv); 13 | 14 | process.on('SIGINT', function() { 15 | program.runningCommand && program.runningCommand.kill('SIGKILL'); 16 | process.exit(0); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/bisheng/bin/bisheng-gh-pages: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const program = require('commander'); 4 | const BiSheng = require('../lib'); 5 | 6 | program 7 | .option('-c, --config ', 'set config path. defaults to ./bisheng.config.js') 8 | .option('-p, --push-only [dir]', 'push the directory to gh-pages directly without build. defaults to ./_site') 9 | .option('--ssr', 'whether to enable ssr while building pages.') 10 | .option('-r, --remote ', 'The name of the remote', 'origin') 11 | .option('-b, --branch ', 'name of the branch you\'ll be pushing to', 'gh-pages') 12 | .parse(process.argv); 13 | 14 | BiSheng.deploy(program); 15 | -------------------------------------------------------------------------------- /packages/bisheng-theme-one/src/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | lazyLoad: true, 5 | pick: { 6 | posts(markdownData) { 7 | return { 8 | meta: markdownData.meta, 9 | description: markdownData.description, 10 | }; 11 | }, 12 | }, 13 | plugins: [path.join(__dirname, '..', 'node_modules', 'bisheng-plugin-description')], 14 | routes: [{ 15 | path: '/', 16 | component: './template/Archive', 17 | }, { 18 | path: '/posts/:post', 19 | dataPath: '/:post', 20 | component: './template/Post', 21 | }, { 22 | path: '/tags', 23 | component: './template/TagCloud', 24 | }], 25 | }; 26 | -------------------------------------------------------------------------------- /packages/bisheng/src/utils/get-theme-config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const pluginHighlight = path.join(__dirname, '..', 'bisheng-plugin-highlight'); 4 | 5 | function isRelative(filepath) { 6 | return filepath.charAt(0) === '.'; 7 | } 8 | 9 | function toAbsolutePath(plugin) { 10 | return isRelative(plugin) ? path.join(process.cwd(), plugin) : plugin; 11 | } 12 | 13 | module.exports = function getThemeConfig(configFile) { 14 | const customizedConfig = require(configFile); 15 | const config = Object.assign({ plugins: [] }, customizedConfig); 16 | config.plugins = [pluginHighlight].concat(config.plugins.map(toAbsolutePath)); 17 | 18 | return config; 19 | }; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint": "eslint .eslintrc.js ./packages/*/src ./packages/*/test", 4 | "lint-fix": "eslint .eslintrc.js --fix ./packages/*/src ./packages/*/test", 5 | "test": "lerna bootstrap && lerna exec -- _mocha --recursive --opts test/mocha.opts", 6 | "clean": "lerna clean", 7 | "bootstrap": "lerna bootstrap", 8 | "publish": "lerna publish" 9 | }, 10 | "devDependencies": { 11 | "eslint": "latest", 12 | "eslint-config-airbnb": "latest", 13 | "eslint-plugin-import": "latest", 14 | "eslint-plugin-jsx-a11y": "latest", 15 | "eslint-plugin-react": "latest", 16 | "lerna": "^2.5.1", 17 | "mocha": "^3.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-description/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bisheng-plugin-description", 3 | "version": "0.1.4", 4 | "description": "To extract description from Markdown.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/benjycui/bisheng-plugin-description.git" 8 | }, 9 | "keywords": [ 10 | "bisheng", 11 | "plugin", 12 | "description" 13 | ], 14 | "author": "Benjy Cui", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/benjycui/bisheng-plugin-description/issues" 18 | }, 19 | "homepage": "https://github.com/benjycui/bisheng-plugin-description#readme", 20 | "dependencies": { 21 | "jsonml.js": "^0.1.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-description/lib/node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const JsonML = require('jsonml.js/lib/utils'); 4 | 5 | module.exports = (markdownData) => { 6 | const content = markdownData.content; 7 | const contentChildren = JsonML.getChildren(content); 8 | const dividerIndex = contentChildren.findIndex((node) => JsonML.getTagName(node) === 'hr'); 9 | 10 | if (dividerIndex >= 0) { 11 | markdownData.description = ['section'] 12 | .concat(contentChildren.slice(0, dividerIndex)); 13 | markdownData.content = [ 14 | JsonML.getTagName(content), 15 | JsonML.getAttributes(content) || {}, 16 | ].concat(contentChildren.slice(dividerIndex + 1)); 17 | } 18 | 19 | return markdownData; 20 | }; 21 | -------------------------------------------------------------------------------- /packages/bisheng/src/config/getBabelCommonConfig.js: -------------------------------------------------------------------------------- 1 | import { tmpdir } from 'os'; 2 | 3 | export default function babel() { 4 | return { 5 | cacheDirectory: tmpdir(), 6 | presets: [ 7 | 'react', 8 | [require.resolve('babel-preset-env'), { 9 | targets: { 10 | browsers: ['last 2 versions', 'Firefox ESR', '> 1%', 'ie >= 8', 'iOS >= 8', 'Android >= 4'], 11 | }, 12 | }], 13 | ], 14 | plugins: [ 15 | require.resolve('babel-plugin-add-module-exports'), 16 | require.resolve('babel-plugin-transform-class-properties'), 17 | require.resolve('babel-plugin-transform-decorators-legacy'), 18 | require.resolve('babel-plugin-transform-object-rest-spread'), 19 | ], 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-toc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bisheng-plugin-toc", 3 | "version": "0.4.4", 4 | "description": "Generate a Table of Contents (TOC) for Markdown files.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/benjycui/bisheng-plugin-toc.git" 8 | }, 9 | "keywords": [ 10 | "bisheng", 11 | "plugin", 12 | "toc", 13 | "markdown" 14 | ], 15 | "author": "Benjy Cui", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/benjycui/bisheng-plugin-toc/issues" 19 | }, 20 | "homepage": "https://github.com/benjycui/bisheng-plugin-toc#readme", 21 | "devDependencies": { 22 | "mark-twain": "^1.1.4" 23 | }, 24 | "dependencies": { 25 | "jsonml.js": "^0.1.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/bisheng/src/bisheng-plugin-highlight/lib/browser.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* eslint-disable no-var */ 4 | var React = require('react'); 5 | var JsonML = require('jsonml.js/lib/utils'); 6 | 7 | module.exports = function () { 8 | return { 9 | converters: [ 10 | [ 11 | function (node) { return JsonML.isElement(node) && JsonML.getTagName(node) === 'pre'; }, 12 | function (node, index) { 13 | var attr = JsonML.getAttributes(node); 14 | return React.createElement('pre', { 15 | key: index, 16 | className: `language-${attr.lang}`, 17 | }, React.createElement('code', { 18 | dangerouslySetInnerHTML: { __html: attr.highlighted }, 19 | })); 20 | }, 21 | ], 22 | ], 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /packages/bisheng/src/utils/stringify.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = function stringify(node, depth = 0) { 4 | const indent = ' '.repeat(depth); 5 | if (Array.isArray(node)) { 6 | return `[\n${ 7 | node.map(item => `${indent} ${stringify(item, depth + 1)}`).join(',\n') 8 | }\n${indent}]`; 9 | } 10 | if ( 11 | typeof node === 'object' && 12 | node !== null && 13 | !(node instanceof Date) 14 | ) { 15 | if (node.__BISHENG_EMBEDED_CODE) { 16 | return node.code; 17 | } 18 | return `{\n${ 19 | Object.keys(node).map((key) => { 20 | const value = node[key]; 21 | return `${indent} "${key}": ${stringify(value, depth + 1)}`; 22 | }).join(',\n') 23 | }\n${indent}}`; 24 | } 25 | return JSON.stringify(node, null, 2); 26 | }; 27 | -------------------------------------------------------------------------------- /docs/pick.md: -------------------------------------------------------------------------------- 1 | # pick 2 | 3 | Sometimes, we will load lots of Markdown data, but we only need part of these Markdown data, such as the archived page. This will slow down our website, but we can use `pick` to improve the performance. 4 | 5 | Each field in `pick` is a function, and the signature of these functions is: 6 | 7 | ```js 8 | function(markdownData: Object): any | undefined 9 | ``` 10 | 11 | `bisheng` will pass Markdown data to each field in `pick` one by one, if those functions return something except `undefined`, `bisheng` will collect them and put them into `props.picked`, for example: 12 | 13 | ```js 14 | pick: { 15 | titles(markdownData) { return markdownData.meta.title }, 16 | }, 17 | ``` 18 | 19 | Then, the `props.picked` will be: 20 | 21 | ```js 22 | { 23 | titles: [...], 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /packages/bisheng/src/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 |
{{ content | safe }}
14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/bisheng/src/bisheng-plugin-highlight/lib/node.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const Prism = require('node-prismjs'); 4 | const JsonML = require('jsonml.js/lib/utils'); 5 | 6 | function getCode(node) { 7 | return JsonML.getChildren(JsonML.getChildren(node)[0] || '')[0] || ''; 8 | } 9 | 10 | function highlight(node) { 11 | if (!JsonML.isElement(node)) return; 12 | 13 | if (JsonML.getTagName(node) !== 'pre') { 14 | JsonML.getChildren(node).forEach(highlight); 15 | return; 16 | } 17 | 18 | const language = Prism.languages[JsonML.getAttributes(node).lang] || 19 | Prism.languages.autoit; 20 | JsonML.getAttributes(node).highlighted = 21 | Prism.highlight(getCode(node), language); 22 | } 23 | 24 | module.exports = (markdownData/* , config */) => { 25 | highlight(markdownData.content); 26 | return markdownData; 27 | }; 28 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-react/lib/node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const transformer = require('./transformer'); 4 | 5 | module.exports = function(markdownData, { 6 | lang = 'react-example', 7 | babelConfig, 8 | noreact, 9 | }) { 10 | const { content } = markdownData; 11 | 12 | // ignore customized content 13 | if (Array.isArray(content)) { 14 | markdownData.content = content.map(node => { 15 | const tagName = node[0]; 16 | const attr = node[1]; 17 | if (tagName === 'pre' && attr && attr.lang === lang) { 18 | const code = node[2][1]; 19 | const processedCode = transformer(code, babelConfig && JSON.parse(babelConfig), noreact); 20 | return { 21 | __BISHENG_EMBEDED_CODE: true, 22 | code: processedCode, 23 | }; 24 | } 25 | return node; 26 | }); 27 | } 28 | 29 | return markdownData; 30 | }; 31 | -------------------------------------------------------------------------------- /packages/bisheng/src/ssr.nunjucks.jsx: -------------------------------------------------------------------------------- 1 | require('babel-polyfill'); 2 | 3 | const React = require('react'); 4 | const ReactDOMServer = require('react-dom/server'); 5 | const ReactRouter = require('react-router'); 6 | const createElement = require('../lib/utils/create-element'); 7 | const data = require('../lib/utils/ssr-data.js'); 8 | const routes = require('{{ routesPath }}')(data); 9 | 10 | module.exports = function ssr(url, callback) { 11 | ReactRouter.match({ routes, location: url }, (error, redirectLocation, renderProps) => { 12 | if (error) { 13 | callback(error, ''); 14 | } else if (redirectLocation) { 15 | callback(null, ''); // TODO 16 | } else if (renderProps) { 17 | try { 18 | const content = ReactDOMServer.renderToString( 19 | , 20 | ); 21 | callback(null, content); 22 | } catch (e) { 23 | callback(e, ''); 24 | } 25 | } else { 26 | callback(null, ''); // TODO 27 | } 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /packages/bisheng/src/entry.nunjucks.jsx: -------------------------------------------------------------------------------- 1 | require('babel-polyfill'); 2 | require('nprogress/nprogress.css'); 3 | 4 | /* eslint-disable no-unused-vars */ 5 | const React = require('react'); 6 | /* eslint-enable no-unused-vars */ 7 | const ReactDOM = require('react-dom'); 8 | const ReactRouter = require('react-router'); 9 | const history = require('history'); 10 | const data = require('../lib/utils/data.js'); 11 | const createElement = require('../lib/utils/create-element'); 12 | const routes = require('{{ routesPath }}')(data); 13 | 14 | const { pathname, search, hash } = window.location; 15 | const location = `${pathname}${search}${hash}`; 16 | const basename = '{{ root }}'; 17 | ReactRouter.match({ routes, location, basename }, () => { 18 | const router = ( 19 | 24 | ); 25 | ReactDOM.render( 26 | router, 27 | document.getElementById('react-content'), 28 | ); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/bisheng/src/utils/resolve-plugins.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const path = require('path'); 4 | const R = require('ramda'); 5 | const loaderUtils = require('loader-utils'); 6 | const resolve = require('resolve'); 7 | const { escapeWinPath } = require('./escape-win-path'); 8 | 9 | function resolvePlugin(plugin) { 10 | let result; 11 | try { 12 | result = resolve.sync(plugin, { 13 | basedir: process.cwd(), 14 | }); 15 | } catch (e) {} // eslint-disable-line no-empty 16 | return result; 17 | } 18 | 19 | module.exports = function resolvePlugins(plugins, moduleName) { 20 | return plugins.map((plugin) => { 21 | const snippets = plugin.split('?'); 22 | const pluginName = path.join(snippets[0], 'lib', moduleName); 23 | const pluginQuery = snippets[1] ? loaderUtils.parseQuery(`?${snippets[1]}`) : {}; 24 | const resolvedPlugin = resolvePlugin(pluginName); 25 | if (!resolvedPlugin) { 26 | return false; 27 | } 28 | return [ 29 | escapeWinPath(resolvedPlugin), 30 | pluginQuery, 31 | ]; 32 | }).filter(R.identity); 33 | }; 34 | -------------------------------------------------------------------------------- /packages/bisheng-theme-one/README.md: -------------------------------------------------------------------------------- 1 | # bisheng-theme-one 2 | 3 | [![npm package](https://img.shields.io/npm/v/bisheng-theme-one.svg?style=flat-square)](https://www.npmjs.org/package/bisheng-theme-one) 4 | [![NPM downloads](http://img.shields.io/npm/dm/bisheng-theme-one.svg?style=flat-square)](https://npmjs.org/package/bisheng-theme-one) 5 | [![Dependency Status](https://david-dm.org/benjycui/bisheng-theme-one.svg?style=flat-square)](https://david-dm.org/benjycui/bisheng-theme-one) 6 | 7 | The one theme for [bisheng](https://github.com/benjycui/bisheng). 8 | 9 | ## Features 10 | 11 | * Load Markdown data lazily. 12 | * Server-side render for SEO. 13 | 14 | ## Usage 15 | 16 | Installation: 17 | 18 | ```bash 19 | npm i --save bisheng-theme-one 20 | ``` 21 | 22 | Configuration in `bisheng.config.js`: 23 | 24 | ```js 25 | { 26 | theme: 'bisheng-theme-one', 27 | ... 28 | } 29 | ``` 30 | 31 | ## Note 32 | 33 | `publishDate` is required in Markdown file: 34 | 35 | ```bash 36 | --- 37 | title: Title 38 | publishDate: 2011-11-11 39 | --- 40 | 41 | Content 42 | ``` 43 | 44 | ## Liscense 45 | 46 | MIT 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Benjy Cui 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Mocha Tests", 11 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 12 | "args": [ 13 | "-u", 14 | "tdd", 15 | "--timeout", 16 | "999999", 17 | "--colors", 18 | "${workspaceRoot}/packages/bisheng/test", 19 | "${workspaceRoot}/packages/bisheng-plugin-description/test", 20 | "${workspaceRoot}/packages/bisheng-plugin-react/test", 21 | "${workspaceRoot}/packages/bisheng-plugin-toc/test", 22 | "--recursive", 23 | "--opts", 24 | "test/mocha.opts" 25 | ], 26 | "internalConsoleOptions": "openOnSessionStart" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /packages/bisheng/test/utils/generate-files-path.test.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const assert = require('assert'); 4 | const generateFilesPath = require('../../lib/utils/generate-files-path'); 5 | 6 | describe('bisheng/utils/generate-files-path', () => { 7 | it('should add 404.html by default', () => { 8 | const result = generateFilesPath([], {}); 9 | assert.deepEqual(result, ['/404.html']); 10 | }); 11 | 12 | it('should add index.html to each directory', () => { 13 | const result = generateFilesPath([{ path: '/' }], {}); 14 | assert.deepEqual(result, ['/index.html', '/404.html']); 15 | }); 16 | 17 | it('should generate corresponding html file to each file', () => { 18 | const result = generateFilesPath([{ path: '/archive' }], {}); 19 | assert.deepEqual(result, ['/archive.html', '/404.html']); 20 | }); 21 | 22 | it('should generate corresponding html file to each markdown data', () => { 23 | const result = generateFilesPath([{ 24 | path: '/:post', 25 | dataPath: '/:post', 26 | }], { 27 | hello: {}, 28 | bye: {}, 29 | }); 30 | assert.deepEqual(result, ['/hello.html', '/bye.html', '/404.html']); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/bisheng/src/loaders/source-loader.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const loaderUtils = require('loader-utils'); 3 | const getThemeConfig = require('../utils/get-theme-config'); 4 | const resolvePlugins = require('../utils/resolve-plugins'); 5 | const context = require('../context'); 6 | const boss = require('./common/boss'); 7 | 8 | module.exports = function sourceLoader(content) { 9 | if (this.cacheable) { 10 | this.cacheable(); 11 | } 12 | const webpackRemainingChain = loaderUtils.getRemainingRequest(this).split('!'); 13 | const fullPath = webpackRemainingChain[webpackRemainingChain.length - 1]; 14 | const filename = path.relative(process.cwd(), fullPath); 15 | 16 | const { bishengConfig } = context; 17 | const themeConfig = getThemeConfig(bishengConfig.theme); 18 | const plugins = resolvePlugins(themeConfig.plugins, 'node'); 19 | 20 | const callback = this.async(); 21 | boss.queue({ 22 | filename, 23 | content, 24 | plugins, 25 | transformers: bishengConfig.transformers, 26 | isBuild: context.isBuild, 27 | callback(err, result) { 28 | callback(err, `module.exports = ${result};`); 29 | }, 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /packages/bisheng/src/loaders/common/boss.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const path = require('path'); 3 | const childProcess = require('child_process'); 4 | 5 | function createWorkers(count) { 6 | const workers = []; 7 | while (workers.length < count) { 8 | const worker = childProcess.fork(path.join(__dirname, './worker.js')); 9 | worker.setMaxListeners(1); 10 | workers.push(worker); 11 | } 12 | return workers; 13 | } 14 | 15 | const workersCount = os.cpus().length - 1; 16 | 17 | module.exports = (function () { 18 | const workers = createWorkers(workersCount); 19 | const tasksQueue = []; 20 | function arrange(task) { 21 | const worker = workers.pop(); 22 | const { callback } = task; 23 | worker.send(task); 24 | worker.once('message', (result) => { 25 | callback(null, result); 26 | workers.push(worker); // mission completed 27 | if (tasksQueue.length > 0) { 28 | arrange(tasksQueue.pop()); 29 | } 30 | }); 31 | } 32 | return { 33 | queue(task) { 34 | if (workers.length <= 0) { 35 | tasksQueue.push(task); 36 | return; 37 | } 38 | arrange(task); 39 | }, 40 | jobDone() { 41 | workers.forEach(w => w.kill()); 42 | }, 43 | }; 44 | }()); 45 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-toc/README.md: -------------------------------------------------------------------------------- 1 | # bisheng-plugin-toc 2 | 3 | [![](https://img.shields.io/travis/benjycui/bisheng.svg?style=flat-square)](https://travis-ci.org/benjycui/bisheng) 4 | [![npm package](https://img.shields.io/npm/v/bisheng-plugin-toc.svg?style=flat-square)](https://www.npmjs.org/package/bisheng-plugin-toc) 5 | [![NPM downloads](http://img.shields.io/npm/dm/bisheng-plugin-toc.svg?style=flat-square)](https://npmjs.org/package/bisheng-plugin-toc) 6 | [![Dependency Status](https://david-dm.org/benjycui/bisheng-plugin-toc.svg?style=flat-square)](https://david-dm.org/benjycui/bisheng-plugin-toc) 7 | 8 | Generate a Table of Contents (TOC) for Markdown files in [`bisheng`](https://github.com/benjycui/bisheng). 9 | 10 | ## Usage 11 | 12 | Install: 13 | 14 | ```bash 15 | npm i --save bisheng-plugin-toc 16 | ``` 17 | 18 | Add 'bisheng-plugin-toc to `bisehng.config.js`'s plugins. 19 | 20 | ```js 21 | module.exports = { 22 | plugins: ['bisheng-plugin-toc?maxDepth=2'], 23 | }; 24 | ``` 25 | 26 | In template: 27 | 28 | ```jsx 29 |
30 | { utils.toReactComponent(pageData.toc) } 31 |
32 | ``` 33 | 34 | ## API 35 | 36 | ### maxDepth: Number 37 | 38 | > default: 6 39 | 40 | ### keepElem: Boolean 41 | 42 | > default: false 43 | 44 | Whether to keep elements in heading text. 45 | 46 | ## License 47 | 48 | MIT 49 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-description/README.md: -------------------------------------------------------------------------------- 1 | # bisheng-plugin-description 2 | 3 | [![](https://img.shields.io/travis/benjycui/bisheng.svg?style=flat-square)](https://travis-ci.org/benjycui/bisheng) 4 | [![npm package](https://img.shields.io/npm/v/bisheng-plugin-description.svg?style=flat-square)](https://www.npmjs.org/package/bisheng-plugin-description) 5 | [![NPM downloads](http://img.shields.io/npm/dm/bisheng-plugin-description.svg?style=flat-square)](https://npmjs.org/package/bisheng-plugin-description) 6 | [![Dependency Status](https://david-dm.org/benjycui/bisheng-plugin-description.svg?style=flat-square)](https://david-dm.org/benjycui/bisheng-plugin-description) 7 | 8 | To extract description from Markdown. 9 | 10 | ## Usage 11 | 12 | Install: 13 | 14 | ```bash 15 | npm i --save bisheng-plugin-description 16 | ``` 17 | 18 | Add 'bisheng-plugin-description' to `bisehng.config.js`'s plugins. 19 | 20 | ```js 21 | module.exports = { 22 | plugins: ['bisheng-plugin-description'], 23 | }; 24 | ``` 25 | 26 | In Markdown: 27 | 28 | ```markdown 29 | --- 30 | title: ... 31 | ... 32 | --- 33 | 34 | This is description. 35 | 36 | --- 37 | 38 | This is main content. 39 | ``` 40 | 41 | In template: 42 | 43 | ```jsx 44 |
45 | { utils.toReactComponent(pageData.description) } 46 |
47 | ``` 48 | 49 | ## Liscense 50 | 51 | MIT 52 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bisheng-plugin-react", 3 | "version": "0.6.3", 4 | "description": "To convert JSX which is written in Markdown to React.Element.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/benjycui/bisheng-plugin-react.git" 8 | }, 9 | "keywords": [ 10 | "bisheng", 11 | "plugin", 12 | "markdown", 13 | "react" 14 | ], 15 | "author": "Benjy Cui", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/benjycui/bisheng-plugin-react/issues" 19 | }, 20 | "homepage": "https://github.com/benjycui/bisheng-plugin-react#readme", 21 | "dependencies": { 22 | "babel-core": "^6.21.0", 23 | "babel-generator": "~6.11.0", 24 | "babel-traverse": "~6.10.4", 25 | "babel-types": "~6.11.1" 26 | }, 27 | "devDependencies": { 28 | "babel-plugin-transform-class-properties": "^6.24.1", 29 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 30 | "babel-preset-env": "^1.6.0", 31 | "babel-preset-react": "^6.5.0" 32 | }, 33 | "peerDependencies": { 34 | "babel-plugin-transform-class-properties": "^6.24.1", 35 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 36 | "babel-preset-env": "^1.6.0", 37 | "babel-preset-react": "^6.5.0", 38 | "react": "^0.14.0 || ^15.0.0 || ^16.0.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-toc/lib/node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const JsonML = require('jsonml.js/lib/utils'); 4 | 5 | function isHeading(tagName) { 6 | return /^h[1-6]$/i.test(tagName); 7 | } 8 | 9 | module.exports = (markdownData, config) => { 10 | const maxDepth = config.maxDepth || 6; 11 | 12 | const listItems = JsonML.getChildren(markdownData.content).filter((node) => { 13 | const tagName = JsonML.getTagName(node); 14 | return isHeading(tagName) && +tagName.charAt(1) <= maxDepth; 15 | }).map((node) => { 16 | const tagName = JsonML.getTagName(node); 17 | const headingNodeChildren = JsonML.getChildren(node); 18 | const headingText = headingNodeChildren.map((node) => { 19 | if (JsonML.isElement(node)) { 20 | if (JsonML.hasAttributes(node)) { 21 | return node[2] || ''; 22 | } 23 | return node[1] || ''; 24 | } 25 | return node; 26 | }).join(''); 27 | const headingTextId = headingText.trim().replace(/\s+/g, '-'); 28 | return [ 29 | 'li', [ 30 | 'a', 31 | { 32 | className: `bisheng-toc-${tagName}`, 33 | href: `#${headingTextId}`, 34 | title: headingText 35 | }, 36 | ].concat(config.keepElem ? headingNodeChildren : [headingText]), 37 | ]; 38 | }); 39 | 40 | markdownData.toc = ['ul'].concat(listItems); 41 | return markdownData; 42 | }; 43 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-react/test/node.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const node = require('../lib/node'); 3 | 4 | function getMarkdownData(lang) { 5 | return { 6 | content: [ 7 | 'article', 8 | [ 9 | 'pre', { 10 | lang, 11 | }, [ 12 | 'code', 13 | 'import { Button } from \'antd\';\n' + 14 | 'ReactDOM.render(\n' + 15 | '
\n' + 16 | ' \n' + 17 | '
\n' + 18 | ', mountNode);', 19 | ], 20 | ], 21 | ], 22 | }; 23 | } 24 | 25 | describe('bisheng-plugin-react', () => { 26 | it('should work', () => { 27 | const markdownData = getMarkdownData('react-example'); 28 | assert.deepEqual(node(markdownData, {}), { 29 | content: [ 30 | 'article', { 31 | __BISHENG_EMBEDED_CODE: true, 32 | code: 'function bishengPluginReactPreviewer() {\n' + 33 | ' var React = require("react");\n\n' + 34 | ' var ReactDOM = require("react-dom");\n\n' + 35 | ' var _antd = require("antd");\n\n' + 36 | ' return React.createElement(\n' + 37 | ' "div",\n' + 38 | ' null,\n' + 39 | ' React.createElement(\n' + 40 | ' _antd.Button,\n' + 41 | ' null,\n' + 42 | ' "Default"\n' + 43 | ' )\n' + 44 | ' );\n' + 45 | '}', 46 | }, 47 | ], 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /packages/bisheng/src/utils/get-bisheng-config.js: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import * as resolve from 'resolve'; 4 | import rucksack from 'rucksack-css'; 5 | import autoprefixer from 'autoprefixer'; 6 | 7 | const markdownTransformer = path.join(__dirname, '..', 'transformers', 'markdown'); 8 | 9 | const defaultConfig = { 10 | port: 8000, 11 | source: './posts', 12 | output: './_site', 13 | theme: './_theme', 14 | htmlTemplate: path.join(__dirname, '../template.html'), 15 | transformers: [], 16 | devServerConfig: {}, 17 | postcssConfig: { 18 | plugins: [ 19 | rucksack(), 20 | autoprefixer({ 21 | browsers: ['last 2 versions', 'Firefox ESR', '> 1%', 'ie >= 8', 'iOS >= 8', 'Android >= 4'], 22 | }), 23 | ], 24 | }, 25 | webpackConfig(config) { 26 | return config; 27 | }, 28 | 29 | entryName: 'index', 30 | root: '/', 31 | filePathMapper(filePath) { 32 | return filePath; 33 | }, 34 | }; 35 | 36 | module.exports = function getBishengConfig(configFile) { 37 | const customizedConfig = fs.existsSync(configFile) ? require(configFile) : {}; 38 | const config = Object.assign({}, defaultConfig, customizedConfig); 39 | config.theme = resolve.sync(config.theme, { basedir: process.cwd() }); 40 | config.transformers = config.transformers.concat({ 41 | test: /\.md$/, 42 | use: markdownTransformer, 43 | }).map(({ test, use }) => ({ 44 | test: test.toString(), // Hack, for we cannot send RegExp to child process 45 | use, 46 | })); 47 | return config; 48 | }; 49 | -------------------------------------------------------------------------------- /packages/bisheng-theme-one/src/template/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'bisheng/router'; 3 | import '../static/style'; 4 | 5 | export default ({ themeConfig, children }) => { 6 | return ( 7 |
8 |
9 |
10 |
11 | {themeConfig.sitename} 12 | { 13 | !themeConfig.tagline ? null : 14 | - {themeConfig.tagline} 15 | } 16 |
17 | { 18 | !themeConfig.navigation ? null : 19 |
20 | { 21 | themeConfig.navigation.map((item, index) => 22 | {item.title} 23 | ) 24 | } 25 |
26 | } 27 |
28 |
29 |
30 | {children} 31 |
32 |
33 | {themeConfig.footer ? themeConfig.footer : null} 34 | { 35 | themeConfig.hideBisheng ? null : 36 |

powered by BiSheng

37 | } 38 |
39 | { 40 | !themeConfig.github ? null : 41 | 42 | } 43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /packages/bisheng/test/utils/get-config.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const path = require('path'); 3 | const getBishengConfig = require('../../lib/utils/get-bisheng-config'); 4 | const getThemeConfig = require('../../lib/utils/get-theme-config'); 5 | 6 | describe('bisheng/utils/get-bisheng-config', () => { 7 | it('should merge custom config to default config', () => { 8 | const bishengConfig = getBishengConfig(path.join(__dirname, '../fixtures/bisheng.config.js')); 9 | delete bishengConfig.webpackConfig; 10 | delete bishengConfig.filePathMapper; 11 | 12 | assert.equal(bishengConfig.postcssConfig.plugins.length, 3); 13 | delete bishengConfig.postcssConfig; 14 | 15 | assert.deepEqual(bishengConfig, { 16 | source: './content', 17 | output: './_site', 18 | entryName: 'index', 19 | theme: path.join(__dirname, '../fixtures/_theme/index.js'), 20 | htmlTemplate: path.join(__dirname, '../../lib/template.html'), 21 | port: 8000, 22 | root: '/', 23 | devServerConfig: {}, 24 | transformers: [{ 25 | test: /\.md$/.toString(), 26 | use: path.join(__dirname, '..', '..', 'lib', 'transformers', 'markdown'), 27 | }], 28 | }); 29 | }); 30 | }); 31 | 32 | describe('bisheng/utils/get-theme-config', () => { 33 | it('should merge custom plugins with default plugin', () => { 34 | const themeConfig = getThemeConfig(path.join(__dirname, '../fixtures/theme.index.js')); 35 | assert.deepEqual(themeConfig, { 36 | plugins: [ 37 | path.join(__dirname, '..', '..', 'lib', 'bisheng-plugin-highlight'), 38 | 'bisheng-plugin-description', 39 | ], 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-description/test/node.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const JsonML = require('jsonml.js'); 3 | const addDescription = require('../lib/node'); 4 | 5 | function getCommonMarkdownData() { 6 | return { 7 | content: [ 8 | 'article', 9 | ['p', 'This is description.'], 10 | ['hr'], 11 | ['p', 'This is main content.'], 12 | ], 13 | }; 14 | } 15 | 16 | describe('bisheng-plugin-description', () => { 17 | it('should add description to markdown data', () => { 18 | const markdownData = getCommonMarkdownData(); 19 | const processedMarkdownData = addDescription(markdownData); 20 | assert.ok(processedMarkdownData.description); 21 | }); 22 | 23 | it('should wrap description in section', () => { 24 | const markdownData = getCommonMarkdownData(); 25 | const { description } = addDescription(markdownData); 26 | assert.strictEqual(JsonML.getTagName(description), 'section'); 27 | }); 28 | 29 | it('should parse description and main content correctly', () => { 30 | const markdownData = getCommonMarkdownData(); 31 | const processedMarkdownData = addDescription(markdownData); 32 | assert.deepEqual(JsonML.getChildren(processedMarkdownData.description), [['p', 'This is description.']]); 33 | assert.deepEqual(JsonML.getChildren(processedMarkdownData.content), [['p', 'This is main content.']]); 34 | }); 35 | 36 | it('should not add description to markdown data, if description doesn\'t exist', () => { 37 | const markdownData = { 38 | content: [ 39 | 'article', 40 | ['p', 'This is main content.'], 41 | ], 42 | }; 43 | const processedMarkdownData = addDescription(markdownData); 44 | assert.ok(!('description' in processedMarkdownData)); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-toc/test/node.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const addToc = require('../lib/node'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const MT = require('mark-twain'); 6 | 7 | const markdownData = MT(fs.readFileSync(path.join(__dirname, 'fixtures.md')).toString()); 8 | 9 | describe('bisheng-plugin-toc', () => { 10 | it('should add `toc` to markdownData', () => { 11 | const data = addToc(markdownData, {}); 12 | assert.ok('toc' in data); 13 | }); 14 | 15 | it('should ignore heading which depth is greater than `config.maxDepth`', () => { 16 | const data = addToc(markdownData, { maxDepth: 2 }); 17 | assert.deepEqual(data.toc, [ 18 | 'ul', 19 | ['li', ['a', { className: 'bisheng-toc-h1', href: '#Heading1', title: 'Heading1' }, 'Heading1']], 20 | ['li', ['a', { className: 'bisheng-toc-h2', href: '#Heading2', title: 'Heading2' }, 'Heading2']], 21 | ]); 22 | }); 23 | 24 | it('should generate slugged id', () => { 25 | const data = addToc(markdownData, { maxDepth: 3 }); 26 | assert.deepEqual(data.toc.slice(4), [ 27 | ['li', ['a', { className: 'bisheng-toc-h3', href: '#hello-world', title: 'hello world' }, 'hello world']], 28 | ['li', ['a', { className: 'bisheng-toc-h3', href: '#hello-world', title: 'hello world' }, 'hello world']], 29 | ]); 30 | }); 31 | 32 | it('should keep elements in heading text', () => { 33 | const data = addToc(markdownData, { maxDepth: 3, keepElem: true }); 34 | assert.deepEqual(data.toc.slice(4), [ 35 | ['li', ['a', { className: 'bisheng-toc-h3', href: '#hello-world', title: 'hello world' }, 'hello world']], 36 | ['li', ['a', { className: 'bisheng-toc-h3', href: '#hello-world', title: 'hello world' }, 'hello ', ['a', { href: './world', title: null }, 'world']]], 37 | ]); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/bisheng-theme-one/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bisheng-theme-one", 3 | "version": "0.2.31", 4 | "description": "The one theme for bisheng.", 5 | "main": "./lib/index.js", 6 | "files": [ 7 | "lib" 8 | ], 9 | "scripts": { 10 | "prepublish": "babel ./src --out-dir ./lib && shx cp -R src/static/* lib/static", 11 | "postpublish": "shx rm -rf lib" 12 | }, 13 | "babel": { 14 | "presets": [ 15 | "react", 16 | [ 17 | "env", 18 | { 19 | "targets": { 20 | "browsers": [ 21 | "last 2 versions", 22 | "Firefox ESR", 23 | "> 1%", 24 | "ie >= 8", 25 | "iOS >= 8", 26 | "Android >= 4" 27 | ] 28 | } 29 | } 30 | ] 31 | ], 32 | "plugins": [ 33 | "transform-class-properties", 34 | "transform-object-rest-spread" 35 | ] 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "git+https://github.com/benjycui/bisheng-theme-one.git" 40 | }, 41 | "keywords": [ 42 | "bisheng", 43 | "theme", 44 | "one" 45 | ], 46 | "author": "Benjy Cui", 47 | "license": "MIT", 48 | "bugs": { 49 | "url": "https://github.com/benjycui/bisheng-theme-one/issues" 50 | }, 51 | "homepage": "https://github.com/benjycui/bisheng-theme-one#readme", 52 | "devDependencies": { 53 | "babel-cli": "^6.18.0", 54 | "babel-core": "^6.26.0", 55 | "babel-plugin-transform-class-properties": "^6.24.1", 56 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 57 | "babel-preset-env": "^1.6.0", 58 | "babel-preset-react": "^6.24.1", 59 | "bisheng": "^0.28.0", 60 | "react": "^16.0.0", 61 | "shx": "^0.2.2" 62 | }, 63 | "dependencies": { 64 | "bisheng-plugin-description": "^0.1.4", 65 | "react-document-title": "^2.0.1" 66 | }, 67 | "peerDependencies": { 68 | "bisheng": "^0.28.0", 69 | "react": "^16.0.0" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-react/README.md: -------------------------------------------------------------------------------- 1 | # bisheng-plugin-react 2 | 3 | [![](https://img.shields.io/travis/benjycui/bisheng.svg?style=flat-square)](https://travis-ci.org/benjycui/bisheng) 4 | [![npm package](https://img.shields.io/npm/v/bisheng-plugin-react.svg?style=flat-square)](https://www.npmjs.org/package/bisheng-plugin-react) 5 | [![NPM downloads](http://img.shields.io/npm/dm/bisheng-plugin-react.svg?style=flat-square)](https://npmjs.org/package/bisheng-plugin-react) 6 | [![Dependency Status](https://david-dm.org/benjycui/bisheng-plugin-react.svg?style=flat-square)](https://david-dm.org/benjycui/bisheng-plugin-react) 7 | 8 | To convert JSX which is written in Markdown to React.Element. 9 | 10 | ## Usage 11 | 12 | Install: 13 | 14 | ```bash 15 | npm i --save bisheng-plugin-react 16 | ``` 17 | 18 | Add 'bisheng-plugin-react to `bisehng.config.js`'s plugins. 19 | 20 | ```js 21 | module.exports = { 22 | plugins: ['bisheng-plugin-react?lang=jsx'], 23 | }; 24 | ``` 25 | 26 | In Markdown: 27 | 28 |
29 | ...
30 | 
31 | This is a button:
32 | 
33 | ```jsx
34 | import { Button } from 'antd';
35 | ReactDOM.render(<Button>Click!</Button>, mountNode);
36 | ```
37 | ...
38 | 
39 | 40 | The above example will be rendered as: 41 | 42 | ![screenshot](https://raw.githubusercontent.com/benjycui/bisheng/master/packages/bisheng-plugin-react/screenshot.png) 43 | 44 | ## API 45 | 46 | ### lang: String 47 | 48 | > default: 'react-component' 49 | 50 | ### babelConfig: Object 51 | 52 | > default: 53 | > { 54 | > presets: [ 55 | > 'react', 56 | > ['env', { 57 | > targets: { 58 | > browsers: ['last 2 versions', 'Firefox ESR', '> 1%', 'ie >= 8', 'iOS >= 8', 'Android >= 4'], 59 | > }, 60 | > }], 61 | > ], 62 | > plugins: [ 63 | > 'transform-class-properties', 64 | > 'transform-object-rest-spread', 65 | > ], 66 | > } 67 | 68 | ### noreact: Boolean 69 | 70 | > default: false 71 | 72 | Whether to import `React` and `ReactDOM` automatically. 73 | 74 | ## License 75 | 76 | MIT 77 | -------------------------------------------------------------------------------- /packages/bisheng/test/utils/strinigfy.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const stringify = require('../../lib/utils/stringify'); 3 | 4 | describe('bisheng/utils/stringify', () => { 5 | it('should stringify array correctly', () => { 6 | assert.strictEqual(stringify([1]), '[\n 1\n]'); 7 | assert.strictEqual(stringify([1, 2]), '[\n 1,\n 2\n]'); 8 | }); 9 | 10 | it('should stringify nested array correctly', () => { 11 | assert.strictEqual(stringify([[1]]), '[\n [\n 1\n ]\n]'); 12 | assert.strictEqual(stringify([1, [2]]), '[\n 1,\n [\n 2\n ]\n]'); 13 | }); 14 | 15 | it('should stringify array with object correctly', () => { 16 | assert.strictEqual(stringify([{}]), '[\n {\n\n }\n]'); 17 | assert.strictEqual(stringify([1, {}]), '[\n 1,\n {\n\n }\n]'); 18 | }); 19 | 20 | it('should stringify object correctly', () => { 21 | assert.strictEqual(stringify({ a: 1 }), '{\n "a": 1\n}'); 22 | assert.strictEqual(stringify({ a: 1, b: 2 }), '{\n "a": 1,\n "b": 2\n}'); 23 | }); 24 | 25 | it('should stringify nested object correctly', () => { 26 | assert.strictEqual(stringify({ a: {} }), '{\n "a": {\n\n }\n}'); 27 | assert.strictEqual(stringify({ a: { b: 1 } }), '{\n "a": {\n "b": 1\n }\n}'); 28 | }); 29 | 30 | it('should stringify object with array correctly', () => { 31 | assert.strictEqual(stringify({ a: [] }), '{\n "a": [\n\n ]\n}'); 32 | assert.strictEqual(stringify({ a: 1, b: [] }), '{\n "a": 1,\n "b": [\n\n ]\n}'); 33 | }); 34 | 35 | it('should stringify `null` correctly', () => { 36 | assert.strictEqual(stringify(null), 'null'); 37 | }); 38 | 39 | it('should strinfify Date object correctly', () => { 40 | const now = new Date(); 41 | assert.strictEqual(stringify(now), JSON.stringify(now)); 42 | }); 43 | 44 | it('should extract code from __BISHENG_EMBENED_CODE', () => { 45 | const res = stringify({ 46 | __BISHENG_EMBEDED_CODE: true, 47 | code: 'console.log("Hello world!")', 48 | }); 49 | assert.strictEqual(res, 'console.log("Hello world!")'); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /packages/bisheng-theme-one/src/template/Post.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'bisheng/router'; 3 | import collect from 'bisheng/collect'; 4 | import DocumentTitle from 'react-document-title'; 5 | import Layout from './Layout'; 6 | 7 | const Post = (props) => { 8 | const { pageData, utils } = props; 9 | const { meta, description, content } = pageData; 10 | return ( 11 | 12 | 13 |
14 |

{meta.title}

15 | { 16 | !description ? null : 17 |
{utils.toReactComponent(description)}
18 | } 19 |
{utils.toReactComponent(content)}
20 | 21 |
22 | 25 | { 26 | !meta.tags ? null : 27 | 28 | in 29 | { 30 | meta.tags.map((tag, index) => 31 | {tag} 32 | ) 33 | } 34 | 35 | 36 | } 37 | { 38 | !meta.source ? null : 39 | 40 | {meta.source} 41 | 42 | } 43 |
44 |
45 |
46 |
47 | ); 48 | } 49 | 50 | export default collect(async (nextProps) => { 51 | if (!nextProps.pageData) { 52 | throw 404; 53 | } 54 | const pageData = await nextProps.pageData(); 55 | return { pageData }; 56 | })(Post); 57 | 58 | // TODO 59 | // {%- if config.disqus %} 60 | // {%- include "_disqus.html" %} 61 | // {%- endif %} 62 | // {%- if config.duoshuo %} 63 | // {%- include "_duoshuo.html" %} 64 | // {%- endif %} 65 | -------------------------------------------------------------------------------- /packages/bisheng/src/config/updateWebpackConfig.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 4 | import context from '../context'; 5 | import getStyleLoadersConfig from './getStyleLoadersConfig'; 6 | 7 | const bishengLib = path.join(__dirname, '..'); 8 | const bishengLibLoaders = path.join(bishengLib, 'loaders'); 9 | 10 | export default function updateWebpackConfig(webpackConfig, mode) { 11 | const { bishengConfig } = context; 12 | const styleLoadersConfig = getStyleLoadersConfig(bishengConfig.postcssConfig); 13 | 14 | /* eslint-disable no-param-reassign */ 15 | webpackConfig.entry = {}; 16 | if (context.isBuild) { 17 | webpackConfig.output.path = path.join(process.cwd(), bishengConfig.output); 18 | } 19 | webpackConfig.output.publicPath = context.isBuild ? bishengConfig.root : '/'; 20 | if (mode === 'start') { 21 | styleLoadersConfig.forEach((config) => { 22 | webpackConfig.module.rules.push({ 23 | test: config.test, 24 | use: ['style-loader', ...config.use], 25 | }); 26 | }); 27 | } 28 | if (mode === 'build') { 29 | styleLoadersConfig.forEach((config) => { 30 | webpackConfig.module.rules.push({ 31 | test: config.test, 32 | use: ExtractTextPlugin.extract({ 33 | use: config.use, 34 | }), 35 | }); 36 | }); 37 | } 38 | webpackConfig.module.rules.push({ 39 | test(filename) { 40 | return filename === path.join(bishengLib, 'utils', 'data.js') || 41 | filename === path.join(bishengLib, 'utils', 'ssr-data.js'); 42 | }, 43 | loader: path.join(bishengLibLoaders, 'bisheng-data-loader'), 44 | }); 45 | /* eslint-enable no-param-reassign */ 46 | 47 | const customizedWebpackConfig = bishengConfig.webpackConfig(webpackConfig, webpack); 48 | 49 | const entryPath = path.join(bishengLib, '..', 'tmp', `entry.${bishengConfig.entryName}.js`); 50 | if (customizedWebpackConfig.entry[bishengConfig.entryName]) { 51 | throw new Error(`Should not set \`webpackConfig.entry.${bishengConfig.entryName}\`!`); 52 | } 53 | customizedWebpackConfig.entry[bishengConfig.entryName] = entryPath; 54 | return customizedWebpackConfig; 55 | } 56 | -------------------------------------------------------------------------------- /packages/bisheng-theme-one/src/template/Archive.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'bisheng/router'; 3 | import DocumentTitle from 'react-document-title'; 4 | import Layout from './Layout'; 5 | 6 | function getTime(date) { 7 | return (new Date(date)).getTime(); 8 | } 9 | 10 | export default (props) => { 11 | const toReactComponent = props.utils.toReactComponent; 12 | const posts = props.picked.posts 13 | .sort((a, b) => getTime(b.meta.publishDate) - getTime(a.meta.publishDate)); 14 | 15 | let year = NaN; 16 | const entryList = []; 17 | posts.forEach(({ meta, description }, index) => { 18 | if (!meta.publishDate) { 19 | console.error(`You must set 'publishDate' in meta data for ${meta.filename}.`); 20 | return; 21 | } 22 | const publishYear = meta.publishDate.slice(0, 4); 23 | if (year !== publishYear) { 24 | year = publishYear; 25 | entryList.push( 26 | 27 | {publishYear} 28 | ); 29 | } 30 | 31 | entryList.push( 32 |
33 |

34 | 35 | {meta.title} 36 |

37 | { 38 | !description ? null : 39 |
40 | { toReactComponent(description) } 41 |
42 | } 43 |
44 | ); 45 | }) 46 | return ( 47 | 48 | 49 |

Archive

50 |
51 | {entryList} 52 |
53 |
54 |
55 | ); 56 | } 57 | 58 | // TODO 59 | // 68 | -------------------------------------------------------------------------------- /packages/bisheng-theme-one/src/template/TagCloud.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'bisheng/router'; 3 | import DocumentTitle from 'react-document-title'; 4 | import Layout from './Layout'; 5 | 6 | function getTags(posts) { 7 | const tags = {}; 8 | Object.keys(posts).forEach((filename) => { 9 | const post = posts[filename]; 10 | const postTags = post.meta.tags; 11 | if (postTags) { 12 | postTags.forEach((tag) => { 13 | if (tags[tag]) { 14 | tags[tag].push(post); 15 | } else { 16 | tags[tag] = [post]; 17 | } 18 | }); 19 | } 20 | }); 21 | return tags; 22 | } 23 | 24 | export default (props) => { 25 | const toReactComponent = props.utils.toReactComponent; 26 | const tags = getTags(props.picked.posts); 27 | 28 | return ( 29 | 30 | 31 |

Tags

32 |
33 | { 34 | Object.keys(tags).map( 35 | (tag, index) => 36 | 37 | {tag} {tags[tag].length} 38 | 39 | ) 40 | } 41 |
42 | 43 |
44 | { 45 | Object.keys(tags).map((tag) => 46 | [ 47 | {tag} 48 | ].concat(tags[tag].map(({ meta, description }, index) => 49 |
50 |

51 | 52 | {meta.title} 53 |

54 | { 55 | !description ? null : 56 |
57 | { toReactComponent(description) } 58 |
59 | } 60 |
61 | )) 62 | ) 63 | } 64 |
65 |
66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /packages/bisheng/src/config/getStyleLoadersConfig.js: -------------------------------------------------------------------------------- 1 | 2 | export default postcssOptions => ([ 3 | { 4 | test(filePath) { 5 | return /\.css$/.test(filePath) && !/\.module\.css$/.test(filePath); 6 | }, 7 | use: [{ 8 | loader: 'css-loader', 9 | options: { 10 | restructuring: false, 11 | autoprefixer: false, 12 | }, 13 | }, { 14 | loader: 'postcss-loader', 15 | options: postcssOptions, 16 | }], 17 | }, 18 | { 19 | test: /\.module\.css$/, 20 | use: [{ 21 | loader: 'css-loader', 22 | options: { 23 | restructuring: false, 24 | modules: true, 25 | localIdentName: '[local]___[hash:base64:5]', 26 | autoprefixer: false, 27 | }, 28 | }, { 29 | loader: 'postcss-loader', 30 | options: postcssOptions, 31 | }], 32 | }, 33 | { 34 | test(filePath) { 35 | return /\.less$/.test(filePath) && !/\.module\.less$/.test(filePath); 36 | }, 37 | use: [{ 38 | loader: 'css-loader', 39 | options: { 40 | autoprefixer: false, 41 | }, 42 | }, { 43 | loader: 'postcss-loader', 44 | options: postcssOptions, 45 | }, 'less-loader'], 46 | }, 47 | { 48 | test: /\.module\.less$/, 49 | use: [{ 50 | loader: 'css-loader', 51 | options: { 52 | modules: true, 53 | localIdentName: '[local]___[hash:base64:5]', 54 | autoprefixer: false, 55 | }, 56 | }, { 57 | loader: 'postcss-loader', 58 | options: postcssOptions, 59 | }, 'less-loader'], 60 | }, 61 | { 62 | test(filePath) { 63 | return /\.scss$/.test(filePath) && !/\.module\.scss$/.test(filePath); 64 | }, 65 | use: [{ 66 | loader: 'css-loader', 67 | options: { 68 | autoprefixer: false, 69 | }, 70 | }, { 71 | loader: 'postcss-loader', 72 | options: postcssOptions, 73 | }, 'sass-loader'], 74 | }, 75 | { 76 | test: /\.module\.scss$/, 77 | use: [{ 78 | loader: 'css-loader', 79 | options: { 80 | modules: true, 81 | localIdentName: '[local]___[hash:base64:5]', 82 | autoprefixer: false, 83 | }, 84 | }, { 85 | loader: 'postcss-loader', 86 | options: postcssOptions, 87 | }, 'sass-loader'], 88 | } 89 | ]); 90 | -------------------------------------------------------------------------------- /packages/bisheng/src/utils/generate-files-path.js: -------------------------------------------------------------------------------- 1 | const R = require('ramda'); 2 | const exist = require('exist.js'); 3 | const { join } = require('path'); 4 | const { toUriPath } = require('./escape-win-path'); 5 | 6 | function hasParams(path) { 7 | return path.split('/').some(snippet => snippet.startsWith(':')); 8 | } 9 | 10 | function has404(filesPath) { 11 | return filesPath.indexOf('/404.html') >= 0; 12 | } 13 | 14 | function flattenRoutes(routes) { 15 | let flattenedRoutes = []; 16 | (Array.isArray(routes) ? routes : [routes]).forEach((item) => { 17 | const copy = Object.assign({}, item); 18 | if (!copy.dataPath) { 19 | copy.dataPath = copy.path; 20 | } 21 | flattenedRoutes.push(copy); 22 | 23 | if (item.childRoutes) { 24 | const nestedRoutes = R.chain(flattenRoutes, item.childRoutes.map(child => Object.assign({}, child, { 25 | path: join(item.path, child.path), 26 | }))); 27 | flattenedRoutes = flattenedRoutes.concat(nestedRoutes); 28 | } 29 | }); 30 | return flattenedRoutes; 31 | } 32 | 33 | module.exports = function generateFilesPath(routes, markdown) { 34 | const flattenedRoutes = flattenRoutes(routes).map(function(item) { 35 | item.path = toUriPath(item.path); 36 | item.dataPath = toUriPath(item.dataPath); 37 | return item; 38 | }); 39 | 40 | const filesPath = R.chain((item) => { 41 | if (hasParams(item.path)) { 42 | const dataPathSnippets = item.dataPath.split('/').slice(1); 43 | const firstParamIndex = dataPathSnippets.findIndex(snippet => snippet.startsWith(':')); 44 | const firstParam = dataPathSnippets[firstParamIndex]; 45 | 46 | const dataSet = exist.get(markdown, dataPathSnippets.slice(0, firstParamIndex), {}); 47 | const processedCompleteRoutes = Object.keys(dataSet).map((key) => { 48 | const pathSnippet = key.replace(/\.md/, ''); 49 | const path = item.path.replace(firstParam, pathSnippet); 50 | const dataPath = item.dataPath.replace(firstParam, pathSnippet); 51 | return { path, dataPath }; 52 | }); 53 | 54 | return generateFilesPath(processedCompleteRoutes, markdown); 55 | } else if (item.path.endsWith('/')) { 56 | return [`${item.path}index.html`]; 57 | } 58 | return [`${item.path}.html`]; 59 | }, flattenedRoutes); 60 | 61 | return has404(filesPath) ? filesPath : filesPath.concat('/404.html'); 62 | }; 63 | -------------------------------------------------------------------------------- /docs/plugin.md: -------------------------------------------------------------------------------- 1 | # Plugin 2 | 3 | A plugin is just a npm package with the following directory structure: 4 | 5 | ```bash 6 | └── lib 7 | ├── browser.js # optional 8 | └── node.js # optional 9 | ``` 10 | 11 | ## browser.js 12 | 13 | `browser.js` exports a function which takes `config` and returns an object. 14 | 15 | ```js 16 | moldule.exports = (config) => { 17 | return { 18 | utils: {...}, 19 | converters: [...], 20 | }; 21 | }; 22 | ``` 23 | 24 | `config` is plugin's config: 25 | 26 | ```js 27 | // in bisheng.config.js 28 | module.exports = { 29 | plugins: [ 'pluginName?config1=value1&config2=value2' ], 30 | } 31 | 32 | // will get this config object in plugin 33 | { 34 | config1: value1, 35 | config2: value2, 36 | } 37 | ``` 38 | 39 | `utils` of the returned value will be merged to `props.utils`, for example: 40 | 41 | ```js 42 | // in browser.js 43 | { 44 | utils: { 45 | say(sth) { 46 | alert(sth); 47 | }, 48 | }, 49 | } 50 | 51 | // in template 52 | props.utils.say('Hello world!'); 53 | ``` 54 | 55 | `converters` will concat other plugins' converters, and then pass to [jsonml-to-react-component](https://github.com/benjycui/jsonml-to-react-component)(which is used by `props.utils.toReactComponent`). 56 | 57 | ## node.js 58 | 59 | `node.js` exports a function which take `markdownData` and `config`, and then return a new `markdownData`. 60 | 61 | ```js 62 | module.exports = (markdownData, config) => { 63 | // do something... 64 | return markdownData; 65 | }; 66 | ``` 67 | 68 | `markdownData` is the returned value of [mark-twain](https://github.com/benjycui/mark-twain). 69 | 70 | `config` is the same as `browser.js`'s `config`. 71 | 72 | In `node.js`, you can add or remove or modify fields in `markdownData`, and the final Markdown data which will be pass to template had been processed by all the plugins. 73 | 74 | **Note:** we cannot add function to `markdownData` directly, for `markdownData` will be `JSON.stringify` to write to file. However, `bisheng` has extended the behaviour of `stringify`, so we can insert code into `markdownData` by the following syntax: 75 | 76 | ```js 77 | markdownData.method = { 78 | __BISHENG_EMBEDED_CODE: true, 79 | code: 'function(message) { console.log(message) }', 80 | }; 81 | ``` 82 | 83 | Then, the stringified `markdownData` will be: 84 | 85 | ```js 86 | { 87 | method: function(message) { console.log(message) }, 88 | ... 89 | } 90 | ``` 91 | -------------------------------------------------------------------------------- /packages/bisheng/src/loaders/bisheng-data-loader.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const getThemeConfig = require('../utils/get-theme-config'); 4 | const sourceData = require('../utils/source-data'); 5 | const resolvePlugins = require('../utils/resolve-plugins'); 6 | const context = require('../context'); 7 | const boss = require('./common/boss'); 8 | 9 | module.exports = function bishengDataLoader(/* content */) { 10 | if (this.cacheable) { 11 | this.cacheable(); 12 | } 13 | 14 | const { bishengConfig } = context; 15 | const themeConfig = getThemeConfig(bishengConfig.theme); 16 | 17 | const markdown = sourceData.generate(bishengConfig.source, bishengConfig.transformers); 18 | const browserPlugins = resolvePlugins(themeConfig.plugins, 'browser'); 19 | const pluginsString = browserPlugins 20 | .map(plugin => `[require('${plugin[0]}'), ${JSON.stringify(plugin[1])}]`) 21 | .join(',\n'); 22 | 23 | const callback = this.async(); 24 | 25 | const picked = {}; 26 | const pickedPromises = []; // Flag to remind loader that job is done. 27 | if (themeConfig.pick) { 28 | const nodePlugins = resolvePlugins(themeConfig.plugins, 'node'); 29 | sourceData.traverse(markdown, (filename) => { 30 | const fileContent = fs.readFileSync(path.join(process.cwd(), filename)).toString(); 31 | pickedPromises.push(new Promise((resolve) => { 32 | boss.queue({ 33 | filename, 34 | content: fileContent, 35 | plugins: nodePlugins, 36 | transformers: bishengConfig.transformers, 37 | isBuild: context.isBuild, 38 | callback(err, result) { 39 | const parsedMarkdown = eval(`(${result})`); // eslint-disable-line no-eval 40 | 41 | Object.keys(themeConfig.pick).forEach((key) => { 42 | if (!picked[key]) { 43 | picked[key] = []; 44 | } 45 | 46 | const picker = themeConfig.pick[key]; 47 | const pickedData = picker(parsedMarkdown); 48 | if (pickedData) { 49 | picked[key].push(pickedData); 50 | } 51 | }); 52 | 53 | resolve(); 54 | }, 55 | }); 56 | })); 57 | }); 58 | } 59 | 60 | Promise.all(pickedPromises) 61 | .then(() => { 62 | const sourceDataString = sourceData.stringify(markdown, { 63 | lazyLoad: themeConfig.lazyLoad, 64 | }); 65 | callback( 66 | null, 67 | 'module.exports = {' + 68 | `\n markdown: ${sourceDataString},` + 69 | `\n picked: ${JSON.stringify(picked, null, 2)},` + 70 | `\n plugins: [\n${pluginsString}\n],` + 71 | '\n};', 72 | ); 73 | }); 74 | }; 75 | -------------------------------------------------------------------------------- /docs/lazy-load.md: -------------------------------------------------------------------------------- 1 | # Lazy Load 2 | 3 | While [`lazyLoad`](https://github.com/benjycui/bisheng#lazyload-boolean--nodepath-nodevalue--boolean) is `true` or a function, Markdown data will be load lazily. And the corresponding fields in [Markdown data tree](https://github.com/benjycui/bisheng#source-string--arraystring--object-category-string--arraystring) will become a **lazy load function**, for example: 4 | 5 | ```js 6 | // lazyLoad: false 7 | { 8 | posts: { 9 | a: {...}, 10 | }, 11 | } 12 | 13 | // lazyLoad: true 14 | { 15 | posts: { 16 | a: function() {...}, 17 | }, 18 | } 19 | ``` 20 | 21 | We can use this function to get the real Markdown data: 22 | 23 | ```js 24 | data.posts.a() 25 | .then((markdownData) => { 26 | console.log(markdownData); 27 | }); 28 | ``` 29 | 30 | ## `collect` decorator in Template 31 | 32 | It's inconvenient to use lazy load function directly, because we want to make sure that: 33 | * The progress bar works correctly. 34 | * The page will be refreshed only when the Markdown data is loaded. 35 | 36 | So, we can add a collect function to template: 37 | 38 | ```js 39 | // theme/template/Template.jsx 40 | import collect from 'bisheng/collect'; 41 | 42 | const Post = (props) => { 43 | ... 44 | }; 45 | 46 | export default collect(async (nextProps) => { 47 | if (!nextProps.pageData) { 48 | throw 404; // Then, bisheng will show `NotFound.jsx` 49 | } 50 | const pageData = await nextProps.pageData(); 51 | return { pageData }; // This value will be merged with `nextProps` and passed to your route component. 52 | }) 53 | ``` 54 | 55 | `nextProps` is the original props that tempalte will get, and we can convert lazy load function in `nextProps` to real Markdown data. Then, we return the Markdown data to notifiy `bisheng` to refresh the page with new props. 56 | 57 | ### collect(collector: async function) => (RouteComponent: React.Component) => React.Component 58 | 59 | `collect` decorator is optional, but we can use it to improve user experience, for `bisheng` will make progress bar work correctly and refresh page at the right timing. 60 | 61 | ### collector(nextProps: Object) => Object 62 | 63 | The returned value of `collector` will be merged with `nextProps` and passed to your `RouteComponent`. If you throw `404` in `collector`, bisheng will show `NotFound.jsx`. 64 | 65 | ## lazyLoad: (nodePath: String, nodeValue: Object) => boolean 66 | 67 | `lazyLoad: true` will make each Markdown data as a file to load lazily. But if we want to merge several Markdown data into one file to load lazily(which will reduce HTTP request), we can pass a function to `lazyload` to do this. 68 | 69 | `bisheng` will traverse every node of the Markdown data tree, and pass node's path and value to `lazyLoad`. If the returned value of `lazyLoad` is `true`, the whole subtree of the node will be merge in one file and the corresponding field will becoome a lazy load function. If the return value is `false`, `bisheng` will keep traversing the subtree of this node. 70 | -------------------------------------------------------------------------------- /packages/bisheng/test/utils/source-data.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const path = require('path'); 3 | const { escapeWinPath } = require('../../lib/utils/escape-win-path'); 4 | const sourceData = require('../../lib/utils/source-data'); 5 | 6 | const context = require('../../lib/context'); 7 | 8 | context.initialize({ bishengConfig: {} }); 9 | 10 | const sourcePath = path.join(__dirname, '../fixtures/posts'); 11 | const transformers = [{ 12 | test: /\.md$/.toString(), 13 | use: path.join(__dirname, '..', '..', 'lib', 'transformers', 'markdown'), 14 | }]; 15 | 16 | describe('bisheng/utils/source-data', () => { 17 | describe('#generate', () => { 18 | it('should generate a files tree from `config.source`', () => { 19 | const filesTree = sourceData.generate(sourcePath, transformers); 20 | assert.deepEqual(filesTree, { 21 | a: { 22 | 'en-US': path.join(sourcePath, 'a.en-US.md'), 23 | 'zh-CN': path.join(sourcePath, 'a.zh-CN.md'), 24 | }, 25 | b: path.join(sourcePath, 'b.md'), 26 | }); 27 | }); 28 | 29 | it('should generate a files tree from `config.source`', () => { 30 | const filesTree = sourceData.generate([sourcePath], transformers); 31 | assert.deepEqual(filesTree, { 32 | a: { 33 | 'en-US': path.join(sourcePath, 'a.en-US.md'), 34 | 'zh-CN': path.join(sourcePath, 'a.zh-CN.md'), 35 | }, 36 | b: path.join(sourcePath, 'b.md'), 37 | }); 38 | }); 39 | 40 | it('should generate files trees while `config.source` is an object', () => { 41 | const filesTree = sourceData.generate({ mockData: sourcePath }, transformers); 42 | assert.deepEqual(filesTree, { 43 | mockData: { 44 | a: { 45 | 'en-US': path.join(sourcePath, 'a.en-US.md'), 46 | 'zh-CN': path.join(sourcePath, 'a.zh-CN.md'), 47 | }, 48 | b: path.join(sourcePath, 'b.md'), 49 | }, 50 | }); 51 | }); 52 | }); 53 | 54 | describe('#stringify', () => { 55 | const sourceLoaderPath = path.join(__dirname, '..', '..', 'lib', 'loaders', 'source-loader'); 56 | const loaderString = `${sourceLoaderPath}!`; 57 | 58 | it('should stringify value to `require` sentence', () => { 59 | const filesTree = sourceData.generate(sourcePath, transformers); 60 | const stringified = sourceData.stringify(filesTree); 61 | assert.strictEqual(stringified, '{\n' + 62 | ' \'a\': {\n' + 63 | ` 'en-US': require('${escapeWinPath(loaderString)}${escapeWinPath(path.join(sourcePath, 'a.en-US.md'))}'),\n` + 64 | ` 'zh-CN': require('${escapeWinPath(loaderString)}${escapeWinPath(path.join(sourcePath, 'a.zh-CN.md'))}'),\n` + 65 | ' },\n' + 66 | ` 'b': require('${escapeWinPath(loaderString)}${escapeWinPath(path.join(sourcePath, 'b.md'))}'),\n` + 67 | '}'); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /packages/bisheng-plugin-react/lib/transformer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const babel = require('babel-core'); 4 | const types = require('babel-types'); 5 | const traverse = require('babel-traverse').default; 6 | const generator = require('babel-generator').default; 7 | 8 | const errorBoxStyle = { 9 | padding: 10, 10 | background: 'rgb(204, 0, 0)', 11 | color: 'white', 12 | fontFamily: 'sans-serif', 13 | fontSize: '16px', 14 | fontWeight: 'bold', 15 | overflow: 'auto', 16 | }; 17 | 18 | function requireGenerator(varName, moduleName) { 19 | return types.variableDeclaration('var', [ 20 | types.variableDeclarator( 21 | types.identifier(varName), 22 | types.callExpression( 23 | types.identifier('require'), 24 | [types.stringLiteral(moduleName)] 25 | ) 26 | ), 27 | ]); 28 | } 29 | 30 | const defaultBabelConfig = { 31 | presets: [ 32 | 'react', 33 | ['env', { 34 | targets: { 35 | browsers: ['last 2 versions', 'Firefox ESR', '> 1%', 'ie >= 8', 'iOS >= 8', 'Android >= 4'], 36 | }, 37 | }], 38 | ], 39 | plugins: [ 40 | 'transform-class-properties', 41 | 'transform-object-rest-spread', 42 | ], 43 | }; 44 | 45 | module.exports = function transformer( 46 | code, 47 | babelConfig = {}, 48 | noreact 49 | ) { 50 | let codeAst = null; 51 | try { 52 | const { ast } = babel.transform(code, Object.assign({}, defaultBabelConfig, babelConfig)); 53 | codeAst = ast; 54 | } catch(e) { 55 | console.error(e); 56 | return `function() { ` + 57 | ` var React = require('react');` + 58 | ` return React.createElement('pre', {` + 59 | ` style: ${JSON.stringify(errorBoxStyle)}` + 60 | ` }, '${e.toString()}'); ` + 61 | `}`; 62 | } 63 | 64 | let renderReturn = null; 65 | traverse(codeAst, { 66 | CallExpression: function(callPath) { 67 | const callPathNode = callPath.node; 68 | if (callPathNode.callee && 69 | callPathNode.callee.object && 70 | callPathNode.callee.object.name === 'ReactDOM' && 71 | callPathNode.callee.property && 72 | callPathNode.callee.property.name === 'render') { 73 | 74 | renderReturn = types.returnStatement( 75 | callPathNode.arguments[0] 76 | ); 77 | 78 | callPath.remove(); 79 | } 80 | }, 81 | }); 82 | 83 | const astProgramBody = codeAst.program.body; 84 | if (!noreact) { 85 | astProgramBody.unshift(requireGenerator('ReactDOM', 'react-dom')); 86 | astProgramBody.unshift(requireGenerator('React', 'react')); 87 | } 88 | // ReactDOM.render always at the last of preview method 89 | if (renderReturn) { 90 | astProgramBody.push(renderReturn); 91 | } 92 | 93 | const codeBlock = types.BlockStatement(astProgramBody); 94 | const previewFunction = types.functionDeclaration( 95 | types.Identifier('bishengPluginReactPreviewer'), 96 | [], 97 | codeBlock 98 | ); 99 | 100 | return generator(types.program([previewFunction]), null, code).code; 101 | }; 102 | -------------------------------------------------------------------------------- /packages/bisheng-theme-one/src/static/syntax.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js default theme for JavaScript, CSS and HTML 3 | * Based on dabblet (http://dabblet.com) 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: black; 10 | background: none; 11 | text-shadow: 0 1px white; 12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 13 | direction: ltr; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 32 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 33 | text-shadow: none; 34 | background: #b3d4fc; 35 | } 36 | 37 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 38 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 39 | text-shadow: none; 40 | background: #b3d4fc; 41 | } 42 | 43 | @media print { 44 | code[class*="language-"], 45 | pre[class*="language-"] { 46 | text-shadow: none; 47 | } 48 | } 49 | 50 | /* Code blocks */ 51 | pre[class*="language-"] { 52 | padding: 1em; 53 | margin: .5em 0; 54 | overflow: auto; 55 | } 56 | 57 | :not(pre) > code[class*="language-"], 58 | pre[class*="language-"] { 59 | background: #f5f2f0; 60 | } 61 | 62 | /* Inline code */ 63 | :not(pre) > code[class*="language-"] { 64 | padding: .1em; 65 | border-radius: .3em; 66 | white-space: normal; 67 | } 68 | 69 | .token.comment, 70 | .token.prolog, 71 | .token.doctype, 72 | .token.cdata { 73 | color: slategray; 74 | } 75 | 76 | .token.punctuation { 77 | color: #999; 78 | } 79 | 80 | .namespace { 81 | opacity: .7; 82 | } 83 | 84 | .token.property, 85 | .token.tag, 86 | .token.boolean, 87 | .token.number, 88 | .token.constant, 89 | .token.symbol, 90 | .token.deleted { 91 | color: #905; 92 | } 93 | 94 | .token.selector, 95 | .token.attr-name, 96 | .token.string, 97 | .token.char, 98 | .token.builtin, 99 | .token.inserted { 100 | color: #690; 101 | } 102 | 103 | .token.operator, 104 | .token.entity, 105 | .token.url, 106 | .language-css .token.string, 107 | .style .token.string { 108 | color: #a67f59; 109 | background: hsla(0, 0%, 100%, .5); 110 | } 111 | 112 | .token.atrule, 113 | .token.attr-value, 114 | .token.keyword { 115 | color: #07a; 116 | } 117 | 118 | .token.function { 119 | color: #DD4A68; 120 | } 121 | 122 | .token.regex, 123 | .token.important, 124 | .token.variable { 125 | color: #e90; 126 | } 127 | 128 | .token.important, 129 | .token.bold { 130 | font-weight: bold; 131 | } 132 | .token.italic { 133 | font-style: italic; 134 | } 135 | 136 | .token.entity { 137 | cursor: help; 138 | } 139 | -------------------------------------------------------------------------------- /packages/bisheng/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bisheng", 3 | "version": "0.28.0", 4 | "description": "Transform Markdown(and other static files with transformers) into a SPA website using React.", 5 | "bin": { 6 | "bisheng": "./bin/bisheng" 7 | }, 8 | "files": [ 9 | "lib", 10 | "bin", 11 | "router.js", 12 | "collect.js" 13 | ], 14 | "scripts": { 15 | "build": "babel src --out-dir lib", 16 | "clean": "rimraf lib", 17 | "prepublish": "npm run build && shx cp src/template.html lib/template.html", 18 | "postpublish": "npm run clean" 19 | }, 20 | "babel": { 21 | "presets": [ 22 | "react", 23 | [ 24 | "env", 25 | { 26 | "targets": { 27 | "browsers": [ 28 | "last 2 versions", 29 | "Firefox ESR", 30 | "> 1%", 31 | "ie >= 8", 32 | "iOS >= 8", 33 | "Android >= 4" 34 | ] 35 | } 36 | } 37 | ] 38 | ], 39 | "plugins": [ 40 | "transform-class-properties", 41 | "transform-object-rest-spread" 42 | ] 43 | }, 44 | "repository": { 45 | "type": "git", 46 | "url": "git+https://github.com/benjycui/bisheng.git" 47 | }, 48 | "keywords": [ 49 | "markdown", 50 | "spa", 51 | "website", 52 | "blog", 53 | "react" 54 | ], 55 | "author": "Benjy Cui", 56 | "license": "MIT", 57 | "bugs": { 58 | "url": "https://github.com/benjycui/bisheng/issues" 59 | }, 60 | "homepage": "https://github.com/benjycui/bisheng#readme", 61 | "dependencies": { 62 | "autoprefixer": "^7.1.4", 63 | "babel-core": "^6.24.1", 64 | "babel-loader": "^7.1.2", 65 | "babel-plugin-add-module-exports": "~0.2.1", 66 | "babel-plugin-transform-class-properties": "^6.24.1", 67 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 68 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 69 | "babel-polyfill": "^6.9.1", 70 | "babel-preset-env": "^1.6.0", 71 | "babel-preset-react": "^6.24.1", 72 | "case-sensitive-paths-webpack-plugin": "^2.0.0", 73 | "chalk": "^1.1.3", 74 | "commander": "^2.9.0", 75 | "css-loader": "~0.28.0", 76 | "exist.js": "^0.3.0", 77 | "extract-text-webpack-plugin": "^3.0.0", 78 | "friendly-errors-webpack-plugin": "^1.6.1", 79 | "gh-pages": "^1.0.0", 80 | "history": "^3.0.0", 81 | "jsonml-to-react-element": "^1.0.0", 82 | "jsonml.js": "^0.1.0", 83 | "less": "~2.7.2", 84 | "less-loader": "~4.0.5", 85 | "loader-utils": "^1.1.0", 86 | "mark-twain": "^2.0.0", 87 | "mkdirp": "^0.5.1", 88 | "node-prismjs": "^0.1.0", 89 | "nprogress": "^0.2.0", 90 | "nunjucks": "^2.5.2", 91 | "postcss": "^6.0.11", 92 | "postcss-loader": "~2.0.6", 93 | "prismjs": "^1.4.1", 94 | "ramda": "^0.22.0", 95 | "react": "^16.0.0", 96 | "react-dev-utils": "^4.1.0", 97 | "react-dom": "^16.0.0", 98 | "react-router": "^3.0.0", 99 | "resolve": "^1.1.7", 100 | "rucksack-css": "~1.0.2", 101 | "sass-loader": "^6.0.6", 102 | "style-loader": "^0.19.0", 103 | "ts-loader": "^2.0.3", 104 | "uglifyjs-webpack-plugin": "^1.1.2", 105 | "url-loader": "~0.5.8", 106 | "webpack": "^3.7.0", 107 | "webpack-dev-server": "^2.9.1" 108 | }, 109 | "devDependencies": { 110 | "babel-cli": "^6.16.0", 111 | "babel-core": "^6.24.1", 112 | "chalk": "^1.1.3", 113 | "postcss-svgo": "^2.1.6", 114 | "rimraf": "^2.5.4", 115 | "shx": "^0.2.2" 116 | }, 117 | "engines": { 118 | "node": ">=6.0.0" 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /packages/bisheng/src/routes.nunjucks.jsx: -------------------------------------------------------------------------------- 1 | const chain = require('ramda/src/chain'); 2 | const toReactElement = require('jsonml-to-react-element'); 3 | const exist = require('exist.js'); 4 | const NProgress = require('nprogress'); 5 | const NotFound = require('{{ themePath }}/template/NotFound'); 6 | const themeConfig = JSON.parse('{{ themeConfig | safe }}'); 7 | 8 | function calcPropsPath(dataPath, params) { 9 | return typeof dataPath === 'function' ? 10 | dataPath(params) : 11 | Object.keys(params).reduce( 12 | (path, param) => path.replace(`:${param}`, params[param]), 13 | dataPath, 14 | ); 15 | } 16 | 17 | function generateUtils(data, props) { 18 | const plugins = data.plugins.map(pluginTupple => pluginTupple[0](pluginTupple[1], props)); 19 | const converters = chain(plugin => plugin.converters || [], plugins); 20 | const utils = { 21 | get: exist.get, 22 | toReactComponent(jsonml) { 23 | return toReactElement(jsonml, converters); 24 | }, 25 | }; 26 | plugins.map(plugin => plugin.utils || {}) 27 | .forEach(u => Object.assign(utils, u)); 28 | return utils; 29 | } 30 | 31 | async function defaultCollector(nextProps) { 32 | return nextProps; 33 | } 34 | 35 | module.exports = function getRoutes(data) { 36 | function templateWrapper(template, dataPath = '') { 37 | const Template = require(`{{ themePath }}/template${template.replace(/^\.\/template/, '')}`); 38 | 39 | return (nextState, callback) => { 40 | const propsPath = calcPropsPath(dataPath, nextState.params); 41 | const pageData = exist.get(data.markdown, propsPath.replace(/^\//, '').split('/')); 42 | const utils = generateUtils(data, nextState); 43 | 44 | const collector = (Template.default || Template).collector || defaultCollector; 45 | const dynamicPropsKey = nextState.location.pathname; 46 | const nextProps = { 47 | ...nextState, 48 | themeConfig, 49 | data: data.markdown, 50 | picked: data.picked, 51 | pageData, 52 | utils, 53 | }; 54 | collector(nextProps) 55 | .then((collectedValue) => { 56 | try { 57 | const Comp = Template.default || Template; 58 | Comp[dynamicPropsKey] = { ...nextProps, ...collectedValue }; 59 | callback(null, Comp); 60 | } catch (e) { console.error(e) } 61 | }) 62 | .catch((err) => { 63 | const Comp = NotFound.default || NotFound; 64 | Comp[dynamicPropsKey] = nextProps; 65 | callback(err === 404 ? null : err, Comp); 66 | }); 67 | }; 68 | } 69 | 70 | const themeRoutes = JSON.parse('{{ themeRoutes | safe }}'); 71 | const routes = Array.isArray(themeRoutes) ? themeRoutes : [themeRoutes]; 72 | 73 | function processRoutes(route) { 74 | if (Array.isArray(route)) { 75 | return route.map(processRoutes); 76 | } 77 | 78 | return Object.assign({}, route, { 79 | onEnter: () => { 80 | if (typeof document !== 'undefined') { 81 | NProgress.start(); 82 | } 83 | }, 84 | component: undefined, 85 | getComponent: templateWrapper(route.component, route.dataPath || route.path), 86 | indexRoute: route.indexRoute && Object.assign({}, route.indexRoute, { 87 | component: undefined, 88 | getComponent: templateWrapper( 89 | route.indexRoute.component, 90 | route.indexRoute.dataPath || route.indexRoute.path, 91 | ), 92 | }), 93 | childRoutes: route.childRoutes && route.childRoutes.map(processRoutes), 94 | }); 95 | } 96 | 97 | const processedRoutes = processRoutes(routes); 98 | processedRoutes.push({ 99 | path: '*', 100 | getComponents: templateWrapper('./template/NotFound'), 101 | }); 102 | 103 | return processedRoutes; 104 | }; 105 | -------------------------------------------------------------------------------- /packages/bisheng/src/config/getWebpackCommonConfig.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import webpack from 'webpack'; 3 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 4 | import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'; 5 | import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin'; 6 | import chalk from 'chalk'; 7 | 8 | import getBabelCommonConfig from './getBabelCommonConfig'; 9 | import getTSCommonConfig from './getTSCommonConfig'; 10 | 11 | /* eslint quotes:0 */ 12 | 13 | export default function getWebpackCommonConfig() { 14 | const jsFileName = '[name].js'; 15 | const cssFileName = '[name].css'; 16 | const commonName = 'common.js'; 17 | 18 | const babelOptions = getBabelCommonConfig(); 19 | const tsOptions = getTSCommonConfig(); 20 | 21 | return { 22 | output: { 23 | filename: jsFileName, 24 | chunkFilename: jsFileName, 25 | }, 26 | 27 | resolve: { 28 | modules: ['node_modules', join(__dirname, '../../node_modules')], 29 | extensions: ['.web.tsx', '.web.ts', '.web.jsx', '.web.js', '.ts', '.tsx', '.js', '.jsx', '.json'], 30 | }, 31 | 32 | resolveLoader: { 33 | modules: ['node_modules', join(__dirname, '../../node_modules')], 34 | }, 35 | 36 | module: { 37 | noParse: [/moment.js/], 38 | rules: [ 39 | { 40 | test: /\.js$/, 41 | exclude: /node_modules/, 42 | loader: 'babel-loader', 43 | options: babelOptions, 44 | }, 45 | { 46 | test: /\.jsx$/, 47 | loader: 'babel-loader', 48 | options: babelOptions, 49 | }, 50 | { 51 | test: /\.tsx?$/, 52 | use: [{ 53 | loader: 'babel-loader', 54 | options: babelOptions, 55 | }, { 56 | loader: 'ts-loader', 57 | options: { 58 | transpileOnly: true, 59 | compilerOptions: tsOptions, 60 | }, 61 | }], 62 | }, 63 | { 64 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 65 | loader: 'url-loader?limit=10000&minetype=application/font-woff', 66 | }, 67 | { 68 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 69 | loader: 'url-loader?limit=10000&minetype=application/font-woff', 70 | }, 71 | { 72 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 73 | loader: 'url-loader?limit=10000&minetype=application/octet-stream', 74 | }, 75 | { 76 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 77 | loader: 'url-loader?limit=10000&minetype=application/vnd.ms-fontobject', 78 | }, 79 | { 80 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 81 | loader: 'url-loader?limit=10000&minetype=image/svg+xml', 82 | }, 83 | { 84 | test: /\.(png|jpg|jpeg|gif)(\?v=\d+\.\d+\.\d+)?$/i, 85 | loader: 'url-loader?limit=10000', 86 | }, 87 | ], 88 | }, 89 | 90 | plugins: [ 91 | new webpack.optimize.CommonsChunkPlugin({ 92 | name: 'common', 93 | filename: commonName, 94 | }), 95 | new ExtractTextPlugin({ 96 | filename: cssFileName, 97 | disable: false, 98 | allChunks: true, 99 | }), 100 | new CaseSensitivePathsPlugin(), 101 | new webpack.ProgressPlugin((percentage, msg, addInfo) => { 102 | const stream = process.stderr; 103 | if (stream.isTTY && percentage < 0.71) { 104 | stream.cursorTo(0); 105 | stream.write(`📦 ${chalk.magenta(msg)} (${chalk.magenta(addInfo)})`); 106 | stream.clearLine(1); 107 | } else if (percentage === 1) { 108 | console.log(chalk.green('\nwebpack: bundle build is now finished.')); 109 | } 110 | }), 111 | new FriendlyErrorsWebpackPlugin(), 112 | ], 113 | }; 114 | } 115 | -------------------------------------------------------------------------------- /packages/bisheng-theme-one/src/static/site.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | .sep { 7 | margin: 0 18px; 8 | } 9 | /* layout */ 10 | .header { 11 | font-size: 16px; 12 | font-family: "Optima", "Avenir", "Helvetica Neue", sans-serif; 13 | padding: 20px 10px; 14 | } 15 | .header a { 16 | color: #323233; 17 | } 18 | .header a:hover { 19 | opacity: 0.6; 20 | } 21 | .header .menu { 22 | text-align: right; 23 | } 24 | .header .brand { 25 | float: left; 26 | margin-right: 30px; 27 | text-transform: uppercase; 28 | } 29 | .brand:before { 30 | content: '»'; 31 | } 32 | .brand a { 33 | text-decoration: none; 34 | } 35 | .menu a { 36 | margin-left: 14px; 37 | } 38 | .container { 39 | max-width: 640px; 40 | *width: 650px; 41 | margin: 0 auto; 42 | zoom: 1; 43 | } 44 | .container:after { 45 | content: " "; 46 | display: block; 47 | visibility: none; 48 | clear: both; 49 | } 50 | .document { 51 | max-width: 640px; 52 | *width: 650px; 53 | margin: 0 auto; 54 | padding: 30px 10px; 55 | } 56 | .footer { 57 | margin: 0 auto; 58 | max-width: 500px; 59 | *width: 500px; 60 | color: #555; 61 | color: rgba(0, 0, 0, 0.5); 62 | font-size: 15px; 63 | line-height: 1.3; 64 | padding: 20px 20px 30px; 65 | } 66 | .copyright { 67 | text-align: center; 68 | color: #ddd; 69 | } 70 | .copyright a { 71 | color: #ddd; 72 | } 73 | @media (max-width: 650px) { 74 | .footer { 75 | padding-bottom: 10px; 76 | } 77 | } 78 | 79 | /* entry */ 80 | .entry-meta { 81 | border-top: 1px solid #eee; 82 | padding-top: 10px; 83 | color: #565655; 84 | font-size: smaller; 85 | } 86 | .entry-description { 87 | color: #676867; 88 | font-size: larger; 89 | } 90 | .entry-tags a:before { 91 | content: '#'; 92 | color: #ccc; 93 | } 94 | .entry-list .item { 95 | margin-bottom: 1em; 96 | } 97 | .entry-list .item-title { 98 | font-size: 16px; 99 | margin-bottom: 0; 100 | } 101 | .entry-list .item-description { 102 | color: #565655; 103 | font-size: 16px; 104 | padding-left: 84px; 105 | } 106 | .entry-list .item-description p { 107 | margin: 0; 108 | } 109 | .entry-list .item time { 110 | display: inline-block; 111 | width: 80px; 112 | font: normal 12px monospace; 113 | color: #999; 114 | } 115 | 116 | .document a.title-permalink { 117 | margin-left: 4px; 118 | padding: 0 4px; 119 | text-decoration: none; 120 | color: #f0f0f2; 121 | } 122 | .document a.title-permalink:hover { 123 | color: #5784bf; 124 | background-color: #eee; 125 | } 126 | 127 | /* pagination */ 128 | .pagination { 129 | position: relative; 130 | margin-top: 40px; 131 | font-size: 12px; 132 | text-transform: uppercase; 133 | font-weight: 700; 134 | font-family: "Optima", "Avenir", "Helvetica Neue", sans-serif; 135 | } 136 | .pagination .newer { 137 | position: absolute; 138 | text-decoration: none; 139 | left: 0; 140 | } 141 | .pagination .older { 142 | position: absolute; 143 | text-decoration: none; 144 | right: 0; 145 | } 146 | .pagination .newer:before { 147 | content: "«"; 148 | } 149 | .pagination .older:after { 150 | content: "»" 151 | } 152 | 153 | /* extra widget */ 154 | .github { 155 | position: fixed; 156 | top: 40px; 157 | right: -50px; 158 | border: 2px solid #121622; 159 | box-shadow: 0 0 8px #555; 160 | -webkit-transform: rotate(45deg); 161 | -moz-transform: rotate(45deg); 162 | -o-transform: rotate(45deg); 163 | -ms-transform: rotate(45deg); 164 | transform: rotate(45deg); 165 | } 166 | .github:hover { 167 | box-shadow: 0 0 12px #444; 168 | } 169 | .github .github-link { 170 | display: block; 171 | font-family: "Lato", sans-serif; 172 | font-size: 14px; 173 | padding: 2px 0; 174 | width: 200px; 175 | border: 1px dotted #383C46; 176 | background-color: #121622; 177 | color: #ECEDEE; 178 | text-decoration: none; 179 | text-align: center; 180 | } 181 | @media (max-width: 890px) { 182 | .github { 183 | display: none; 184 | } 185 | } 186 | 187 | .document a.button { 188 | display: inline-block; 189 | padding: 1em; 190 | margin: 0; 191 | background-color: #222223; 192 | color: #fcfcfd; 193 | text-transform: uppercase; 194 | border: none; 195 | text-decoration: none; 196 | outline: none; 197 | border-radius: 3px; 198 | text-align: center; 199 | letter-spacing: 0.1em; 200 | font: bold 0.7em/1 "Arial", sans-serif; 201 | cursor: pointer; 202 | vertical-align: middle; 203 | } 204 | .document a.button:hover { 205 | opacity: 0.9; 206 | color: white; 207 | } 208 | 209 | .tagcloud { 210 | margin-bottom: 2em; 211 | } 212 | .tagcloud .count { 213 | font-size: 12px; 214 | } 215 | .tagcloud a { 216 | display: inline-block; 217 | margin: 0.2em 0.4em; 218 | opacity: 0.6; 219 | } 220 | .tagcloud a:hover { 221 | opacity: 1; 222 | } 223 | -------------------------------------------------------------------------------- /docs/theme.md: -------------------------------------------------------------------------------- 1 | # Theme 2 | 3 | A theme includes a config file and styles as well as templates. The directory structure of a theme is: 4 | 5 | ```bash 6 | theme 7 | ├── index.js # config file, required 8 | ├── static # style files 9 | │   └── style.css 10 | └── template # templates are JSX files 11 | ├── NotFound.jsx # required 12 | └── Template.jsx 13 | ``` 14 | 15 | e.g. [ant-design](https://github.com/ant-design/ant-design/tree/master/site/theme) 16 | 17 | ## Theme as NPM Package 18 | 19 | We can also publish our theme as a NPM package, so that other can install it as a dependency. 20 | 21 | Directory structure: 22 | 23 | ```bash 24 | lib 25 | ├── index.js # config file, required 26 | ├── static # style files 27 | │   └── style.css 28 | └── template # templates are JSX files 29 | ├── NotFound.jsx # required 30 | └── Template.jsx 31 | ``` 32 | 33 | `package.json`: 34 | 35 | ```json 36 | { 37 | "main": "./lib/index.js", 38 | "files": ["lib", ...] 39 | } 40 | ``` 41 | 42 | e.g. [bisheng-theme-one](https://github.com/benjycui/bisheng-theme-one) 43 | 44 | ## `index.js` 45 | 46 | `index.js` includes `routes` and theme's own config. 47 | 48 | ```js 49 | module.exports = { 50 | // routes is required 51 | routes: { 52 | path: '/', 53 | component: './template/Archive', 54 | 55 | // optional, it's equal to `path` if omitted. 56 | dataPath: 'path-to-markdown-file', 57 | ... 58 | childRoutes: [{ 59 | path: 'posts/:post', 60 | component: './template/Post', 61 | 62 | // we can use variables in `dataPath`, and values of variables are equal to them in path 63 | dataPath: 'posts/:post', 64 | }], 65 | }, 66 | 67 | // the following configs are optional 68 | lazyLoad: true, 69 | pick: { 70 | archive(markdownData) { ... }, 71 | ... 72 | }, 73 | plugins: ['bisheng-plugin-react', ...], 74 | }; 75 | ``` 76 | 77 | The configuration of `routes` is similar with [react-router's](https://github.com/reactjs/react-router/blob/master/docs/guides/RouteConfiguration.md#configuration-with-plain-routes). The differences are: 78 | 79 | * `component` should be a string which is a path of a component. 80 | * `dataPath` means which Markdown file need to be rendered. 81 | 82 | ### lazyLoad: Boolean | (nodePath, nodeValue) => Boolean 83 | 84 | > default: false 85 | 86 | If `lazyLoad` is `false`, it means that the whole Markdown data tree will be load while users visit any page. 87 | 88 | If `lazyLoad` is `true`, it meas that Markdown data will only be loaded while it's needed. 89 | 90 | And `lazyLoad` could be a function, it's similar to `ture`, but you can determine whether a subtree of the Markdown data tree should be loaded lazily as one file. 91 | 92 | **Note:** when `lazyLoad` or the returned value of `lazyLoad()` is `true`, the Markdown data(or subtree) will be wrapped in a function which will return a promise. 93 | 94 | [**More about lazy load**](https://github.com/benjycui/bisheng/tree/master/docs/lazy-load.md). 95 | 96 | ### pick: Object { [field]: Function } 97 | 98 | > default: {} 99 | 100 | To get part of data from Markdown data, and then put all the snippets into `props.picked` and pass it to template. 101 | 102 | [**More about pick**](https://github.com/benjycui/bisheng/tree/master/docs/pick.md). 103 | 104 | ### plugins: Array[String] 105 | 106 | > default: [] 107 | 108 | A list of plugins. 109 | 110 | ```js 111 | module.exports = { 112 | plugins: [ 113 | 'pluginName?config1=value1&config2=value2', 114 | 'anotherPluginName', 115 | ], 116 | }; 117 | ``` 118 | 119 | The config of plugins follows [webpack loaders' convention](https://www.npmjs.com/package/loader-utils#parsequery). 120 | 121 | [**More about plugin**](https://github.com/benjycui/bisheng/tree/master/docs/plugin.md). 122 | 123 | * [bisheng-plugin-description](https://github.com/benjycui/bisheng-plugin-description) 124 | * [bisheng-plugin-toc](https://github.com/benjycui/bisheng-plugin-toc) 125 | * [bisheng-plugin-react](https://github.com/benjycui/bisheng-plugin-react) 126 | 127 | 128 | ## Templates 129 | 130 | A template is just a React component, `bisheng` will pass [`themeConfig`](https://github.com/benjycui/bisheng#themeconfig-any) `data` `pageData` `utils` and all the [react-router](https://github.com/reactjs/react-router) props to it. 131 | 132 | * `data` contains the whole Markdown data tree which is generated from [`source`](https://github.com/benjycui/bisheng#source-string--arraystring--object-category-string--arraystring). 133 | * `pageData` is the content of a specific Markdown file. Actually, `bisheng` just get it from `data`. `bisheng` will determine which Markdown data should be pass as `pageData` by the configuration of `path` & `dataPath` in routes, for example: 134 | 1. User visits `/posts/hello-world`. 135 | 2. The URL matches the route `/posts/:post`. 136 | 3. The corresponding `dataPath` is `/posts/:post`. 137 | 4. So, `bisheng` will get `data.posts['hello-world']` and pass it as `pageData` to template. 138 | * `uitls` includes `bisheng`'s and plugins' utilities: 139 | * `utils.toReactComponent` to convert JsonML to React component. 140 | * `utils.get` to get nested value from an object, it's from [exist.js](https://github.com/benjycui/exist.js#existgetobj-nestedprop-defaultvalue). 141 | -------------------------------------------------------------------------------- /packages/bisheng-theme-one/src/static/yue.css: -------------------------------------------------------------------------------- 1 | /** 2 | * yue.css 3 | * 4 | * yue.css is designed for readable content. 5 | * 6 | * Copyright (c) 2013 - 2014 by Hsiaoming Yang. 7 | */ 8 | 9 | .yue { 10 | font: 400 18px/1.62 "Georgia", "Xin Gothic", "Hiragino Sans GB", "Droid Sans Fallback", "Microsoft YaHei", sans-serif; 11 | color: #444443; 12 | } 13 | 14 | .windows .yue { 15 | font-size: 16px; 16 | font-family: "Georgia", "SimSun", sans-serif; 17 | } 18 | 19 | .yue ::-moz-selection { 20 | background-color: rgba(0,0,0,0.2); 21 | } 22 | 23 | .yue ::selection { 24 | background-color: rgba(0,0,0,0.2); 25 | } 26 | 27 | .yue h1, 28 | .yue h2, 29 | .yue h3, 30 | .yue h4, 31 | .yue h5, 32 | .yue h6 { 33 | font-family: "Georgia", "Xin Gothic", "Hiragino Sans GB", "Droid Sans Fallback", "Microsoft YaHei", "SimSun", sans-serif; 34 | color: #222223; 35 | } 36 | 37 | .yue h1 { 38 | font-size: 1.8em; 39 | margin: 0.67em 0; 40 | } 41 | 42 | .yue > h1 { 43 | margin-top: 0; 44 | font-size: 2em; 45 | } 46 | 47 | .yue h2 { 48 | font-size: 1.5em; 49 | margin: 0.83em 0; 50 | } 51 | 52 | .yue h3 { 53 | font-size: 1.17em; 54 | margin: 1em 0; 55 | } 56 | 57 | .yue h4, 58 | .yue h5, 59 | .yue h6 { 60 | font-size: 1em; 61 | margin: 1.6em 0 1em 0; 62 | } 63 | 64 | .yue h6 { 65 | font-weight: 500; 66 | } 67 | 68 | .yue p { 69 | margin-top: 0; 70 | margin-bottom: 1.46em; 71 | } 72 | 73 | .yue a { 74 | color: #111; 75 | word-wrap: break-word; 76 | -moz-text-decoration-color: rgba(0, 0, 0, 0.4); 77 | text-decoration-color: rgba(0, 0, 0, 0.4); 78 | } 79 | 80 | .yue a:hover { 81 | color: #555; 82 | -moz-text-decoration-color: rgba(0, 0, 0, 0.6); 83 | text-decoration-color: rgba(0, 0, 0, 0.6); 84 | } 85 | 86 | .yue h1 a, 87 | .yue h2 a, 88 | .yue h3 a { 89 | text-decoration: none; 90 | } 91 | 92 | .yue strong, 93 | .yue b { 94 | font-weight: 700; 95 | color: #222223; 96 | } 97 | 98 | .yue em, 99 | .yue i { 100 | font-style: italic; 101 | color: #222223; 102 | } 103 | 104 | .yue img { 105 | max-width: 100%; 106 | height: auto; 107 | margin: 0.2em 0; 108 | } 109 | 110 | .yue a img { 111 | /* Remove border on IE */ 112 | border: none; 113 | } 114 | 115 | .yue figure { 116 | position: relative; 117 | clear: both; 118 | outline: 0; 119 | margin: 10px 0 30px; 120 | padding: 0; 121 | min-height: 100px; 122 | } 123 | 124 | .yue figure img { 125 | display: block; 126 | max-width: 100%; 127 | margin: auto auto 4px; 128 | box-sizing: border-box; 129 | } 130 | 131 | .yue figure figcaption { 132 | position: relative; 133 | width: 100%; 134 | text-align: center; 135 | left: 0; 136 | margin-top: 10px; 137 | font-weight: 400; 138 | font-size: 14px; 139 | color: #666665; 140 | } 141 | 142 | .yue figure figcaption a { 143 | text-decoration: none; 144 | color: #666665; 145 | } 146 | 147 | .yue hr { 148 | display: block; 149 | width: 14%; 150 | margin: 40px auto 34px; 151 | border: 0 none; 152 | border-top: 3px solid #dededc; 153 | } 154 | 155 | .yue blockquote { 156 | margin: 0 0 1.64em 0; 157 | border-left: 3px solid #dadada; 158 | padding-left: 12px; 159 | color: #666664; 160 | } 161 | 162 | .yue blockquote a { 163 | color: #666664; 164 | } 165 | 166 | .yue ul, 167 | .yue ol { 168 | margin: 0 0 24px 6px; 169 | padding-left: 16px; 170 | } 171 | 172 | .yue ul { 173 | list-style-type: square; 174 | } 175 | 176 | .yue ol { 177 | list-style-type: decimal; 178 | } 179 | 180 | .yue li { 181 | margin-bottom: 0.2em; 182 | } 183 | 184 | .yue li ul, 185 | .yue li ol { 186 | margin-top: 0; 187 | margin-bottom: 0; 188 | margin-left: 14px; 189 | } 190 | 191 | .yue li ul { 192 | list-style-type: disc; 193 | } 194 | 195 | .yue li ul ul { 196 | list-style-type: circle; 197 | } 198 | 199 | .yue li p { 200 | margin: 0.4em 0 0.6em; 201 | } 202 | 203 | .yue .unstyled { 204 | list-style-type: none; 205 | margin: 0; 206 | padding: 0; 207 | } 208 | 209 | .yue code, 210 | .yue tt { 211 | color: #808080; 212 | font-size: 0.96em; 213 | background-color: #f9f9f7; 214 | padding: 1px 2px; 215 | border: 1px solid #dadada; 216 | border-radius: 3px; 217 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace; 218 | word-wrap: break-word; 219 | } 220 | 221 | .yue pre { 222 | margin: 1.64em 0; 223 | padding: 7px; 224 | border: none; 225 | border-left: 3px solid #dadada; 226 | padding-left: 10px; 227 | overflow: auto; 228 | line-height: 1.5; 229 | font-size: 0.96em; 230 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace; 231 | color: #4c4c4c; 232 | background-color: #f9f9f7; 233 | } 234 | 235 | .yue pre code, 236 | .yue pre tt { 237 | color: #4c4c4c; 238 | border: none; 239 | background: none; 240 | padding: 0; 241 | } 242 | 243 | .yue table { 244 | width: 100%; 245 | max-width: 100%; 246 | border-collapse: collapse; 247 | border-spacing: 0; 248 | margin-bottom: 1.5em; 249 | font-size: 0.96em; 250 | box-sizing: border-box; 251 | } 252 | 253 | .yue th, 254 | .yue td { 255 | text-align: left; 256 | padding: 4px 8px 4px 10px; 257 | border: 1px solid #dadada; 258 | } 259 | 260 | .yue td { 261 | vertical-align: top; 262 | } 263 | 264 | .yue tr:nth-child(even) { 265 | background-color: #efefee; 266 | } 267 | 268 | .yue iframe { 269 | display: block; 270 | max-width: 100%; 271 | margin-bottom: 30px; 272 | } 273 | 274 | .yue figure iframe { 275 | margin: auto; 276 | } 277 | 278 | .yue table pre { 279 | margin: 0; 280 | padding: 0; 281 | border: none; 282 | background: none; 283 | } 284 | 285 | @media (min-width: 1100px) { 286 | .yue blockquote { 287 | margin-left: -24px; 288 | padding-left: 20px; 289 | border-width: 4px; 290 | } 291 | 292 | .yue blockquote blockquote { 293 | margin-left: 0; 294 | } 295 | } -------------------------------------------------------------------------------- /packages/bisheng/src/utils/source-data.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const R = require('ramda'); 4 | const context = require('../context'); 5 | const { escapeWinPath, toUriPath } = require('./escape-win-path'); 6 | 7 | const sourceLoaderPath = path.join(__dirname, '..', 'loaders', 'source-loader'); 8 | 9 | function ensureToBeArray(maybeArray) { 10 | return Array.isArray(maybeArray) ? 11 | maybeArray : [maybeArray]; 12 | } 13 | 14 | function shouldBeIgnore(filename) { 15 | const { exclude } = context.bishengConfig; 16 | return exclude && exclude.test(filename); 17 | } 18 | 19 | function isDirectory(filename) { 20 | return fs.statSync(filename).isDirectory(); 21 | } 22 | 23 | const isValidFile = transformers => filename => 24 | transformers.some(({ test }) => eval(test).test(filename)); // eslint-disable-line no-eval 25 | 26 | function findValidFiles(source, transformers) { 27 | return R.pipe( 28 | R.reject(shouldBeIgnore), 29 | R.filter(R.either(isDirectory, isValidFile(transformers))), 30 | R.chain((filename) => { 31 | if (isDirectory(filename)) { 32 | const subFiles = fs.readdirSync(filename) 33 | .map(subFile => path.join(filename, subFile)); 34 | return findValidFiles(subFiles, transformers); 35 | } 36 | return [filename]; 37 | }), 38 | )(source); 39 | } 40 | 41 | const rxSep = new RegExp(`[${escapeWinPath(path.sep)}.]`); 42 | function getPropPath(filename, sources) { 43 | return sources.reduce( 44 | (f, source) => f.replace(source, ''), 45 | filename.replace(new RegExp(`${path.extname(filename)}$`), ''), 46 | ).replace(/^\.?(?:\\|\/)+/, '').split(rxSep); 47 | } 48 | 49 | function filesToTreeStructure(files, sources) { 50 | const cleanedSources = sources.map(source => source.replace(/^\.?(?:\\|\/)/, '')); 51 | const filesTree = files.reduce((subFilesTree, filename) => { 52 | const propLens = R.lensPath(getPropPath(filename, cleanedSources)); 53 | return R.set(propLens, filename, subFilesTree); 54 | }, {}); 55 | return filesTree; 56 | } 57 | 58 | function stringifyObject({ 59 | nodePath, nodeValue, depth, ...rest 60 | }) { 61 | const indent = ' '.repeat(depth); 62 | const kvStrings = R.pipe( 63 | R.toPairs, 64 | /* eslint-disable no-use-before-define */ 65 | R.map((kv) => { 66 | const valueString = stringify({ 67 | ...rest, 68 | nodePath: `${nodePath}/${kv[0]}`, 69 | nodeValue: kv[1], 70 | depth: depth + 1, 71 | }); 72 | return `${indent} '${kv[0]}': ${valueString},`; 73 | }), 74 | /* eslint-enable no-use-before-define */ 75 | )(nodeValue); 76 | return kvStrings.join('\n'); 77 | } 78 | 79 | function lazyLoadWrapper({ 80 | filePath, 81 | filename, 82 | isLazyLoadWrapper, 83 | }) { 84 | const { isSSR } = context; 85 | const loaderString = isLazyLoadWrapper ? '' : `${sourceLoaderPath}!`; 86 | return `${'function () {\n' + 87 | ' return new Promise(function (resolve) {\n'}${ 88 | isSSR ? '' : ' require.ensure([], function (require) {\n' 89 | } resolve(require('${escapeWinPath(loaderString)}${escapeWinPath(filePath)}'));\n${ 90 | isSSR ? '' : ` }, '${toUriPath(filename)}');\n` 91 | } });\n` + 92 | '}'; 93 | } 94 | 95 | function shouldLazyLoad(nodePath, nodeValue, lazyLoad) { 96 | if (typeof lazyLoad === 'function') { 97 | return lazyLoad(nodePath, nodeValue); 98 | } 99 | 100 | return typeof nodeValue === 'object' ? false : lazyLoad; 101 | } 102 | 103 | function stringify(params) { 104 | const { 105 | nodePath = '/', 106 | nodeValue, 107 | lazyLoad, 108 | depth = 0, 109 | } = params; 110 | const indent = ' '.repeat(depth); 111 | const shouldBeLazy = shouldLazyLoad(nodePath, nodeValue, lazyLoad); 112 | return R.cond([ 113 | [n => typeof n === 'object', (obj) => { 114 | if (shouldBeLazy) { 115 | const filePath = `${path.join( 116 | __dirname, '..', '..', 'tmp', 117 | nodePath.replace(/^\/+/, '').replace(/\//g, '-'), 118 | )}.${context.bishengConfig.entryName}.js`; 119 | const fileInnerContent = stringifyObject({ 120 | ...params, 121 | nodeValue: obj, 122 | lazyLoad: false, 123 | depth: 1, 124 | }); 125 | const fileContent = `module.exports = {\n${fileInnerContent}\n}`; 126 | fs.writeFileSync(filePath, fileContent); 127 | return lazyLoadWrapper({ 128 | filePath, 129 | filename: nodePath.replace(/^\/+/, ''), 130 | isLazyLoadWrapper: true, 131 | }); 132 | } 133 | const objectKVString = stringifyObject({ 134 | ...params, 135 | nodePath, // fix: generated file name 136 | depth, // fix: indentation 137 | nodeValue: obj, 138 | }); 139 | return `{\n${objectKVString}\n${indent}}`; 140 | }], 141 | [R.T, (filename) => { 142 | const filePath = path.isAbsolute(filename) ? 143 | filename : path.join(process.cwd(), filename); 144 | if (shouldBeLazy) { 145 | return lazyLoadWrapper({ filePath, filename }); 146 | } 147 | return `require('${escapeWinPath(sourceLoaderPath)}!${escapeWinPath(filePath)}')`; 148 | }], 149 | ])(nodeValue); 150 | } 151 | 152 | exports.generate = function generate(source, transformers = []) { 153 | if (source === null || source === undefined) { 154 | return {}; // For motion.ant.design, it doesn't need source sometimes. 155 | } 156 | if (R.is(Object, source) && !Array.isArray(source)) { 157 | return R.mapObjIndexed(value => generate(value, transformers), source); 158 | } 159 | const sources = ensureToBeArray(source); 160 | const validFiles = findValidFiles(sources, transformers); 161 | const filesTree = filesToTreeStructure(validFiles, sources); 162 | return filesTree; 163 | }; 164 | 165 | exports.stringify = ( 166 | filesTree, 167 | options = {}, /* { lazyLoad } */ 168 | ) => stringify({ nodeValue: filesTree, ...options }); 169 | 170 | exports.traverse = function traverse(filesTree, fn) { 171 | Object.keys(filesTree).forEach((key) => { 172 | const value = filesTree[key]; 173 | if (typeof value === 'string') { 174 | fn(value); 175 | return; 176 | } 177 | 178 | traverse(value, fn); 179 | }); 180 | }; 181 | 182 | // `.process` will be use in child process, so it cannot use `context` 183 | exports.process = ( 184 | filename, 185 | fileContent, 186 | plugins, 187 | transformers = [], 188 | isBuild, /* 'undefined' | true */ 189 | ) => { 190 | // Mock Array.prototype.find(fn) 191 | let transformerIndex = -1; 192 | transformers.some(({ test }, index) => { 193 | transformerIndex = index; 194 | return eval(test).test(filename); // eslint-disable-line no-eval 195 | }); 196 | const transformer = transformers[transformerIndex]; 197 | 198 | const markdown = require(transformer.use)(filename, fileContent); 199 | const parsedMarkdown = plugins.reduce( 200 | (markdownData, plugin) => 201 | require(plugin[0])(markdownData, plugin[1], isBuild === true), 202 | markdown, 203 | ); 204 | return parsedMarkdown; 205 | }; 206 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bi Sheng 2 | 3 | [![](https://img.shields.io/travis/benjycui/bisheng.svg?style=flat-square)](https://travis-ci.org/benjycui/bisheng) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/lu5ut8vphqdfbxhi?svg=true)](https://ci.appveyor.com/project/benjycui/bisheng) 5 | [![npm package](https://img.shields.io/npm/v/bisheng.svg?style=flat-square)](https://www.npmjs.org/package/bisheng) 6 | [![NPM downloads](http://img.shields.io/npm/dm/bisheng.svg?style=flat-square)](https://npmjs.org/package/bisheng) 7 | [![Dependency Status](https://david-dm.org/benjycui/bisheng.svg?style=flat-square)](https://david-dm.org/benjycui/bisheng) 8 | 9 | > [Bi Sheng](https://en.wikipedia.org/wiki/Bi_Sheng) was the Chinese inventor of the first known movable type technology. 10 | 11 | `bisheng` is designed to transform [Markdown](https://en.wikipedia.org/wiki/Markdown)(and other static files with transformers) into static websites and blogs using [React](https://facebook.github.io/react/). 12 | 13 | ## Sites built with BiSheng 14 | 15 | * [A simple blog](http://benjycui.github.io/bisheng/) 16 | * [Ant Design](http://ant.design) 17 | * [Ant Motion](http://motion.ant.design) 18 | * [Ant Design Mobile](http://mobile.ant.design/) 19 | * [Ant Financial Design Platform](https://design.alipay.com/) 20 | * [React AMap](https://elemefe.github.io/react-amap/articles/start) 21 | 22 | You can create a PR to extend this list with your amazing website which is built with BiSheng. 23 | 24 | ## Features 25 | 26 | `bisheng` is based on [dora](https://github.com/dora-js/dora) & [webpack](https://webpack.github.io/) & [React](https://facebook.github.io/react/) & [react-router](https://github.com/ReactTraining/react-router), and it has the following features: 27 | 28 | * Support [`browserHistory`](https://github.com/ReactTraining/react-router/blob/v3/docs/API.md#browserhistory), even in [GitHub Pages](https://pages.github.com/). 29 | * Lazy load for Markdown data. 30 | * [Plugin](https://github.com/benjycui/bisheng/blob/master/docs/plugin.md) system to extend default behaviour. 31 | * Server-side render for SEO. 32 | 33 | ## Big picture 34 | 35 | ![Big picture of BiSheng](https://raw.githubusercontent.com/benjycui/bisheng/master/big-picture.jpg) 36 | 37 | ### Articles 38 | 39 | * [bisheng-sourceCode-plugin](https://github.com/liangklfangl/bisheng-sourceCode-plugin) 40 | 41 | ## Usage 42 | 43 | Installation: 44 | 45 | ```bash 46 | npm install --save-dev bisheng 47 | ``` 48 | 49 | Then, add `start` to [npm scripts](https://docs.npmjs.com/misc/scripts): 50 | 51 | ```json 52 | { 53 | "scripts": { 54 | "start": "bisheng start" 55 | } 56 | } 57 | ``` 58 | 59 | Create `bisheng.config.js`, otherwise `bisheng` will use the default config: 60 | 61 | ```js 62 | module.exports = { 63 | source: './posts', 64 | output: './_site', 65 | theme: './_theme', 66 | port: 8000, 67 | }; 68 | ``` 69 | 70 | **Note:** please make sure that `source` and `theme` exists, and `theme` should not be an empty directory. Just use [bisheng-theme-one](https://github.com/benjycui/bisheng/tree/master/packages/bisheng-theme-one), if you don't know how to develop a theme. See a simple demo [here](https://github.com/benjycui/bisheng/tree/master/packages/bisheng-example). 71 | 72 | Now, just run `npm start`. 73 | 74 | ## Documentation 75 | 76 | ### CLI 77 | 78 | We can install `bisheng` as a cli command and explore what it can do by `bisheng -h`. However, the recommended way to use `bisheng` is to install it as `devDependencies`. 79 | 80 | ```bash 81 | $ npm install -g bisheng 82 | $ bisheng -h 83 | Usage: bisheng [command] [options] 84 | 85 | Commands: 86 | 87 | start [options] to start a server 88 | build [options] to build and write static files to `config.output` 89 | gh-pages [options] to deploy website to gh-pages 90 | help [cmd] display help for [cmd] 91 | 92 | Options: 93 | 94 | -h, --help output usage information 95 | -V, --version output the version number 96 | ``` 97 | 98 | ### Configuration 99 | 100 | `bisheng` will read `bisheng.config.js` as its config file, but we can set the config file name by `--config`, something like this `bisheng --config another.config.js`. 101 | 102 | The content of `bisheng.config.js` looks like this: 103 | 104 | ```js 105 | module.exports = { 106 | port: 8000, 107 | source: './posts', 108 | output: './_site', 109 | theme: './_theme', 110 | htmlTemplate: path.join(__dirname, '../template.html'), 111 | devServerConfig: {}, 112 | webpackConfig(config) { 113 | return config; 114 | }, 115 | 116 | entryName: 'index', 117 | root: '/', 118 | }; 119 | ``` 120 | 121 | #### port: Number 122 | 123 | > default: 8000 124 | 125 | To set the port which will be listened when we start a local server. 126 | 127 | #### source: String | Array[String] | Object{ [category]: String | Array[String]} 128 | 129 | > default: './posts' 130 | 131 | To set directory/directories where we place Markdown files. 132 | 133 | And all the Markdown files in `source` will be parsed and then structured as a tree data, for example: 134 | 135 | ```bash 136 | posts 137 | └── dir1 138 | ├── a.md 139 | └── b.md 140 | ``` 141 | 142 | Will output a **Markdown data tree**: 143 | 144 | ```js 145 | { 146 | dir1: { 147 | a: {...}, 148 | b: {...}, 149 | }, 150 | } 151 | ``` 152 | 153 | And each Markdown file will be parsed as a **Markdown data**. Actually, a Markdown data is the returned value of [mark-twain](https://github.com/benjycui/mark-twain), and it could be preprocessed by plugins. 154 | 155 | #### exclude: RegExp 156 | 157 | > default: null 158 | 159 | If you want to exclude some files in your `source`, just use `exclude`. Then bisheng will not parse files which match `exclude`. 160 | 161 | #### output: String 162 | 163 | > default: './_site' 164 | 165 | To set directory where `bisheng` will generate (HTML & CSS & JavaScript) files to. 166 | 167 | #### theme: String 168 | 169 | > default: './_theme' 170 | 171 | To set directory where we put the theme of website, and it also can be a npm package name. 172 | 173 | [**More about theme**](https://github.com/benjycui/bisheng/tree/master/docs/theme.md). 174 | 175 | * [bisheng-theme-one](https://github.com/benjycui/bisheng/tree/master/packages/bisheng-theme-one) 176 | 177 | #### themeConfig: any 178 | 179 | > undefined 180 | 181 | A set of configuration that your theme provides, and then your theme can read it from `props.themeConfig`. 182 | 183 | > Note: `themeConfig` will be `JSON.stringify` before it's passed to props, so you cannot pass function/RegExp through `themeConfig`. 184 | 185 | #### htmlTemplate: String 186 | 187 | > default: [`bisheng/lib/template.html`](https://github.com/benjycui/bisheng/blob/master/packages/bisheng/src/template.html) 188 | 189 | The HTML template which will be use to generate HTML files which will be sent to users. 190 | 191 | **Note:** template will be parsed by [nunjucks](https://mozilla.github.io/nunjucks/), and you can use the following variables in this template: 192 | 193 | * [`root`](https://github.com/benjycui/bisheng#root-string) 194 | * all attribute of [htmlTemplateExtraData](#htmltemplateextradata-object) 195 | 196 | #### htmlTemplateExtraData: Object 197 | 198 | > default: `{}` 199 | 200 | The Extra Data which will be used to render [htmlTemplate](#htmltemplate-string). 201 | 202 | #### devServerConfig: Object 203 | 204 | > default: {} 205 | 206 | You can consult [webpack-dev-server's documentation](https://webpack.js.org/configuration/dev-server/). 207 | 208 | #### postcssConfig: Object 209 | 210 | ```js 211 | default: { 212 | plugins: [ 213 | rucksack(), 214 | autoprefixer({ 215 | browsers: ['last 2 versions', 'Firefox ESR', '> 1%', 'ie >= 8', 'iOS >= 8', 'Android >= 4'], 216 | }), 217 | ], 218 | } 219 | ``` 220 | 221 | 222 | You can consult [wenpack postcss-loader's documentation](https://webpack.js.org/loaders/postcss-loader/#options). 223 | 224 | #### webpackConfig: (config) => config 225 | 226 | > default: (config) => config 227 | 228 | To modify the webpack config, you can extend the config like [this](https://github.com/ant-tool/atool-build#配置扩展). 229 | 230 | #### transformers: Object[] 231 | 232 | > [{ test: /\.md$/, use: [MarkdownTransformer](https://github.com/benjycui/bisheng/blob/master/packages/bisheng/src/transformers/markdown.js) }] 233 | 234 | A list of transformers that will be used to transform static files. 235 | 236 | #### entryName: String 237 | 238 | > default: 'index' 239 | 240 | The name of files which will be generated by webpack, such as `[entryName].js` & `[entryName].css`. 241 | 242 | #### root: String 243 | 244 | > default: '/' 245 | 246 | If the website will be deployed under a sub-directory of a domain (something like `http://benjycui.github.io/bisheng-theme-one/`), we must set it (such as `/bisheng-theme-one/`). 247 | 248 | ## License 249 | 250 | MIT 251 | -------------------------------------------------------------------------------- /packages/bisheng/src/index.js: -------------------------------------------------------------------------------- 1 | import UglifyJsPlugin from 'uglifyjs-webpack-plugin'; 2 | import openBrowser from 'react-dev-utils/openBrowser'; 3 | import getWebpackCommonConfig from './config/getWebpackCommonConfig'; 4 | import updateWebpackConfig from './config/updateWebpackConfig'; 5 | 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | const { escapeWinPath } = require('./utils/escape-win-path'); 9 | const mkdirp = require('mkdirp'); 10 | const nunjucks = require('nunjucks'); 11 | const webpack = require('webpack'); 12 | const WebpackDevServer = require('webpack-dev-server'); 13 | const R = require('ramda'); 14 | const ghPages = require('gh-pages'); 15 | const getBishengConfig = require('./utils/get-bisheng-config'); 16 | const sourceData = require('./utils/source-data'); 17 | const generateFilesPath = require('./utils/generate-files-path'); 18 | const context = require('./context'); 19 | 20 | const entryTemplate = fs.readFileSync(path.join(__dirname, 'entry.nunjucks.js')).toString(); 21 | const routesTemplate = fs.readFileSync(path.join(__dirname, 'routes.nunjucks.js')).toString(); 22 | const tmpDirPath = path.join(__dirname, '..', 'tmp'); 23 | mkdirp.sync(tmpDirPath); 24 | 25 | function getDefaultOfModule(module) { 26 | return module.default || module; 27 | } 28 | 29 | function getRoutesPath(configPath, themePath, configEntryName) { 30 | const routesPath = path.join(tmpDirPath, `routes.${configEntryName}.js`); 31 | const themeConfig = require(escapeWinPath(configPath)).themeConfig || {}; 32 | fs.writeFileSync( 33 | routesPath, 34 | nunjucks.renderString(routesTemplate, { 35 | themePath: escapeWinPath(themePath), 36 | themeConfig: JSON.stringify(themeConfig), 37 | themeRoutes: JSON.stringify(getDefaultOfModule(require(themePath)).routes), 38 | }), 39 | ); 40 | return routesPath; 41 | } 42 | 43 | function generateEntryFile(configPath, configTheme, configEntryName, root) { 44 | const entryPath = path.join(tmpDirPath, `entry.${configEntryName}.js`); 45 | const routesPath = getRoutesPath( 46 | configPath, 47 | path.dirname(configTheme), 48 | configEntryName, 49 | ); 50 | fs.writeFileSync( 51 | entryPath, 52 | nunjucks.renderString(entryTemplate, { 53 | routesPath: escapeWinPath(routesPath), 54 | root: escapeWinPath(root), 55 | }), 56 | ); 57 | } 58 | 59 | exports.start = function start(program) { 60 | const configFile = path.join(process.cwd(), program.config || 'bisheng.config.js'); 61 | const bishengConfig = getBishengConfig(configFile); 62 | context.initialize({ bishengConfig }); 63 | mkdirp.sync(bishengConfig.output); 64 | 65 | const template = fs.readFileSync(bishengConfig.htmlTemplate).toString(); 66 | const templateData = Object.assign({ root: '/' }, bishengConfig.htmlTemplateExtraData || {}); 67 | const templatePath = path.join(process.cwd(), bishengConfig.output, 'index.html'); 68 | fs.writeFileSync(templatePath, nunjucks.renderString(template, templateData)); 69 | 70 | generateEntryFile( 71 | configFile, 72 | bishengConfig.theme, 73 | bishengConfig.entryName, 74 | '/', 75 | ); 76 | 77 | const webpackConfig = updateWebpackConfig(getWebpackCommonConfig(), 'start'); 78 | webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin()); 79 | const serverOptions = { 80 | quiet: true, 81 | ...bishengConfig.devServerConfig, 82 | contentBase: path.join(process.cwd(), bishengConfig.output), 83 | historyApiFallback: true, 84 | hot: true, 85 | host: 'localhost', 86 | }; 87 | WebpackDevServer.addDevServerEntrypoints(webpackConfig, serverOptions); 88 | const compiler = webpack(webpackConfig); 89 | 90 | // Ref: https://github.com/pigcan/blog/issues/6 91 | // Webpack startup recompilation fix. Remove when @sokra fixes the bug. 92 | // https://github.com/webpack/webpack/issues/2983 93 | // https://github.com/webpack/watchpack/issues/25 94 | const timefix = 11000; 95 | compiler.plugin('watch-run', (watching, callback) => { 96 | watching.startTime += timefix; 97 | callback() 98 | }); 99 | compiler.plugin('done', (stats) => { 100 | stats.startTime -= timefix 101 | }) 102 | 103 | const server = new WebpackDevServer(compiler, serverOptions); 104 | server.listen( 105 | bishengConfig.port, '0.0.0.0', 106 | () => openBrowser(`http://localhost:${bishengConfig.port}`) 107 | ); 108 | }; 109 | 110 | const ssrTemplate = fs.readFileSync(path.join(__dirname, 'ssr.nunjucks.js')).toString(); 111 | 112 | function filenameToUrl(filename) { 113 | if (filename.endsWith('index.html')) { 114 | return filename.replace(/index\.html$/, ''); 115 | } 116 | return filename.replace(/\.html$/, ''); 117 | } 118 | exports.build = function build(program, callback) { 119 | const configFile = path.join(process.cwd(), program.config || 'bisheng.config.js'); 120 | const bishengConfig = getBishengConfig(configFile); 121 | context.initialize({ 122 | bishengConfig, 123 | isBuild: true, 124 | }); 125 | mkdirp.sync(bishengConfig.output); 126 | 127 | const { entryName } = bishengConfig; 128 | generateEntryFile( 129 | configFile, 130 | bishengConfig.theme, 131 | entryName, 132 | bishengConfig.root, 133 | ); 134 | const webpackConfig = updateWebpackConfig(getWebpackCommonConfig(), 'build'); 135 | webpackConfig.plugins.push(new UglifyJsPlugin({ 136 | uglifyOptions: { 137 | output: { 138 | ascii_only: true, 139 | }, 140 | }, 141 | })); 142 | webpackConfig.plugins.push(new webpack.DefinePlugin({ 143 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'), 144 | })); 145 | 146 | 147 | const ssrWebpackConfig = Object.assign({}, webpackConfig); 148 | const ssrPath = path.join(tmpDirPath, `ssr.${entryName}.js`); 149 | const routesPath = getRoutesPath(configFile, path.dirname(bishengConfig.theme), entryName); 150 | fs.writeFileSync(ssrPath, nunjucks.renderString(ssrTemplate, { routesPath: escapeWinPath(routesPath) })); 151 | 152 | ssrWebpackConfig.entry = { 153 | [`${entryName}-ssr`]: ssrPath, 154 | }; 155 | ssrWebpackConfig.target = 'node'; 156 | ssrWebpackConfig.output = Object.assign({}, ssrWebpackConfig.output, { 157 | path: tmpDirPath, 158 | library: 'ssr', 159 | libraryTarget: 'commonjs', 160 | }); 161 | ssrWebpackConfig.plugins = ssrWebpackConfig.plugins 162 | .filter(plugin => !(plugin instanceof webpack.optimize.CommonsChunkPlugin)); 163 | 164 | webpack(webpackConfig, (err, stats) => { 165 | if (err !== null) { 166 | return console.error(err); 167 | } 168 | 169 | if (stats.hasErrors()) { 170 | console.log(stats.toString('errors-only')); 171 | return; 172 | } 173 | 174 | const markdown = sourceData.generate(bishengConfig.source, bishengConfig.transformers); 175 | const themeConfig = require(bishengConfig.theme); 176 | let filesNeedCreated = generateFilesPath(themeConfig.routes, markdown).map(bishengConfig.filePathMapper); 177 | filesNeedCreated = R.unnest(filesNeedCreated); 178 | 179 | const template = fs.readFileSync(bishengConfig.htmlTemplate).toString(); 180 | 181 | if (!program.ssr) { 182 | require('./loaders/common/boss').jobDone(); 183 | const templateData = Object.assign({ root: bishengConfig.root }, bishengConfig.htmlTemplateExtraData || {}); 184 | const fileContent = nunjucks.renderString(template, templateData); 185 | filesNeedCreated.forEach((file) => { 186 | const output = path.join(bishengConfig.output, file); 187 | mkdirp.sync(path.dirname(output)); 188 | fs.writeFileSync(output, fileContent); 189 | console.log('Created: ', output); 190 | }); 191 | 192 | if (callback) { 193 | callback(); 194 | } 195 | return; 196 | } 197 | 198 | context.turnOnSSRFlag(); 199 | // If we can build webpackConfig without errors, we can build ssrWebpackConfig without errors. 200 | // Because ssrWebpackConfig are just part of webpackConfig. 201 | webpack(ssrWebpackConfig, () => { 202 | require('./loaders/common/boss').jobDone(); 203 | 204 | const { ssr } = require(path.join(tmpDirPath, `${entryName}-ssr`)); 205 | const fileCreatedPromises = filesNeedCreated.map((file) => { 206 | const output = path.join(bishengConfig.output, file); 207 | mkdirp.sync(path.dirname(output)); 208 | return new Promise((resolve) => { 209 | ssr(filenameToUrl(file), (error, content) => { 210 | if (error) { 211 | console.error(error); 212 | process.exit(1); 213 | } 214 | const templateData = Object.assign({ root: bishengConfig.root, content }, bishengConfig.htmlTemplateExtraData || {}); 215 | const fileContent = nunjucks 216 | .renderString(template, templateData); 217 | fs.writeFileSync(output, fileContent); 218 | console.log('Created: ', output); 219 | resolve(); 220 | }); 221 | }); 222 | }); 223 | Promise.all(fileCreatedPromises) 224 | .then(() => { 225 | if (callback) { 226 | callback(); 227 | } 228 | }); 229 | }); 230 | }); 231 | }; 232 | 233 | function pushToGhPages(basePath, config) { 234 | const options = { 235 | ...config, 236 | depth: 1, 237 | logger(message) { 238 | console.log(message); 239 | }, 240 | }; 241 | if (process.env.RUN_ENV_USER) { 242 | options.user = { 243 | name: process.env.RUN_ENV_USER, 244 | email: process.env.RUN_ENV_EMAIL, 245 | }; 246 | } 247 | ghPages.publish(basePath, options, (err) => { 248 | if (err) { 249 | throw err; 250 | } 251 | console.log('Site has been published!'); 252 | }); 253 | } 254 | exports.deploy = function deploy(program) { 255 | const config = { 256 | remote: program.remote, 257 | branch: program.branch, 258 | }; 259 | if (program.pushOnly) { 260 | const output = typeof program.pushOnly === 'string' ? program.pushOnly : './_site'; 261 | const basePath = path.join(process.cwd(), output); 262 | pushToGhPages(basePath, config); 263 | } else { 264 | const configFile = path.join(process.cwd(), program.config || 'bisheng.config.js'); 265 | const bishengConfig = getBishengConfig(configFile); 266 | const basePath = path.join(process.cwd(), bishengConfig.output); 267 | exports.build(program, () => pushToGhPages(basePath, config)); 268 | } 269 | }; 270 | --------------------------------------------------------------------------------