Skip to content
This repository has been archived by the owner on Oct 1, 2020. It is now read-only.

Env-friendly default cache dir + drop legacy istanbul #247

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
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
2 changes: 1 addition & 1 deletion .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
119 changes: 119 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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"
}
}
22 changes: 15 additions & 7 deletions src/config-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -172,15 +173,16 @@ 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];
}

// Are we still package.json (i.e. is there no babel info whatsoever?)
if ('name' in info && 'version' in info) {
let appRoot = path.dirname(file);
return createCompilerHostFromConfiguration({
env: ourEnv,
appRoot: appRoot,
options: getDefaultConfiguration(appRoot),
rootCacheDir,
Expand All @@ -189,6 +191,7 @@ export async function createCompilerHostFromBabelRc(file, rootCacheDir=null, sou
}

return createCompilerHostFromConfiguration({
env: ourEnv,
appRoot: path.dirname(file),
options: {
'application/javascript': info
Expand All @@ -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,
Expand Down Expand Up @@ -272,15 +276,16 @@ 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];
}

// Are we still package.json (i.e. is there no babel info whatsoever?)
if ('name' in info && 'version' in info) {
let appRoot = path.dirname(file)
return createCompilerHostFromConfiguration({
env: ourEnv,
appRoot: appRoot,
options: getDefaultConfiguration(appRoot),
rootCacheDir,
Expand All @@ -289,6 +294,7 @@ export function createCompilerHostFromBabelRcSync(file, rootCacheDir=null, sourc
}

return createCompilerHostFromConfiguration({
env: ourEnv,
appRoot: path.dirname(file),
options: {
'application/javascript': info
Expand All @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/digest-for-object.js
Original file line number Diff line number Diff line change
@@ -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.
Expand Down