From a056954278598960a584d53dd54b5aa1d1752683 Mon Sep 17 00:00:00 2001 From: homer0 Date: Wed, 28 Feb 2018 07:43:09 -0300 Subject: [PATCH] Add support for the new node target output settings --- src/services/building/configuration.js | 13 +- .../nodeDevelopmentConfiguration.js | 5 +- .../nodeProductionConfiguration.js | 3 + .../configurations/rulesConfiguration.js | 247 +++++---- tests/services/building/configuration.test.js | 33 +- .../nodeDevelopmentConfiguration.test.js | 5 + .../nodeProductionConfiguration.test.js | 4 + .../configurations/rulesConfiguration.test.js | 516 +++++++++++++++--- 8 files changed, 645 insertions(+), 181 deletions(-) diff --git a/src/services/building/configuration.js b/src/services/building/configuration.js index a5cbb31..99e5d6a 100644 --- a/src/services/building/configuration.js +++ b/src/services/building/configuration.js @@ -74,17 +74,6 @@ class WebpackConfiguration { return definitions; } - /** - * Generate the output paths for a target files. - * @param {Target} target The target information. - * @param {string} buildType The intended build type: `production` or `development`. - * @return {WebpackConfigurationTargetOutput} - */ - getOutput(target, buildType) { - return target.is.node ? - { js: target.output[buildType] } : - Object.assign({}, target.output[buildType]); - } /** * In case the target is a library, this method will be called to generate the library options * for Webpack. @@ -125,7 +114,7 @@ class WebpackConfiguration { [target.name]: entries, }, definitions: this.getDefinitions(target, buildType), - output: this.getOutput(target, buildType), + output: target.output[buildType], buildType, }; diff --git a/src/services/configurations/nodeDevelopmentConfiguration.js b/src/services/configurations/nodeDevelopmentConfiguration.js index 13a1316..5f88ec0 100644 --- a/src/services/configurations/nodeDevelopmentConfiguration.js +++ b/src/services/configurations/nodeDevelopmentConfiguration.js @@ -1,4 +1,5 @@ const webpackNodeUtils = require('webpack-node-utils'); +const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const { NoEmitOnErrorsPlugin, } = require('webpack'); @@ -54,8 +55,10 @@ class WebpackNodeDevelopmentConfiguration extends ConfigurationFile { const plugins = [ // To avoid pushing assets with errors. new NoEmitOnErrorsPlugin(), + // To optimize the SCSS and remove repeated declarations. + new OptimizeCssAssetsPlugin(), ]; - // If the target needsto run on development... + // If the target needs to run on development... if (target.runOnDevelopment) { // ...watch the source files. watch = true; diff --git a/src/services/configurations/nodeProductionConfiguration.js b/src/services/configurations/nodeProductionConfiguration.js index 9a342ef..4aa694a 100644 --- a/src/services/configurations/nodeProductionConfiguration.js +++ b/src/services/configurations/nodeProductionConfiguration.js @@ -1,4 +1,5 @@ const webpackNodeUtils = require('webpack-node-utils'); +const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const { NoEmitOnErrorsPlugin, } = require('webpack'); @@ -59,6 +60,8 @@ class WebpackNodeProductionConfiguration extends ConfigurationFile { plugins: [ // To avoid pushing assets with errors. new NoEmitOnErrorsPlugin(), + // To optimize the SCSS and remove repeated declarations. + new OptimizeCssAssetsPlugin(), ], target: 'node', node: { diff --git a/src/services/configurations/rulesConfiguration.js b/src/services/configurations/rulesConfiguration.js index 6303fdf..17633a3 100644 --- a/src/services/configurations/rulesConfiguration.js +++ b/src/services/configurations/rulesConfiguration.js @@ -30,6 +30,10 @@ class WebpackRulesConfiguration extends ConfigurationFile { } /** * Creates the rules configuration for the required target. + * This method uses the reducer events `webpack-rules-configuration-for-node` or + * `webpack-rules-configuration-for-browser`, depending on the target type, and then + * `webpack-rules-configuration`. The event receives the configuration object, the `params` and + * it expects an updated configuration object on return. * @param {WebpackConfigurationParams} params A dictionary generated by the top service building * the configuration and that includes things like the * target information, its entry settings, output @@ -38,40 +42,6 @@ class WebpackRulesConfiguration extends ConfigurationFile { * @property {Array} rules The list of rules */ createConfig(params) { - const rules = params.target.is.node ? - this.createNodeConfig(params) : - this.createBrowserConfig(params); - - return this.events.reduce('webpack-rules-configuration', { rules }, params); - } - /** - * Creates the rules configuration for a Node target. - * This method uses the reducer event `webpack-rules-configuration-for-node`, which sends - * the rules, the `params` and expects a rules list on return. - * @param {WebpackConfigurationParams} params A dictionary generated by the top service building - * the configuration and that includes things like the - * target information, its entry settings, output - * paths, etc. - * @return {Array} - */ - createNodeConfig(params) { - const rules = [ - ...this.getJSRules(params), - ]; - - return this.events.reduce('webpack-rules-configuration-for-node', rules, params); - } - /** - * Creates the rules configuration for a browser target. - * This method uses the reducer event `webpack-rules-configuration-for-browser`, which sends - * the rules, the `params` and expects a rules list on return. - * @param {WebpackConfigurationParams} params A dictionary generated by the top service building - * the configuration and that includes things like the - * target information, its entry settings, output - * paths, etc. - * @return {Array} - */ - createBrowserConfig(params) { const rules = [ ...this.getJSRules(params), ...this.getSCSSRules(params), @@ -82,13 +52,21 @@ class WebpackRulesConfiguration extends ConfigurationFile { ...this.getFaviconsRules(params), ]; - return this.events.reduce('webpack-rules-configuration-for-browser', rules, params); + const eventName = params.target.is.node ? + 'webpack-rules-configuration-for-node' : + 'webpack-rules-configuration-for-browser'; + return this._reduceConfig( + [eventName, 'webpack-rules-configuration'], + { rules }, + params + ); } /** * Defines the list of rules for Javascript files. - * This method uses the reducer event `webpack-js-rules-configuration-for-browser` and - * `webpack-js-rules-configuration-for-node` depending on the target type. The event receives - * the rules, the `params` and expects a rules list on return. + * This method uses the reducer event `webpack-js-rules-configuration-for-browser` or + * `webpack-js-rules-configuration-for-node`, depending on the target type, and then + * `webpack-js-rules-configuration`. The event receives the rules, the `params` and expects a + * rules list on return. * @param {WebpackConfigurationParams} params A dictionary generated by the top service building * the configuration and that includes things like the * target information, its entry settings, output @@ -97,9 +75,6 @@ class WebpackRulesConfiguration extends ConfigurationFile { */ getJSRules(params) { const { target } = params; - const eventName = target.is.node ? - 'webpack-js-rules-configuration-for-node' : - 'webpack-js-rules-configuration-for-browser'; const rules = [{ test: /\.jsx?$/i, // Only check for files on the target source directory and the configurations folder. @@ -114,12 +89,21 @@ class WebpackRulesConfiguration extends ConfigurationFile { }], }]; // Reduce the rules. - return this.events.reduce(eventName, rules, params); + const eventName = target.is.node ? + 'webpack-js-rules-configuration-for-node' : + 'webpack-js-rules-configuration-for-browser'; + return this._reduceConfig( + [eventName, 'webpack-js-rules-configuration'], + rules, + params + ); } /** * Define the list of rules for SCSS stylesheets. - * This method uses the reducer event `webpack-scss-rules-configuration-for-browser`, which - * sends the rules, the `params` and expects a rules list on return. + * This method uses the reducer event `webpack-scss-rules-configuration-for-browser` or + * `webpack-scss-rules-configuration-for-node`, depending on the target type, and then + * `webpack-scss-rules-configuration`. The event receives the rules, the `params` and expects a + * rules list on return. * @param {WebpackConfigurationParams} params A dictionary generated by the top service building * the configuration and that includes things like the * target information, its entry settings, output @@ -140,40 +124,56 @@ class WebpackRulesConfiguration extends ConfigurationFile { cssLoaderConfig.localIdentName = '[name]__[local]___[hash:base64:5]'; } + let eventName = 'webpack-scss-rules-configuration-for-node'; + let use = [ + { + loader: 'css-loader', + query: cssLoaderConfig, + }, + 'resolve-url-loader', + { + loader: 'sass-loader', + options: { + /** + * This is necessary for the `resolve-url-loader` to be able to find and fix the + * relative paths for font files. + */ + sourceMap: true, + outputStyle: 'expanded', + includePaths: ['node_modules'], + }, + }, + ]; + if (params.target.is.browser) { + eventName = 'webpack-scss-rules-configuration-for-browser'; + /** + * Wrap the loaders settings on the the plugin that extracts all the stylesheets on a + * single file. + */ + use = ExtractTextPlugin.extract({ + fallback: 'style-loader', + use, + }); + } + const rules = [{ test: /\.scss$/i, exclude: /node_modules/, - // As main loader, it uses the plugin to extract and push all the stylesheets on a same file. - use: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: [ - { - loader: 'css-loader', - query: cssLoaderConfig, - }, - 'resolve-url-loader', - { - loader: 'sass-loader', - options: { - /** - * This is necessary for the `resolve-url-loader` to be able to find and fix the - * relative paths for font files. - */ - sourceMap: true, - outputStyle: 'expanded', - includePaths: ['node_modules'], - }, - }, - ], - }), + use, }]; // Reduce the rules. - return this.events.reduce('webpack-scss-rules-configuration-for-browser', rules, params); + return this._reduceConfig( + [eventName, 'webpack-scss-rules-configuration'], + rules, + params + ); } /** * Define the list of rules for CSS stylesheets. - * This method uses the reducer event `webpack-css-rules-configuration-for-browser`, which - * sends the rules, the `params` and expects a rules list on return. + * This method uses the reducer event `webpack-css-rules-configuration-for-browser` or + * `webpack-css-rules-configuration-for-node`, depending on the target type, and then + * `webpack-css-rules-configuration`. The event receives the rules, the `params` and expects a + * rules list on return. * @param {WebpackConfigurationParams} params A dictionary generated by the top service building * the configuration and that includes things like the * target information, its entry settings, output @@ -181,26 +181,40 @@ class WebpackRulesConfiguration extends ConfigurationFile { * @return {Array} */ getCSSRules(params) { - const rules = [{ - test: /\.css$/i, + let eventName = 'webpack-css-rules-configuration-for-node'; + let use = [ + 'css-loader', + ]; + + if (params.target.is.browser) { + eventName = 'webpack-css-rules-configuration-for-browser'; /** - * Like for SCSS files, this also uses the plugin to extract and push all the stylesheets on - * a same file. + * Wrap the loaders settings on the the plugin that extracts all the stylesheets on a + * single file. */ - use: ExtractTextPlugin.extract({ + use = ExtractTextPlugin.extract({ fallback: 'style-loader', - use: [ - 'css-loader', - ], - }), + use, + }); + } + + const rules = [{ + test: /\.css$/i, + use, }]; // Reduce the rules. - return this.events.reduce('webpack-css-rules-configuration-for-browser', rules, params); + return this._reduceConfig( + [eventName, 'webpack-css-rules-configuration'], + rules, + params + ); } /** * Define the list of rules for HTML files. - * This method uses the reducer event `webpack-html-rules-configuration-for-browser`, which - * sends the rules, the `params` and expects a rules list on return. + * This method uses the reducer event `webpack-html-rules-configuration-for-browser` or + * `webpack-html-rules-configuration-for-node`, depending on the target type, and then + * `webpack-html-rules-configuration`. The event receives the rules, the `params` and expects a + * rules list on return. * @param {WebpackConfigurationParams} params A dictionary generated by the top service building * the configuration and that includes things like the * target information, its entry settings, output @@ -218,12 +232,21 @@ class WebpackRulesConfiguration extends ConfigurationFile { ], }]; // Reduce the rules. - return this.events.reduce('webpack-html-rules-configuration-for-browser', rules, params); + const eventName = params.target.is.node ? + 'webpack-html-rules-configuration-for-node' : + 'webpack-html-rules-configuration-for-browser'; + return this._reduceConfig( + [eventName, 'webpack-html-rules-configuration'], + rules, + params + ); } /** * Define the list of rules for font files. - * This method uses the reducer event `webpack-fonts-rules-configuration-for-browser`, which - * sends the rules, the `params` and expects a rules list on return. + * This method uses the reducer event `webpack-fonts-rules-configuration-for-browser` or + * `webpack-fonts-rules-configuration-for-node`, depending on the target type, and then + * `webpack-fonts-rules-configuration`. The event receives the rules, the `params` and expects a + * rules list on return. * @param {WebpackConfigurationParams} params A dictionary generated by the top service building * the configuration and that includes things like the * target information, its entry settings, output @@ -293,12 +316,21 @@ class WebpackRulesConfiguration extends ConfigurationFile { }, ]; // Reduce the rules. - return this.events.reduce('webpack-fonts-rules-configuration-for-browser', rules, params); + const eventName = params.target.is.node ? + 'webpack-fonts-rules-configuration-for-node' : + 'webpack-fonts-rules-configuration-for-browser'; + return this._reduceConfig( + [eventName, 'webpack-fonts-rules-configuration'], + rules, + params + ); } /** * Define the list of rules for images files. - * This method uses the reducer event `webpack-images-rules-configuration-for-browser`, which - * sends the rules, the `params` and expects a rules list on return. + * This method uses the reducer event `webpack-images-rules-configuration-for-browser` or + * `webpack-images-rules-configuration-for-node`, depending on the target type, and then + * `webpack-images-rules-configuration`. The event receives the rules, the `params` and expects a + * rules list on return. * @param {WebpackConfigurationParams} params A dictionary generated by the top service building * the configuration and that includes things like the * target information, its entry settings, output @@ -344,15 +376,24 @@ class WebpackRulesConfiguration extends ConfigurationFile { ], }]; // Reduce the rules. - return this.events.reduce('webpack-images-rules-configuration-for-browser', rules, params); + const eventName = params.target.is.node ? + 'webpack-images-rules-configuration-for-node' : + 'webpack-images-rules-configuration-for-browser'; + return this._reduceConfig( + [eventName, 'webpack-images-rules-configuration'], + rules, + params + ); } /** * Define the list of rules for the favicons file. * The reason this is not with the images rules is because favicons need to be on the root * directory for the browser to automatically detect them, and they only include optimization * options for `png`. - * This method uses the reducer event `webpack-favicons-rules-configuration-for-browser`, which - * sends the rules, the `params` and expects a rules list on return. + * This method uses the reducer event `webpack-favicons-rules-configuration-for-browser` or + * `webpack-favicons-rules-configuration-for-node`, depending on the target type, and then + * `webpack-favicons-rules-configuration`. The event receives the rules, the `params` and expects + * a rules list on return. * @param {WebpackConfigurationParams} params A dictionary generated by the top service building * the configuration and that includes things like the * target information, its entry settings, output @@ -387,12 +428,32 @@ class WebpackRulesConfiguration extends ConfigurationFile { ], }]; // Reduce the rules. - return this.events.reduce( - 'webpack-favicons-rules-configuration-for-browser', + const eventName = params.target.is.node ? + 'webpack-favicons-rules-configuration-for-node' : + 'webpack-favicons-rules-configuration-for-browser'; + return this._reduceConfig( + [eventName, 'webpack-favicons-rules-configuration'], rules, params ); } + /** + * Processes a list of reducer events for rules configurations. + * @param {Array} events A list of events names. + * @param {Object|Array} config The configuration to reduce. + * @param {WebpackConfigurationParams} params A dictionary generated by the top service building + * the configuration and that includes things like the + * target information, its entry settings, output + * paths, etc. + * @return {Object|Array} + * @todo Remove this once `EventsHub` adds support for it. + */ + _reduceConfig(events, config, params) { + return events.reduce( + (currentConfig, eventName) => this.events.reduce(eventName, currentConfig, params), + config + ); + } } /** * The service provider that once registered on the app container will set an instance of diff --git a/tests/services/building/configuration.test.js b/tests/services/building/configuration.test.js index fe95a4c..0fba020 100644 --- a/tests/services/building/configuration.test.js +++ b/tests/services/building/configuration.test.js @@ -125,7 +125,12 @@ describe('services/building:configuration', () => { [buildType]: 'index.js', }, output: { - [buildType]: 'target.js', + [buildType]: { + js: 'target.js', + css: 'css/target/file.2509.css', + fonts: 'fonts/target/[name].2509.[ext]', + images: 'images/target/[name].2509.[ext]', + }, }, babel: {}, library: false, @@ -174,9 +179,7 @@ describe('services/building:configuration', () => { 'process.env.NODE_ENV': `'${buildType}'`, [versionVariable]: `"${version}"`, }, - output: { - js: target.output[buildType], - }, + output: target.output[buildType], }); expect(pathUtils.join).toHaveBeenCalledTimes(1); expect(pathUtils.join).toHaveBeenCalledWith(config.output.path); @@ -408,7 +411,12 @@ describe('services/building:configuration', () => { [buildType]: 'index.js', }, output: { - [buildType]: 'target.js', + [buildType]: { + js: 'target.js', + css: 'css/target/file.2509.css', + fonts: 'fonts/target/[name].2509.[ext]', + images: 'images/target/[name].2509.[ext]', + }, }, babel: { polyfill: true, @@ -462,9 +470,7 @@ describe('services/building:configuration', () => { 'process.env.NODE_ENV': `'${buildType}'`, [versionVariable]: `"${version}"`, }, - output: { - js: target.output[buildType], - }, + output: target.output[buildType], }); expect(pathUtils.join).toHaveBeenCalledTimes(1); expect(pathUtils.join).toHaveBeenCalledWith(config.output.path); @@ -502,7 +508,12 @@ describe('services/building:configuration', () => { [buildType]: 'index.js', }, output: { - [buildType]: 'target.js', + [buildType]: { + js: 'target.js', + css: 'css/target/file.2509.css', + fonts: 'fonts/target/[name].2509.[ext]', + images: 'images/target/[name].2509.[ext]', + }, }, babel: {}, library: true, @@ -558,9 +569,7 @@ describe('services/building:configuration', () => { 'process.env.NODE_ENV': `'${buildType}'`, [versionVariable]: `"${version}"`, }, - output: { - js: target.output[buildType], - }, + output: target.output[buildType], }); expect(pathUtils.join).toHaveBeenCalledTimes(1); expect(pathUtils.join).toHaveBeenCalledWith(config.output.path); diff --git a/tests/services/configurations/nodeDevelopmentConfiguration.test.js b/tests/services/configurations/nodeDevelopmentConfiguration.test.js index ff5dc92..77b19a9 100644 --- a/tests/services/configurations/nodeDevelopmentConfiguration.test.js +++ b/tests/services/configurations/nodeDevelopmentConfiguration.test.js @@ -7,9 +7,11 @@ jest.mock('jimple', () => JimpleMock); jest.mock('webpack', () => webpackMock); jest.mock('webpack-node-utils', () => webpackNodeUtilsMock); jest.mock('/src/abstracts/configurationFile', () => ConfigurationFileMock); +jest.mock('optimize-css-assets-webpack-plugin'); jest.unmock('/src/services/configurations/nodeDevelopmentConfiguration'); require('jasmine-expect'); +const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const { WebpackNodeDevelopmentConfiguration, @@ -21,6 +23,7 @@ describe('services/configurations:nodeDevelopmentConfiguration', () => { ConfigurationFileMock.reset(); webpackMock.reset(); webpackNodeUtilsMock.reset(); + OptimizeCssAssetsPlugin.mockReset(); }); it('should be instantiated with all its dependencies', () => { @@ -101,6 +104,7 @@ describe('services/configurations:nodeDevelopmentConfiguration', () => { // Then expect(result).toEqual(expectedConfig); expect(webpackMock.NoEmitOnErrorsPluginMock).toHaveBeenCalledTimes(1); + expect(OptimizeCssAssetsPlugin).toHaveBeenCalledTimes(1); expect(webpackNodeUtilsMock.WebpackNodeUtilsRunnerMockMock) .toHaveBeenCalledTimes(0); expect(events.reduce).toHaveBeenCalledTimes(1); @@ -166,6 +170,7 @@ describe('services/configurations:nodeDevelopmentConfiguration', () => { // Then expect(result).toEqual(expectedConfig); expect(webpackMock.NoEmitOnErrorsPluginMock).toHaveBeenCalledTimes(1); + expect(OptimizeCssAssetsPlugin).toHaveBeenCalledTimes(1); expect(webpackNodeUtilsMock.WebpackNodeUtilsRunnerMockMock) .toHaveBeenCalledTimes(1); expect(webpackNodeUtilsMock.externals).toHaveBeenCalledTimes(1); diff --git a/tests/services/configurations/nodeProductionConfiguration.test.js b/tests/services/configurations/nodeProductionConfiguration.test.js index 4945d28..441e2fe 100644 --- a/tests/services/configurations/nodeProductionConfiguration.test.js +++ b/tests/services/configurations/nodeProductionConfiguration.test.js @@ -7,9 +7,11 @@ jest.mock('jimple', () => JimpleMock); jest.mock('webpack', () => webpackMock); jest.mock('webpack-node-utils', () => webpackNodeUtilsMock); jest.mock('/src/abstracts/configurationFile', () => ConfigurationFileMock); +jest.mock('optimize-css-assets-webpack-plugin'); jest.unmock('/src/services/configurations/nodeProductionConfiguration'); require('jasmine-expect'); +const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const { WebpackNodeProductionConfiguration, @@ -21,6 +23,7 @@ describe('services/configurations:nodeProductionConfiguration', () => { ConfigurationFileMock.reset(); webpackMock.reset(); webpackNodeUtilsMock.reset(); + OptimizeCssAssetsPlugin.mockReset(); }); it('should be instantiated with all its dependencies', () => { @@ -97,6 +100,7 @@ describe('services/configurations:nodeProductionConfiguration', () => { // Then expect(result).toEqual(expectedConfig); expect(webpackMock.NoEmitOnErrorsPluginMock).toHaveBeenCalledTimes(1); + expect(OptimizeCssAssetsPlugin).toHaveBeenCalledTimes(1); expect(webpackNodeUtilsMock.externals).toHaveBeenCalledTimes(1); expect(events.reduce).toHaveBeenCalledTimes(1); expect(events.reduce).toHaveBeenCalledWith( diff --git a/tests/services/configurations/rulesConfiguration.test.js b/tests/services/configurations/rulesConfiguration.test.js index b4e55bf..840119d 100644 --- a/tests/services/configurations/rulesConfiguration.test.js +++ b/tests/services/configurations/rulesConfiguration.test.js @@ -20,11 +20,14 @@ describe('services/configurations:rulesConfiguration', () => { extractResult = '', targetOutput = null ) => { + // Define the dictionary of paths for the static files. const output = targetOutput || { images: expect.any(String), fonts: expect.any(String), }; + // Define the rules expectations... const rules = {}; + // - JS Rules rules.jsRules = [{ test: expect.any(RegExp), include: [ @@ -36,53 +39,77 @@ describe('services/configurations:rulesConfiguration', () => { options: babelConfig, }], }]; + // - SCSS Rules + const scssUse = [ + { + loader: 'css-loader', + query: expect.any(Object), + }, + 'resolve-url-loader', + { + loader: 'sass-loader', + options: expect.any(Object), + }, + ]; + const scssUseWithModules = [ + { + loader: 'css-loader', + query: { + importRules: 2, + modules: true, + localIdentName: '[name]__[local]___[hash:base64:5]', + }, + }, + 'resolve-url-loader', + { + loader: 'sass-loader', + options: expect.any(Object), + }, + ]; + // - - Extract options rules.scssExtractOptions = { fallback: expect.any(String), - use: [ - { - loader: 'css-loader', - query: expect.any(Object), - }, - 'resolve-url-loader', - { - loader: 'sass-loader', - options: expect.any(Object), - }, - ], + use: scssUse, }; rules.scssExtractOptionsWithModules = { fallback: expect.any(String), - use: [ - { - loader: 'css-loader', - query: { - importRules: 2, - modules: true, - localIdentName: '[name]__[local]___[hash:base64:5]', - }, - }, - 'resolve-url-loader', - { - loader: 'sass-loader', - options: expect.any(Object), - }, - ], + use: scssUseWithModules, }; + // - - Rules + rules.scssRulesForBrowser = [{ + test: expect.any(RegExp), + exclude: expect.any(RegExp), + use: extractResult, + }]; + rules.scssRulesForNode = [{ + test: expect.any(RegExp), + exclude: expect.any(RegExp), + use: scssUse, + }]; + rules.scssRulesForNodeWithModules = [{ + test: expect.any(RegExp), + exclude: expect.any(RegExp), + use: scssUseWithModules, + }]; + // - CSS Rules + const cssUse = [ + 'css-loader', + ]; + // - - Extract options rules.cssExtractOptions = { fallback: 'style-loader', - use: [ - 'css-loader', - ], + use: cssUse, }; - rules.scssRules = [{ + // - - Rules + rules.cssRulesForBrowser = [{ test: expect.any(RegExp), - exclude: expect.any(RegExp), use: extractResult, }]; - rules.cssRules = [{ + rules.cssRulesForNode = [{ test: expect.any(RegExp), - use: extractResult, + use: cssUse, }]; + // - HTML Rules rules.htmlRules = [{ test: expect.any(RegExp), exclude: expect.any(RegExp), @@ -90,6 +117,7 @@ describe('services/configurations:rulesConfiguration', () => { 'raw-loader', ], }]; + // - Fonts Rules rules.fontsRules = [ { test: expect.any(RegExp), @@ -142,6 +170,7 @@ describe('services/configurations:rulesConfiguration', () => { }], }, ]; + // - Images Rules rules.imagesRules = [ { test: expect.any(RegExp), @@ -161,6 +190,7 @@ describe('services/configurations:rulesConfiguration', () => { ], }, ]; + // - Favicon Rules rules.faviconRules = [ { test: expect.any(RegExp), @@ -214,6 +244,10 @@ describe('services/configurations:rulesConfiguration', () => { it('should return the rules for a node target', () => { // Given + const output = { + fonts: 'statics/fonts/[name].[ext]', + images: 'statics/images/[name].[ext]', + }; const version = 'latest'; const babelConfig = 'babel'; const babelConfiguration = { @@ -234,13 +268,15 @@ describe('services/configurations:rulesConfiguration', () => { }, is: { node: true, + browser: false, }, }; const params = { target, version, + output, }; - const expectedRules = getExpectedRules(target, babelConfig); + const expectedRules = getExpectedRules(target, babelConfig, '', output); let sut = null; let result = null; // When @@ -256,7 +292,20 @@ describe('services/configurations:rulesConfiguration', () => { rules: expect.any(Array), }); expect(events.reduce).toHaveBeenCalledTimes([ + 'getJSRulesForNode', 'getJSRules', + 'getSCSSRulesForNode', + 'getSCSSRules', + 'getCSSRulesForNode', + 'getCSSRules', + 'getHTMLRulesForNode', + 'getHTMLRules', + 'getFontsRulesForNode', + 'getFontsRules', + 'getImagesRulesForNode', + 'getImagesRules', + 'getFaviconsRulesForNode', + 'getFaviconsRules', 'createNodeConfig', 'createConfig', ].length); @@ -265,11 +314,84 @@ describe('services/configurations:rulesConfiguration', () => { expectedRules.jsRules, params ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-js-rules-configuration', + expectedRules.jsRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-scss-rules-configuration-for-node', + expectedRules.scssRulesForNode, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-scss-rules-configuration', + expectedRules.scssRulesForNode, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-css-rules-configuration-for-node', + expectedRules.cssRulesForNode, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-css-rules-configuration', + expectedRules.cssRulesForNode, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-html-rules-configuration-for-node', + expectedRules.htmlRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-html-rules-configuration', + expectedRules.htmlRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-fonts-rules-configuration-for-node', + expectedRules.fontsRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-fonts-rules-configuration', + expectedRules.fontsRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-images-rules-configuration-for-node', + expectedRules.imagesRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-images-rules-configuration', + expectedRules.imagesRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-favicons-rules-configuration-for-node', + expectedRules.faviconRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-favicons-rules-configuration', + expectedRules.faviconRules, + params + ); expect(events.reduce).toHaveBeenCalledWith( 'webpack-rules-configuration-for-node', - [ - ...expectedRules.jsRules, - ], + { + rules: [ + ...expectedRules.jsRules, + ...expectedRules.scssRulesForNode, + ...expectedRules.cssRulesForNode, + ...expectedRules.htmlRules, + ...expectedRules.fontsRules, + ...expectedRules.imagesRules, + ...expectedRules.faviconRules, + ], + }, params ); expect(events.reduce).toHaveBeenCalledWith( @@ -277,6 +399,184 @@ describe('services/configurations:rulesConfiguration', () => { { rules: [ ...expectedRules.jsRules, + ...expectedRules.scssRulesForNode, + ...expectedRules.cssRulesForNode, + ...expectedRules.htmlRules, + ...expectedRules.fontsRules, + ...expectedRules.imagesRules, + ...expectedRules.faviconRules, + ], + }, + params + ); + expect(pathUtils.join).toHaveBeenCalledTimes(1); + expect(pathUtils.join).toHaveBeenCalledWith('config'); + }); + + it('should return the rules for a node target that uses CSS Modules', () => { + // Given + const output = { + fonts: 'statics/fonts/[name].[ext]', + images: 'statics/images/[name].[ext]', + }; + const version = 'latest'; + const babelConfig = 'babel'; + const babelConfiguration = { + getConfigForTarget: jest.fn(() => babelConfig), + }; + const events = { + reduce: jest.fn((eventName, rules) => rules), + }; + const pathUtils = { + join: jest.fn((rest) => rest), + }; + const projectConfiguration = 'projectConfiguration'; + const targetName = 'some-target'; + const target = { + name: targetName, + folders: { + source: 'src/target', + }, + is: { + node: true, + browser: false, + }, + CSSModules: true, + }; + const params = { + target, + version, + output, + }; + const expectedRules = getExpectedRules(target, babelConfig, '', output); + let sut = null; + let result = null; + // When + sut = new WebpackRulesConfiguration( + babelConfiguration, + events, + pathUtils, + projectConfiguration + ); + result = sut.getConfig(params); + // Then + expect(result).toEqual({ + rules: expect.any(Array), + }); + expect(events.reduce).toHaveBeenCalledTimes([ + 'getJSRulesForNode', + 'getJSRules', + 'getSCSSRulesForNode', + 'getSCSSRules', + 'getCSSRulesForNode', + 'getCSSRules', + 'getHTMLRulesForNode', + 'getHTMLRules', + 'getFontsRulesForNode', + 'getFontsRules', + 'getImagesRulesForNode', + 'getImagesRules', + 'getFaviconsRulesForNode', + 'getFaviconsRules', + 'createNodeConfig', + 'createConfig', + ].length); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-js-rules-configuration-for-node', + expectedRules.jsRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-js-rules-configuration', + expectedRules.jsRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-scss-rules-configuration-for-node', + expectedRules.scssRulesForNodeWithModules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-scss-rules-configuration', + expectedRules.scssRulesForNodeWithModules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-css-rules-configuration-for-node', + expectedRules.cssRulesForNode, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-css-rules-configuration', + expectedRules.cssRulesForNode, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-html-rules-configuration-for-node', + expectedRules.htmlRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-html-rules-configuration', + expectedRules.htmlRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-fonts-rules-configuration-for-node', + expectedRules.fontsRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-fonts-rules-configuration', + expectedRules.fontsRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-images-rules-configuration-for-node', + expectedRules.imagesRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-images-rules-configuration', + expectedRules.imagesRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-favicons-rules-configuration-for-node', + expectedRules.faviconRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-favicons-rules-configuration', + expectedRules.faviconRules, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-rules-configuration-for-node', + { + rules: [ + ...expectedRules.jsRules, + ...expectedRules.scssRulesForNodeWithModules, + ...expectedRules.cssRulesForNode, + ...expectedRules.htmlRules, + ...expectedRules.fontsRules, + ...expectedRules.imagesRules, + ...expectedRules.faviconRules, + ], + }, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-rules-configuration', + { + rules: [ + ...expectedRules.jsRules, + ...expectedRules.scssRulesForNodeWithModules, + ...expectedRules.cssRulesForNode, + ...expectedRules.htmlRules, + ...expectedRules.fontsRules, + ...expectedRules.imagesRules, + ...expectedRules.faviconRules, ], }, params @@ -312,6 +612,7 @@ describe('services/configurations:rulesConfiguration', () => { }, is: { node: false, + browser: true, }, }; const params = { @@ -339,12 +640,19 @@ describe('services/configurations:rulesConfiguration', () => { rules: expect.any(Array), }); expect(events.reduce).toHaveBeenCalledTimes([ + 'getJSRulesForBrowser', 'getJSRules', + 'getSCSSRulesForBrowser', 'getSCSSRules', + 'getCSSRulesForBrowser', 'getCSSRules', + 'getHTMLRulesForBrowser', 'getHTMLRules', + 'getFontsRulesForBrowser', 'getFontsRules', + 'getImagesRulesForBrowser', 'getImagesRules', + 'getFaviconsRulesForBrowser', 'getFaviconsRules', 'createBrowserConfig', 'createConfig', @@ -354,14 +662,29 @@ describe('services/configurations:rulesConfiguration', () => { expectedRules.jsRules, params ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-js-rules-configuration', + expectedRules.jsRules, + params + ); expect(events.reduce).toHaveBeenCalledWith( 'webpack-scss-rules-configuration-for-browser', - expectedRules.scssRules, + expectedRules.scssRulesForBrowser, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-scss-rules-configuration', + expectedRules.scssRulesForBrowser, params ); expect(events.reduce).toHaveBeenCalledWith( 'webpack-css-rules-configuration-for-browser', - expectedRules.cssRules, + expectedRules.cssRulesForBrowser, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-css-rules-configuration', + expectedRules.cssRulesForBrowser, params ); expect(events.reduce).toHaveBeenCalledWith( @@ -369,32 +692,54 @@ describe('services/configurations:rulesConfiguration', () => { expectedRules.htmlRules, params ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-html-rules-configuration', + expectedRules.htmlRules, + params + ); expect(events.reduce).toHaveBeenCalledWith( 'webpack-fonts-rules-configuration-for-browser', expectedRules.fontsRules, params ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-fonts-rules-configuration', + expectedRules.fontsRules, + params + ); expect(events.reduce).toHaveBeenCalledWith( 'webpack-images-rules-configuration-for-browser', expectedRules.imagesRules, params ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-images-rules-configuration', + expectedRules.imagesRules, + params + ); expect(events.reduce).toHaveBeenCalledWith( 'webpack-favicons-rules-configuration-for-browser', expectedRules.faviconRules, params ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-favicons-rules-configuration', + expectedRules.faviconRules, + params + ); expect(events.reduce).toHaveBeenCalledWith( 'webpack-rules-configuration-for-browser', - [ - ...expectedRules.jsRules, - ...expectedRules.scssRules, - ...expectedRules.cssRules, - ...expectedRules.htmlRules, - ...expectedRules.fontsRules, - ...expectedRules.imagesRules, - ...expectedRules.faviconRules, - ], + { + rules: [ + ...expectedRules.jsRules, + ...expectedRules.scssRulesForBrowser, + ...expectedRules.cssRulesForBrowser, + ...expectedRules.htmlRules, + ...expectedRules.fontsRules, + ...expectedRules.imagesRules, + ...expectedRules.faviconRules, + ], + }, params ); expect(events.reduce).toHaveBeenCalledWith( @@ -402,8 +747,8 @@ describe('services/configurations:rulesConfiguration', () => { { rules: [ ...expectedRules.jsRules, - ...expectedRules.scssRules, - ...expectedRules.cssRules, + ...expectedRules.scssRulesForBrowser, + ...expectedRules.cssRulesForBrowser, ...expectedRules.htmlRules, ...expectedRules.fontsRules, ...expectedRules.imagesRules, @@ -454,6 +799,7 @@ describe('services/configurations:rulesConfiguration', () => { }, is: { node: false, + browser: true, }, CSSModules: true, }; @@ -483,12 +829,19 @@ describe('services/configurations:rulesConfiguration', () => { rules: expect.any(Array), }); expect(events.reduce).toHaveBeenCalledTimes([ + 'getJSRulesForBrowser', 'getJSRules', + 'getSCSSRulesForBrowser', 'getSCSSRules', + 'getCSSRulesForBrowser', 'getCSSRules', + 'getHTMLRulesForBrowser', 'getHTMLRules', + 'getFontsRulesForBrowser', 'getFontsRules', + 'getImagesRulesForBrowser', 'getImagesRules', + 'getFaviconsRulesForBrowser', 'getFaviconsRules', 'createBrowserConfig', 'createConfig', @@ -498,14 +851,29 @@ describe('services/configurations:rulesConfiguration', () => { expectedRules.jsRules, params ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-js-rules-configuration', + expectedRules.jsRules, + params + ); expect(events.reduce).toHaveBeenCalledWith( 'webpack-scss-rules-configuration-for-browser', - expectedRules.scssRules, + expectedRules.scssRulesForBrowser, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-scss-rules-configuration', + expectedRules.scssRulesForBrowser, params ); expect(events.reduce).toHaveBeenCalledWith( 'webpack-css-rules-configuration-for-browser', - expectedRules.cssRules, + expectedRules.cssRulesForBrowser, + params + ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-css-rules-configuration', + expectedRules.cssRulesForBrowser, params ); expect(events.reduce).toHaveBeenCalledWith( @@ -513,32 +881,54 @@ describe('services/configurations:rulesConfiguration', () => { expectedRules.htmlRules, params ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-html-rules-configuration', + expectedRules.htmlRules, + params + ); expect(events.reduce).toHaveBeenCalledWith( 'webpack-fonts-rules-configuration-for-browser', expectedRules.fontsRules, params ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-fonts-rules-configuration', + expectedRules.fontsRules, + params + ); expect(events.reduce).toHaveBeenCalledWith( 'webpack-images-rules-configuration-for-browser', expectedRules.imagesRules, params ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-images-rules-configuration', + expectedRules.imagesRules, + params + ); expect(events.reduce).toHaveBeenCalledWith( 'webpack-favicons-rules-configuration-for-browser', expectedRules.faviconRules, params ); + expect(events.reduce).toHaveBeenCalledWith( + 'webpack-favicons-rules-configuration', + expectedRules.faviconRules, + params + ); expect(events.reduce).toHaveBeenCalledWith( 'webpack-rules-configuration-for-browser', - [ - ...expectedRules.jsRules, - ...expectedRules.scssRules, - ...expectedRules.cssRules, - ...expectedRules.htmlRules, - ...expectedRules.fontsRules, - ...expectedRules.imagesRules, - ...expectedRules.faviconRules, - ], + { + rules: [ + ...expectedRules.jsRules, + ...expectedRules.scssRulesForBrowser, + ...expectedRules.cssRulesForBrowser, + ...expectedRules.htmlRules, + ...expectedRules.fontsRules, + ...expectedRules.imagesRules, + ...expectedRules.faviconRules, + ], + }, params ); expect(events.reduce).toHaveBeenCalledWith( @@ -546,8 +936,8 @@ describe('services/configurations:rulesConfiguration', () => { { rules: [ ...expectedRules.jsRules, - ...expectedRules.scssRules, - ...expectedRules.cssRules, + ...expectedRules.scssRulesForBrowser, + ...expectedRules.cssRulesForBrowser, ...expectedRules.htmlRules, ...expectedRules.fontsRules, ...expectedRules.imagesRules,