diff --git a/package.json b/package.json index 95bb031a44..90e1c84850 100644 --- a/package.json +++ b/package.json @@ -105,10 +105,12 @@ "less-loader": "^2.2.0", "lesshint-antd": "^1.2.1", "lodash": "^4.1.0", + "mark-twain": "^0.2.0-beta", "nico-jsx": "~0.8.2", "postcss-loader": "^0.8.0", "pre-commit": "1.x", "querystring": "^0.2.0", + "ramda": "^0.19.1", "rc-scroll-anim": "^0.1.7", "rc-tween-one": "^0.1.8", "react": "0.14.x", @@ -117,6 +119,7 @@ "react-dom": "0.14.x", "react-router": "^2.0.0", "react-stateless-wrapper": "~1.0.2", + "recast": "^0.11.2", "reqwest": "~2.0.5", "semver": "^5.1.0", "values.js": "^1.0.3", @@ -127,7 +130,7 @@ }, "scripts": { "babel": "babel components index.js --out-dir lib", - "start": "dora -p 8001 --plugins atool-build?config=webpack.website.config.js", + "start": "npm run clean && ./scripts/build-website.js && dora -p 8001 --plugins atool-build?config=webpack.website.config.js", "clean": "rm -rf _site dist", "site": "npm run clean && webpack --config webpack.deploy.config.js && webpack --config webpack.antd.config.js && NODE_ENV=PRODUCTION nico build", "deploy": "rm -rf node_modules && node scripts/install.js && npm run just-deploy", diff --git a/scripts/build-components-list.js b/scripts/build-components-list.js new file mode 100644 index 0000000000..47e9c3d96b --- /dev/null +++ b/scripts/build-components-list.js @@ -0,0 +1,20 @@ +'use strict'; + +const fs = require('fs'); +const R = require('ramda'); +const utils = require('./utils'); + +module.exports = function buildComponentsList(indexes, outputPath) { + const componentMetas = R.map((fileName) => { + const fileContent = utils.parseFileContent(fileName); + return utils.parseMeta(fileContent); + }, indexes); + + const groupByType = R.groupBy(R.compose(R.defaultTo('其它'), R.prop('type'))); + const componentsList = groupByType(componentMetas); + + const content = 'module.exports = ' + + JSON.stringify(componentsList, null, 2) + ';'; + + fs.writeFile(outputPath, content); +}; diff --git a/scripts/build-demos-list.js b/scripts/build-demos-list.js new file mode 100644 index 0000000000..d62b348f0a --- /dev/null +++ b/scripts/build-demos-list.js @@ -0,0 +1,70 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const R = require('ramda'); +const devil = require('./devil'); // TODO: extract as a module? +const utils = require('./utils'); + +const stringify = function stringify(data, d) { + const depth = d || 1; + const indent = ' '.repeat(depth); + let output = ''; + if (Array.isArray(data)) { + output += '[\n'; + data.forEach((item) => output += indent + stringify(item, depth + 1) + ',\n'); + output += indent + ']'; + } else if (typeof data === 'object') { + output += '{\n'; + for (const key of Object.keys(data)) { + output += indent + JSON.stringify(key) + ': ' + stringify(data[key], depth + 1) + ',\n'; + } + output += indent + '}'; + } else if (typeof data === 'function') { + output += data.toString(); + } else if (typeof data === 'string') { + output += JSON.stringify(data); + } else { + output += data; + } + return output + .replace(/var _antd = require\(['"]antd['"]\);/, '') + .replace(/require\('antd\/lib/, 'require(\'../../components'); // TODO +}; + +const isIntro = function isIntro(element) { + const type = element.type; + return type !== 'h1' && type !== 'ul' && type !== 'code' && type !== 'hr'; +}; +const isCode = R.whereEq({ type: 'code', props: { lang: 'jsx' } }); +const isStyle = R.whereEq({ type: 'code', props: { lang: 'css' } }); +const getChildren = R.compose(R.prop('children'), R.defaultTo({})); +const sortByOrder = R.sortBy(R.prop('order')); +module.exports = function buildDemosList(demoList, outputPath) { + const demos = R.map((fileName) => { + const data = utils.parseFileContent(fileName); + const parts = fileName.split(path.sep); + + const demo = {}; + demo.order = parseInt(utils.parseMeta(data).order); + demo.parent = parts[parts.indexOf('components') + 1]; + demo.id = 'components-' + demo.parent + '-demo-' + path.basename(fileName, '.md'); + demo.title = data[0].children; + demo.intro = data.filter(isIntro); + demo.code = getChildren(data.find(isCode)); + demo.preview = devil(demo.code); + demo.style = getChildren(data.find(isStyle)); + + return demo; + }, demoList); + + const demosList = R.groupBy((demo) => demo.parent.replace('-', ''), demos); + const sortedDemosList = R.mapObjIndexed(sortByOrder, demosList); + const content = 'const React = require(\'react\');\n' + + 'const ReactDOM = require(\'react-dom\');\n' + + 'const _antd = require(\'../../\');\n' + + 'module.exports = ' + + stringify(sortedDemosList, null, 2) + ';'; + + fs.writeFile(outputPath, content); +}; diff --git a/scripts/build-docs-list.js b/scripts/build-docs-list.js new file mode 100644 index 0000000000..31a4fb9cd1 --- /dev/null +++ b/scripts/build-docs-list.js @@ -0,0 +1,26 @@ +'use strict'; + +const fs = require('fs'); +const R = require('ramda'); +const utils = require('./utils'); + +const isMeta = R.complement(R.propEq('type', 'hr')); +const isDescription = R.complement(R.propEq('children', 'API')); +module.exports = function buildDocsList(indexes, outputPath) { + const indexesList = R.map((fileName) => { + const fileContent = utils.parseFileContent(fileName); + const meta = utils.parseMeta(fileContent); + const description = R.tail(R.dropWhile( + isMeta, + R.takeWhile(isDescription, fileContent) + )); + const api = R.dropWhile(isDescription, fileContent); + + return { meta, description, api }; + }, indexes); + + const content = 'module.exports = ' + + JSON.stringify(indexesList, null, 2) + ';'; + + fs.writeFile(outputPath, content); +}; diff --git a/scripts/build-website.js b/scripts/build-website.js new file mode 100755 index 0000000000..008474979c --- /dev/null +++ b/scripts/build-website.js @@ -0,0 +1,77 @@ +#! /usr/bin/env node + +'use strict'; + +const fs = require('fs'); +const R = require('ramda'); +const utils = require('./utils'); +const buildComponentsList = require('./build-components-list'); +const buildDocsList = require('./build-docs-list'); +const buildDemosList = require('./build-demos-list'); + +// Ensure that data directory exist. +try { + fs.statSync('./_site'); +} catch (e) { + fs.mkdir('./_site'); +} +try { + fs.statSync('./_site/data'); +} catch (e) { + fs.mkdir('./_site/data'); +} + +// TODO: configurable +const componentPath = './components'; +const mds = utils.findMDFile(componentPath); + +const indexes = R.filter(utils.isIndex, mds); +buildComponentsList(indexes, './_site/data/components-list.js'); +buildDocsList(indexes, './_site/data/component-docs-list.js'); + +const demos = R.filter(utils.isDemo, mds); +buildDemosList(demos, './_site/data/demos-list.js'); + +const changelogPath = './CHANGELOG.md'; +buildDocsList([changelogPath], './_site/data/changelog.js'); + +const introducePath = './docs/react/introduce.md'; +buildDocsList([introducePath], './_site/data/introduce.js'); + +const gettingStartedPath = './docs/react/getting-started.md'; +buildDocsList([gettingStartedPath], './_site/data/getting-started.js'); + +const installPath = './docs/react/install.md'; +buildDocsList([installPath], './_site/data/install.js'); + +const upgradeNotesPath = './docs/react/upgrade-notes.md'; +buildDocsList([upgradeNotesPath], './_site/data/upgrade-notes.js'); + +const downloadPath = './docs/resource/download.md'; +buildDocsList([downloadPath], './_site/data/download.js') + +const referencePath = + './docs/resource/reference.md'; +buildDocsList([referencePath], './_site/data/reference.js') + +const specIntroPath = + './docs/spec/introduce.md' +buildDocsList([specIntroPath], './_site/data/specIntro.js'); + +const fontPath = + './docs/spec/font.md' +buildDocsList([fontPath], './_site/data/font.js'); + +const typographyPath = + './docs/spec/typography.md' +buildDocsList([typographyPath], './_site/data/typography.js'); + +const easingPath = + './docs/spec/easing.md' +buildDocsList([easingPath], './_site/data/easing.js'); +const pageTransitionPath = + './docs/spec/page-transition.md' +buildDocsList([pageTransitionPath], './_site/data/page-transition.js'); +const motionPath = + './docs/spec/motion.md' +buildDocsList([motionPath], './_site/data/motion.js'); diff --git a/scripts/devil.js b/scripts/devil.js new file mode 100644 index 0000000000..1d2e0bbd02 --- /dev/null +++ b/scripts/devil.js @@ -0,0 +1,31 @@ +'use strict'; + +const babel = require('babel-core'); +const recast = require('recast'); +const builders = recast.types.builders; + +const babelrc = { + presets: ['es2015', 'react'] +}; + +// const demo = 'import { Button } from \'antd\';' + +// 'ReactDOM.render(
' + +// ' ' + +// ' ' + +// ' ' + +// '
,' + +// 'document.getElementById(\'components-button-demo-basic\'));'; +// devil(demo); +module.exports = function devil(demo, params) { + const compiled = babel.transform(demo, babelrc).code; + + const ast = recast.parse(compiled); + const astProgramBody = ast.program.body; + const lastIndex = astProgramBody.length - 1; + astProgramBody[lastIndex] = builders.returnStatement( + astProgramBody[lastIndex].expression.arguments[0] + ); + + const code = recast.print(ast).code; + return new Function((params || []).join(', '), code); +} diff --git a/scripts/utils.js b/scripts/utils.js new file mode 100644 index 0000000000..19891649b0 --- /dev/null +++ b/scripts/utils.js @@ -0,0 +1,47 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const R = require('ramda'); + +const isMDFile = R.compose(R.equals('.md'), path.extname); +exports.findMDFile = function findMDFile(dirPath) { + let mds = []; + + R.forEach((fileName) => { + const filePath = path.join(dirPath, fileName); + const stat = fs.statSync(filePath); + if (stat.isFile() && isMDFile(filePath)) { + mds.push(filePath); + } + if (stat.isDirectory()) { + mds = mds.concat(findMDFile(filePath)); + } + }, fs.readdirSync(dirPath)); + + return mds; +}; +exports.isIndex = R.compose(R.equals('index.md'), R.unary(path.basename)); +exports.isDemo = R.complement(exports.isIndex); + +const MT = require('mark-twain'); +exports.parseFileContent = R.pipe( + fs.readFileSync, + R.toString, + MT, + R.prop('content') +); + +const parseBasicMeta = R.pipe( + R.path(['1', 'children']), + R.map((child) => R.split(/:\s?/, child.children[0].children)), + R.fromPairs +); +const parseEnglishTitle = R.path(['0', 'children']); +exports.parseMeta = function parseMeta(fileContent) { + const meta = parseBasicMeta(fileContent); + meta.english = parseEnglishTitle(fileContent); + meta.title = `${meta.english} ${meta.chinese || ''}`; + + return meta; +};