From 5d730e05e6c5585cd7053679acae2c5fae61df7f Mon Sep 17 00:00:00 2001 From: OJ Kwon Date: Sat, 31 Dec 2016 15:51:33 -0800 Subject: [PATCH 1/3] chore(package.json): update dependencies --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index a90ede8..7a3e588 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,8 @@ "@paulcbetts/mime-types": "^2.1.10", "btoa": "^1.1.2", "debug": "^2.5.1", + "lodash.merge": "^4.6.0", + "lodash.omit": "^4.5.0", "lru-cache": "^4.0.1", "mkdirp": "^0.5.1", "pify": "^2.3.0", From 6c8f0e738da31876bb8fa4d4ee8c43cb1112cbf0 Mon Sep 17 00:00:00 2001 From: OJ Kwon Date: Sat, 31 Dec 2016 15:51:44 -0800 Subject: [PATCH 2/3] feat(configParser): support configuration inheritance --- src/config-parser.js | 83 ++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/src/config-parser.js b/src/config-parser.js index 25c1b98..c46d18d 100644 --- a/src/config-parser.js +++ b/src/config-parser.js @@ -7,6 +7,8 @@ import {pfs} from './promise'; import FileChangedCache from './file-change-cache'; import CompilerHost from './compiler-host'; import registerRequireExtension from './require-hook'; +import omit from 'lodash.omit'; +import merge from 'lodash.merge'; const d = require('debug')('electron-compile:config-parser'); @@ -26,6 +28,37 @@ function statSyncNoException(fsPath) { } } +/** + * Read compiler configuration from specified file. + * If configuration contains 'common' denominator with `env` object for environment + * specific configuration, returned object will inherit environment specific configuration + * from common configuration values. + * + * @param {string} file The path to a compiler configuration file + * @returns {Object} configuration objec from given file + */ +async function readCompilerConfig(file, env = 'development') { + const configuration = JSON.parse(await pfs.readFile(file, 'utf8')); + return readCompilerConfiguration(configuration, env); +} + +function readCompilerConfigSync(file, env = 'development') { + const configuration = JSON.parse(fs.readFileSync(file, 'utf8')); + return readCompilerConfiguration(configuration, env); +} + +export function readCompilerConfiguration(configObject, env) { + let config = configObject; + + if ('babel' in configObject) { + d(`found 'babel' section in configuration, assume given object is package.json`); + config = configObject.babel; + } + + return 'env' in config ? + merge({}, omit(config, 'env'), config.env[env]) : + config; +} /** * Initialize the global hooks (protocol hook for file:, node.js hook) @@ -89,12 +122,7 @@ export function init(appRoot, mainModule, productionMode = null, cacheDir = null compilerHost = CompilerHost.createReadonlyFromConfigurationSync(rootCacheDir, appRoot); } else { // if cacheDir was passed in, pass it along. Otherwise, default to a tempdir. - if (cacheDir) { - compilerHost = createCompilerHostFromProjectRootSync(appRoot, rootCacheDir); - } else { - compilerHost = createCompilerHostFromProjectRootSync(appRoot); - } - + compilerHost = createCompilerHostFromProjectRootSync(appRoot, cacheDir ? rootCacheDir: null); } initializeGlobalHooks(compilerHost); @@ -158,17 +186,8 @@ export function createCompilerHostFromConfiguration(info) { * @return {Promise} A set-up compiler host */ export async function createCompilerHostFromBabelRc(file, rootCacheDir=null) { - let info = JSON.parse(await pfs.readFile(file, 'utf8')); - - // package.json - if ('babel' in info) { - info = info.babel; - } - - if ('env' in info) { - let ourEnv = process.env.BABEL_ENV || process.env.NODE_ENV || 'development'; - info = info.env[ourEnv]; - } + const environment = process.env.BABEL_ENV || process.env.NODE_ENV || 'development'; + const info = await readCompilerConfig(file, environment); // Are we still package.json (i.e. is there no babel info whatsoever?) if ('name' in info && 'version' in info) { @@ -188,7 +207,6 @@ export async function createCompilerHostFromBabelRc(file, rootCacheDir=null) { }); } - /** * Creates a compiler host from a .compilerc file. This method is usually called * from {@link createCompilerHostFromProjectRoot} instead of used directly. @@ -200,12 +218,8 @@ export async function createCompilerHostFromBabelRc(file, rootCacheDir=null) { * @return {Promise} A set-up compiler host */ export async function createCompilerHostFromConfigFile(file, rootCacheDir=null) { - let info = JSON.parse(await pfs.readFile(file, 'utf8')); - - if ('env' in info) { - let ourEnv = process.env.ELECTRON_COMPILE_ENV || process.env.NODE_ENV || 'development'; - info = info.env[ourEnv]; - } + const environment = process.env.ELECTRON_COMPILE_ENV || process.env.NODE_ENV || 'development'; + const info = await readCompilerConfig(file, environment); return createCompilerHostFromConfiguration({ appRoot: path.dirname(file), @@ -251,17 +265,8 @@ export async function createCompilerHostFromProjectRoot(rootDir, rootCacheDir=nu } export function createCompilerHostFromBabelRcSync(file, rootCacheDir=null) { - let info = JSON.parse(fs.readFileSync(file, 'utf8')); - - // package.json - if ('babel' in info) { - info = info.babel; - } - - if ('env' in info) { - let ourEnv = process.env.BABEL_ENV || process.env.NODE_ENV || 'development'; - info = info.env[ourEnv]; - } + const environment = process.env.BABEL_ENV || process.env.NODE_ENV || 'development'; + const info = readCompilerConfigSync(file, environment); // Are we still package.json (i.e. is there no babel info whatsoever?) if ('name' in info && 'version' in info) { @@ -282,12 +287,8 @@ export function createCompilerHostFromBabelRcSync(file, rootCacheDir=null) { } export function createCompilerHostFromConfigFileSync(file, rootCacheDir=null) { - let info = JSON.parse(fs.readFileSync(file, 'utf8')); - - if ('env' in info) { - let ourEnv = process.env.ELECTRON_COMPILE_ENV || process.env.NODE_ENV || 'development'; - info = info.env[ourEnv]; - } + const environment = process.env.ELECTRON_COMPILE_ENV || process.env.NODE_ENV || 'development'; + const info = readCompilerConfigSync(file, environment); return createCompilerHostFromConfiguration({ appRoot: path.dirname(file), From 365defdd0d0c96f6a2a0804c8b1981ba40524921 Mon Sep 17 00:00:00 2001 From: OJ Kwon Date: Sat, 31 Dec 2016 15:52:00 -0800 Subject: [PATCH 3/3] test(configParser): update test coverage for config inheritance --- test/config-parser.js | 243 ++++++++++++++++++++++++++++++------------ 1 file changed, 175 insertions(+), 68 deletions(-) diff --git a/test/config-parser.js b/test/config-parser.js index a998d81..c843d7f 100644 --- a/test/config-parser.js +++ b/test/config-parser.js @@ -2,48 +2,50 @@ import fs from 'fs'; import path from 'path'; import mkdirp from 'mkdirp'; import rimraf from 'rimraf'; +import merge from 'lodash.merge'; import { - createCompilers, - createCompilerHostFromConfiguration, + createCompilers, + createCompilerHostFromConfiguration, createCompilerHostFromConfigFile, - createCompilerHostFromBabelRc + createCompilerHostFromBabelRc, + readCompilerConfiguration } from '../src/config-parser'; const d = require('debug')('test:config-parser'); let testCount = 0; -describe('the configuration parser module', function() { +describe('the configuration parser module', function () { this.timeout(10 * 1000); - describe('the createCompilers method', function() { - it('should return compilers', function() { + describe('the createCompilers method', function () { + it('should return compilers', function () { let result = createCompilers(); expect(Object.keys(result).length > 0).to.be.ok; }); - it('should definitely have these compilers', function() { + it('should definitely have these compilers', function () { let result = createCompilers(); expect(result['application/javascript']).to.be.ok; expect(result['text/less']).to.be.ok; }); }); - - describe('the createCompilerHostFromConfiguration method', function() { - beforeEach(function() { + + describe('the createCompilerHostFromConfiguration method', function () { + beforeEach(function () { this.tempCacheDir = path.join(__dirname, `__create_compiler_host_${testCount++}`); mkdirp.sync(this.tempCacheDir); }); - - afterEach(function() { + + afterEach(function () { rimraf.sync(this.tempCacheDir); }); - - it('respects suppressing source maps (scenario test)', async function() { + + it('respects suppressing source maps (scenario test)', async function () { let fixtureDir = path.join(__dirname, '..', 'test', 'fixtures'); - + let result = createCompilerHostFromConfiguration({ appRoot: fixtureDir, rootCacheDir: this.tempCacheDir, @@ -54,18 +56,18 @@ describe('the configuration parser module', function() { } } }); - + let compileInfo = await result.compile(path.join(fixtureDir, 'valid.js')); d(JSON.stringify(compileInfo)); - + expect(compileInfo.mimeType).to.equal('application/javascript'); - + let lines = compileInfo.code.split('\n'); expect(lines.length > 5).to.be.ok; expect(lines.find((x) => x.match(/sourceMappingURL=/))).not.to.be.ok; }); - - it('creates a no-op compiler when passthrough is set for a mime type', async function() { + + it('creates a no-op compiler when passthrough is set for a mime type', async function () { let fixtureDir = path.join(__dirname, '..', 'test', 'fixtures'); let sourceFilePath = path.join(fixtureDir, 'valid.js'); @@ -77,7 +79,7 @@ describe('the configuration parser module', function() { d(JSON.stringify(compileInfo)); expect(compileInfo.mimeType).to.equal('application/javascript'); - + if (compileInfo.code) { expect(compileInfo.code).to.deep.equal(sourceFile.toString()); } else { @@ -86,123 +88,228 @@ describe('the configuration parser module', function() { }); }); - describe('the createCompilerHostFromBabelRc method', function() { - beforeEach(function() { + describe('the createCompilerHostFromBabelRc method', function () { + beforeEach(function () { this.tempCacheDir = path.join(__dirname, `__create_compiler_host_${testCount++}`); mkdirp.sync(this.tempCacheDir); }); - - afterEach(function() { + + afterEach(function () { rimraf.sync(this.tempCacheDir); if ('BABEL_ENV' in process.env) { delete process.env.ELECTRON_COMPILE_ENV; } }); - - it('reads from an environment-free file', async function() { + + it('reads from an environment-free file', async function () { let fixtureDir = path.join(__dirname, '..', 'test', 'fixtures'); - + let result = await createCompilerHostFromBabelRc(path.join(fixtureDir, 'babelrc-noenv')); - + let compileInfo = await result.compile(path.join(fixtureDir, 'valid.js')); d(JSON.stringify(compileInfo)); - + expect(compileInfo.mimeType).to.equal('application/javascript'); - + let lines = compileInfo.code.split('\n'); expect(lines.length > 5).to.be.ok; expect(lines.find((x) => x.match(/sourceMappingURL=/))).to.be.ok; }); - - it('uses the development env when env is unset', async function() { + + it('uses the development env when env is unset', async function () { let fixtureDir = path.join(__dirname, '..', 'test', 'fixtures'); - + let result = await createCompilerHostFromBabelRc(path.join(fixtureDir, 'babelrc-production')); - + let compileInfo = await result.compile(path.join(fixtureDir, 'valid.js')); d(JSON.stringify(compileInfo)); - + expect(compileInfo.mimeType).to.equal('application/javascript'); - + let lines = compileInfo.code.split('\n'); expect(lines.length > 5).to.be.ok; expect(lines.find((x) => x.match(/sourceMappingURL=/))).to.be.ok; }); - - it('uses the production env when env is set', async function() { + + it('uses the production env when env is set', async function () { process.env.BABEL_ENV = 'production'; let fixtureDir = path.join(__dirname, '..', 'test', 'fixtures'); - + let result = await createCompilerHostFromBabelRc(path.join(fixtureDir, 'babelrc-production')); - + let compileInfo = await result.compile(path.join(fixtureDir, 'valid.js')); d(JSON.stringify(compileInfo)); - + expect(compileInfo.mimeType).to.equal('application/javascript'); - + let lines = compileInfo.code.split('\n'); expect(lines.length > 5).to.be.ok; expect(lines.find((x) => x.match(/sourceMappingURL=/))).not.to.be.ok; - }); + }); }); - - describe('the createCompilerHostFromConfigFile method', function() { - beforeEach(function() { + + describe('the createCompilerHostFromConfigFile method', function () { + beforeEach(function () { this.tempCacheDir = path.join(__dirname, `__create_compiler_host_${testCount++}`); mkdirp.sync(this.tempCacheDir); }); - - afterEach(function() { + + afterEach(function () { rimraf.sync(this.tempCacheDir); if ('ELECTRON_COMPILE_ENV' in process.env) { delete process.env.ELECTRON_COMPILE_ENV; } }); - - it('reads from an environment-free file', async function() { + + it('reads from an environment-free file', async function () { let fixtureDir = path.join(__dirname, '..', 'test', 'fixtures'); - + let result = await createCompilerHostFromConfigFile(path.join(fixtureDir, 'compilerc-noenv')); - + let compileInfo = await result.compile(path.join(fixtureDir, 'valid.js')); d(JSON.stringify(compileInfo)); - + expect(compileInfo.mimeType).to.equal('application/javascript'); - + let lines = compileInfo.code.split('\n'); expect(lines.length > 5).to.be.ok; expect(lines.find((x) => x.match(/sourceMappingURL=/))).to.be.ok; }); - - it('uses the development env when env is unset', async function() { + + it('uses the development env when env is unset', async function () { let fixtureDir = path.join(__dirname, '..', 'test', 'fixtures'); - + let result = await createCompilerHostFromConfigFile(path.join(fixtureDir, 'compilerc-production')); - + let compileInfo = await result.compile(path.join(fixtureDir, 'valid.js')); d(JSON.stringify(compileInfo)); - + expect(compileInfo.mimeType).to.equal('application/javascript'); - + let lines = compileInfo.code.split('\n'); expect(lines.length > 5).to.be.ok; expect(lines.find((x) => x.match(/sourceMappingURL=/))).to.be.ok; }); - - it('uses the production env when env is set', async function() { + + it('uses the production env when env is set', async function () { process.env.ELECTRON_COMPILE_ENV = 'production'; let fixtureDir = path.join(__dirname, '..', 'test', 'fixtures'); - + let result = await createCompilerHostFromConfigFile(path.join(fixtureDir, 'compilerc-production')); - + let compileInfo = await result.compile(path.join(fixtureDir, 'valid.js')); d(JSON.stringify(compileInfo)); - + expect(compileInfo.mimeType).to.equal('application/javascript'); - + let lines = compileInfo.code.split('\n'); expect(lines.length > 5).to.be.ok; expect(lines.find((x) => x.match(/sourceMappingURL=/))).not.to.be.ok; - }); + }); + }); + + describe('readCompilerConfiguration', () => { + const defaultFixture = { + "application/javascript": { + "presets": ["es2016-node5", "react"], + "sourceMaps": "inline" + }, + "text/typescript": { + "declaration": false + } + }; + + const envFixture = { + "env": { + "production": { + "application/javascript": { + "sourceMaps": false + }, + "text/typescript": { + "declaration": true, + "jsx": "react" + } + }, + "development": { + "application/javascript": { + "plugins": ["transform-async-to-generator"] + }, + "text/typescript": { + "declaration": false + } + } + } + }; + + it('should read environment-free config', () => { + const expected = { + "presets": ["es2016-node5", "react"], + "sourceMaps": "inline" + }; + + expect(readCompilerConfiguration(expected)).to.be.equal(expected); + }); + + it('should read from production env without common', () => { + const env = 'production'; + + expect(readCompilerConfiguration(envFixture, env)) + .to.be.deep.equal(envFixture.env[env]); + }); + + it('should read from development env without common', () => { + const env = 'development'; + + expect(readCompilerConfiguration(envFixture, env)) + .to.be.deep.equal(envFixture.env[env]); + }); + + it('should read from common config inherited production env', () => { + const env = 'production'; + const fixture = merge({}, defaultFixture, envFixture); + const expected = { + "application/javascript": { + "presets": ["es2016-node5", "react"], + "sourceMaps": false + }, + "text/typescript": { + "declaration": true, + "jsx": "react" + } + }; + + expect(readCompilerConfiguration(fixture, env)) + .to.be.deep.equal(expected); + }); + + it('should read from common config inherited development env', () => { + const env = 'development'; + const fixture = merge({}, defaultFixture, envFixture); + const expected = { + "application/javascript": { + "presets": ["es2016-node5", "react"], + "sourceMaps": "inline", + "plugins": ["transform-async-to-generator"] + }, + "text/typescript": { + "declaration": false, + } + }; + + expect(readCompilerConfiguration(fixture, env)) + .to.be.deep.equal(expected); + }); + + it('should accept environment-free package.json-like config object', () => { + const expected = { + "presets": ["es2016-node5", "react"], + "sourceMaps": "inline" + }; + const fixture = { + babel: expected + }; + + expect(readCompilerConfiguration(fixture)).to.be.equal(expected); + }); }); });