From ce6e7d3698b7c0995a18266a050559d1c39a8cb4 Mon Sep 17 00:00:00 2001 From: Lyrkan Date: Wed, 26 Jul 2017 22:24:58 +0200 Subject: [PATCH 1/2] Add Encore.configureRuntimeEnvironment() and Encore.clearRuntimeEnvironment() methods --- index.js | 128 +++++++++++++++++++++++++++++++++++++++++-------- test/index.js | 130 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 237 insertions(+), 21 deletions(-) diff --git a/index.js b/index.js index c8c20f08..7f6a560f 100644 --- a/index.js +++ b/index.js @@ -13,20 +13,22 @@ const WebpackConfig = require('./lib/WebpackConfig'); const configGenerator = require('./lib/config-generator'); const validator = require('./lib/config/validator'); const PrettyError = require('pretty-error'); -const runtimeConfig = require('./lib/context').runtimeConfig; const logger = require('./lib/logger'); +const parseRuntime = require('./lib/config/parse-runtime'); -// at this time, the encore executable should have set the runtimeConfig -if (!runtimeConfig) { - throw new Error('Are you trying to require index.js directly?'); -} +let webpackConfig = null; +let runtimeConfig = require('./lib/context').runtimeConfig; -let webpackConfig = new WebpackConfig(runtimeConfig); -if (runtimeConfig.verbose) { - logger.verbose(); +// If runtimeConfig is already set webpackConfig can directly +// be initialized here. +if (runtimeConfig) { + webpackConfig = new WebpackConfig(runtimeConfig); + if (runtimeConfig.verbose) { + logger.verbose(); + } } -module.exports = { +const publicApi = { /** * The directory where your files should be output. * @@ -431,17 +433,9 @@ module.exports = { * @returns {*} */ getWebpackConfig() { - try { - validator(webpackConfig); + validator(webpackConfig); - return configGenerator(webpackConfig); - } catch (error) { - // prettifies errors thrown by our library - const pe = new PrettyError(); - - console.log(pe.render(error)); - process.exit(1); // eslint-disable-line - } + return configGenerator(webpackConfig); }, /** @@ -454,5 +448,99 @@ module.exports = { */ reset() { webpackConfig = new WebpackConfig(runtimeConfig); - } + }, + + /** + * Initialize the runtime environment. + * + * It can be used to directly manipulate the Encore API without + * executing the "./node_module/.bin/encore" utility. + * + * Encore.configureRuntimeEnvironment( + * // Environment to use (dev, dev-server, production) + * 'dev-server', + * + * // Same options you would use with the + * // CLI utility with their name in + * // camelCase. + * { + * https: true, + * keepPublicPath: true + * } + * ) + * + * Be aware than using this method will also reset the current + * webpack configuration. + * + * @param {string} environment + * @param {object} options + * @returns {exports} + */ + configureRuntimeEnvironment(environment, options = {}) { + runtimeConfig = parseRuntime( + Object.assign( + {}, + require('yargs/yargs')([environment]).argv, + options + ), + process.cwd() + ); + + if (runtimeConfig.verbose) { + logger.verbose(); + } + + webpackConfig = new WebpackConfig(runtimeConfig); + + return this; + }, + + /** + * Clear the runtime environment. + * + * Be aware than using this method will also reset the + * current webpack configuration. + * + * @returns {void} + */ + clearRuntimeEnvironment() { + runtimeConfig = null; + webpackConfig = null; + }, }; + +// Proxy the API in order to prevent calls to most of its methods +// if the webpackConfig object hasn't been initialized yet. +const publicApiProxy = new Proxy(publicApi, { + get: (target, prop) => { + if (typeof target[prop] === 'function') { + // These methods of the public API can be called even if the + // webpackConfig object hasn't been initialized yet. + const safeMethods = [ + 'configureRuntimeEnvironment', + 'clearRuntimeEnvironment', + ]; + + if (!webpackConfig && (safeMethods.indexOf(prop) === -1)) { + throw new Error(`Encore.${prop}() cannot be called yet because the runtime environment doesn't appear to be configured. Try calling Encore.configureRuntimeEnvironment() first.`); + } else { + // Either a safe method has been called or the webpackConfig + // object is already available. In this case act as a passthrough. + return (...parameters) => { + try { + const res = target[prop](...parameters); + return (res === target) ? publicApiProxy : res; + } catch (error) { + // prettifies errors thrown by our library + const pe = new PrettyError(); + + console.log(pe.render(error)); + process.exit(1); // eslint-disable-line + } + }; + } + } + } +}); + +module.exports = publicApiProxy; diff --git a/test/index.js b/test/index.js index d1d2b444..4abdaee3 100644 --- a/test/index.js +++ b/test/index.js @@ -10,14 +10,25 @@ 'use strict'; const expect = require('chai').expect; -require('../lib/context').runtimeConfig = {}; const api = require('../index'); +function configureApi() { + return api.configureRuntimeEnvironment('dev'); +} + describe('Public API', () => { + beforeEach(() => { + api.clearRuntimeEnvironment(); + }); describe('setOutputPath', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.setOutputPath('/')).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.setOutputPath('/'); expect(returnedValue).to.equal(api); }); @@ -26,7 +37,12 @@ describe('Public API', () => { describe('setPublicPath', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.setPublicPath('/')).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.setPublicPath('/'); expect(returnedValue).to.equal(api); }); @@ -35,7 +51,12 @@ describe('Public API', () => { describe('setManifestKeyPrefix', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.setManifestKeyPrefix('/build')).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.setManifestKeyPrefix('/build'); expect(returnedValue).to.equal(api); }); @@ -44,7 +65,12 @@ describe('Public API', () => { describe('addEntry', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.addEntry('entry', 'main.js')).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.addEntry('entry', 'main.js'); expect(returnedValue).to.equal(api); }); @@ -53,7 +79,12 @@ describe('Public API', () => { describe('addStyleEntry', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.addStyleEntry('styleEntry', 'main.css')).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.addStyleEntry('styleEntry', 'main.css'); expect(returnedValue).to.equal(api); }); @@ -62,7 +93,12 @@ describe('Public API', () => { describe('addPlugin', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.addPlugin(null)).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.addPlugin(null); expect(returnedValue).to.equal(api); }); @@ -71,7 +107,12 @@ describe('Public API', () => { describe('addLoader', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.addLoader(null)).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.addLoader(null); expect(returnedValue).to.equal(api); }); @@ -80,7 +121,12 @@ describe('Public API', () => { describe('addRule', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.addRule(null)).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.addRule(null); expect(returnedValue).to.equal(api); }); @@ -89,7 +135,12 @@ describe('Public API', () => { describe('enableVersioning', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.enableVersioning()).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.enableVersioning(); expect(returnedValue).to.equal(api); }); @@ -98,7 +149,12 @@ describe('Public API', () => { describe('enableSourceMaps', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.enableSourceMaps()).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.enableSourceMaps(); expect(returnedValue).to.equal(api); }); @@ -107,7 +163,12 @@ describe('Public API', () => { describe('createSharedEntry', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.createSharedEntry('sharedEntry', 'vendor.js')).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.createSharedEntry('sharedEntry', 'vendor.js'); expect(returnedValue).to.equal(api); }); @@ -116,7 +177,12 @@ describe('Public API', () => { describe('autoProvideVariables', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.autoProvideVariables({})).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.autoProvideVariables({}); expect(returnedValue).to.equal(api); }); @@ -125,7 +191,12 @@ describe('Public API', () => { describe('autoProvidejQuery', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.autoProvidejQuery()).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.autoProvidejQuery(); expect(returnedValue).to.equal(api); }); @@ -134,7 +205,12 @@ describe('Public API', () => { describe('enablePostCssLoader', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.enablePostCssLoader()).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.enablePostCssLoader(); expect(returnedValue).to.equal(api); }); @@ -143,7 +219,12 @@ describe('Public API', () => { describe('enableSassLoader', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.enableSassLoader()).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.enableSassLoader(); expect(returnedValue).to.equal(api); }); @@ -152,7 +233,12 @@ describe('Public API', () => { describe('enableLessLoader', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.enableLessLoader()).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.enableLessLoader(); expect(returnedValue).to.equal(api); }); @@ -161,7 +247,12 @@ describe('Public API', () => { describe('setOutputPath', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.configureBabel(() => {})).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.configureBabel(() => {}); expect(returnedValue).to.equal(api); }); @@ -170,7 +261,12 @@ describe('Public API', () => { describe('enableReactPreset', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.enableReactPreset()).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.enableReactPreset(); expect(returnedValue).to.equal(api); }); @@ -179,7 +275,12 @@ describe('Public API', () => { describe('enableTypeScriptLoader', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.enableTypeScriptLoader()).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.enableTypeScriptLoader(); expect(returnedValue).to.equal(api); }); @@ -188,7 +289,12 @@ describe('Public API', () => { describe('enableVueLoader', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.enableVueLoader()).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.enableVueLoader(); expect(returnedValue).to.equal(api); }); @@ -197,10 +303,32 @@ describe('Public API', () => { describe('cleanupOutputBeforeBuild', () => { + it('should not be callable before the runtime environment has been configured', () => { + expect(() => api.cleanupOutputBeforeBuild()).to.throw(); + }); + it('must return the API object', () => { + configureApi(); const returnedValue = api.cleanupOutputBeforeBuild(); expect(returnedValue).to.equal(api); }); }); + + describe('configureRuntimeEnvironment', () => { + + it('should return the API object', () => { + const returnedValue = api.configureRuntimeEnvironment('dev'); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('clearRuntimeEnvironment', () => { + + it('should be callable even if the runtime environment has not been configured', () => { + expect(() => api.clearRuntimeEnvironment()).to.not.throw(); + }); + + }); }); From f760e5299c249b1d0d24c9f3ee9baf5fb1e45d1e Mon Sep 17 00:00:00 2001 From: Lyrkan Date: Sat, 5 Aug 2017 16:28:30 +0200 Subject: [PATCH 2/2] Remove some tests related to the public API proxy and improve some texts/comments --- index.js | 58 +++++++++++++----------- test/index.js | 121 ++++---------------------------------------------- 2 files changed, 40 insertions(+), 139 deletions(-) diff --git a/index.js b/index.js index 7f6a560f..8bf83449 100644 --- a/index.js +++ b/index.js @@ -19,13 +19,18 @@ const parseRuntime = require('./lib/config/parse-runtime'); let webpackConfig = null; let runtimeConfig = require('./lib/context').runtimeConfig; -// If runtimeConfig is already set webpackConfig can directly -// be initialized here. -if (runtimeConfig) { - webpackConfig = new WebpackConfig(runtimeConfig); +function initializeWebpackConfig() { if (runtimeConfig.verbose) { logger.verbose(); } + + webpackConfig = new WebpackConfig(runtimeConfig); +} + +// If runtimeConfig is already set webpackConfig can directly +// be initialized here. +if (runtimeConfig) { + initializeWebpackConfig(); } const publicApi = { @@ -453,8 +458,9 @@ const publicApi = { /** * Initialize the runtime environment. * - * It can be used to directly manipulate the Encore API without - * executing the "./node_module/.bin/encore" utility. + * This can be used to configure the Encore runtime if you're + * using Encore without executing the "./node_module/.bin/encore" + * utility (e.g. with karma-webpack). * * Encore.configureRuntimeEnvironment( * // Environment to use (dev, dev-server, production) @@ -486,11 +492,7 @@ const publicApi = { process.cwd() ); - if (runtimeConfig.verbose) { - logger.verbose(); - } - - webpackConfig = new WebpackConfig(runtimeConfig); + initializeWebpackConfig(); return this; }, @@ -522,24 +524,26 @@ const publicApiProxy = new Proxy(publicApi, { ]; if (!webpackConfig && (safeMethods.indexOf(prop) === -1)) { - throw new Error(`Encore.${prop}() cannot be called yet because the runtime environment doesn't appear to be configured. Try calling Encore.configureRuntimeEnvironment() first.`); - } else { - // Either a safe method has been called or the webpackConfig - // object is already available. In this case act as a passthrough. - return (...parameters) => { - try { - const res = target[prop](...parameters); - return (res === target) ? publicApiProxy : res; - } catch (error) { - // prettifies errors thrown by our library - const pe = new PrettyError(); - - console.log(pe.render(error)); - process.exit(1); // eslint-disable-line - } - }; + throw new Error(`Encore.${prop}() cannot be called yet because the runtime environment doesn't appear to be configured. Make sure you're using the encore executable or call Encore.configureRuntimeEnvironment() first if you're purposely not calling Encore directly.`); } + + // Either a safe method has been called or the webpackConfig + // object is already available. In this case act as a passthrough. + return (...parameters) => { + try { + const res = target[prop](...parameters); + return (res === target) ? publicApiProxy : res; + } catch (error) { + // prettifies errors thrown by our library + const pe = new PrettyError(); + + console.log(pe.render(error)); + process.exit(1); // eslint-disable-line + } + }; } + + return target[prop]; } }); diff --git a/test/index.js b/test/index.js index 4abdaee3..db6b4ac5 100644 --- a/test/index.js +++ b/test/index.js @@ -12,23 +12,14 @@ const expect = require('chai').expect; const api = require('../index'); -function configureApi() { - return api.configureRuntimeEnvironment('dev'); -} - describe('Public API', () => { beforeEach(() => { - api.clearRuntimeEnvironment(); + api.configureRuntimeEnvironment('dev'); }); describe('setOutputPath', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.setOutputPath('/')).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.setOutputPath('/'); expect(returnedValue).to.equal(api); }); @@ -37,12 +28,7 @@ describe('Public API', () => { describe('setPublicPath', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.setPublicPath('/')).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.setPublicPath('/'); expect(returnedValue).to.equal(api); }); @@ -51,12 +37,7 @@ describe('Public API', () => { describe('setManifestKeyPrefix', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.setManifestKeyPrefix('/build')).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.setManifestKeyPrefix('/build'); expect(returnedValue).to.equal(api); }); @@ -65,12 +46,7 @@ describe('Public API', () => { describe('addEntry', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.addEntry('entry', 'main.js')).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.addEntry('entry', 'main.js'); expect(returnedValue).to.equal(api); }); @@ -79,12 +55,7 @@ describe('Public API', () => { describe('addStyleEntry', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.addStyleEntry('styleEntry', 'main.css')).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.addStyleEntry('styleEntry', 'main.css'); expect(returnedValue).to.equal(api); }); @@ -93,12 +64,7 @@ describe('Public API', () => { describe('addPlugin', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.addPlugin(null)).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.addPlugin(null); expect(returnedValue).to.equal(api); }); @@ -107,12 +73,7 @@ describe('Public API', () => { describe('addLoader', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.addLoader(null)).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.addLoader(null); expect(returnedValue).to.equal(api); }); @@ -121,12 +82,7 @@ describe('Public API', () => { describe('addRule', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.addRule(null)).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.addRule(null); expect(returnedValue).to.equal(api); }); @@ -135,12 +91,7 @@ describe('Public API', () => { describe('enableVersioning', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.enableVersioning()).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.enableVersioning(); expect(returnedValue).to.equal(api); }); @@ -149,12 +100,7 @@ describe('Public API', () => { describe('enableSourceMaps', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.enableSourceMaps()).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.enableSourceMaps(); expect(returnedValue).to.equal(api); }); @@ -163,12 +109,7 @@ describe('Public API', () => { describe('createSharedEntry', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.createSharedEntry('sharedEntry', 'vendor.js')).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.createSharedEntry('sharedEntry', 'vendor.js'); expect(returnedValue).to.equal(api); }); @@ -177,12 +118,7 @@ describe('Public API', () => { describe('autoProvideVariables', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.autoProvideVariables({})).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.autoProvideVariables({}); expect(returnedValue).to.equal(api); }); @@ -191,12 +127,7 @@ describe('Public API', () => { describe('autoProvidejQuery', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.autoProvidejQuery()).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.autoProvidejQuery(); expect(returnedValue).to.equal(api); }); @@ -205,12 +136,7 @@ describe('Public API', () => { describe('enablePostCssLoader', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.enablePostCssLoader()).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.enablePostCssLoader(); expect(returnedValue).to.equal(api); }); @@ -219,12 +145,7 @@ describe('Public API', () => { describe('enableSassLoader', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.enableSassLoader()).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.enableSassLoader(); expect(returnedValue).to.equal(api); }); @@ -233,12 +154,7 @@ describe('Public API', () => { describe('enableLessLoader', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.enableLessLoader()).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.enableLessLoader(); expect(returnedValue).to.equal(api); }); @@ -247,12 +163,7 @@ describe('Public API', () => { describe('setOutputPath', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.configureBabel(() => {})).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.configureBabel(() => {}); expect(returnedValue).to.equal(api); }); @@ -261,12 +172,7 @@ describe('Public API', () => { describe('enableReactPreset', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.enableReactPreset()).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.enableReactPreset(); expect(returnedValue).to.equal(api); }); @@ -275,12 +181,7 @@ describe('Public API', () => { describe('enableTypeScriptLoader', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.enableTypeScriptLoader()).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.enableTypeScriptLoader(); expect(returnedValue).to.equal(api); }); @@ -289,12 +190,7 @@ describe('Public API', () => { describe('enableVueLoader', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.enableVueLoader()).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.enableVueLoader(); expect(returnedValue).to.equal(api); }); @@ -303,12 +199,7 @@ describe('Public API', () => { describe('cleanupOutputBeforeBuild', () => { - it('should not be callable before the runtime environment has been configured', () => { - expect(() => api.cleanupOutputBeforeBuild()).to.throw(); - }); - it('must return the API object', () => { - configureApi(); const returnedValue = api.cleanupOutputBeforeBuild(); expect(returnedValue).to.equal(api); }); @@ -324,11 +215,17 @@ describe('Public API', () => { }); - describe('clearRuntimeEnvironment', () => { + describe('Runtime environment proxy', () => { + beforeEach(() => { + api.clearRuntimeEnvironment(); + }); - it('should be callable even if the runtime environment has not been configured', () => { + it('safe methods should be callable even if the runtime environment has not been configured', () => { expect(() => api.clearRuntimeEnvironment()).to.not.throw(); }); + it('unsafe methods should NOT be callable if the runtime environment has not been configured', () => { + expect(() => api.setOutputPath('/')).to.throw('Encore.setOutputPath() cannot be called yet'); + }); }); });