react-native/website/server/convert.js

258 lines
7.4 KiB
JavaScript

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
var fs = require('fs')
var glob = require('glob');
var mkdirp = require('mkdirp');
var optimist = require('optimist');
var path = require('path');
var removeMd = require('remove-markdown');
var extractDocs = require('./extractDocs');
var cache = require('memory-cache');
var argv = optimist.argv;
function splitHeader(content) {
var lines = content.split(/\r?\n/);
for (var i = 1; i < lines.length - 1; ++i) {
if (lines[i] === '---') {
break;
}
}
return {
header: i < lines.length - 1 ?
lines.slice(1, i + 1).join('\n') : null,
content: lines.slice(i + 1).join('\n')
};
}
function rmFile(file) {
try {
fs.unlinkSync(file);
} catch(e) {
/* seriously, unlink throws when the file doesn't exist :( */
}
}
function backtickify(str) {
var escaped = '`' + str.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/{/g, '\\{') + '`';
// Replace require( with require\( so node-haste doesn't replace example
// require calls in the docs
return escaped.replace(/require\(/g, 'require\\(');
}
function writeFileAndCreateFolder(file, content) {
mkdirp.sync(file.replace(new RegExp('/[^/]*$'), ''));
fs.writeFileSync(file, content);
}
// Extract markdown metadata header
function extractMetadata(content) {
var metadata = {};
var both = splitHeader(content);
var lines = both.header.split('\n');
for (var i = 0; i < lines.length - 1; ++i) {
var keyvalue = lines[i].split(':');
var key = keyvalue[0].trim();
var value = keyvalue.slice(1).join(':').trim();
// Handle the case where you have "Community #10"
try { value = JSON.parse(value); } catch(e) { }
metadata[key] = value;
}
return {metadata: metadata, rawContent: both.content};
}
function buildFile(layout, metadata, rawContent) {
return [
'/**',
' * @generated',
' */',
'var React = require("React");',
'var Layout = require("' + layout + '");',
rawContent && 'var content = ' + backtickify(rawContent) + ';',
'var Post = React.createClass({',
rawContent && ' statics: { content: content },',
' render: function() {',
' return (',
' <Layout metadata={' + JSON.stringify(metadata) + '}>',
rawContent && ' {content}',
' </Layout>',
' );',
' }',
'});',
'module.exports = Post;'
].filter(e => e).join('\n');
}
function execute(options) {
if (options === undefined) {
options = {};
}
var DOCS_MD_DIR = '../docs/';
var BLOG_MD_DIR = '../blog/';
glob.sync('src/react-native/docs/*.*').forEach(rmFile);
glob.sync('src/react-native/blog/*.*').forEach(rmFile);
glob.sync('../blog/img/*.*').forEach(file => {
writeFileAndCreateFolder(
'src/react-native/blog/img/' + path.basename(file),
fs.readFileSync(file)
);
});
var metadatas = {
files: [],
};
function handleMarkdown(content, filename) {
if (content.slice(0, 3) !== '---') {
return;
}
const res = extractMetadata(content);
const metadata = res.metadata;
const rawContent = res.rawContent;
if (metadata.sidebar !== false) {
metadatas.files.push(metadata);
}
if (metadata.permalink.match(/^https?:/)) {
return;
}
metadata.filename = filename;
// Create a dummy .js version that just calls the associated layout
var layout = metadata.layout[0].toUpperCase() + metadata.layout.substr(1) + 'Layout';
writeFileAndCreateFolder(
'src/react-native/' + metadata.permalink.replace(/\.html$/, '.js'),
buildFile(layout, metadata, rawContent)
);
}
if (options.extractDocs) {
// Rendering docs can take up to 8 seconds. We wait until /docs/ are
// requested before doing so, then we store the results in memory to
// speed up subsequent requests.
var extractedDocs = cache.get('extractedDocs');
if (!extractedDocs) {
extractedDocs = extractDocs();
cache.put('extractedDocs', extractedDocs);
}
extractedDocs.forEach(function(content) {
handleMarkdown(content, null);
});
var files = glob.sync(DOCS_MD_DIR + '**/*.*');
files.forEach(function(file) {
var extension = path.extname(file);
if (extension === '.md' || extension === '.markdown') {
var content = fs.readFileSync(file, {encoding: 'utf8'});
handleMarkdown(content, path.basename(file));
}
if (extension === '.json') {
var content = fs.readFileSync(file, {encoding: 'utf8'});
metadatas[path.basename(file, '.json')] = JSON.parse(content);
}
});
}
// we need to pass globals for the components to be configurable
// metadata is generated in this process which has access to process.env
// but the web pages are generated in a sandbox context and have only access to CommonJS module files
metadatas.config = Object.create(null);
Object
.keys(process.env)
.filter(key => key.startsWith('RN_'))
.forEach((key) => {
metadatas.config[key] = process.env[key];
});
fs.writeFileSync(
'core/metadata.js',
'/**\n' +
' * @generated\n' +
' * @providesModule Metadata\n' +
' */\n' +
'module.exports = ' + JSON.stringify(metadatas, null, 2) + ';'
);
files = glob.sync(BLOG_MD_DIR + '**/*.md');
const metadatasBlog = {
files: [],
};
files.sort().reverse().forEach(file => {
// Transform
// 2015-08-13-blog-post-name-0.5.md
// into
// 2015/08/13/blog-post-name-0-5.html
var filePath = path.basename(file)
.replace('-', '/')
.replace('-', '/')
.replace('-', '/')
// react-middleware is broken with files that contains multiple . like react-0.14.js
.replace(/\./g, '-')
.replace(/\-md$/, '.html');
// Extract 2015-08-13 from 2015/08/13/blog-post-name-0-5.html
var match = filePath.match(/([0-9]+)\/([0-9]+)\/([0-9]+)/);
var year = match[1];
var month = match[2];
var day = match[3];
var publishedAt = year + '-' + month + '-' + day;
var res = extractMetadata(fs.readFileSync(file, {encoding: 'utf8'}));
var rawContent = res.rawContent;
var excerpt = removeMd(rawContent).trim().split('\n')[0];
var metadata = Object.assign({path: filePath, content: rawContent, publishedAt: publishedAt, excerpt: excerpt}, res.metadata);
metadatasBlog.files.push(metadata);
writeFileAndCreateFolder(
'src/react-native/blog/' + filePath.replace(/\.html$/, '.js'),
buildFile('BlogPostLayout', metadata, rawContent)
);
});
var perPage = 15;
for (var page = 0; page < Math.ceil(metadatasBlog.files.length / perPage); ++page) {
writeFileAndCreateFolder(
'src/react-native/blog' + (page > 0 ? '/page' + (page + 1) : '') + '/index.js',
buildFile('BlogPageLayout', { page: page, perPage: perPage })
);
}
fs.writeFileSync(
'core/metadata-blog.js',
'/**\n' +
' * @generated\n' +
' * @providesModule MetadataBlog\n' +
' */\n' +
'module.exports = ' + JSON.stringify(metadatasBlog, null, 2) + ';'
);
fs.writeFileSync(
'server/metadata-blog.json',
JSON.stringify(metadatasBlog, null, 2)
);
}
if (argv.convert) {
console.log('convert!');
execute();
}
module.exports = execute;