diff --git a/.babelrc b/.babelrc index 1aa43c3..c2fb3cd 100644 --- a/.babelrc +++ b/.babelrc @@ -2,7 +2,7 @@ "env": { "test": { "presets": ["es2016-node5", "react"], - "plugins": ["transform-async-to-generator", "array-includes", "istanbul"], + "plugins": ["transform-async-to-generator", "array-includes"], "sourceMaps": "inline" }, "production": { diff --git a/.gitignore b/.gitignore index 43bb3c3..1c88cba 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ lib-cov # Coverage directory used by tools like istanbul coverage +.nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt diff --git a/README.md b/README.md index f7b9d97..8773a85 100644 --- a/README.md +++ b/README.md @@ -235,6 +235,125 @@ Run `electron-compile` on all of your application assets, even if they aren't st electron-compile --appDir /path/to/my/app ./src ./static ``` +### How can I compile with TypeScript *then* Babel? + +If the TypeScript configuration contains a `babel` block, electron-compile +will run Babel on the output of the TypeScript compiler. + +Here's an example `.compilerc`: + +```json +{ + "text/typescript": { + "target": "es2017", + "lib": ["dom", "es6"], + "module": "commonjs", + "babel": { + "presets": ["async-to-bluebird"], + "plugins": ["transform-es2015-modules-commonjs"] + } + } +} +``` + +Enabling `sourceMap` or `inlineSourceMap` in the typescript configuration +will seamlessly forward these options to Babel and preserve the whole +source map chain. + +### How can I measure code coverage? + +Both the Babel and TypeScript compilers support a `coverage` option, +powered by [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul). + +Here's a simple `.compilerc` that instruments compiled TypeScript code: + +```json +{ + "text/typescript": { + "inlineSourceMap": true, + "coverage": true + } +} +``` + +The code will only be instrumented for the `test` environment. + +Enabling inline source maps is strongly recommended: since electron-compile +does not write the intermediate code to disk, istanbul reporters will not +be able to output, for example, HTML pages with that code and coverage information. +See **How can I report coverage information ?** for more on that. + +If you're using a TypeScript+Babel setup, you only need to set `coverage` +on the TypeScript config, not in the babel block. Like so: + +```json +{ + "text/typescript": { + "inlineSourceMap": true, + "coverage": true, + "babel": { + "plugins": "transform-inline-environment-variables" + } + } +} +``` + +To customize which files are included or excluded by the instrumenter, pass +an object instead. Valid options are described in the +[babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul) documentation. + +For example, if your test files end in `.spec.ts`, you might use the following `.compilerc`: + +```json +{ + "text/typescript": { + "inlineSourceMap": true, + "coverage": { + "ignore": [ + "**/*.spec.ts" + ] + } + } +} +``` + +### How can I report coverage information? + +Unfortunately, [nyc](https://www.npmjs.com/package/nyc) cannot be used directly +with electron applications. Fortunately, the collection, remapping and +reporting part is pretty easy to replicate in code. + +Instrumented code writes coverage data in the global variable `__coverage__`. +Using the [istanbuljs low-level API](https://github.com/istanbuljs/istanbuljs/), one can collect, remap and report coverage information like this: + +```javascript +// first, create a coverage map from the data gathered by the instrumented code +const libCoverage = require("istanbul-lib-coverage"); +let map = libCoverage.createCoverageMap(global["__coverage__"]); + +// then, remap the coverage data according to the source maps +const libSourceMaps = require("istanbul-lib-source-maps"); +// no source maps are actually read here, all the information +// needed is already baked into the instrumented code by babel-plugin-istanbul +const sourceMapCache = libSourceMaps.createSourceMapStore(); +map = sourceMapCache.transformCoverage(map).map; + +// now to emit reports. here we only emit an HTML report in the 'coverage' directory. +const libReport = require("istanbul-lib-report"); +const context = libReport.createContext({dir: "coverage"}); + +const reports = require("istanbul-reports"); +const tree = libReport.summarizers.pkg(map); +// see the istanbul-reports package for other reporters (text, lcov, etc.) +tree.visit(reports.create("html"), context); +``` + +Note that the above only works if you set `coverage: true` in a TypeScript +or Babel configuration block. Otherwise, no coverage information is collected +and `global["__coverage__"]` will be undefined. + +See **How do I measure code coverage?** for more on this. + ### But I use Grunt / Gulp / I want to do Something Interesting Compilation also has its own API, check out the [documentation](http://electron.github.io/electron-compile/docs/) for more information. diff --git a/package.json b/package.json index 613a24f..a7fa027 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "prepublish": "npm run compile", "start": "npm run compile && electron ./test-dist/electron-smoke-test.js", "test": "mocha --compilers js:babel-register test/*.js", - "test-cov": "cross-env NODE_ENV='test' istanbul cover ./node_modules/mocha/bin/_mocha -- --compilers js:babel-register test/*.js" + "test-cov": "cross-env NODE_ENV='test' nyc --require babel-register mocha -- test/*.js" }, "bin": { "electron-compile": "lib/cli.js", @@ -50,7 +50,6 @@ "babel-cli": "^6.11.4", "babel-eslint": "^6.1.2", "babel-plugin-array-includes": "^2.0.3", - "babel-plugin-istanbul": "^4.0.0", "babel-plugin-transform-async-to-generator": "^6.8.0", "babel-preset-es2016-node5": "^1.1.2", "babel-preset-react": "^6.11.1", @@ -66,7 +65,7 @@ "esdoc-es7-plugin": "0.0.3", "esdoc-plugin-async-to-sync": "^0.5.0", "eslint": "^3.3.0", - "istanbul": "^0.4.5", - "mocha": "^3.0.2" + "mocha": "^3.0.2", + "nyc": "^11.0.2" } } diff --git a/src/config-parser.js b/src/config-parser.js index 8a22829..35768bc 100644 --- a/src/config-parser.js +++ b/src/config-parser.js @@ -7,6 +7,7 @@ import {pfs} from './promise'; import FileChangedCache from './file-change-cache'; import CompilerHost from './compiler-host'; import registerRequireExtension from './require-hook'; +import createDigestForObject from './digest-for-object'; const d = require('debug')('electron-compile:config-parser'); @@ -111,7 +112,7 @@ export function init(appRoot, mainModule, productionMode = null, cacheDir = null */ export function createCompilerHostFromConfiguration(info) { let compilers = createCompilers(); - let rootCacheDir = info.rootCacheDir || calculateDefaultCompileCacheDirectory(); + let rootCacheDir = info.rootCacheDir || calculateDefaultCompileCacheDirectory(info); const sourceMapPath = info.sourceMapPath || info.rootCacheDir; if (info.sourceMapPath) { @@ -172,8 +173,8 @@ export async function createCompilerHostFromBabelRc(file, rootCacheDir=null, sou info = info.babel; } + let ourEnv = process.env.BABEL_ENV || process.env.NODE_ENV || 'development'; if ('env' in info) { - let ourEnv = process.env.BABEL_ENV || process.env.NODE_ENV || 'development'; info = info.env[ourEnv]; } @@ -181,6 +182,7 @@ export async function createCompilerHostFromBabelRc(file, rootCacheDir=null, sou if ('name' in info && 'version' in info) { let appRoot = path.dirname(file); return createCompilerHostFromConfiguration({ + env: ourEnv, appRoot: appRoot, options: getDefaultConfiguration(appRoot), rootCacheDir, @@ -189,6 +191,7 @@ export async function createCompilerHostFromBabelRc(file, rootCacheDir=null, sou } return createCompilerHostFromConfiguration({ + env: ourEnv, appRoot: path.dirname(file), options: { 'application/javascript': info @@ -212,12 +215,13 @@ export async function createCompilerHostFromBabelRc(file, rootCacheDir=null, sou export async function createCompilerHostFromConfigFile(file, rootCacheDir=null, sourceMapPath = null) { let info = JSON.parse(await pfs.readFile(file, 'utf8')); + let ourEnv = process.env.ELECTRON_COMPILE_ENV || process.env.NODE_ENV || 'development'; if ('env' in info) { - let ourEnv = process.env.ELECTRON_COMPILE_ENV || process.env.NODE_ENV || 'development'; info = info.env[ourEnv]; } return createCompilerHostFromConfiguration({ + env: ourEnv, appRoot: path.dirname(file), options: info, rootCacheDir, @@ -272,8 +276,8 @@ export function createCompilerHostFromBabelRcSync(file, rootCacheDir=null, sourc info = info.babel; } + let ourEnv = process.env.BABEL_ENV || process.env.NODE_ENV || 'development'; if ('env' in info) { - let ourEnv = process.env.BABEL_ENV || process.env.NODE_ENV || 'development'; info = info.env[ourEnv]; } @@ -281,6 +285,7 @@ export function createCompilerHostFromBabelRcSync(file, rootCacheDir=null, sourc if ('name' in info && 'version' in info) { let appRoot = path.dirname(file) return createCompilerHostFromConfiguration({ + env: ourEnv, appRoot: appRoot, options: getDefaultConfiguration(appRoot), rootCacheDir, @@ -289,6 +294,7 @@ export function createCompilerHostFromBabelRcSync(file, rootCacheDir=null, sourc } return createCompilerHostFromConfiguration({ + env: ourEnv, appRoot: path.dirname(file), options: { 'application/javascript': info @@ -301,12 +307,13 @@ export function createCompilerHostFromBabelRcSync(file, rootCacheDir=null, sourc export function createCompilerHostFromConfigFileSync(file, rootCacheDir=null, sourceMapPath = null) { let info = JSON.parse(fs.readFileSync(file, 'utf8')); + let ourEnv = process.env.ELECTRON_COMPILE_ENV || process.env.NODE_ENV || 'development'; if ('env' in info) { - let ourEnv = process.env.ELECTRON_COMPILE_ENV || process.env.NODE_ENV || 'development'; info = info.env[ourEnv]; } return createCompilerHostFromConfiguration({ + env: ourEnv, appRoot: path.dirname(file), options: info, rootCacheDir, @@ -338,9 +345,10 @@ export function createCompilerHostFromProjectRootSync(rootDir, rootCacheDir = nu * @return {string} A path that may or may not exist where electron-compile would * set up a development mode cache. */ -export function calculateDefaultCompileCacheDirectory() { +export function calculateDefaultCompileCacheDirectory(info) { let tmpDir = process.env.TEMP || process.env.TMPDIR || '/tmp'; - let hash = require('crypto').createHash('md5').update(process.execPath).digest('hex'); + let hashInput = process.execPath + info.env + createDigestForObject(info.options); + let hash = require('crypto').createHash('md5').update(hashInput).digest('hex'); let cacheDir = path.join(tmpDir, `compileCache_${hash}`); mkdirp.sync(cacheDir); diff --git a/src/digest-for-object.js b/src/digest-for-object.js index e0ef23f..e11ec48 100644 --- a/src/digest-for-object.js +++ b/src/digest-for-object.js @@ -1,7 +1,7 @@ import crypto from 'crypto'; function updateDigestForJsonValue(shasum, value) { - // Implmentation is similar to that of pretty-printing a JSON object, except: + // Implementation is similar to that of pretty-printing a JSON object, except: // * Strings are not escaped. // * No effort is made to avoid trailing commas. // These shortcuts should not affect the correctness of this function.