pokemon-showdown-client/build-tools/compiler.js
Guangcong Luo 6475e7a268 Switch .gitignore back to blacklist
VS Code's search feature doesn't support .gitignore whitelisting. :(

(Also don't compile .d.ts files.)
2021-04-14 02:02:41 -07:00

226 lines
5.6 KiB
JavaScript

/**
* Tiny wrapper around babel/core to do most of the things babel-cli does,
* plus incremental compilation
*
* Adds one option in addition to babel's built-in options: `incremental`
*
* Heavily copied from `babel-cli`: https://github.com/babel/babel/tree/main/packages/babel-cli
*
* @author Guangcong Luo <guangcongluo@gmail.com>
* @license MIT
*/
const babel = require('@babel/core');
const fs = require('fs');
const path = require('path');
const sourceMap = require('source-map');
const VERBOSE = false;
function outputFileSync(filePath, res, opts) {
fs.mkdirSync(path.dirname(filePath), {recursive: true});
// we've requested explicit sourcemaps to be written to disk
if (
res.map &&
opts.sourceMaps &&
opts.sourceMaps !== "inline"
) {
const mapLoc = filePath + ".map";
res.code += "\n//# sourceMappingURL=" + path.basename(mapLoc);
res.map.file = path.basename(filePath);
fs.writeFileSync(mapLoc, JSON.stringify(res.map));
}
fs.writeFileSync(filePath, res.code);
}
function slash(path) {
const isExtendedLengthPath = /^\\\\\?\\/.test(path);
const hasNonAscii = /[^\u0000-\u0080]+/.test(path);
if (isExtendedLengthPath || hasNonAscii) {
return path;
}
return path.replace(/\\/g, '/');
}
function combineResults(fileResults, sourceMapOptions, opts) {
let map = null;
if (fileResults.some(result => result?.map)) {
map = new sourceMap.SourceMapGenerator(sourceMapOptions);
}
let code = "";
let offset = 0;
for (const result of fileResults) {
if (!result) continue;
code += result.code + "\n";
if (result.map) {
const consumer = new sourceMap.SourceMapConsumer(result.map);
const sources = new Set();
consumer.eachMapping(function (mapping) {
if (mapping.source != null) sources.add(mapping.source);
map.addMapping({
generated: {
line: mapping.generatedLine + offset,
column: mapping.generatedColumn,
},
source: mapping.source,
original:
mapping.source == null
? null
: {
line: mapping.originalLine,
column: mapping.originalColumn,
},
});
});
for (const source of sources) {
const content = consumer.sourceContentFor(source, true);
if (content !== null) {
map.setSourceContent(source, content);
}
}
offset = code.split("\n").length - 1;
}
}
if (opts.sourceMaps === "inline") {
const json = JSON.stringify(map);
const base64 = Buffer.from(json, 'utf8').toString('base64');
code += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + base64;
}
return {
map: map,
code: code,
};
}
function noRebuildNeeded(src, dest) {
try {
const srcStat = fs.statSync(src, {throwIfNoEntry: false});
if (!srcStat) return true;
const destStat = fs.statSync(dest);
if (srcStat.ctimeMs < destStat.ctimeMs) return true;
} catch (e) {}
return false;
}
function compileToDir(srcDir, destDir, opts = {}) {
const incremental = opts.incremental;
delete opts.incremental;
function handleFile(src, base) {
let relative = path.relative(base, src);
if (!relative.endsWith('.ts') && !relative.endsWith('.tsx')) {
return 0;
}
if (relative.endsWith('.d.ts')) return 0;
relative = relative.slice(0, relative.endsWith('.tsx') ? -4 : -3) + '.js';
const dest = path.join(destDir, relative);
if (incremental && noRebuildNeeded(src, dest)) return 0;
const res = babel.transformFileSync(src, {
...opts,
sourceFileName: slash(path.relative(dest + "/..", src)),
});
if (!res) return 0;
outputFileSync(dest, res, opts);
fs.chmodSync(dest, fs.statSync(src).mode);
if (VERBOSE) {
console.log(src + " -> " + dest);
}
return 1;
}
function handle(src, base) {
const stat = fs.statSync(src, {throwIfNoEntry: false});
if (!stat) return 0;
if (stat.isDirectory()) {
if (!base) base = src;
let count = 0;
const files = fs.readdirSync(src);
for (const filename of files) {
if (filename.startsWith('.')) continue;
const srcFile = path.join(src, filename);
count += handle(srcFile, base);
}
return count;
} else {
if (!base) base = path.dirname(src);
return handleFile(src, base);
}
}
let total = 0;
fs.mkdirSync(destDir, {recursive: true});
const srcDirs = typeof srcDir === 'string' ? [srcDir] : srcDir;
for (const dir of srcDirs) total += handle(dir);
if (incremental) opts.incremental = true; // incredibly dumb hack to preserve the option
return total;
}
function compileToFile(srcFile, destFile, opts) {
const incremental = opts.incremental;
delete opts.incremental;
const srcFiles = typeof srcFile === 'string' ? [srcFile] : srcFile;
if (incremental && srcFiles.every(src => noRebuildNeeded(src, destFile))) {
opts.incremental = true; // incredibly dumb hack to preserve the option
return 0;
}
const results = [];
for (const src of srcFiles) {
if (!fs.existsSync(src)) continue;
const res = babel.transformFileSync(src, opts);
if (res) results.push(res);
if (VERBOSE) console.log(src + " ->");
}
const combined = combineResults(results, {
file: path.basename(destFile),
sourceRoot: opts.sourceRoot,
}, opts);
outputFileSync(destFile, combined, opts);
if (VERBOSE) console.log("-> " + destFile);
if (incremental) opts.incremental = true; // incredibly dumb hack to preserve the option
return results.length;
}
exports.compileToDir = compileToDir;
exports.compileToFile = compileToFile;