From 9892516c787938326dc1f1373c85112a0d6cb83c Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Sat, 2 Feb 2019 08:05:40 +0100 Subject: [PATCH 01/12] feat: implement "configureLoaderRule" method --- lib/WebpackConfig.js | 15 +++++++++++++++ lib/config-generator.js | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index 6c0d9f5f..a13eb367 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -100,6 +100,9 @@ class WebpackConfig { this.eslintLoaderOptionsCallback = () => {}; this.tsConfigurationCallback = () => {}; this.handlebarsConfigurationCallback = () => {}; + this.loaderConfigurationCallbacks = { + 'eslint': () => {}, + }; // Plugins options this.cleanWebpackPluginPaths = ['**/*']; @@ -716,6 +719,18 @@ class WebpackConfig { }); } + configureLoaderRule(name, callback) { + if (!(name in this.loaderConfigurationCallbacks)) { + throw new Error(`Loader "${name}" is not configurable. Either open an issue or a pull request.`); + } + + if (typeof callback !== 'function') { + throw new Error('Argument 2 to configureLoaderRule() must be a callback function.'); + } + + this.loaderConfigurationCallbacks[name] = callback; + } + useDevServer() { return this.runtimeConfig.useDevServer; } diff --git a/lib/config-generator.js b/lib/config-generator.js index 90b112af..b316be9d 100644 --- a/lib/config-generator.js +++ b/lib/config-generator.js @@ -357,13 +357,13 @@ class ConfigGenerator { } if (this.webpackConfig.useEslintLoader) { - rules.push({ + rules.push(applyOptionsCallback(this.webpackConfig.loaderConfigurationCallbacks['eslint'], { test: /\.jsx?$/, loader: 'eslint-loader', exclude: /node_modules/, enforce: 'pre', options: eslintLoaderUtil.getOptions(this.webpackConfig) - }); + })); } if (this.webpackConfig.useTypeScriptLoader) { From 49a4258fc80a749d1ff606a252af2792d1b599c1 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Sat, 2 Feb 2019 08:05:46 +0100 Subject: [PATCH 02/12] add tests --- test/WebpackConfig.js | 32 ++++++++++++++++++++++++++++++++ test/loaders/eslint.js | 27 +++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/test/WebpackConfig.js b/test/WebpackConfig.js index 6d2f1ffc..8dea504b 100644 --- a/test/WebpackConfig.js +++ b/test/WebpackConfig.js @@ -1144,4 +1144,36 @@ describe('WebpackConfig object', () => { }).to.throw('Argument 1 to configureWatchOptions() must be a callback function.'); }); }); + + describe('configureLoaderRule()', () => { + it('works properly', () => { + const config = createConfig(); + const callback = (loader) => {}; + + expect(config.loaderConfigurationCallbacks['eslint']).to.not.equal(callback); + + config.configureLoaderRule('eslint', callback); + expect(config.loaderConfigurationCallbacks['eslint']).to.equal(callback); + }); + + it('Call method with a not supported loader', () => { + const config = createConfig(); + + expect(() => { + config.configureLoaderRule('vue'); + }).to.throw('Loader "vue" is not configurable. Either open an issue or a pull request.'); + }); + + it('Call method with not a valid callback', () => { + const config = createConfig(); + + expect(() => { + config.configureLoaderRule('eslint'); + }).to.throw('Argument 2 to configureLoaderRule() must be a callback function.'); + + expect(() => { + config.configureLoaderRule('eslint', {}); + }).to.throw('Argument 2 to configureLoaderRule() must be a callback function.'); + }); + }); }); diff --git a/test/loaders/eslint.js b/test/loaders/eslint.js index 1160e326..77957ede 100644 --- a/test/loaders/eslint.js +++ b/test/loaders/eslint.js @@ -12,7 +12,9 @@ const expect = require('chai').expect; const WebpackConfig = require('../../lib/WebpackConfig'); const RuntimeConfig = require('../../lib/config/RuntimeConfig'); +const configGenerator = require('../../lib/config-generator'); const eslintLoader = require('../../lib/loaders/eslint'); +const isWindows = (process.platform === 'win32'); function createConfig() { const runtimeConfig = new RuntimeConfig(); @@ -77,4 +79,29 @@ describe('loaders/eslint', () => { const actualOptions = eslintLoader.getOptions(config); expect(actualOptions).to.deep.equals({ foo: true }); }); + + it('configure ESLint loader rule', () => { + const config = createConfig(); + config.outputPath = isWindows ? 'C:\\tmp\\public' : '/tmp/public'; + config.setPublicPath('/'); + config.enableEslintLoader(); + config.configureLoaderRule('eslint', (loader) => { + loader.test = /\.(jsx?|vue)/; + }); + + const webpackConfig = configGenerator(config); + const eslintLoader = webpackConfig.module.rules.find(rule => rule.loader === 'eslint-loader'); + + expect(eslintLoader).to.deep.equals({ + test: /\.(jsx?|vue)/, + enforce: 'pre', + exclude: /node_modules/, + loader: 'eslint-loader', + options: { + cache: true, + emitWarning: true, + parser: 'babel-eslint' + } + }); + }); }); From 01880cf6b07f84d06106df8480cc32ebdc23a3db Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Sat, 2 Feb 2019 08:25:00 +0100 Subject: [PATCH 03/12] Add method on "Encore" --- index.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/index.js b/index.js index f0325126..74328532 100644 --- a/index.js +++ b/index.js @@ -1100,6 +1100,32 @@ class Encore { return this; } + /** + * Configure Webpack loaders rules (`module.rules`). + * This is a low-level function, be careful when using it. + * + * https://webpack.js.org/concepts/loaders/#configuration + * + * For example, if you are using Vue and ESLint loader, + * this is how you can configure ESLint to lint Vue files: + * + * Encore + * .enableEslintLoader() + * .enableVueLoader() + * .configureLoaderRule('eslint', (loader) => { + * loader.test = /\.(jsx?|vue)/; + * }); + * + * @param {string} name + * @param {function} callback + * @return {Encore} + */ + configureLoaderRule(name, callback) { + webpackConfig.configureLoaderRule(name, callback); + + return this; + } + /** * If enabled, the output directory is emptied between each build (to remove old files). * From bc1d025814dbc7fd5ef57b0c28203295a62025c8 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Fri, 1 Mar 2019 17:37:16 +0100 Subject: [PATCH 04/12] chore(cr): more user-friendly error for valid configurable loaders --- lib/WebpackConfig.js | 2 +- test/WebpackConfig.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index a13eb367..eb653e30 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -721,7 +721,7 @@ class WebpackConfig { configureLoaderRule(name, callback) { if (!(name in this.loaderConfigurationCallbacks)) { - throw new Error(`Loader "${name}" is not configurable. Either open an issue or a pull request.`); + throw new Error(`Loader "${name}" is not configurable. Valid loaders are "${Object.keys(this.loaderConfigurationCallbacks).join('", "')}".`); } if (typeof callback !== 'function') { diff --git a/test/WebpackConfig.js b/test/WebpackConfig.js index 8dea504b..992e73af 100644 --- a/test/WebpackConfig.js +++ b/test/WebpackConfig.js @@ -1161,7 +1161,7 @@ describe('WebpackConfig object', () => { expect(() => { config.configureLoaderRule('vue'); - }).to.throw('Loader "vue" is not configurable. Either open an issue or a pull request.'); + }).to.throw('Loader "vue" is not configurable. Valid loaders are "eslint".'); }); it('Call method with not a valid callback', () => { From bc0e9bfe7a9585f1db593c23cf94a359dd118208 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Fri, 1 Mar 2019 17:37:57 +0100 Subject: [PATCH 05/12] chore(cr): add shortcut function applyRuleConfigurationCallback --- lib/config-generator.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/config-generator.js b/lib/config-generator.js index b316be9d..e49440e5 100644 --- a/lib/config-generator.js +++ b/lib/config-generator.js @@ -221,6 +221,14 @@ class ConfigGenerator { } buildRulesConfig() { + const applyRuleConfigurationCallback = (name, defaultRules) => { + if (!(name in this.webpackConfig.loaderConfigurationCallbacks)) { + throw new Error(`Loader "${name}" is not configurable. Valid loaders are "${Object.keys(this.webpackConfig.loaderConfigurationCallbacks).join('", "')}".`); + } + + return applyOptionsCallback(this.webpackConfig.loaderConfigurationCallbacks[name], defaultRules); + }; + let rules = [ { // match .js and .jsx @@ -357,7 +365,7 @@ class ConfigGenerator { } if (this.webpackConfig.useEslintLoader) { - rules.push(applyOptionsCallback(this.webpackConfig.loaderConfigurationCallbacks['eslint'], { + rules.push(applyRuleConfigurationCallback('eslint', { test: /\.jsx?$/, loader: 'eslint-loader', exclude: /node_modules/, From 39cb9bde06f7aab1a735cd2531a5953aabc7cd71 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Fri, 1 Mar 2019 17:38:26 +0100 Subject: [PATCH 06/12] chore(cr): move tests into --- test/config-generator.js | 26 ++++++++++++++++++++++++++ test/loaders/eslint.js | 27 --------------------------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/test/config-generator.js b/test/config-generator.js index 214d1a14..67c77fbe 100644 --- a/test/config-generator.js +++ b/test/config-generator.js @@ -1014,4 +1014,30 @@ describe('The config-generator function', () => { }); }); }); + + describe('Test configureLoaderRule()', () => { + it('configure rule for "eslint"', () => { + const config = createConfig(); + config.setPublicPath('/'); + config.enableEslintLoader(); + config.configureLoaderRule('eslint', (loader) => { + loader.test = /\.(jsx?|vue)/; + }); + + const webpackConfig = configGenerator(config); + const eslintLoader = webpackConfig.module.rules.find(rule => rule.loader === 'eslint-loader'); + + expect(eslintLoader).to.deep.equals({ + test: /\.(jsx?|vue)/, + enforce: 'pre', + exclude: /node_modules/, + loader: 'eslint-loader', + options: { + cache: true, + emitWarning: true, + parser: 'babel-eslint' + } + }); + }); + }); }); diff --git a/test/loaders/eslint.js b/test/loaders/eslint.js index 77957ede..1160e326 100644 --- a/test/loaders/eslint.js +++ b/test/loaders/eslint.js @@ -12,9 +12,7 @@ const expect = require('chai').expect; const WebpackConfig = require('../../lib/WebpackConfig'); const RuntimeConfig = require('../../lib/config/RuntimeConfig'); -const configGenerator = require('../../lib/config-generator'); const eslintLoader = require('../../lib/loaders/eslint'); -const isWindows = (process.platform === 'win32'); function createConfig() { const runtimeConfig = new RuntimeConfig(); @@ -79,29 +77,4 @@ describe('loaders/eslint', () => { const actualOptions = eslintLoader.getOptions(config); expect(actualOptions).to.deep.equals({ foo: true }); }); - - it('configure ESLint loader rule', () => { - const config = createConfig(); - config.outputPath = isWindows ? 'C:\\tmp\\public' : '/tmp/public'; - config.setPublicPath('/'); - config.enableEslintLoader(); - config.configureLoaderRule('eslint', (loader) => { - loader.test = /\.(jsx?|vue)/; - }); - - const webpackConfig = configGenerator(config); - const eslintLoader = webpackConfig.module.rules.find(rule => rule.loader === 'eslint-loader'); - - expect(eslintLoader).to.deep.equals({ - test: /\.(jsx?|vue)/, - enforce: 'pre', - exclude: /node_modules/, - loader: 'eslint-loader', - options: { - cache: true, - emitWarning: true, - parser: 'babel-eslint' - } - }); - }); }); From 86dc788993d4b708ca8facc4c7aadbbc282f8b6e Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Fri, 1 Mar 2019 17:39:04 +0100 Subject: [PATCH 07/12] refactor(test): `configureLoaderRule()` will be easier to test --- test/config-generator.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/test/config-generator.js b/test/config-generator.js index 67c77fbe..ac1d4275 100644 --- a/test/config-generator.js +++ b/test/config-generator.js @@ -1016,18 +1016,26 @@ describe('The config-generator function', () => { }); describe('Test configureLoaderRule()', () => { - it('configure rule for "eslint"', () => { - const config = createConfig(); + let config; + + const getLoader = (loaderName) => { + const webpackConfig = configGenerator(config); + return webpackConfig.module.rules.find(rule => rule.loader === loaderName); + }; + + beforeEach(() => { + config = createConfig(); + config.outputPath = '/tmp/public/build'; config.setPublicPath('/'); + }); + + it('configure rule for "eslint"', () => { config.enableEslintLoader(); config.configureLoaderRule('eslint', (loader) => { loader.test = /\.(jsx?|vue)/; }); - const webpackConfig = configGenerator(config); - const eslintLoader = webpackConfig.module.rules.find(rule => rule.loader === 'eslint-loader'); - - expect(eslintLoader).to.deep.equals({ + expect(getLoader('eslint-loader')).to.deep.equals({ test: /\.(jsx?|vue)/, enforce: 'pre', exclude: /node_modules/, From 5cf3d02be375520970c34970e8d22b41dde8a129 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Fri, 1 Mar 2019 17:47:28 +0100 Subject: [PATCH 08/12] feat: add missing loaders, add more tests --- index.js | 4 +- lib/WebpackConfig.js | 15 +++- lib/config-generator.js | 50 ++++++----- test/WebpackConfig.js | 4 +- test/config-generator.js | 174 +++++++++++++++++++++++++++++++++++---- 5 files changed, 204 insertions(+), 43 deletions(-) diff --git a/index.js b/index.js index 74328532..0254ac12 100644 --- a/index.js +++ b/index.js @@ -1112,8 +1112,8 @@ class Encore { * Encore * .enableEslintLoader() * .enableVueLoader() - * .configureLoaderRule('eslint', (loader) => { - * loader.test = /\.(jsx?|vue)/; + * .configureLoaderRule('eslint', (loaderRule) => { + * loaderRule.test = /\.(jsx?|vue)/; * }); * * @param {string} name diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index eb653e30..f4fe286d 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -101,7 +101,20 @@ class WebpackConfig { this.tsConfigurationCallback = () => {}; this.handlebarsConfigurationCallback = () => {}; this.loaderConfigurationCallbacks = { - 'eslint': () => {}, + javascript: () => {}, + js: () => {}, + css: () => {}, + images: () => {}, + fonts: () => {}, + sass: () => {}, + scss: () => {}, + less: () => {}, + stylus: () => {}, + vue: () => {}, + eslint: () => {}, + typescript: () => {}, + ts: () => {}, + handlebars: () => {}, }; // Plugins options diff --git a/lib/config-generator.js b/lib/config-generator.js index e49440e5..00ab6688 100644 --- a/lib/config-generator.js +++ b/lib/config-generator.js @@ -229,14 +229,24 @@ class ConfigGenerator { return applyOptionsCallback(this.webpackConfig.loaderConfigurationCallbacks[name], defaultRules); }; + const applyRuleConfigurationCallbacks = (names, defaultRules) => { + let rules = defaultRules; + + names.forEach(name => { + rules = applyRuleConfigurationCallback(name, rules); + }); + + return rules; + }; + let rules = [ - { + applyRuleConfigurationCallbacks(['javascript', 'js'], { // match .js and .jsx test: /\.jsx?$/, exclude: this.webpackConfig.babelOptions.exclude, use: babelLoaderUtil.getLoaders(this.webpackConfig) - }, - { + }), + applyRuleConfigurationCallback('css', { test: /\.css$/, oneOf: [ { @@ -253,7 +263,7 @@ class ConfigGenerator { ) } ] - } + }) ]; if (this.webpackConfig.useImagesLoader) { @@ -277,11 +287,11 @@ class ConfigGenerator { Object.assign(loaderOptions, this.webpackConfig.urlLoaderOptions.images); } - rules.push({ + rules.push(applyRuleConfigurationCallback('images', { test: /\.(png|jpg|jpeg|gif|ico|svg|webp)$/, loader: loaderName, options: loaderOptions - }); + })); } if (this.webpackConfig.useFontsLoader) { @@ -305,15 +315,15 @@ class ConfigGenerator { Object.assign(loaderOptions, this.webpackConfig.urlLoaderOptions.fonts); } - rules.push({ + rules.push(applyRuleConfigurationCallback('fonts', { test: /\.(woff|woff2|ttf|eot|otf)$/, loader: loaderName, options: loaderOptions - }); + })); } if (this.webpackConfig.useSassLoader) { - rules.push({ + rules.push(applyRuleConfigurationCallbacks(['sass', 'scss'], { test: /\.s[ac]ss$/, oneOf: [ { @@ -324,11 +334,11 @@ class ConfigGenerator { use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, sassLoaderUtil.getLoaders(this.webpackConfig)) } ] - }); + })); } if (this.webpackConfig.useLessLoader) { - rules.push({ + rules.push(applyRuleConfigurationCallback('less', { test: /\.less/, oneOf: [ { @@ -339,11 +349,11 @@ class ConfigGenerator { use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, lessLoaderUtil.getLoaders(this.webpackConfig)) } ] - }); + })); } if (this.webpackConfig.useStylusLoader) { - rules.push({ + rules.push(applyRuleConfigurationCallback('stylus', { test: /\.styl/, oneOf: [ { @@ -354,14 +364,14 @@ class ConfigGenerator { use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, stylusLoaderUtil.getLoaders(this.webpackConfig)) } ] - }); + })); } if (this.webpackConfig.useVueLoader) { - rules.push({ + rules.push(applyRuleConfigurationCallback('vue', { test: /\.vue$/, use: vueLoaderUtil.getLoaders(this.webpackConfig) - }); + })); } if (this.webpackConfig.useEslintLoader) { @@ -375,18 +385,18 @@ class ConfigGenerator { } if (this.webpackConfig.useTypeScriptLoader) { - rules.push({ + rules.push(applyRuleConfigurationCallbacks(['typescript', 'ts'], { test: /\.tsx?$/, exclude: /node_modules/, use: tsLoaderUtil.getLoaders(this.webpackConfig) - }); + })); } if (this.webpackConfig.useHandlebarsLoader) { - rules.push({ + rules.push(applyRuleConfigurationCallback('handlebars', { test: /\.(handlebars|hbs)$/, use: handlebarsLoaderUtil.getLoaders(this.webpackConfig) - }); + })); } this.webpackConfig.loaders.forEach((loader) => { diff --git a/test/WebpackConfig.js b/test/WebpackConfig.js index 992e73af..3cfa9d4c 100644 --- a/test/WebpackConfig.js +++ b/test/WebpackConfig.js @@ -1160,8 +1160,8 @@ describe('WebpackConfig object', () => { const config = createConfig(); expect(() => { - config.configureLoaderRule('vue'); - }).to.throw('Loader "vue" is not configurable. Valid loaders are "eslint".'); + config.configureLoaderRule('reason'); + }).to.throw('Loader "reason" is not configurable. Valid loaders are "javascript", "js", "css", "images", "fonts", "sass", "scss", "less", "stylus", "vue", "eslint", "typescript", "ts", "handlebars".'); }); it('Call method with not a valid callback', () => { diff --git a/test/config-generator.js b/test/config-generator.js index ac1d4275..73a3cb50 100644 --- a/test/config-generator.js +++ b/test/config-generator.js @@ -1018,34 +1018,172 @@ describe('The config-generator function', () => { describe('Test configureLoaderRule()', () => { let config; - const getLoader = (loaderName) => { - const webpackConfig = configGenerator(config); - return webpackConfig.module.rules.find(rule => rule.loader === loaderName); - }; - beforeEach(() => { config = createConfig(); config.outputPath = '/tmp/public/build'; config.setPublicPath('/'); + config.enableSingleRuntimeChunk(); + }); + + it('configure rule for "javascript and "js"', () => { + config.configureLoaderRule('javascript', (loaderRule) => { + loaderRule.test = /\.m?js$/; + }); + config.configureLoaderRule('js', (loaderRule) => { + loaderRule.use[0].options.fooBar = 'fooBar'; + }); + + const webpackConfig = configGenerator(config); + const rule = findRule(/\.m?js$/, webpackConfig.module.rules); + + expect('file.js').to.match(rule.test); + expect('file.mjs').to.match(rule.test); + expect(rule.use[0].options.fooBar).to.equal('fooBar'); + }); + + it('configure rule for "css"', () => { + config.configureLoaderRule('css', (loaderRule) => { + loaderRule.camelCase = true; + }); + + const webpackConfig = configGenerator(config); + const rule = findRule(/\.css$/, webpackConfig.module.rules); + + expect(rule.camelCase).to.be.true; + }); + + it('configure rule for "images"', () => { + config.configureLoaderRule('images', (loaderRule) => { + loaderRule.options.name = 'dirname-images/[hash:42].[ext]'; + }); + + const webpackConfig = configGenerator(config); + const rule = findRule(/\.(png|jpg|jpeg|gif|ico|svg|webp)$/, webpackConfig.module.rules); + + expect(rule.options.name).to.equal('dirname-images/[hash:42].[ext]'); + }); + + it('configure rule for "fonts"', () => { + config.configureLoaderRule('fonts', (loader) => { + loader.options.name = 'dirname-fonts/[hash:42].[ext]'; + }); + + const webpackConfig = configGenerator(config); + const rule = findRule(/\.(woff|woff2|ttf|eot|otf)$/, webpackConfig.module.rules); + + expect(rule.options.name).to.equal('dirname-fonts/[hash:42].[ext]'); + }); + + it('configure rule for "sass" and "scss"', () => { + config.enableSassLoader(); + config.configureLoaderRule('sass', (loaderRule) => { + loaderRule.use.push('Option pushed when configuring Sass.'); + }); + config.configureLoaderRule('scss', (loaderRule) => { + loaderRule.use.push('Option pushed when configuring SCSS.'); + }); + + const webpackConfig = configGenerator(config); + const rule = findRule(/\.s[ac]ss$/, webpackConfig.module.rules); + + expect(rule.use.pop()).to.equal('Option pushed when configuring SCSS.'); + expect(rule.use.pop()).to.equal('Option pushed when configuring Sass.'); + }); + + it('configure rule for "less"', () => { + config.enableLessLoader((options) => { + options.optionA = 'optionA'; + }); + config.configureLoaderRule('less', (loaderRule) => { + loaderRule.use[2].options.optionB = 'optionB'; + }); + + const webpackConfig = configGenerator(config); + const rule = findRule(/\.less/, webpackConfig.module.rules); + + expect(rule.use[2].options.optionA).to.equal('optionA'); + expect(rule.use[2].options.optionB).to.equal('optionB'); + }); + + it('configure rule for "stylus"', () => { + config.enableStylusLoader((options) => { + options.optionA = 'optionA'; + }); + config.configureLoaderRule('stylus', (loaderRule) => { + loaderRule.use[2].options.optionB = 'optionB'; + }); + + const webpackConfig = configGenerator(config); + const rule = findRule(/\.styl/, webpackConfig.module.rules); + + expect(rule.use[2].options.optionA).to.equal('optionA'); + expect(rule.use[2].options.optionB).to.equal('optionB'); + }); + + it('configure rule for "vue"', () => { + config.enableVueLoader((options) => { + options.shadowMode = true; + }); + config.configureLoaderRule('vue', (loaderRule) => { + loaderRule.use[0].options.prettify = false; + }); + + const webpackConfig = configGenerator(config); + const rule = findRule(/\.vue$/, webpackConfig.module.rules); + + expect(rule.use[0].options.shadowMode).to.be.true; + expect(rule.use[0].options.prettify).to.be.false; }); it('configure rule for "eslint"', () => { - config.enableEslintLoader(); - config.configureLoaderRule('eslint', (loader) => { - loader.test = /\.(jsx?|vue)/; + config.enableEslintLoader((options) => { + options.extends = 'airbnb'; + }); + config.configureLoaderRule('eslint', (loaderRule) => { + loaderRule.test = /\.(jsx?|vue)/; }); - expect(getLoader('eslint-loader')).to.deep.equals({ - test: /\.(jsx?|vue)/, - enforce: 'pre', - exclude: /node_modules/, - loader: 'eslint-loader', - options: { - cache: true, - emitWarning: true, - parser: 'babel-eslint' - } + const webpackConfig = configGenerator(config); + const rule = findRule(/\.(jsx?|vue)/, webpackConfig.module.rules); + + expect(rule.options.extends).to.equal('airbnb'); + expect('file.js').to.match(rule.test); + expect('file.jsx').to.match(rule.test); + expect('file.vue').to.match(rule.test); + }); + + it('configure rule for "typescript" and "ts"', () => { + config.enableTypeScriptLoader((options) => { + options.silent = true; + }); + config.configureLoaderRule('typescript', (loaderRule) => { + loaderRule.use[1].options.happyPackMode = true; }); + config.configureLoaderRule('ts', (loaderRule) => { + loaderRule.use[1].options.logInfoToStdOut = true; + }); + + const webpackConfig = configGenerator(config); + const rule = findRule(/\.tsx?$/, webpackConfig.module.rules); + + expect(rule.use[1].options.silent).to.be.true; + expect(rule.use[1].options.happyPackMode).to.be.true; + expect(rule.use[1].options.logInfoToStdOut).to.be.true; + }); + + it('configure rule for "handlebars"', () => { + config.enableHandlebarsLoader((options) => { + options.debug = true; + }); + config.configureLoaderRule('handlebars', (loaderRule) => { + loaderRule.use[0].options.fooBar = 'fooBar'; + }); + + const webpackConfig = configGenerator(config); + const rule = findRule(/\.(handlebars|hbs)$/, webpackConfig.module.rules); + + expect(rule.use[0].options.debug).to.be.true; + expect(rule.use[0].options.fooBar).to.be.equal('fooBar'); }); }); }); From 729a696d58a1e1db9641f3155d174314f2f8e305 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Fri, 1 Mar 2019 17:47:49 +0100 Subject: [PATCH 09/12] feat: add warning --- lib/WebpackConfig.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index f4fe286d..f51b27dc 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -733,6 +733,8 @@ class WebpackConfig { } configureLoaderRule(name, callback) { + logger.warning('Be careful when using Encore.configureLoaderRule(), this is a low-level method that can potentially breaks Encore and Webpack when not used carefully.'); + if (!(name in this.loaderConfigurationCallbacks)) { throw new Error(`Loader "${name}" is not configurable. Valid loaders are "${Object.keys(this.loaderConfigurationCallbacks).join('", "')}".`); } From 4dbe443b819997ab361b822e80399c4e43c2720d Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Fri, 1 Mar 2019 17:53:21 +0100 Subject: [PATCH 10/12] chore(cr): add real aliases support --- lib/WebpackConfig.js | 16 ++++++++++---- lib/config-generator.js | 20 +++-------------- test/WebpackConfig.js | 2 +- test/config-generator.js | 47 +++++++++++++++++++++++++++++++++------- 4 files changed, 55 insertions(+), 30 deletions(-) diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index f51b27dc..82c003e0 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -102,18 +102,15 @@ class WebpackConfig { this.handlebarsConfigurationCallback = () => {}; this.loaderConfigurationCallbacks = { javascript: () => {}, - js: () => {}, css: () => {}, images: () => {}, fonts: () => {}, sass: () => {}, - scss: () => {}, less: () => {}, stylus: () => {}, vue: () => {}, eslint: () => {}, typescript: () => {}, - ts: () => {}, handlebars: () => {}, }; @@ -735,8 +732,19 @@ class WebpackConfig { configureLoaderRule(name, callback) { logger.warning('Be careful when using Encore.configureLoaderRule(), this is a low-level method that can potentially breaks Encore and Webpack when not used carefully.'); + // Key: alias, Value: existing loader in `this.loaderConfigurationCallbacks` + const aliases = { + js: 'javascript', + ts: 'typescript', + scss: 'sass', + }; + + if (name in aliases) { + name = aliases[name]; + } + if (!(name in this.loaderConfigurationCallbacks)) { - throw new Error(`Loader "${name}" is not configurable. Valid loaders are "${Object.keys(this.loaderConfigurationCallbacks).join('", "')}".`); + throw new Error(`Loader "${name}" is not configurable. Valid loaders are "${Object.keys(this.loaderConfigurationCallbacks).join('", "')}" and the aliases "${Object.keys(aliases).join('", "')}".`); } if (typeof callback !== 'function') { diff --git a/lib/config-generator.js b/lib/config-generator.js index 00ab6688..29555454 100644 --- a/lib/config-generator.js +++ b/lib/config-generator.js @@ -222,25 +222,11 @@ class ConfigGenerator { buildRulesConfig() { const applyRuleConfigurationCallback = (name, defaultRules) => { - if (!(name in this.webpackConfig.loaderConfigurationCallbacks)) { - throw new Error(`Loader "${name}" is not configurable. Valid loaders are "${Object.keys(this.webpackConfig.loaderConfigurationCallbacks).join('", "')}".`); - } - return applyOptionsCallback(this.webpackConfig.loaderConfigurationCallbacks[name], defaultRules); }; - const applyRuleConfigurationCallbacks = (names, defaultRules) => { - let rules = defaultRules; - - names.forEach(name => { - rules = applyRuleConfigurationCallback(name, rules); - }); - - return rules; - }; - let rules = [ - applyRuleConfigurationCallbacks(['javascript', 'js'], { + applyRuleConfigurationCallback('javascript', { // match .js and .jsx test: /\.jsx?$/, exclude: this.webpackConfig.babelOptions.exclude, @@ -323,7 +309,7 @@ class ConfigGenerator { } if (this.webpackConfig.useSassLoader) { - rules.push(applyRuleConfigurationCallbacks(['sass', 'scss'], { + rules.push(applyRuleConfigurationCallback('sass', { test: /\.s[ac]ss$/, oneOf: [ { @@ -385,7 +371,7 @@ class ConfigGenerator { } if (this.webpackConfig.useTypeScriptLoader) { - rules.push(applyRuleConfigurationCallbacks(['typescript', 'ts'], { + rules.push(applyRuleConfigurationCallback('typescript', { test: /\.tsx?$/, exclude: /node_modules/, use: tsLoaderUtil.getLoaders(this.webpackConfig) diff --git a/test/WebpackConfig.js b/test/WebpackConfig.js index 3cfa9d4c..17eb8f89 100644 --- a/test/WebpackConfig.js +++ b/test/WebpackConfig.js @@ -1161,7 +1161,7 @@ describe('WebpackConfig object', () => { expect(() => { config.configureLoaderRule('reason'); - }).to.throw('Loader "reason" is not configurable. Valid loaders are "javascript", "js", "css", "images", "fonts", "sass", "scss", "less", "stylus", "vue", "eslint", "typescript", "ts", "handlebars".'); + }).to.throw('Loader "reason" is not configurable. Valid loaders are "javascript", "css", "images", "fonts", "sass", "less", "stylus", "vue", "eslint", "typescript", "handlebars" and the aliases "js", "ts", "scss".'); }); it('Call method with not a valid callback', () => { diff --git a/test/config-generator.js b/test/config-generator.js index 73a3cb50..64bc7e4e 100644 --- a/test/config-generator.js +++ b/test/config-generator.js @@ -1025,11 +1025,23 @@ describe('The config-generator function', () => { config.enableSingleRuntimeChunk(); }); - it('configure rule for "javascript and "js"', () => { + it('configure rule for "javascript"', () => { config.configureLoaderRule('javascript', (loaderRule) => { loaderRule.test = /\.m?js$/; + loaderRule.use[0].options.fooBar = 'fooBar'; }); + + const webpackConfig = configGenerator(config); + const rule = findRule(/\.m?js$/, webpackConfig.module.rules); + + expect('file.js').to.match(rule.test); + expect('file.mjs').to.match(rule.test); + expect(rule.use[0].options.fooBar).to.equal('fooBar'); + }); + + it('configure rule for the alias "js"', () => { config.configureLoaderRule('js', (loaderRule) => { + loaderRule.test = /\.m?js$/; loaderRule.use[0].options.fooBar = 'fooBar'; }); @@ -1074,20 +1086,28 @@ describe('The config-generator function', () => { expect(rule.options.name).to.equal('dirname-fonts/[hash:42].[ext]'); }); - it('configure rule for "sass" and "scss"', () => { + it('configure rule for "sass"', () => { config.enableSassLoader(); config.configureLoaderRule('sass', (loaderRule) => { - loaderRule.use.push('Option pushed when configuring Sass.'); + loaderRule.use[2].options.fooBar = 'fooBar'; }); + + const webpackConfig = configGenerator(config); + const rule = findRule(/\.s[ac]ss$/, webpackConfig.module.rules); + + expect(rule.use[2].options.fooBar).to.equal('fooBar'); + }); + + it('configure rule for the alias "scss"', () => { + config.enableSassLoader(); config.configureLoaderRule('scss', (loaderRule) => { - loaderRule.use.push('Option pushed when configuring SCSS.'); + loaderRule.use[2].options.fooBar = 'fooBar'; }); const webpackConfig = configGenerator(config); const rule = findRule(/\.s[ac]ss$/, webpackConfig.module.rules); - expect(rule.use.pop()).to.equal('Option pushed when configuring SCSS.'); - expect(rule.use.pop()).to.equal('Option pushed when configuring Sass.'); + expect(rule.use[2].options.fooBar).to.equal('fooBar'); }); it('configure rule for "less"', () => { @@ -1159,8 +1179,20 @@ describe('The config-generator function', () => { config.configureLoaderRule('typescript', (loaderRule) => { loaderRule.use[1].options.happyPackMode = true; }); + + const webpackConfig = configGenerator(config); + const rule = findRule(/\.tsx?$/, webpackConfig.module.rules); + + expect(rule.use[1].options.silent).to.be.true; + expect(rule.use[1].options.happyPackMode).to.be.true; + }); + + it('configure rule for the alias "ts"', () => { + config.enableTypeScriptLoader((options) => { + options.silent = true; + }); config.configureLoaderRule('ts', (loaderRule) => { - loaderRule.use[1].options.logInfoToStdOut = true; + loaderRule.use[1].options.happyPackMode = true; }); const webpackConfig = configGenerator(config); @@ -1168,7 +1200,6 @@ describe('The config-generator function', () => { expect(rule.use[1].options.silent).to.be.true; expect(rule.use[1].options.happyPackMode).to.be.true; - expect(rule.use[1].options.logInfoToStdOut).to.be.true; }); it('configure rule for "handlebars"', () => { From 9dd6ca4eeaceb6bed0530d1bb243545d293198fc Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Fri, 1 Mar 2019 18:02:16 +0100 Subject: [PATCH 11/12] chore(tests): correct some tests due to last feature for CSSModules --- test/config-generator.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/config-generator.js b/test/config-generator.js index 64bc7e4e..110c6864 100644 --- a/test/config-generator.js +++ b/test/config-generator.js @@ -1089,25 +1089,25 @@ describe('The config-generator function', () => { it('configure rule for "sass"', () => { config.enableSassLoader(); config.configureLoaderRule('sass', (loaderRule) => { - loaderRule.use[2].options.fooBar = 'fooBar'; + loaderRule.oneOf[1].use[2].options.fooBar = 'fooBar'; }); const webpackConfig = configGenerator(config); const rule = findRule(/\.s[ac]ss$/, webpackConfig.module.rules); - expect(rule.use[2].options.fooBar).to.equal('fooBar'); + expect(rule.oneOf[1].use[2].options.fooBar).to.equal('fooBar'); }); it('configure rule for the alias "scss"', () => { config.enableSassLoader(); config.configureLoaderRule('scss', (loaderRule) => { - loaderRule.use[2].options.fooBar = 'fooBar'; + loaderRule.oneOf[1].use[2].options.fooBar = 'fooBar'; }); const webpackConfig = configGenerator(config); const rule = findRule(/\.s[ac]ss$/, webpackConfig.module.rules); - expect(rule.use[2].options.fooBar).to.equal('fooBar'); + expect(rule.oneOf[1].use[2].options.fooBar).to.equal('fooBar'); }); it('configure rule for "less"', () => { @@ -1115,14 +1115,14 @@ describe('The config-generator function', () => { options.optionA = 'optionA'; }); config.configureLoaderRule('less', (loaderRule) => { - loaderRule.use[2].options.optionB = 'optionB'; + loaderRule.oneOf[1].use[2].options.optionB = 'optionB'; }); const webpackConfig = configGenerator(config); const rule = findRule(/\.less/, webpackConfig.module.rules); - expect(rule.use[2].options.optionA).to.equal('optionA'); - expect(rule.use[2].options.optionB).to.equal('optionB'); + expect(rule.oneOf[1].use[2].options.optionA).to.equal('optionA'); + expect(rule.oneOf[1].use[2].options.optionB).to.equal('optionB'); }); it('configure rule for "stylus"', () => { @@ -1130,14 +1130,14 @@ describe('The config-generator function', () => { options.optionA = 'optionA'; }); config.configureLoaderRule('stylus', (loaderRule) => { - loaderRule.use[2].options.optionB = 'optionB'; + loaderRule.oneOf[1].use[2].options.optionB = 'optionB'; }); const webpackConfig = configGenerator(config); const rule = findRule(/\.styl/, webpackConfig.module.rules); - expect(rule.use[2].options.optionA).to.equal('optionA'); - expect(rule.use[2].options.optionB).to.equal('optionB'); + expect(rule.oneOf[1].use[2].options.optionA).to.equal('optionA'); + expect(rule.oneOf[1].use[2].options.optionB).to.equal('optionB'); }); it('configure rule for "vue"', () => { From 94da0c213c0fa175ed0421c8655d8ab67df775a6 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 25 Mar 2019 19:13:17 -0400 Subject: [PATCH 12/12] language tweak --- lib/WebpackConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index 82c003e0..1dfe85ba 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -730,7 +730,7 @@ class WebpackConfig { } configureLoaderRule(name, callback) { - logger.warning('Be careful when using Encore.configureLoaderRule(), this is a low-level method that can potentially breaks Encore and Webpack when not used carefully.'); + logger.warning('Be careful when using Encore.configureLoaderRule(), this is a low-level method that can potentially break Encore and Webpack when not used carefully.'); // Key: alias, Value: existing loader in `this.loaderConfigurationCallbacks` const aliases = {