#!/usr/bin/env node "use strict"; const fs = require("fs"); const browserify = require("browserify"); const config = require("./config"); const curDir = process.cwd(); const build_conf = config.build; // Handle home page const b = browserify(); b.add(`${curDir}/src/main.js`) .bundle() .pipe(fs.createWriteStream(`${curDir}/public/main.js`)); // Handle subpages function getPageHtml(pageName, pageMeta) { let html = fs.readFileSync(`${curDir}/public/index.html`, "utf-8"); const setMeta = function (metaName, value) { html = html.replace( html.match(new RegExp(`<meta\\s*name="${metaName}"[^>]+>`, "g"))[0], `<meta name="${metaName}" content="${value}"/>` ); }; const setTitle = function () { html = html.replace( html.match(new RegExp(`<title.+</title>`, "g"))[0], `<title>${pageMeta.title}</title>` ); html = html.replace( html.match(new RegExp(`<h1.+</h1>`, "g")), `<h1 style="visibility: hidden">${pageMeta.title}</h1>` ); }; const setStyleSheet = function () { html = html.replace( html.match(new RegExp(`<link.+/style.css[^>]+>`, "g"))[0], `<link href="/style/style.css" rel="stylesheet" />` ); }; const setJs = function () { html = html.replace( html.match(new RegExp(`<script.+main.js.+</script>`, "g"))[0], `<script type="text/javascript" src="./${pageName}.js"></script>` ); }; const setAdditionalMeta = function (metas) { html = html.replace( "</head>", `${metas .map(kv => { const [name, content] = kv; return `<meta name="${name}" content="${content}"/>`; }) .join("\n")}</head>` ); }; const setOgMeta = function () { const pageOgMeta = pageMeta.open_graph || {}; const getOgMetaSearchRegex = function (name) { return new RegExp(`<meta\\s*property="og:${name}"[^>]+>`, "g"); }; const getDefaultOgMetaContent = function (name) { const getRegexMatch = function (source, searchRe) { const m = source.match(searchRe); return m ? m[0] : ""; }; return getRegexMatch( getRegexMatch( getRegexMatch(html, getOgMetaSearchRegex(name)), new RegExp(`content=".+"`, "g") ), new RegExp(`".+"`, "g") ).replace(/"/g, ""); }; const requiredOgMeta = [ { key: "title", defaultValue: pageMeta.title }, { key: "description", defaultValue: pageMeta.description }, { key: "image", defaultValue: pageMeta.image || getDefaultOgMetaContent("image"), }, { key: "url", defaultValue: (function () { const urlContent = getDefaultOgMetaContent("url"); return `${urlContent}${ urlContent.charAt(urlContent.length - 1) !== "/" ? "/" : "" }${pageName}`; })(), }, { key: "locale", defaultValue: getDefaultOgMetaContent("locale"), }, // TODO : handle locale:alternate meta tags array ]; requiredOgMeta.forEach(entry => { const { key, defaultValue } = entry; html = html.replace( html.match(getOgMetaSearchRegex(key)), (function () { const customValue = pageOgMeta[key]; if (!customValue) return `<meta property="og:${key}" content="${defaultValue}"/>`; else { return Array.isArray(customValue) ? customValue .map(alt => `<meta property="og:${key}" content="${alt}"/>`) .join("\n") : `<meta property="og:${key}" content="${customValue}"/>`; } })() ); }); const additionalOgMeta = Object.keys(pageMeta.open_graph).filter( k => !requiredOgMeta.map(rom => rom.key).includes(k) ); if (additionalOgMeta.length > 0) { html = html.replace( "</head>", `${additionalOgMeta .map(k => `<meta property="og:${k}" content="${pageMeta.open_graph[k]}"/>`) .join("\n")}</head>` ); } }; const setJsonLdScript = function () { const jsonLd = pageMeta.json_ld; html = html.replace( html.match(new RegExp(`<script.+json.+>[^<]+</script>`, "g"))[0], `<script type="application/ld+json">${JSON.stringify(jsonLd)}</script>` ); }; setMeta("description", pageMeta.description); pageMeta.image && setMeta("image", pageMeta.image); setTitle(); setStyleSheet(); setJs(); setAdditionalMeta( Object.entries(pageMeta).filter(kv => !build_conf.default_meta_keys.includes(kv[0])) ); setOgMeta(); // set twitter image html = html.replace( html.match(new RegExp(`<meta\\s*property="twitter:image"[^>]+>`, "g"))[0], `<meta property="twitter:image" content="${ pageMeta.image || html .match(new RegExp(`<meta\\s*name="image"[^>]+>`, "g"))[0] .match(new RegExp(`content=".+"`, "g"))[0] .match(new RegExp(`".+"`, "g")) .replace(/"/g, "") }"/>` ); setJsonLdScript(); return html; } const pages = fs.readdirSync(`${curDir}/src/pages`); for (const p of pages) { const fPath = `${curDir}/src/pages/${p}`; const targetDirPath = `${curDir}/public/${p}`; if (!fs.existsSync(targetDirPath)) { fs.mkdirSync(targetDirPath); } const b = browserify(); b.add(fPath) .bundle() .pipe(fs.createWriteStream(`${targetDirPath}/${p}.js`)); const page = fs.createWriteStream(`${targetDirPath}/index.html`); const pageMeta = JSON.parse(fs.readFileSync(`${fPath}/meta.json`, "utf-8")); page.write(getPageHtml(p, pageMeta)); } // If pages have been deleted in source, remove them in output directory too. for (const dir of fs.readdirSync(`${curDir}/public`).filter(f => { if (build_conf.protected_dirs.includes(f)) return false; const stats = fs.statSync(`${curDir}/public/${f}`); return stats.isDirectory(); })) { if (!pages.includes(dir)) { const dPath = `${curDir}/public/${dir}`; try { const nestedFiles = fs.readdirSync(dPath); for (const nf of nestedFiles) { fs.unlinkSync(`${dPath}/${nf}`); } fs.rmdirSync(dPath); } catch (error) { console.error(error); } } }