const fs = require("fs").promises; const fss = require("fs"); const glob = require("glob"); const zlib = require('zlib'); const { getLanguages } = require("./lib/language.js"); const { filter } = require("./lib/dependencies.js"); const config = require("./build_config.js"); const { install, installCleanCSS, mkdir } = require("./lib/makestuff.js"); const log = (...args) => console.log(...args); const { buildCore } = require("./build_browser.js"); const { buildPackageJSON, writePackageJSON } = require("./build_node.js"); const path = require("path"); const bundling = require('./lib/bundling.js'); async function installPackageJSON(options) { const json = buildPackageJSON(options); json.name = "@highlightjs/cdn-assets"; json.description = json.description.concat(" (pre-compiled CDN assets)"); // this is not a replacement for `highlightjs` package // CDN assets do not need an export map, they are just a bunch of files. // The NPM package mostly only exists to populate CDNs and provide raw files. delete json.exports; delete json.type; delete json.main; delete json.types; await writePackageJSON(json); } let shas = {}; async function buildCDN(options) { install("./LICENSE", "LICENSE"); install("./README.CDN.md", "README.md"); await installPackageJSON(options); installStyles(); // all the languages are built for the CDN and placed into `/languages` const languages = await getLanguages(); let esmCoreSize = {}; let esmCommonSize = {}; await installLanguages(languages, options); // filter languages for inclusion in the highlight.js bundle let embedLanguages = filter(languages, options.languages); // it really makes no sense to embed ALL languages with the CDN build, it's // more likely we want to embed NONE and have completely separate run-time // loading of some sort if (embedLanguages.length === languages.length) { embedLanguages = []; } const size = await buildCore("highlight", embedLanguages, { minify: options.minify, format: "cjs" }); if (options.esm) { mkdir("es"); await fs.writeFile(`${process.env.BUILD_DIR}/es/package.json`, `{ "type": "module" }`); esmCoreSize = await buildCore("core", [], { minify: options.minify, format: "es" }); esmCommonSize = await buildCore("highlight", embedLanguages, { minify: options.minify, format: "es" }); } shas = { ...size.shas, ...esmCommonSize.shas, ...esmCoreSize.shas, ...shas }; await buildSRIDigests(shas); log("-----"); log("Embedded Lang :", embedLanguages.map((el) => el.minified.length).reduce((acc, curr) => acc + curr, 0), "bytes"); log("All Lang :", languages.map((el) => el.minified.length).reduce((acc, curr) => acc + curr, 0), "bytes"); log("highlight.js :", size.fullSize, "bytes"); if (options.minify) { log("highlight.min.js :", size.minified, "bytes"); log("highlight.min.js.gz :", zlib.gzipSync(size.minifiedSrc).length, "bytes"); } else { log("highlight.js.gz :", zlib.gzipSync(size.fullSrc).length, "bytes"); } if (options.esm) { log("es/core.js :", esmCoreSize.fullSize, "bytes"); log("es/highlight.js :", esmCommonSize.fullSize, "bytes"); if (options.minify) { log("es/core.min.js :", esmCoreSize.minified, "bytes"); log("es/core.min.js.gz :", zlib.gzipSync(esmCoreSize.minifiedSrc).length, "bytes"); log("es/highlight.min.js :", esmCommonSize.minified, "bytes"); log("es/highlight.min.js.gz :", zlib.gzipSync(esmCommonSize.minifiedSrc).length, "bytes"); } else { log("es/highlight.js.gz :", zlib.gzipSync(esmCommonSize.fullSrc).length, "bytes"); } } log("-----"); } async function buildSRIDigests(shas) { const temp = await fs.readFile("./tools/templates/DIGESTS.md"); const DIGEST_MD = temp.toString(); const version = require("../package.json").version; const digestList = Object.entries(shas).map(([k, v]) => `${v} ${k}`).join("\n"); const out = DIGEST_MD .replace("", digestList) .replace("", shas["highlight.min.js"]) .replace("", shas["languages/go.min.js"]) .replace(//g, version); fs.writeFile(`${process.env.BUILD_DIR}/DIGESTS.md`, out); } async function installLanguages(languages, options) { log("Building language files."); mkdir("languages"); if (options.esm) mkdir("es/languages"); await Promise.all( languages.map(async(language) => { await buildCDNLanguage(language, options); process.stdout.write("."); }) ); log(""); await Promise.all( languages.filter((l) => l.third_party) .map(async(lang) => await buildDistributable(lang, options)) ); log(""); } function installStyles() { log("Writing style files."); mkdir("styles/base16"); glob.sync("**", { cwd: "./src/styles" }).forEach((file) => { const stat = fss.statSync(`./src/styles/${file}`); if (stat.isDirectory()) return; if (file.endsWith(".css")) { installCleanCSS(`./src/styles/${file}`, `styles/${file}`, { minify: false }); installCleanCSS(`./src/styles/${file}`, `styles/${file.replace(".css", ".min.css")}`, { minify: true }); } else { // images, backgrounds, etc install(`./src/styles/${file}`, `styles/${file}`); } }); } async function buildDistributable(language, options) { const filename = `${language.name}.min.js`; const distDir = path.join(language.moduleDir, "dist"); log(`Building ${distDir}/${filename}.`); await fs.mkdir(distDir, { recursive: true }); await fs.writeFile(path.join(language.moduleDir, "dist", filename), language.minified); if (options.esm) { await fs.writeFile(path.join(language.moduleDir, "dist", filename.replace(".min.js", ".es.min.js")), language.minifiedESM); } } async function buildCDNLanguage(language, options) { const name = `languages/${language.name}${options.minify ? '.min' : ''}.js`; await language.compile({ terser: config.terser }); const source = options.minify ? language.minified : language.module; shas[name] = bundling.sha384(source); await fs.writeFile(`${process.env.BUILD_DIR}/${name}`, source); if (options.esm) { const sourceESM = options.minify ? language.minifiedESM : language.esm; shas[`es/${name}`] = bundling.sha384(sourceESM); await fs.writeFile(`${process.env.BUILD_DIR}/es/${name}`, sourceESM); } } module.exports.build = buildCDN;