Skip to content

Commit

Permalink
typescript+babel and coverage improvements
Browse files Browse the repository at this point in the history
See electron-userland#76

- Drop [email protected] dependency, add babel-plugin-istanbul
- Add getEnv() helper to SimpleCompilerBase
- Support an environment string in the `coverage` option for both typescript and babel compilers
- Make babel compiler use compilerContext as compile options
- Drop sorcery dependency, pass inputSourceMap to babel instead
- Always use babel+babel-plugin-istanbul to instrument code for measuring coverage
- In ts+babel, translate ts sourcemap options to babel. With a twist, for
inline sourcemaps.
  • Loading branch information
fasterthanlime committed Jun 21, 2017
1 parent 7570ddf commit a98839a
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 50 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@paulcbetts/mime-types": "^2.1.10",
"@paulcbetts/vueify": "9.4.3",
"babel-core": "^6.13.2",
"babel-plugin-istanbul": "^4.1.4",
"babel-preset-env": "^1.3.2",
"btoa": "^1.1.2",
"cheerio": "^0.20.0",
Expand All @@ -40,7 +41,6 @@
"detective-stylus": "^1.0.0",
"graphql": "^0.9.3",
"graphql-tag": "^2.0.0",
"istanbul": "^0.4.5",
"jade": "^1.11.0",
"js-string-escape": "^1.0.1",
"less": "^2.7.1",
Expand Down
7 changes: 7 additions & 0 deletions src/compiler-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,11 @@ export class SimpleCompilerBase extends CompilerBase {
determineDependentFilesSync(sourceCode, filePath, compilerContext) {
return [];
}

getEnv() {
if (!this.__env) {
this.__env = process.env.ELECTRON_COMPILE_ENV || process.env.NODE_ENV || 'development';
}
return this.__env;
}
}
26 changes: 18 additions & 8 deletions src/js/babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {SimpleCompilerBase} from '../compiler-base';

const mimeTypes = ['text/jsx', 'application/javascript'];
let babel = null;
let istanbul = null;
let babelPluginIstanbul = null;

export default class BabelCompiler extends SimpleCompilerBase {
constructor() {
Expand Down Expand Up @@ -80,10 +80,16 @@ export default class BabelCompiler extends SimpleCompilerBase {
filename: filePath,
ast: false,
babelrc: false
});
}, compilerContext);

let useCoverage = false;
if ('coverage' in opts) {
if (typeof opts.coverage === 'string') {
useCoverage = this.getEnv() === opts.coverage;
} else {
useCoverage = !!opts.coverage;
}

useCoverage = !!opts.coverage;
delete opts.coverage;
}
Expand All @@ -98,16 +104,20 @@ export default class BabelCompiler extends SimpleCompilerBase {
if (presets && presets.length === opts.presets.length) opts.presets = presets;
}

if (useCoverage) {
babelPluginIstanbul = babelPluginIstanbul || require('babel-plugin-istanbul')
const coveragePlugin = [
babelPluginIstanbul, {
inputSourceMap: compilerContext.inputSourceMap,
},
];
opts.plugins = (opts.plugins || []).concat([coveragePlugin])
}

const output = babel.transform(sourceCode, opts);
let sourceMaps = output.map ? JSON.stringify(output.map) : null;

let code = output.code;
if (useCoverage) {
istanbul = istanbul || require('istanbul');

sourceMaps = null;
code = (new istanbul.Instrumenter()).instrumentSync(output.code, filePath);
}

return { code, sourceMaps, mimeType: 'application/javascript', };
}
Expand Down
96 changes: 55 additions & 41 deletions src/js/typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ const inputMimeTypes = ['text/typescript', 'text/tsx'];
const d = require('debug')('electron-compile:typescript-compiler');

let ts = null;
let istanbul = null;
let sorcery = null;

const builtinKeys = ['hotModuleReload', 'coverage', 'babel'];

Expand Down Expand Up @@ -51,13 +49,37 @@ export default class TypeScriptCompiler extends SimpleCompilerBase {
return parsedConfig;
}

compileSync(sourceCode, filePath) {
compileSync(sourceCode, filePath, compilerContext) {
ts = ts || require('typescript');
const options = this._getParsedConfigOptions(ts);

let userBabelOpts = this.parsedConfig.builtinOpts.babel;
let useCoverage = false;
if ('coverage' in options.builtinOpts) {
const coverage = options.builtinOpts.coverage;
if (typeof coverage === 'string') {
useCoverage = this.getEnv() === coverage;
} else {
useCoverage = !!coverage;
}
}

let useBabel = !!userBabelOpts || useCoverage;

let compilerOptions = Object.assign({}, options.typescriptOpts);
if (useBabel) {
if (compilerOptions.inlineSourceMap) {
// force generating external sourceMaps, so we can feed them through babel.
// babel will then generate them inline.
delete compilerOptions.inlineSourceMap;
delete compilerOptions.inlineSources;
compilerOptions.sourceMap = true;
}
}

const isTsx = filePath.match(/\.tsx$/i);
const transpileOptions = {
compilerOptions: options.typescriptOpts,
compilerOptions,
fileName: filePath.match(/\.(ts|tsx)$/i) ? path.basename(filePath) : null
};

Expand All @@ -66,57 +88,49 @@ export default class TypeScriptCompiler extends SimpleCompilerBase {
}

let output = ts.transpileModule(sourceCode, transpileOptions);
let sourceMaps = output.sourceMapText ? output.sourceMapText : null;
if (options.builtinOpts.coverage) {
sourceMaps = null;
istanbul = istanbul || require('istanbul');

sourceMaps = null;
output.outputText = (new istanbul.Instrumenter()).instrumentSync(output.outputText, filePath);
}

d(JSON.stringify(output.diagnostics));

const babelOpts = this.parsedConfig.builtinOpts.babel;
if (babelOpts) {
// map typescript compiler output to electron-compilers expectations
let code = output.outputText;
let sourceMaps = output.sourceMapText ? output.sourceMapText : null; // prefer 'null' over 'undefined'

if (useBabel) {
if (!this.babel) {
const BabelCompiler = require("./babel").default;
const BabelCompiler = require('./babel').default;
this.babel = new BabelCompiler();

let babelOpts = Object.assign({}, userBabelOpts || {});
if (useCoverage) {
babelOpts.coverage = true;
}

// translate sourceMap options from typescript to babel
if (options.typescriptOpts.inlineSourceMap) {
babelOpts.sourceMaps = 'inline';
} else if (options.typescriptOpts.sourceMap) {
babelOpts.sourceMaps = true;
}

this.babel.compilerOptions = babelOpts;
}

sorcery = sorcery || require('sorcery');
let babelOutput = this.babel.compileSync(code, filePath, {
// babel API wants sourceMap as an object or a path. let's not touch the disk.
inputSourceMap: sourceMaps ? JSON.parse(sourceMaps) : null,
});

let tsOutputPath = filePath.replace(/.tsx?$/i, ".js");
let babelOutputPath = filePath.replace(/.tsx?$/i, ".babel.js");
// babel-transformed, potentially instrumented code
code = babelOutput.code;

output.outputText = output.outputText.replace(/\/\/# sourceMap.*/g, "");
// null if inline sourceMaps are used, which is okay.
sourceMaps = babelOutput.sourceMaps;

let babelOutput = this.babel.compileSync(output.outputText, tsOutputPath);
let chain = sorcery.loadSync(babelOutputPath, {
content: {
[filePath]: sourceCode,
[tsOutputPath]: output.outputText,
[babelOutputPath]: babelOutput.code,
},
sourcemaps: {
[tsOutputPath]: JSON.parse(sourceMaps),
[babelOutputPath]: JSON.parse(babelOutput.sourceMaps),
}
});
let finalSourceMaps = chain.apply();
let outputCode = babelOutput.code + "\n//# sourceMappingURL=" + finalSourceMaps.toUrl();

// the only way to make sourceMaps usable seems to be to have
// them inlined right now, see https://github.com/electron/electron-compile/issues/172#issuecomment-277146112
return {
code: outputCode,
mimeType: babelOutput.mimeType,
};
// NB we don't need to mess with mime type, it's application/javascript all the way down.
}

return {
code: output.outputText,
code,
mimeType: this.outMimeType,
sourceMaps
};
Expand Down

0 comments on commit a98839a

Please sign in to comment.