UNPKG

nobuilder-json

Version:

nobuilder

263 lines (235 loc) 5.79 kB
"use strict"; const fs = require("fs"); const path = require("path"); const marked = require("marked"); const rescape = require("escape-string-regexp"); const PATTERN_START_TOKEN = /^`{3}(\w+)\s*/; const template = (text, values, opts) => { opts = Object.assign( { left: "{", right: "}" }, opts ); let rule = new RegExp( rescape(opts.left) + "([\\w\\s\\.\\(\\)\\|-]+)" + rescape(opts.right), "g" ); return text.replace(rule, (total, name) => { if (typeof values === "function") { return values(name, total); } else { return name in values ? values[name] : total; } }); }; class Renderer extends marked.Renderer { constructor() { super(); // codes: [{ // type: 'js', // content: <string> // }, ...] this.codes = []; this._headHtmls = []; } getHeadHtml() { return this._headHtmls.join("\n"); } /** * 渲染可执行的代码片段 * @param {String} code * @param {String} lang * @param {Boolean} escaped */ code(code, lang, escaped) { let executable = false; // 判断 lang 是否以 ! 开头, 如果是就是可执行的 if (lang && lang.startsWith("!")) { lang = lang.substring(1); executable = true; } let parsed = super.code(code, lang, escaped); let renderred = ""; // 只有可执行的代码, 才走特殊的渲染方式, codeIndex ++ if (executable) { switch (lang) { case "js": case "jsx": this.codes.push({ type: "js", content: code }); let index = this.codes.length - 1; renderred = ` <div class="mount-node" id="mount-node-${index}"></div> `; break; case "css": this.codes.push({ type: "css", content: code }); // CSS renderred = ""; break; case "html": renderred = code; break; case "html:head": this._headHtmls.push(code); renderred = ""; break; default: renderred = parsed; } } else { renderred = parsed; } return renderred; } } function lowerCamelCase(text) { return text.replace(/-(\w)/g, function(all, letter) { return letter.toUpperCase(); }); } /** * parse markdown * * source: * ``` * # header1 * * - author: zoujie.wzj * - created-time: yyyy/mm/dd * - charset: utf8 * * description ... * --- * * code ... * ``` * * result: * ``` * meta: { * charset: 'utf8', * author: 'zoujie.wzj', * createdTime: 'yyyy/mm/dd', * title: 'header1', * description: 'description ...' * } * ``` * * @param {String} content * @return {Object} * - meta * - content */ function parseMeta(content) { let lines = content.split("\n"); let meta = {}; let titleIndex = -1; // parse meta for (let i = 0; i < lines.length; i++) { let line = lines[i]; if (line[0] === "#") { if (line[1] !== "#" && !meta.title) { meta.title = line.substring(1).trim(); titleIndex = i; } } else if (line.match(/^[*-]/)) { let matches = line.match(/^[*-]\s*([\w-]+)\s*:\s*(.*)/); if (matches) { let key = lowerCamelCase(matches[1]); meta[key] = String(matches[2]).trim(); } lines[i] = ""; } else if (!line || line.match(/^\s+/)) { continue; } else { break; } } // add executable mark for (let i = 0; i < lines.length; i++) { if (lines[i].match(PATTERN_START_TOKEN)) { lines[i] = lines[i].replace(PATTERN_START_TOKEN, (total, lang) => { return "```!" + lang; }); } } if (meta.layout === "test" && titleIndex >= 0) { lines[titleIndex] = ""; } return { meta, content: lines.join("\n") }; } function fixDocsCode(index, code, pkgName, relativePath) { return code .replace(/\bmountNode\b/g, `document.getElementById('mount-node-${index}')`) .replace( new RegExp(`(import .*["'])${pkgName}(\\/src|\\/lib)?(\\S*["'])`, "g"), `$1${relativePath}/src$3` ); } module.exports = function(content) { const cwd = process.cwd(); const pkg = require(path.join(cwd, "package.json")); const resourcePath = this.resourcePath; const relativePath = path.relative(cwd, resourcePath); const parsed = parseMeta(content); const meta = parsed.meta; const renderer = new Renderer(); const bodyHtml = marked(parsed.content, { renderer }); const codes = renderer.codes; // codes let js = []; let css = []; let realtiveRoot = path.relative(path.dirname(resourcePath), cwd); for (let i = 0; i < codes.length; i++) { let code = codes[i]; if (code.type === "js") { js.push(fixDocsCode(i, code.content, pkg.name, realtiveRoot)); } else if (code.type === "css") { css.push(code.content); } } // params let title = meta.title || resourcePath; let layout = meta.layout || "default"; let charset = meta.charset || "utf8"; let headHtml = renderer.getHeadHtml(); let tplFile = path.join(__dirname, "layout", layout + ".html"); if (!fs.existsSync(tplFile)) { throw new Error(`读取 layout 文件 ${tplFile} 失败`); } let tplContent = fs.readFileSync(tplFile, "utf8"); let html = template(tplContent, { name: pkg.name, title, charset, headHtml, bodyHtml, jsUrl: `/static/js/${relativePath}.js`, css: css.join("\n") }); let htmlPath = path.relative(cwd, resourcePath).replace(/\.md$/, ".html"); this.emitFile(htmlPath, html); return ` // js import React from 'react' import ReactDOM from 'react-dom' ${js.join("\n")} // HMR code if (module.hot) { module.hot.accept(); } `; };