Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions bin/advanced.txt
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,20 @@ Advanced Options:
Consider files with specified EXTENSION as modules, this option can used
multiple times.

--no-typescript

Turn off compiling .ts and .tsx files with the typescript compiler, and
stop considering those extensions during module lookup.

--typescript=TSCONFIG, --typescript [ COMPILER OPTIONS ]

Compile .ts and .tsx files using TSCONFIG, or using the given compiler
options, as they are named in tsconfig.json. For example:

browserify --typescript [ --target es2017 ] main.ts

Use --typescript [ --no-tsconfig ] to ignore tsconfig.json files.

--global-transform=MODULE, -g MODULE

Use a transform module on all files after any ordinary transforms have run.
Expand Down
18 changes: 17 additions & 1 deletion bin/args.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ module.exports = function (args, opts) {
insertGlobalVars: insertGlobalVars,
ignoreMissing: argv['ignore-missing'] || argv.im,
debug: argv['debug'] || argv.d,
standalone: argv['standalone'] || argv.s
standalone: argv['standalone'] || argv.s,
typescript: typescriptOptions(argv.typescript)
}, opts));
function error (msg) {
var e = new Error(msg);
Expand Down Expand Up @@ -246,6 +247,21 @@ module.exports = function (args, opts) {
return b;
};

function typescriptOptions (arg) {
if (arg === undefined || arg === true) return undefined;
if (arg === false) return false;
if (typeof arg === 'string') return { tsconfig: arg };
if (typeof arg !== 'object') return undefined;

var compilerOptions = xtend(arg);
delete compilerOptions._;
delete compilerOptions.tsconfig;
delete compilerOptions.project;
var tsconfig = arg.tsconfig !== undefined ? arg.tsconfig : arg.project;
if (tsconfig === 'false') tsconfig = false;
return { tsconfig: tsconfig, compilerOptions: compilerOptions };
}

function splitOnColon (f) {
var pos = f.lastIndexOf(':');
if (pos == -1) {
Expand Down
32 changes: 30 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var insertGlobals = require('insert-module-globals');
var syntaxError = require('syntax-error');

var builtins = require('./lib/builtins.js');
var typescript = require('./lib/typescript.js');

var splicer = require('labeled-stream-splicer');
var through = require('through2');
Expand Down Expand Up @@ -469,9 +470,24 @@ Browserify.prototype._createDeps = function (opts) {
// anyway.
mopts.expose = this._expose;
mopts.extensions = [ '.js', '.json' ].concat(mopts.extensions || []);

var tsopts = opts.typescript;
self._typescript = tsopts !== false;
var tsTransform;
if (self._typescript) {
typescript.extensions.forEach(function (ex) {
if (mopts.extensions.indexOf(ex) < 0) mopts.extensions.push(ex);
});
tsTransform = typescript.createTransform(xtend(
typeof tsopts === 'object' && tsopts !== null ? tsopts : {},
{ basedir: basedir, debug: opts.debug }
));
}
self._extensions = mopts.extensions;

mopts.transform = [];
// typescript compiles ahead of every other transform so that the rest of
// the pipeline, user transforms included, only ever sees javascript
mopts.transform = tsTransform ? [ tsTransform ] : [];
mopts.transformKey = defined(opts.transformKey, [ 'browserify', 'transform' ]);
mopts.postFilter = function (id, file, pkg) {
if (opts.postFilter && !opts.postFilter(id, file, pkg)) return false;
Expand Down Expand Up @@ -557,7 +573,9 @@ Browserify.prototype._createDeps = function (opts) {
if (!hasOwn(mopts.modules, key)) self._exclude.push(key);
});

mopts.globalTransform = [];
// mdeps only applies mopts.transform to top-level files, so .ts files
// inside node_modules need the global list to get compiled too
mopts.globalTransform = tsTransform ? [ nodeModulesOnly(tsTransform) ] : [];
if (!this._bundled) {
this.once('bundle', function () {
self.pipeline.write({
Expand Down Expand Up @@ -857,6 +875,16 @@ Browserify.prototype.bundle = function (cb) {
return output;
};

function nodeModulesOnly (tr) {
return function (file, opts) {
if (typeof file === 'string'
&& file.replace(/\\/g, '/').indexOf('/node_modules/') >= 0) {
return tr(file, opts);
}
return through();
};
}

function isStream (s) { return s && typeof s.pipe === 'function' }
function isAbsolutePath (file) {
var regexp = process.platform === 'win32' ?
Expand Down
226 changes: 226 additions & 0 deletions lib/typescript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
var path = require('path');
var through = require('through2');
var resolve = require('resolve');
var xtend = require('xtend');
var hasOwn = require('hasown');

var extensions = [ '.ts', '.tsx' ];

var compilerCache = {};
var configCache = {};

exports.extensions = extensions;
exports.isTypeScript = isTypeScript;
exports.isDeclaration = isDeclaration;
exports.createTransform = createTransform;

function isTypeScript (file) {
if (typeof file !== 'string') return false;
for (var i = 0; i < extensions.length; i++) {
var ex = extensions[i];
if (file.length > ex.length && file.slice(-ex.length) === ex) {
return true;
}
}
return false;
}

function isDeclaration (file) {
return typeof file === 'string' && /\.d\.ts$/.test(file);
}

// Returns a browserify transform that compiles .ts and .tsx files with the
// typescript compiler. Files with any other extension pass through untouched.
//
// opts:
// basedir - where to look for the typescript module and tsconfig.json
// debug - emit inline source maps
// tsconfig - path to a tsconfig.json, or false to not look for one
// compilerOptions - compiler options, as they appear in tsconfig.json
// compiler - a typescript module to use instead of requiring one
function createTransform (opts) {
if (!opts) opts = {};

return function (file) {
if (!isTypeScript(file)) return through();

var chunks = [];
return through(function (chunk, enc, next) {
chunks.push(chunk);
next();
}, function (next) {
var source = Buffer.concat(chunks).toString('utf8');
var output;
try {
output = compile(source, file, opts);
}
catch (err) {
return next(err);
}
this.push(output);
next();
});
};
}

function compile (source, file, opts) {
// declaration files carry no code, only types
if (isDeclaration(file)) return '';

var ts = loadCompiler(opts);
var res = ts.transpileModule(source, {
fileName: file,
compilerOptions: compilerOptions(ts, file, opts),
reportDiagnostics: true
});

var diagnostics = (res.diagnostics || []).filter(function (d) {
return d.category === ts.DiagnosticCategory.Error;
});
if (diagnostics.length) throw diagnosticError(ts, diagnostics[0], file);

return res.outputText;
}

function loadCompiler (opts) {
if (opts.compiler) return opts.compiler;

var basedir = opts.basedir || process.cwd();
if (hasOwn(compilerCache, basedir)) return compilerCache[basedir];

var file;
try {
file = resolve.sync('typescript', { basedir: basedir });
}
catch (err) {
try {
file = require.resolve('typescript');
}
catch (err_) {
throw new Error(
'Cannot find module \'typescript\' from ' + basedir + '\n'
+ 'browserify compiles .ts and .tsx files with the typescript '
+ 'compiler, which is not installed as a dependency because '
+ 'most bundles do not need it. Install it with:\n\n'
+ ' npm install typescript\n\n'
+ 'or pass --no-typescript to turn off TypeScript support.'
);
}
}
try {
compilerCache[basedir] = require(file);
}
catch (err) {
throw new Error(
'Cannot load the typescript compiler at ' + file + ': '
+ err.message
);
}
return compilerCache[basedir];
}

function compilerOptions (ts, file, opts) {
var options = xtend(
readConfig(ts, file, opts),
convert(ts, opts.compilerOptions, opts.basedir || process.cwd(), null,
'the typescript compilerOptions')
);

// browserify walks require() calls, so the emitted modules have to be
// commonjs no matter what the surrounding project compiles to
options.module = ts.ModuleKind.CommonJS;
if (options.target === undefined) options.target = ts.ScriptTarget.ES5;
if (options.esModuleInterop === undefined) options.esModuleInterop = true;
if (options.jsx === undefined && /\.tsx$/.test(file)) {
options.jsx = ts.JsxEmit.React;
}

// one file in, one file out: everything about writing files to disk or
// type checking across files is the bundler's business, not tsc's
options.inlineSourceMap = Boolean(opts.debug);
options.inlineSources = Boolean(opts.debug);
options.sourceMap = false;
options.declaration = false;
options.declarationMap = false;
options.composite = false;
options.noEmit = false;
// these describe how a project resolves and emits whole programs, which
// says nothing about how to transpile a single file
delete options.moduleResolution;
delete options.importsNotUsedAsValues;
options.verbatimModuleSyntax = false;
options.emitDeclarationOnly = false;
delete options.out;
delete options.outFile;
delete options.outDir;

return options;
}

function readConfig (ts, file, opts) {
if (opts.tsconfig === false) return {};
if (!ts.sys || !ts.readConfigFile) return {};

var basedir = opts.basedir || process.cwd();
var configFile;
if (typeof opts.tsconfig === 'string') {
configFile = path.resolve(basedir, opts.tsconfig);
}
else {
var dir = path.dirname(file);
if (hasOwn(configCache, dir)) return configCache[dir];
configFile = ts.findConfigFile(dir, ts.sys.fileExists, 'tsconfig.json');
if (!configFile) {
configCache[dir] = {};
return configCache[dir];
}
configCache[dir] = parseConfig(ts, configFile);
return configCache[dir];
}

if (hasOwn(configCache, configFile)) return configCache[configFile];
configCache[configFile] = parseConfig(ts, configFile);
return configCache[configFile];
}

function parseConfig (ts, configFile) {
var res = ts.readConfigFile(configFile, ts.sys.readFile);
if (res.error) throw diagnosticError(ts, res.error, configFile);
var config = res.config || {};
return convert(ts, config.compilerOptions, path.dirname(configFile), configFile);
}

function convert (ts, options, basedir, configFile, name) {
if (!options) return {};
var res = ts.convertCompilerOptionsFromJson(options, basedir, configFile);
if (res.errors && res.errors.length) {
throw diagnosticError(ts, res.errors[0], name || configFile || basedir);
}
return res.options || {};
}

function diagnosticError (ts, diagnostic, file) {
var message = ts.flattenDiagnosticMessageText
? ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')
: String(diagnostic.messageText)
;
var name = (diagnostic.file && diagnostic.file.fileName) || file;
var line, column;
if (diagnostic.file && typeof diagnostic.start === 'number') {
var pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
line = pos.line + 1;
column = pos.character + 1;
}

var err = new Error(
message
+ ' (TS' + diagnostic.code + ') while compiling '
+ name + (line === undefined ? '' : ':' + line + ':' + column)
);
err.filename = name;
if (line !== undefined) {
err.line = line;
err.column = column;
}
return err;
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@
"seq": "0.3.5",
"tap": "^10.7.2",
"temp": "=0.8.3",
"through": "^2.3.4"
"through": "^2.3.4",
"typescript": "^5.0.0"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why v5? v5 still requires node 12, so it's already violating engines.node - why not use v6 or v7?

},
"author": {
"name": "James Halliday",
Expand Down
Loading