/** * 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 { // else send corresponding blog post 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;