dts-bundle
Version:
Export TypeScript .d.ts files as an external module definition
660 lines (659 loc) • 26 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
var os = require("os");
var fs = require("fs");
var path = require("path");
var util = require("util");
var assert = require("assert");
var glob = require("glob");
var mkdirp = require("mkdirp");
var detectIndent = require("detect-indent");
var pkg = require('../package');
var dtsExp = /\.d\.ts$/;
var bomOptExp = /^\uFEFF?/;
var externalExp = /^([ \t]*declare module )(['"])(.+?)(\2[ \t]*{?.*)$/;
var importExp = /^([ \t]*(?:export )?(?:import .+? )= require\()(['"])(.+?)(\2\);.*)$/;
var importEs6Exp = /^([ \t]*(?:export|import) ?(?:(?:\* (?:as [^ ,]+)?)|.*)?,? ?(?:[^ ,]+ ?,?)(?:\{(?:[^ ,]+ ?,?)*\})? ?from )(['"])([^ ,]+)(\2;.*)$/;
var referenceTagExp = /^[ \t]*\/\/\/[ \t]*<reference[ \t]+path=(["'])(.*?)\1?[ \t]*\/>.*$/;
var identifierExp = /^\w+(?:[\.-]\w+)*$/;
var fileExp = /^([\./].*|.:.*)$/;
var privateExp = /^[ \t]*(?:static )?private (?:static )?/;
var publicExp = /^([ \t]*)(static |)(public |)(static |)(.*)/;
function bundle(options) {
assert(typeof options === 'object' && options, 'options must be an object');
var allFiles = stringEndsWith(options.main, "**/*.d.ts");
var main = allFiles ? "*.d.ts" : options.main;
var exportName = options.name;
var _baseDir = (function () {
var baseDir = optValue(options.baseDir, path.dirname(options.main));
if (allFiles) {
baseDir = baseDir.substr(0, baseDir.length - 2);
}
return baseDir;
})();
var out = optValue(options.out, exportName + '.d.ts').replace(/\//g, path.sep);
var newline = optValue(options.newline, os.EOL);
var indent = optValue(options.indent, ' ');
var outputAsModuleFolder = optValue(options.outputAsModuleFolder, false);
var prefix = optValue(options.prefix, '');
var separator = optValue(options.separator, '/');
var externals = optValue(options.externals, false);
var exclude = optValue(options.exclude, null);
var removeSource = optValue(options.removeSource, false);
var referenceExternals = optValue(options.referenceExternals, false);
var emitOnIncludedFileNotFound = optValue(options.emitOnIncludedFileNotFound, false);
var emitOnNoIncludedFileNotFound = optValue(options.emitOnNoIncludedFileNotFound, false);
var _headerPath = optValue(options.headerPath, null);
var headerText = optValue(options.headerText, '');
var comments = false;
var verbose = optValue(options.verbose, false);
assert.ok(main, 'option "main" must be defined');
assert.ok(exportName, 'option "name" must be defined');
assert(typeof newline === 'string', 'option "newline" must be a string');
assert(typeof indent === 'string', 'option "indent" must be a string');
assert(typeof prefix === 'string', 'option "prefix" must be a string');
assert(separator.length > 0, 'option "separator" must have non-zero length');
var baseDir = path.resolve(_baseDir);
var mainFile = allFiles ? path.resolve(baseDir, "**/*.d.ts") : path.resolve(main.replace(/\//g, path.sep));
var outFile = calcOutFilePath(out, baseDir);
var headerData = '// Generated by dts-bundle v' + pkg.version + newline;
var headerPath = _headerPath && _headerPath !== "none" ? path.resolve(_headerPath.replace(/\//g, path.sep)) : _headerPath;
trace('### settings object passed ###');
traceObject(options);
trace('### settings ###');
trace('main: %s', main);
trace('name: %s', exportName);
trace('out: %s', out);
trace('baseDir: %s', baseDir);
trace('mainFile: %s', mainFile);
trace('outFile: %s', outFile);
trace('externals: %s', externals ? 'yes' : 'no');
trace('exclude: %s', exclude);
trace('removeSource: %s', removeSource ? 'yes' : 'no');
trace('comments: %s', comments ? 'yes' : 'no');
trace('emitOnIncludedFileNotFound: %s', emitOnIncludedFileNotFound ? "yes" : "no");
trace('emitOnNoIncludedFileNotFound: %s', emitOnNoIncludedFileNotFound ? "yes" : "no");
trace("headerPath %s", headerPath);
trace("headerText %s", headerText);
if (!allFiles) {
assert(fs.existsSync(mainFile), 'main does not exist: ' + mainFile);
}
if (headerPath) {
if (headerPath === "none") {
headerData = "";
}
else {
assert(fs.existsSync(headerPath), 'header does not exist: ' + headerPath);
headerData = fs.readFileSync(headerPath, 'utf8') + headerData;
}
}
else if (headerText) {
headerData = '/*' + headerText + '*/\n';
}
var isExclude;
if (typeof exclude === 'function') {
isExclude = exclude;
}
else if (exclude instanceof RegExp) {
isExclude = function (file) { return exclude.test(file); };
}
else {
isExclude = function () { return false; };
}
var sourceTypings = glob.sync('**/*.d.ts', { cwd: baseDir }).map(function (file) { return path.resolve(baseDir, file); });
if (allFiles) {
var mainFileContent_1 = "";
trace("## temporally main file ##");
sourceTypings.forEach(function (file) {
var generatedLine = "export * from './" + path.relative(baseDir, file.substr(0, file.length - 5)).replace(path.sep, "/") + "';";
trace(generatedLine);
mainFileContent_1 += generatedLine + "\n";
});
mainFile = path.resolve(baseDir, "dts-bundle.tmp." + exportName + ".d.ts");
fs.writeFileSync(mainFile, mainFileContent_1, 'utf8');
}
trace('\n### find typings ###');
var inSourceTypings = function (file) {
return sourceTypings.indexOf(file) !== -1 || sourceTypings.indexOf(path.join(file, 'index.d.ts')) !== -1;
};
trace('source typings (will be included in output if actually used)');
sourceTypings.forEach(function (file) { return trace(' - %s ', file); });
trace('excluded typings (will always be excluded from output)');
var fileMap = Object.create(null);
var globalExternalImports = [];
var mainParse;
var externalTypings = [];
var inExternalTypings = function (file) { return externalTypings.indexOf(file) !== -1; };
{
trace('\n### parse files ###');
var queue = [mainFile];
var queueSeen = Object.create(null);
while (queue.length > 0) {
var target = queue.shift();
if (queueSeen[target]) {
continue;
}
queueSeen[target] = true;
var parse = parseFile(target);
if (!mainParse) {
mainParse = parse;
}
fileMap[parse.file] = parse;
pushUniqueArr(queue, parse.refs, parse.relativeImports);
}
}
trace('\n### map exports ###');
var exportMap = Object.create(null);
Object.keys(fileMap).forEach(function (file) {
var parse = fileMap[file];
parse.exports.forEach(function (name) {
assert(!(name in exportMap), 'already got export for: ' + name);
exportMap[name] = parse;
trace('- %s -> %s', name, parse.file);
});
});
trace('\n### determine typings to include ###');
var excludedTypings = [];
var usedTypings = [];
var externalDependencies = [];
{
var queue_1 = [mainParse];
var queueSeen = Object.create(null);
trace('queue');
trace(queue_1);
while (queue_1.length > 0) {
var parse = queue_1.shift();
if (queueSeen[parse.file]) {
continue;
}
queueSeen[parse.file] = true;
trace('%s (%s)', parse.name, parse.file);
usedTypings.push(parse);
parse.externalImports.forEach(function (name) {
var p = exportMap[name];
if (!externals) {
trace(' - exclude external %s', name);
pushUnique(externalDependencies, !p ? name : p.file);
return;
}
if (isExclude(path.relative(baseDir, p.file), true)) {
trace(' - exclude external filter %s', name);
pushUnique(excludedTypings, p.file);
return;
}
trace(' - include external %s', name);
assert(p, name);
queue_1.push(p);
});
parse.relativeImports.forEach(function (file) {
var p = fileMap[file];
if (isExclude(path.relative(baseDir, p.file), false)) {
trace(' - exclude internal filter %s', file);
pushUnique(excludedTypings, p.file);
return;
}
trace(' - import relative %s', file);
assert(p, file);
queue_1.push(p);
});
}
}
trace('\n### rewrite global external modules ###');
usedTypings.forEach(function (parse) {
trace(parse.name);
parse.relativeRef.forEach(function (line, i) {
line.modified = replaceExternal(line.original, getLibName);
trace(' - %s ==> %s', line.original, line.modified);
});
parse.importLineRef.forEach(function (line, i) {
if (outputAsModuleFolder) {
trace(' - %s was skipped.', line.original);
line.skip = true;
return;
}
if (importExp.test(line.original)) {
line.modified = replaceImportExport(line.original, getLibName);
}
else {
line.modified = replaceImportExportEs6(line.original, getLibName);
}
trace(' - %s ==> %s', line.original, line.modified);
});
});
trace('\n### build output ###');
var content = headerData;
if (externalDependencies.length > 0) {
content += '// Dependencies for this module:' + newline;
externalDependencies.forEach(function (file) {
if (referenceExternals) {
content += formatReference(path.relative(baseDir, file).replace(/\\/g, '/')) + newline;
}
else {
content += '// ' + path.relative(baseDir, file).replace(/\\/g, '/') + newline;
}
});
}
if (globalExternalImports.length > 0) {
content += newline;
content += globalExternalImports.join(newline) + newline;
}
content += newline;
content += usedTypings.filter(function (parse) {
parse.lines = parse.lines.filter(function (line) {
return (true !== line.skip);
});
return (parse.lines.length > 0);
}).map(function (parse) {
if (inSourceTypings(parse.file)) {
return formatModule(parse.file, parse.lines.map(function (line) {
return getIndenter(parse.indent, indent)(line);
}));
}
else {
return parse.lines.map(function (line) {
return getIndenter(parse.indent, indent)(line);
}).join(newline) + newline;
}
}).join(newline) + newline;
if (removeSource) {
trace('\n### remove source typings ###');
sourceTypings.forEach(function (p) {
if (p !== outFile && dtsExp.test(p) && fs.statSync(p).isFile()) {
trace(' - %s', p);
fs.unlinkSync(p);
}
});
}
var inUsed = function (file) {
return usedTypings.filter(function (parse) { return parse.file === file; }).length !== 0;
};
var bundleResult = {
fileMap: fileMap,
includeFilesNotFound: [],
noIncludeFilesNotFound: [],
options: options
};
trace('## files not found ##');
for (var p in fileMap) {
var parse = fileMap[p];
if (!parse.fileExists) {
if (inUsed(parse.file)) {
bundleResult.includeFilesNotFound.push(parse.file);
warning(' X Included file NOT FOUND %s ', parse.file);
}
else {
bundleResult.noIncludeFilesNotFound.push(parse.file);
trace(' X Not used file not found %s', parse.file);
}
}
}
trace('\n### write output ###');
if ((bundleResult.includeFilesNotFound.length == 0
|| (bundleResult.includeFilesNotFound.length > 0 && emitOnIncludedFileNotFound))
&& (bundleResult.noIncludeFilesNotFound.length == 0
|| (bundleResult.noIncludeFilesNotFound.length > 0 && emitOnNoIncludedFileNotFound))) {
trace(outFile);
{
var outDir = path.dirname(outFile);
if (!fs.existsSync(outDir)) {
mkdirp.sync(outDir);
}
}
fs.writeFileSync(outFile, content, 'utf8');
bundleResult.emitted = true;
}
else {
warning(" XXX Not emit due to exist files not found.");
trace("See documentation for emitOnIncludedFileNotFound and emitOnNoIncludedFileNotFound options.");
bundleResult.emitted = false;
}
if (verbose) {
trace('\n### statistics ###');
trace('used sourceTypings');
sourceTypings.forEach(function (p) {
if (inUsed(p)) {
trace(' - %s', p);
}
});
trace('unused sourceTypings');
sourceTypings.forEach(function (p) {
if (!inUsed(p)) {
trace(' - %s', p);
}
});
trace('excludedTypings');
excludedTypings.forEach(function (p) {
trace(' - %s', p);
});
trace('used external typings');
externalTypings.forEach(function (p) {
if (inUsed(p)) {
trace(' - %s', p);
}
});
trace('unused external typings');
externalTypings.forEach(function (p) {
if (!inUsed(p)) {
trace(' - %s', p);
}
});
trace('external dependencies');
externalDependencies.forEach(function (p) {
trace(' - %s', p);
});
}
trace('\n### done ###\n');
if (allFiles) {
fs.unlinkSync(mainFile);
}
return bundleResult;
function stringEndsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
function stringStartsWith(str, prefix) {
return str.slice(0, prefix.length) == prefix;
}
function calcOutFilePath(out, baseDir) {
var result = path.resolve(baseDir, out);
if (stringStartsWith(out, "~" + path.sep)) {
result = path.resolve(".", out.substr(2));
}
return result;
}
function traceObject(obj) {
if (verbose) {
console.log(obj);
}
}
function trace() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (verbose) {
console.log(util.format.apply(null, args));
}
}
function warning() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
console.log(util.format.apply(null, args));
}
function getModName(file) {
return path.relative(baseDir, path.dirname(file) + path.sep + path.basename(file).replace(/\.d\.ts$/, ''));
}
function getExpName(file) {
if (file === mainFile) {
return exportName;
}
return getExpNameRaw(file);
}
function getExpNameRaw(file) {
return prefix + exportName + separator + cleanupName(getModName(file));
}
function getLibName(ref) {
return getExpNameRaw(mainFile) + separator + prefix + separator + ref;
}
function cleanupName(name) {
return name.replace(/\.\./g, '--').replace(/[\\\/]/g, separator);
}
function mergeModulesLines(lines) {
var i = (outputAsModuleFolder ? '' : indent);
return (lines.length === 0 ? '' : i + lines.join(newline + i)) + newline;
}
function formatModule(file, lines) {
var out = '';
if (outputAsModuleFolder) {
return mergeModulesLines(lines);
}
out += 'declare module \'' + getExpName(file) + '\' {' + newline;
out += mergeModulesLines(lines);
out += '}' + newline;
return out;
}
function parseFile(file) {
var name = getModName(file);
trace('%s (%s)', name, file);
var res = {
file: file,
name: name,
indent: indent,
exp: getExpName(file),
refs: [],
externalImports: [],
relativeImports: [],
exports: [],
lines: [],
fileExists: true,
importLineRef: [],
relativeRef: []
};
if (!fs.existsSync(file)) {
trace(' X - File not found: %s', file);
res.fileExists = false;
return res;
}
if (fs.lstatSync(file).isDirectory()) {
file = path.join(file, 'index.d.ts');
}
var code = fs.readFileSync(file, 'utf8').replace(bomOptExp, '').replace(/\s*$/, '');
res.indent = detectIndent(code) || indent;
var multiComment = [];
var queuedJSDoc;
var inBlockComment = false;
var popBlock = function () {
if (multiComment.length > 0) {
if (/^[ \t]*\/\*\*/.test(multiComment[0])) {
queuedJSDoc = multiComment;
}
else if (comments) {
multiComment.forEach(function (line) { return res.lines.push({ original: line }); });
}
multiComment = [];
}
inBlockComment = false;
};
var popJSDoc = function () {
if (queuedJSDoc) {
queuedJSDoc.forEach(function (line) {
var match = line.match(/^([ \t]*)(\*.*)/);
if (match) {
res.lines.push({ original: match[1] + ' ' + match[2] });
}
else {
res.lines.push({ original: line });
}
});
queuedJSDoc = null;
}
};
code.split(/\r?\n/g).forEach(function (line) {
var match;
if (/^[((=====)(=*)) \t]*\*+\//.test(line)) {
multiComment.push(line);
popBlock();
return;
}
if (/^[ \t]*\/\*/.test(line)) {
multiComment.push(line);
inBlockComment = true;
if (/\*+\/[ \t]*$/.test(line)) {
popBlock();
}
return;
}
if (inBlockComment) {
multiComment.push(line);
return;
}
if (/^\s*$/.test(line)) {
res.lines.push({ original: '' });
return;
}
if (/^\/\/\//.test(line)) {
var ref = extractReference(line);
if (ref) {
var refPath = path.resolve(path.dirname(file), ref);
if (inSourceTypings(refPath)) {
trace(' - reference source typing %s (%s)', ref, refPath);
}
else {
var relPath = path.relative(baseDir, refPath).replace(/\\/g, '/');
trace(' - reference external typing %s (%s) (relative: %s)', ref, refPath, relPath);
if (!inExternalTypings(refPath)) {
externalTypings.push(refPath);
}
}
pushUnique(res.refs, refPath);
return;
}
}
if (/^\/\//.test(line)) {
if (comments) {
res.lines.push({ original: line });
}
return;
}
if (privateExp.test(line)) {
queuedJSDoc = null;
return;
}
popJSDoc();
if ((line.indexOf("from") >= 0 && (match = line.match(importEs6Exp))) ||
(line.indexOf("require") >= 0 && (match = line.match(importExp)))) {
var _ = match[0], lead = match[1], quote = match[2], moduleName = match[3], trail = match[4];
assert(moduleName);
var impPath = path.resolve(path.dirname(file), moduleName);
if (fileExp.test(moduleName)) {
var modLine = {
original: lead + quote + getExpName(impPath) + trail
};
res.lines.push(modLine);
var full = path.resolve(path.dirname(file), impPath);
if (!fs.existsSync(full) || fs.existsSync(full + '.d.ts')) {
full += '.d.ts';
}
trace(' - import relative %s (%s)', moduleName, full);
pushUnique(res.relativeImports, full);
res.importLineRef.push(modLine);
}
else {
var modLine = {
original: line
};
trace(' - import external %s', moduleName);
pushUnique(res.externalImports, moduleName);
if (externals) {
res.importLineRef.push(modLine);
}
if (!outputAsModuleFolder) {
res.lines.push(modLine);
}
else {
pushUnique(globalExternalImports, line);
}
}
}
else if ((match = line.match(externalExp))) {
var _ = match[0], declareModule = match[1], lead = match[2], moduleName = match[3], trail = match[4];
assert(moduleName);
trace(' - declare %s', moduleName);
pushUnique(res.exports, moduleName);
var modLine = {
original: line
};
res.relativeRef.push(modLine);
res.lines.push(modLine);
}
else {
if ((match = line.match(publicExp))) {
var _ = match[0], sp = match[1], static1 = match[2], pub = match[3], static2 = match[4], ident = match[5];
line = sp + static1 + static2 + ident;
}
if (inSourceTypings(file)) {
res.lines.push({ original: line.replace(/^(export )?declare /g, '$1') });
}
else {
res.lines.push({ original: line });
}
}
});
return res;
}
}
exports.bundle = bundle;
function pushUnique(arr, value) {
if (arr.indexOf(value) < 0) {
arr.push(value);
}
return arr;
}
function pushUniqueArr(arr) {
var values = [];
for (var _i = 1; _i < arguments.length; _i++) {
values[_i - 1] = arguments[_i];
}
values.forEach(function (vs) { return vs.forEach(function (v) { return pushUnique(arr, v); }); });
return arr;
}
function formatReference(file) {
return '/// <reference path="' + file.replace(/\\/g, '/') + '" />';
}
function extractReference(tag) {
var match = tag.match(referenceTagExp);
if (match) {
return match[2];
}
return null;
}
function replaceImportExport(line, replacer) {
var match = line.match(importExp);
if (match) {
assert(match[4]);
if (identifierExp.test(match[3])) {
return match[1] + match[2] + replacer(match[3]) + match[4];
}
}
return line;
}
function replaceImportExportEs6(line, replacer) {
if (line.indexOf("from") < 0) {
return line;
}
var match = line.match(importEs6Exp);
if (match) {
assert(match[4]);
if (identifierExp.test(match[3])) {
return match[1] + match[2] + replacer(match[3]) + match[4];
}
}
return line;
}
function replaceExternal(line, replacer) {
var match = line.match(externalExp);
if (match) {
var _ = match[0], declareModule = match[1], beforeIndent = match[2], moduleName = match[3], afterIdent = match[4];
assert(afterIdent);
if (identifierExp.test(moduleName)) {
return declareModule + beforeIndent + replacer(moduleName) + afterIdent;
}
}
return line;
}
function getIndenter(actual, use) {
if (actual === use || !actual) {
return function (line) { return line.modified || line.original; };
}
return function (line) { return (line.modified || line.original).replace(new RegExp('^' + actual + '+', 'g'), function (match) { return match.split(actual).join(use); }); };
}
function optValue(passed, def) {
if (typeof passed === 'undefined') {
return def;
}
return passed;
}
function regexEscape(s) {
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}