/** * Copyright (c) 2017-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. */ function execute () { const translation = require('./translation.js'); translation(); const CWD = process.cwd(); const express = require('express'); const React = require('react'); const request = require('request'); const renderToStaticMarkup = require('react-dom/server').renderToStaticMarkup; const fs = require('fs-extra'); const os = require('os'); const path = require('path'); const readMetadata = require('./readMetadata.js'); const toSlug = require('../core/toSlug.js'); const mkdirp = require('mkdirp'); const glob = require('glob'); let siteConfig = require(CWD + '/siteConfig.js'); /** * Removes a module from the cache */ function purgeCache(moduleName) { // Traverse the cache looking for the files // loaded by the specified module name searchCache(moduleName, function (mod) { delete require.cache[mod.id]; }); // Remove cached paths to the module. Object.keys(module.constructor._pathCache).forEach(function(cacheKey) { if (cacheKey.indexOf(moduleName)>0) { delete module.constructor._pathCache[cacheKey]; } }); }; /** * Traverses the cache to search for all the cached * files of the specified module name */ function searchCache(moduleName, callback) { // Resolve the module identified by the specified name let mod = require.resolve(moduleName); // Check if the module has been resolved and found within // the cache if (mod && ((mod = require.cache[mod]) !== undefined)) { // Recursively go over the results (function traverse(mod) { // Go over each of the module's children and // traverse them mod.children.forEach(function (child) { traverse(child); }); // Call the specified callback providing the // found cached module callback(mod); }(mod)); } }; /****************************************************************************/ let Metadata; let readCategories; function reloadMetadataCategories() { readMetadata.generateDocsMetadata(); purgeCache('../core/metadata.js'); Metadata = require('../core/metadata.js'); purgeCache('./readCategories.js'); readCategories = require('./readCategories.js'); let layouts = {}; for (let i = 0; i < Metadata.length; i++) { let layout = Metadata[i].layout; if (layouts[layout] !== true) { layouts[layout] = true; readCategories(layout); } } } /****************************************************************************/ const TABLE_OF_CONTENTS_TOKEN = ''; const insertTableOfContents = rawContent => { const regexp = /\n###\s+(`.*`.*)\n/g; let match; const headers = []; while ((match = regexp.exec(rawContent))) { headers.push(match[1]); } const tableOfContents = headers .map(header => ` - [${header}](#${toSlug(header)})`) .join('\n'); return rawContent.replace(TABLE_OF_CONTENTS_TOKEN, tableOfContents); }; /****************************************************************************/ console.log('server.js triggered...'); const port = 3000; reloadMetadataCategories(); /* handle all requests for document pages */ const app = express() .get(/docs\/[\s\S]*html$/, (req, res) => { purgeCache(CWD + '/siteConfig.js'); siteConfig = require(CWD + '/siteConfig.js'); console.log(req.path); reloadMetadataCategories(); let links = {}; for (let i = 0; i < Metadata.length; i++) { const metadata = Metadata[i]; links[metadata.permalink] = 'docs/' + metadata.language + '/' + metadata.source; } let mdToHtml = {}; for (let i = 0; i < Metadata.length; i++) { const metadata = Metadata[i]; mdToHtml['/docs/' + metadata.language + '/' + metadata.source] = siteConfig.baseUrl + metadata.permalink; } let file = links[req.path.toString().replace(siteConfig.baseUrl, '')]; file = CWD + '/../' + file; console.log(file); const result = readMetadata.processMetadata(file); const metadata = result.metadata; const language = metadata.language; let rawContent = result.rawContent; /* generate table of contents if appropriate */ if (rawContent && rawContent.indexOf(TABLE_OF_CONTENTS_TOKEN) !== -1) { rawContent = insertTableOfContents(rawContent); } /* replace any links to markdown files to their website html links */ Object.keys(mdToHtml).forEach(function(key, index) { rawContent = rawContent.replace(new RegExp(key,'g'), mdToHtml[key]); }); purgeCache('../core/DocsLayout.js'); const DocsLayout = require('../core/DocsLayout.js'); const docComp = {rawContent}; res.send(renderToStaticMarkup(docComp)); }); /* handle all requests for blog pages and posts */ app.get(/blog\/[\s\S]*html$/, (req, res) => { purgeCache(CWD + '/siteConfig.js'); siteConfig = require(CWD + '/siteConfig.js'); if (fs.existsSync(__dirname + '../core/MetadataBlog.js')) { purgeCache('../core/MetadataBlog.js'); fs.removeSync(__dirname + '../core/MetadataBlog.js') } readMetadata.generateBlogMetadata(); MetadataBlog = require('../core/MetadataBlog.js'); /* generate all of the blog pages */ purgeCache('../core/BlogPageLayout.js'); const BlogPageLayout = require('../core/BlogPageLayout.js'); const blogPages = {}; /* make blog pages with 10 posts per page */ const perPage = 10; for ( let page = 0; page < Math.ceil(MetadataBlog.length / perPage); page++ ) { let language = 'en'; const metadata = {page: page, perPage: perPage}; const blogPageComp = ; const str = renderToStaticMarkup(blogPageComp); let path = (page > 0 ? 'page' + (page + 1) : '') + '/index.html'; blogPages[path] = str; } let parts = req.path.toString().split('blog/'); // send corresponding blog page if appropriate if (parts[1] === 'index.html') { res.send(blogPages['/index.html']); } else if (parts[1].endsWith('/index.html')) { res.send(blogPages[parts[1]]); } else if (parts[1].match(/page([0-9]+)/)) { if (parts[1].endsWith('/')) { res.send(blogPages[parts[1] + 'index.html']); } else { res.send(blogPages[parts[1] + '/index.html']); } } // else send corresponding blog post else { let file = parts[1]; file = file.replace(/\.html$/, '.md'); file = file.replace(new RegExp('/', 'g'), '-'); file = CWD + '/../blog/' + file; const result = readMetadata.extractMetadata(fs.readFileSync(file, {encoding: 'utf8'})); const rawContent = result.rawContent; const metadata = Object.assign( {path: req.path.toString().split('blog/')[1], content: rawContent}, result.metadata ); metadata.id = metadata.title; let language = 'en'; purgeCache('../core/BlogPostLayout.js') const BlogPostLayout = require('../core/BlogPostLayout.js'); const blogPostComp = {rawContent}; res.send(renderToStaticMarkup(blogPostComp)); } }); /* handle all other main pages */ app.get('*.html', (req, res) => { purgeCache(CWD + '/siteConfig.js'); siteConfig = require(CWD + '/siteConfig.js'); console.log(req.path); /* look for user provided html file first */ let htmlFile = req.path.toString().replace(siteConfig.baseUrl, ''); htmlFile = CWD + '/pages/' + htmlFile; if (fs.existsSync(htmlFile) || fs.existsSync(htmlFile=htmlFile.replace(path.basename(htmlFile), 'en/' + path.basename(htmlFile)))) { res.send(fs.readFileSync(htmlFile, {encoding: 'utf8'})); return; } /* look for user provided react file either in specified path or in path for english files */ let file = req.path.toString().replace(/\.html$/, '.js'); file = file.replace(siteConfig.baseUrl, ''); let userFile = CWD + '/pages/' + file; let language = 'en'; const regexLang = /(.*)\/.*\.html$/; const match = regexLang.exec(req.path); const parts = match[1].split('/'); const enabledLangTags = []; for (let i = 0; i < siteConfig['languages'].length; i++) { enabledLangTags.push(siteConfig['languages'][i].tag); } for (let i = 0; i < parts.length; i++) { if (enabledLangTags.indexOf(parts[i]) !== -1) { language = parts[i]; } } if (fs.existsSync(userFile) || fs.existsSync(userFile=userFile.replace(path.basename(userFile), 'en/' + path.basename(userFile)))) { /* copy into docusaurus so require paths work */ let parts = userFile.split('pages/'); let tempFile = __dirname + '/../pages/' + parts[1]; tempFile = tempFile.replace(path.basename(file), 'temp' + path.basename(file)); mkdirp.sync(tempFile.replace(new RegExp('/[^/]*$'), '')); fs.copySync(userFile, tempFile); /* render into a string */ purgeCache(tempFile); const ReactComp = require(tempFile); purgeCache('../core/Site.js'); const Site = require('../core/Site.js'); const str = renderToStaticMarkup(); fs.removeSync(tempFile); res.send(str); } else { console.log(req.path); res.send('No file found'); } }); /* generate the main.css file by concatenating user provided css to the end */ app.get(/main\.css$/, (req,res) => { const mainCssPath = __dirname +'/../static/' + req.path.toString().replace(siteConfig.baseUrl, '/'); let cssContent = fs.readFileSync(mainCssPath, {encoding: 'utf8'}); let files = glob.sync(CWD + '/static/**/*.css') files.forEach(file => { cssContent = cssContent + '\n' + fs.readFileSync(file, {encoding: 'utf8'}); }); cssContent = cssContent.toString().replace(new RegExp('{primaryColor}', 'g'), siteConfig.colors.primaryColor); cssContent = cssContent.replace(new RegExp('{secondaryColor}', 'g'), siteConfig.colors.secondaryColor); cssContent = cssContent.replace(new RegExp('{prismColor}', 'g'), siteConfig.colors.prismColor); res.send(cssContent); }); /* serve static content first from user folder then from docusaurus */ app.use(siteConfig.baseUrl, express.static(CWD + '/static')); app.use(siteConfig.baseUrl, express.static(__dirname + '/../static')); app.get(/\/[^\.]*\/?$/, (req, res) => { if (req.path.toString().endsWith('/')) { request.get('http://localhost:3000' + req.path + 'index.html', (err, response, body) => { if (!err) { res.send(body); } }); } else { request.get('http://localhost:3000' + req.path + '/index.html', (err, response, body) => { if (!err) { res.send(body); } }); } }); app.listen(port); console.log('listening on port: ' + port); } module.exports = execute;