From 1834aefd0c0e5049dd006f3a820d082bca07e761 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Fri, 14 Jul 2017 17:25:33 +0200 Subject: [PATCH 01/68] Fix issue incorrect paths for fonts. Add methods to Use relative paths. --- fixtures/css/style_with_fonts.scss | 9 + fixtures/fonts/Roboto.svg | 643 +++++++++++++++++++++++++++++ index.js | 12 + lib/WebpackConfig.js | 38 ++ lib/config-generator.js | 4 +- test/functional.js | 45 ++ 6 files changed, 749 insertions(+), 2 deletions(-) create mode 100644 fixtures/css/style_with_fonts.scss create mode 100644 fixtures/fonts/Roboto.svg diff --git a/fixtures/css/style_with_fonts.scss b/fixtures/css/style_with_fonts.scss new file mode 100644 index 00000000..590236ea --- /dev/null +++ b/fixtures/css/style_with_fonts.scss @@ -0,0 +1,9 @@ +$font-path: '../fonts'; + +@font-face { + font-family: 'Roboto'; + src:url('#{$font-path}/Roboto.woff2') format('woff2'), + url('#{$font-path}/Roboto.svg') format('svg'); + font-weight: normal; + font-style: normal; +} diff --git a/fixtures/fonts/Roboto.svg b/fixtures/fonts/Roboto.svg new file mode 100644 index 00000000..417a2a9e --- /dev/null +++ b/fixtures/fonts/Roboto.svg @@ -0,0 +1,643 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/index.js b/index.js index 9eea6a31..188ce2e7 100644 --- a/index.js +++ b/index.js @@ -62,6 +62,18 @@ module.exports = { return this; }, + setFontsPublicPath(publicPath) { + webpackConfig.setFontsPublicPath(publicPath); + + return this; + }, + + setImagesPublicPath(publicPath) { + webpackConfig.setImagesPublicPath(publicPath); + + return this; + }, + /** * Used as a prefix to the *keys* in manifest.json. Not usually needed. * diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index 79458ca6..16b638b9 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -33,6 +33,8 @@ class WebpackConfig { this.runtimeConfig = runtimeConfig; this.outputPath = null; this.publicPath = null; + this.imagesPublicPath = null; + this.fontsPublicPath = null; this.manifestKeyPrefix = null; this.entries = new Map(); this.styleEntries = new Map(); @@ -112,6 +114,22 @@ class WebpackConfig { this.publicPath = publicPath; } + setFontsPublicPath(publicPath) { + // guarantee a single trailing slash + publicPath = publicPath.replace(/\/$/,''); + publicPath = publicPath + '/'; + + this.fontsPublicPath = publicPath; + } + + setImagesPublicPath(publicPath) { + // guarantee a single trailing slash + publicPath = publicPath.replace(/\/$/,''); + publicPath = publicPath + '/'; + + this.imagesPublicPath = publicPath; + } + setManifestKeyPrefix(manifestKeyPrefix) { // guarantee a single trailing slash, except for blank strings if (manifestKeyPrefix !== '') { @@ -138,6 +156,26 @@ class WebpackConfig { return this.publicPath; } + getFontsPublicPath() { + // if we're using webpack-dev-server, use it & add the publicPath + if (this.useDevServer()) { + // avoid 2 middle slashes + return this.runtimeConfig.devServerUrl.replace(/\/$/,'') + this.publicPath; + } else { + return this.fontsPublicPath; + } + } + + getImagesPublicPath() { + // if we're using webpack-dev-server, use it & add the publicPath + if (this.useDevServer()) { + // avoid 2 middle slashes + return this.runtimeConfig.devServerUrl.replace(/\/$/,'') + this.publicPath; + } else { + return this.imagesPublicPath; + } + } + addEntry(name, src) { if (this.entries.has(name)) { throw new Error(`Duplicate name "${name}" passed to addEntry(): entries must be unique.`); diff --git a/lib/config-generator.js b/lib/config-generator.js index 26fca25c..b1b3f83d 100644 --- a/lib/config-generator.js +++ b/lib/config-generator.js @@ -128,7 +128,7 @@ class ConfigGenerator { loader: 'file-loader', options: { name: `images/[name]${this.webpackConfig.useVersioning ? '.[hash]' : ''}.[ext]`, - publicPath: this.webpackConfig.getRealPublicPath() + publicPath: this.webpackConfig.getImagesPublicPath() } }, { @@ -136,7 +136,7 @@ class ConfigGenerator { loader: 'file-loader', options: { name: `fonts/[name]${this.webpackConfig.useVersioning ? '.[hash]' : ''}.[ext]`, - publicPath: this.webpackConfig.getRealPublicPath() + publicPath: this.webpackConfig.getFontsPublicPath() } }, ]; diff --git a/test/functional.js b/test/functional.js index 5f839206..ddb2cfff 100644 --- a/test/functional.js +++ b/test/functional.js @@ -351,6 +351,51 @@ describe('Functional tests using webpack', function() { }); }); + it('setFontsPublicPath() generates relativePath for fonts', (done) => { + const config = createWebpackConfig('www/build', 'dev'); + config.setPublicPath('/build'); + config.addStyleEntry('css/style', './css/style_with_fonts.scss'); + config.setFontsPublicPath('../'); + config.enableSassLoader(); + + testSetup.runWebpack(config, (webpackAssert) => { + webpackAssert.assertManifestPath( + 'build/css/style.css', + '/build/css/style.css' + ); + + webpackAssert.assertOutputFileContains( + 'css/style.css', + 'src: url(../fonts/Roboto.woff2) format("woff2"), url(/build/images/Roboto.svg) format("svg");' + ); + + done(); + }); + }); + + it('setImagesPublicPath() generates relativePath for images', (done) => { + const config = createWebpackConfig('www/build', 'dev'); + config.setPublicPath('/build'); + config.addStyleEntry('css/style', './css/style_with_fonts.scss'); + config.setFontsPublicPath('../'); + config.setImagesPublicPath('../'); + config.enableSassLoader(); + + testSetup.runWebpack(config, (webpackAssert) => { + webpackAssert.assertManifestPath( + 'build/css/style.css', + '/build/css/style.css' + ); + + webpackAssert.assertOutputFileContains( + 'css/style.css', + 'src: url(../fonts/Roboto.woff2) format("woff2"), url(../images/Roboto.svg) format("svg");' + ); + + done(); + }); + }); + it('enableSourceMaps() adds to .js, css & scss', (done) => { const config = createWebpackConfig('www/build', 'dev'); config.setPublicPath('/build'); From 69f039c681d7c4ec9a05784a46609491f115ad41 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Fri, 14 Jul 2017 17:38:49 +0200 Subject: [PATCH 02/68] Fix lint (spaces instead of tabs) --- lib/WebpackConfig.js | 8 ++++---- test/functional.js | 26 +++++++++++++------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index 16b638b9..5c3c0d94 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -162,8 +162,8 @@ class WebpackConfig { // avoid 2 middle slashes return this.runtimeConfig.devServerUrl.replace(/\/$/,'') + this.publicPath; } else { - return this.fontsPublicPath; - } + return this.fontsPublicPath; + } } getImagesPublicPath() { @@ -172,8 +172,8 @@ class WebpackConfig { // avoid 2 middle slashes return this.runtimeConfig.devServerUrl.replace(/\/$/,'') + this.publicPath; } else { - return this.imagesPublicPath; - } + return this.imagesPublicPath; + } } addEntry(name, src) { diff --git a/test/functional.js b/test/functional.js index ddb2cfff..7d9f006e 100644 --- a/test/functional.js +++ b/test/functional.js @@ -351,50 +351,50 @@ describe('Functional tests using webpack', function() { }); }); - it('setFontsPublicPath() generates relativePath for fonts', (done) => { + it('setFontsPublicPath() generates relativePath for fonts', (done) => { const config = createWebpackConfig('www/build', 'dev'); config.setPublicPath('/build'); config.addStyleEntry('css/style', './css/style_with_fonts.scss'); - config.setFontsPublicPath('../'); - config.enableSassLoader(); + config.setFontsPublicPath('../'); + config.enableSassLoader(); testSetup.runWebpack(config, (webpackAssert) => { - webpackAssert.assertManifestPath( + webpackAssert.assertManifestPath( 'build/css/style.css', '/build/css/style.css' ); webpackAssert.assertOutputFileContains( 'css/style.css', - 'src: url(../fonts/Roboto.woff2) format("woff2"), url(/build/images/Roboto.svg) format("svg");' + 'src: url(../fonts/Roboto.woff2) format("woff2"), url(/build/images/Roboto.svg) format("svg");' ); done(); }); - }); + }); - it('setImagesPublicPath() generates relativePath for images', (done) => { + it('setImagesPublicPath() generates relativePath for images', (done) => { const config = createWebpackConfig('www/build', 'dev'); config.setPublicPath('/build'); config.addStyleEntry('css/style', './css/style_with_fonts.scss'); - config.setFontsPublicPath('../'); - config.setImagesPublicPath('../'); - config.enableSassLoader(); + config.setFontsPublicPath('../'); + config.setImagesPublicPath('../'); + config.enableSassLoader(); testSetup.runWebpack(config, (webpackAssert) => { - webpackAssert.assertManifestPath( + webpackAssert.assertManifestPath( 'build/css/style.css', '/build/css/style.css' ); webpackAssert.assertOutputFileContains( 'css/style.css', - 'src: url(../fonts/Roboto.woff2) format("woff2"), url(../images/Roboto.svg) format("svg");' + 'src: url(../fonts/Roboto.woff2) format("woff2"), url(../images/Roboto.svg) format("svg");' ); done(); }); - }); + }); it('enableSourceMaps() adds to .js, css & scss', (done) => { const config = createWebpackConfig('www/build', 'dev'); From 0bdb0b5bdf8fab77738b87f39954a8299e202b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFC=20DIRINGER?= Date: Wed, 19 Jul 2017 16:01:28 +0200 Subject: [PATCH 03/68] Fix missing tsloader return statement --- index.js | 2 + test/index.js | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 test/index.js diff --git a/index.js b/index.js index 188ce2e7..2a00eef1 100644 --- a/index.js +++ b/index.js @@ -384,6 +384,8 @@ module.exports = { */ enableTypeScriptLoader(callback = () => {}) { webpackConfig.enableTypeScriptLoader(callback); + + return this; }, /** diff --git a/test/index.js b/test/index.js new file mode 100644 index 00000000..d1d2b444 --- /dev/null +++ b/test/index.js @@ -0,0 +1,206 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const expect = require('chai').expect; +require('../lib/context').runtimeConfig = {}; +const api = require('../index'); + +describe('Public API', () => { + + describe('setOutputPath', () => { + + it('must return the API object', () => { + const returnedValue = api.setOutputPath('/'); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('setPublicPath', () => { + + it('must return the API object', () => { + const returnedValue = api.setPublicPath('/'); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('setManifestKeyPrefix', () => { + + it('must return the API object', () => { + const returnedValue = api.setManifestKeyPrefix('/build'); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('addEntry', () => { + + it('must return the API object', () => { + const returnedValue = api.addEntry('entry', 'main.js'); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('addStyleEntry', () => { + + it('must return the API object', () => { + const returnedValue = api.addStyleEntry('styleEntry', 'main.css'); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('addPlugin', () => { + + it('must return the API object', () => { + const returnedValue = api.addPlugin(null); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('addLoader', () => { + + it('must return the API object', () => { + const returnedValue = api.addLoader(null); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('addRule', () => { + + it('must return the API object', () => { + const returnedValue = api.addRule(null); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('enableVersioning', () => { + + it('must return the API object', () => { + const returnedValue = api.enableVersioning(); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('enableSourceMaps', () => { + + it('must return the API object', () => { + const returnedValue = api.enableSourceMaps(); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('createSharedEntry', () => { + + it('must return the API object', () => { + const returnedValue = api.createSharedEntry('sharedEntry', 'vendor.js'); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('autoProvideVariables', () => { + + it('must return the API object', () => { + const returnedValue = api.autoProvideVariables({}); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('autoProvidejQuery', () => { + + it('must return the API object', () => { + const returnedValue = api.autoProvidejQuery(); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('enablePostCssLoader', () => { + + it('must return the API object', () => { + const returnedValue = api.enablePostCssLoader(); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('enableSassLoader', () => { + + it('must return the API object', () => { + const returnedValue = api.enableSassLoader(); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('enableLessLoader', () => { + + it('must return the API object', () => { + const returnedValue = api.enableLessLoader(); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('setOutputPath', () => { + + it('must return the API object', () => { + const returnedValue = api.configureBabel(() => {}); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('enableReactPreset', () => { + + it('must return the API object', () => { + const returnedValue = api.enableReactPreset(); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('enableTypeScriptLoader', () => { + + it('must return the API object', () => { + const returnedValue = api.enableTypeScriptLoader(); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('enableVueLoader', () => { + + it('must return the API object', () => { + const returnedValue = api.enableVueLoader(); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('cleanupOutputBeforeBuild', () => { + + it('must return the API object', () => { + const returnedValue = api.cleanupOutputBeforeBuild(); + expect(returnedValue).to.equal(api); + }); + + }); +}); From 9efc3c2c71ea2ebd87cc6b792f5af2c5e31edaaf Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 20 Jul 2017 20:09:07 -0400 Subject: [PATCH 04/68] Tagging with webpack 3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8fe1c8a7..adb95c49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@symfony/webpack-encore", - "version": "0.10.0", + "version": "0.11.0", "description": "", "main": "index.js", "scripts": { From 62afb630cb666fc8ca81d37515ff0e3f79795da6 Mon Sep 17 00:00:00 2001 From: robertfausk Date: Fri, 30 Jun 2017 14:09:28 +0200 Subject: [PATCH 05/68] convert error into warning when using dev-server and an absolute URL for publicPath to get webpack encore working in docker environment --- lib/WebpackConfig.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index 5c3c0d94..4a1478ca 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -9,6 +9,7 @@ 'use strict'; +const chalk = require('chalk'); const path = require('path'); const fs = require('fs'); @@ -96,7 +97,8 @@ class WebpackConfig { */ if (publicPath.includes('://')) { if (this.useDevServer()) { - throw new Error('You cannot pass an absolute URL to setPublicPath() and use the dev-server at the same time. Try using Encore.isProduction() to only configure your absolute publicPath for production.'); + console.log(chalk.bgYellow.black(' WARNING ') + chalk.yellow(' You should not pass an absolute URL to setPublicPath() and use the dev-server at the same time')); + console.log(); } } else { if (publicPath.indexOf('/') !== 0) { From 5ed6374e86faeaa43de65f6995ae866b14ea6185 Mon Sep 17 00:00:00 2001 From: robertfausk Date: Fri, 30 Jun 2017 14:35:24 +0200 Subject: [PATCH 06/68] convert error into warning test for successful config set when using devServer and setPublicPath with an absolute URL --- test/WebpackConfig.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/WebpackConfig.js b/test/WebpackConfig.js index 68d14b94..293e6678 100644 --- a/test/WebpackConfig.js +++ b/test/WebpackConfig.js @@ -105,13 +105,12 @@ describe('WebpackConfig object', () => { }).to.throw('The value passed to setPublicPath() must start with "/"'); }); - it('Setting to a URL when using devServer throws an error', () => { + it('You can set to a URL when using devServer', () => { const config = createConfig(); config.runtimeConfig.useDevServer = true; + config.setPublicPath('https://examplecdn.com'); - expect(() => { - config.setPublicPath('https://examplecdn.com'); - }).to.throw('You cannot pass an absolute URL to setPublicPath() and use the dev-server'); + expect(config.publicPath).to.equal('https://examplecdn.com/'); }); }); From bdbbd840d38f7617d8fd8b636bf32b7e26c9a6a2 Mon Sep 17 00:00:00 2001 From: robertfausk Date: Fri, 30 Jun 2017 15:32:22 +0200 Subject: [PATCH 07/68] fix issue in generated public path of manifest.json - when using devServer and absolute URL for publicPath - also add test case --- lib/WebpackConfig.js | 4 ++-- test/config-generator.js | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index 4a1478ca..630af568 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -149,8 +149,8 @@ class WebpackConfig { * @returns {string} */ getRealPublicPath() { - // if we're using webpack-dev-server, use it & add the publicPath - if (this.useDevServer()) { + // if we're using webpack-dev-server and have no absolute url, use it & add the publicPath + if (this.useDevServer() && !this.publicPath.includes('://')) { // avoid 2 middle slashes return this.runtimeConfig.devServerUrl.replace(/\/$/,'') + this.publicPath; } diff --git a/test/config-generator.js b/test/config-generator.js index 9d5b739e..059a8a21 100644 --- a/test/config-generator.js +++ b/test/config-generator.js @@ -375,6 +375,23 @@ describe('The config-generator function', () => { expect(actualConfig.devServer).to.be.undefined; }); + it('devServer and an absolute URL as publicPath', () => { + const config = createConfig(); + config.runtimeConfig.useDevServer = true; + config.runtimeConfig.devServerUrl = 'http://localhost:8080/'; + config.outputPath = isWindows ? 'C:\\tmp\\public' : '/tmp/public'; + config.setManifestKeyPrefix('public'); + config.setPublicPath('https://cdn.example.com'); + config.addEntry('main', './main'); + + const actualConfig = configGenerator(config); + expect(actualConfig.output.publicPath).to.equal('https://cdn.example.com/'); + expect(actualConfig.devServer).to.not.be.undefined; + + const manifestPlugin = findPlugin(ManifestPlugin, actualConfig.plugins); + expect(manifestPlugin.opts.publicPath).to.equal('https://cdn.example.com/'); + }); + it('devServer no hot mode', () => { const config = createConfig(); config.runtimeConfig.useDevServer = true; From d8238c083d6916ce92d70ec60c37a0faa5f0348e Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 12 Jul 2017 20:41:28 -0400 Subject: [PATCH 08/68] Reversing some of the changes we won't do for now, and adding the failing test --- lib/WebpackConfig.js | 8 +++---- test/WebpackConfig.js | 46 +++++++++++++++++++++++++++++++++++++--- test/config-generator.js | 17 --------------- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index 630af568..5c3c0d94 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -9,7 +9,6 @@ 'use strict'; -const chalk = require('chalk'); const path = require('path'); const fs = require('fs'); @@ -97,8 +96,7 @@ class WebpackConfig { */ if (publicPath.includes('://')) { if (this.useDevServer()) { - console.log(chalk.bgYellow.black(' WARNING ') + chalk.yellow(' You should not pass an absolute URL to setPublicPath() and use the dev-server at the same time')); - console.log(); + throw new Error('You cannot pass an absolute URL to setPublicPath() and use the dev-server at the same time. Try using Encore.isProduction() to only configure your absolute publicPath for production.'); } } else { if (publicPath.indexOf('/') !== 0) { @@ -149,8 +147,8 @@ class WebpackConfig { * @returns {string} */ getRealPublicPath() { - // if we're using webpack-dev-server and have no absolute url, use it & add the publicPath - if (this.useDevServer() && !this.publicPath.includes('://')) { + // if we're using webpack-dev-server, use it & add the publicPath + if (this.useDevServer()) { // avoid 2 middle slashes return this.runtimeConfig.devServerUrl.replace(/\/$/,'') + this.publicPath; } diff --git a/test/WebpackConfig.js b/test/WebpackConfig.js index 293e6678..90a981e6 100644 --- a/test/WebpackConfig.js +++ b/test/WebpackConfig.js @@ -105,12 +105,52 @@ describe('WebpackConfig object', () => { }).to.throw('The value passed to setPublicPath() must start with "/"'); }); - it('You can set to a URL when using devServer', () => { + it('Setting to a URL when using devServer throws an error', () => { const config = createConfig(); config.runtimeConfig.useDevServer = true; - config.setPublicPath('https://examplecdn.com'); - expect(config.publicPath).to.equal('https://examplecdn.com/'); + expect(() => { + config.setPublicPath('https://examplecdn.com'); + }).to.throw('You cannot pass an absolute URL to setPublicPath() and use the dev-server'); + }); + }); + + describe('getRealPublicPath', () => { + it('Returns normal with no dev server', () => { + const config = createConfig(); + config.setPublicPath('/public'); + + expect(config.getRealPublicPath()).to.equal('/public/'); + }); + + it('Prefix when using devServer', () => { + const config = createConfig(); + config.runtimeConfig.useDevServer = true; + config.runtimeConfig.devServerUrl = 'http://localhost:8080/'; + config.setPublicPath('/public'); + + expect(config.getRealPublicPath()).to.equal('http://localhost:8080/public/'); + }); + + it('No prefix with devServer & devServerKeepPublicPath option', () => { + const config = createConfig(); + config.runtimeConfig.useDevServer = true; + config.runtimeConfig.devServerUrl = 'http://localhost:8080/'; + config.runtimeConfig.devServerKeepPublicPath = true; + config.setPublicPath('/public'); + + expect(config.getRealPublicPath()).to.equal('/public/'); + }); + + it('devServer & devServerKeepPublicPath option allows absolute publicPath', () => { + const config = createConfig(); + config.runtimeConfig.useDevServer = true; + config.runtimeConfig.devServerUrl = 'http://localhost:8080/'; + config.runtimeConfig.devServerKeepPublicPath = true; + config.setPublicPath('http://coolcdn.com/public'); + config.setManifestKeyPrefix('/public/'); + + expect(config.getRealPublicPath()).to.equal('http://coolcdn.com/public'); }); }); diff --git a/test/config-generator.js b/test/config-generator.js index 059a8a21..9d5b739e 100644 --- a/test/config-generator.js +++ b/test/config-generator.js @@ -375,23 +375,6 @@ describe('The config-generator function', () => { expect(actualConfig.devServer).to.be.undefined; }); - it('devServer and an absolute URL as publicPath', () => { - const config = createConfig(); - config.runtimeConfig.useDevServer = true; - config.runtimeConfig.devServerUrl = 'http://localhost:8080/'; - config.outputPath = isWindows ? 'C:\\tmp\\public' : '/tmp/public'; - config.setManifestKeyPrefix('public'); - config.setPublicPath('https://cdn.example.com'); - config.addEntry('main', './main'); - - const actualConfig = configGenerator(config); - expect(actualConfig.output.publicPath).to.equal('https://cdn.example.com/'); - expect(actualConfig.devServer).to.not.be.undefined; - - const manifestPlugin = findPlugin(ManifestPlugin, actualConfig.plugins); - expect(manifestPlugin.opts.publicPath).to.equal('https://cdn.example.com/'); - }); - it('devServer no hot mode', () => { const config = createConfig(); config.runtimeConfig.useDevServer = true; From afc8daec228b9e4b43a3c30a08c9188fffa28056 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 21 Jul 2017 16:47:58 -0400 Subject: [PATCH 09/68] Fixing a bug where @import CSS files were not put through the autoprefixer --- fixtures/css/imports_autoprefixer.css | 1 + lib/loaders/css.js | 11 +++++++++-- test/functional.js | 4 +++- 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 fixtures/css/imports_autoprefixer.css diff --git a/fixtures/css/imports_autoprefixer.css b/fixtures/css/imports_autoprefixer.css new file mode 100644 index 00000000..4dce6d71 --- /dev/null +++ b/fixtures/css/imports_autoprefixer.css @@ -0,0 +1 @@ +@import "autoprefixer_test.css"; \ No newline at end of file diff --git a/lib/loaders/css.js b/lib/loaders/css.js index 002e2e46..231f16d2 100644 --- a/lib/loaders/css.js +++ b/lib/loaders/css.js @@ -18,17 +18,24 @@ const loaderFeatures = require('../loader-features'); */ module.exports = { getLoaders(webpackConfig, skipPostCssLoader) { + const usePostCssLoader = webpackConfig.usePostCssLoader && !skipPostCssLoader; + const cssLoaders = [ { loader: 'css-loader', options: { minimize: webpackConfig.isProduction(), - sourceMap: webpackConfig.useSourceMaps + sourceMap: webpackConfig.useSourceMaps, + // when using @import, how many loaders *before* css-loader should + // be applied to those imports? This defaults to 0. When postcss-loader + // is used, we set it to 1, so that postcss-loader is applied + // to @import resources. + importLoaders: usePostCssLoader ? 1 : 0 } }, ]; - if (webpackConfig.usePostCssLoader && !skipPostCssLoader) { + if (usePostCssLoader) { loaderFeatures.ensureLoaderPackagesExist('postcss'); cssLoaders.push({ diff --git a/test/functional.js b/test/functional.js index 7d9f006e..7912dc69 100644 --- a/test/functional.js +++ b/test/functional.js @@ -528,7 +528,9 @@ module.exports = { const config = testSetup.createWebpackConfig(appDir, 'www/build', 'dev'); config.setPublicPath('/build'); - config.addStyleEntry('styles', ['./css/autoprefixer_test.css']); + // load a file that @import's another file, so that we can + // test that @import resources are parsed through postcss + config.addStyleEntry('styles', ['./css/imports_autoprefixer.css']); config.enablePostCssLoader(); testSetup.runWebpack(config, (webpackAssert) => { From 54b3ecb77f915731d93b8856618068e904ad77b3 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 12 Jul 2017 20:54:33 -0400 Subject: [PATCH 10/68] Adding --keep-public-path to dev-server to allow you to fully control the publicPath --- bin/encore.js | 3 +++ lib/WebpackConfig.js | 6 +++--- lib/config/RuntimeConfig.js | 1 + lib/config/parse-runtime.js | 1 + test/WebpackConfig.js | 2 +- test/config/parse-runtime.js | 9 +++++++++ 6 files changed, 18 insertions(+), 4 deletions(-) diff --git a/bin/encore.js b/bin/encore.js index 73087d4f..6ab9613a 100755 --- a/bin/encore.js +++ b/bin/encore.js @@ -62,11 +62,14 @@ function showUsageInstructions() { console.log('Commands:'); console.log(` ${chalk.green('dev')} : runs webpack for development`); console.log(' - Supports any webpack options (e.g. --watch)'); + console.log(); console.log(` ${chalk.green('dev-server')} : runs webpack-dev-server`); console.log(` - ${chalk.yellow('--host')} The hostname/ip address the webpack-dev-server will bind to`); console.log(` - ${chalk.yellow('--port')} The port the webpack-dev-server will bind to`); console.log(` - ${chalk.yellow('--hot')} Enable HMR on webpack-dev-server`); + console.log(` - ${chalk.yellow('--keep-public-path')} Do not change the public path (it is usually prefixed by the dev server URL)`); console.log(' - Supports any webpack-dev-server options'); + console.log(); console.log(` ${chalk.green('production')} : runs webpack for production`); console.log(' - Supports any webpack options (e.g. --watch)'); console.log(); diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index 5c3c0d94..cdcfc6c8 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -95,8 +95,8 @@ class WebpackConfig { * is simply used as the default manifestKeyPrefix. */ if (publicPath.includes('://')) { - if (this.useDevServer()) { - throw new Error('You cannot pass an absolute URL to setPublicPath() and use the dev-server at the same time. Try using Encore.isProduction() to only configure your absolute publicPath for production.'); + if (this.useDevServer() && false === this.runtimeConfig.devServerKeepPublicPath) { + throw new Error('You cannot pass an absolute URL to setPublicPath() and use the dev-server at the same time. This is because the public path is automatically set to point to the dev server. Try using Encore.isProduction() to only configure your absolute publicPath for production. Or, if you want to override this behavior, pass the --keep-public-path option to allow this.'); } } else { if (publicPath.indexOf('/') !== 0) { @@ -148,7 +148,7 @@ class WebpackConfig { */ getRealPublicPath() { // if we're using webpack-dev-server, use it & add the publicPath - if (this.useDevServer()) { + if (this.useDevServer() && false === this.runtimeConfig.devServerKeepPublicPath) { // avoid 2 middle slashes return this.runtimeConfig.devServerUrl.replace(/\/$/,'') + this.publicPath; } diff --git a/lib/config/RuntimeConfig.js b/lib/config/RuntimeConfig.js index 37ed2ee9..4b035ac5 100644 --- a/lib/config/RuntimeConfig.js +++ b/lib/config/RuntimeConfig.js @@ -19,6 +19,7 @@ class RuntimeConfig { this.useDevServer = null; this.devServerUrl = null; this.devServerHttps = null; + this.devServerKeepPublicPath = false; this.useHotModuleReplacement = null; this.babelRcFileExists = null; diff --git a/lib/config/parse-runtime.js b/lib/config/parse-runtime.js index 7295a560..99cde4a9 100644 --- a/lib/config/parse-runtime.js +++ b/lib/config/parse-runtime.js @@ -40,6 +40,7 @@ module.exports = function(argv, cwd) { runtimeConfig.useDevServer = true; runtimeConfig.devServerHttps = argv.https; runtimeConfig.useHotModuleReplacement = argv.hot || false; + runtimeConfig.devServerKeepPublicPath = argv.keepPublicPath || false; var host = argv.host ? argv.host : 'localhost'; var port = argv.port ? argv.port : '8080'; diff --git a/test/WebpackConfig.js b/test/WebpackConfig.js index 90a981e6..27a31641 100644 --- a/test/WebpackConfig.js +++ b/test/WebpackConfig.js @@ -150,7 +150,7 @@ describe('WebpackConfig object', () => { config.setPublicPath('http://coolcdn.com/public'); config.setManifestKeyPrefix('/public/'); - expect(config.getRealPublicPath()).to.equal('http://coolcdn.com/public'); + expect(config.getRealPublicPath()).to.equal('http://coolcdn.com/public/'); }); }); diff --git a/test/config/parse-runtime.js b/test/config/parse-runtime.js index b28f6410..817af584 100644 --- a/test/config/parse-runtime.js +++ b/test/config/parse-runtime.js @@ -69,6 +69,7 @@ describe('parse-runtime', () => { expect(config.useDevServer).to.be.true; expect(config.devServerUrl).to.equal('http://localhost:8080/'); expect(config.useHotModuleReplacement).to.be.false; + expect(config.devServerKeepPublicPath).to.be.false; }); it('dev-server command with options', () => { @@ -114,4 +115,12 @@ describe('parse-runtime', () => { expect(config.useDevServer).to.be.true; expect(config.useHotModuleReplacement).to.be.true; }); + + it('dev-server command --keep-public-path', () => { + const testDir = createTestDirectory(); + const config = parseArgv(createArgv(['dev-server', '--keep-public-path']), testDir); + + expect(config.useDevServer).to.be.true; + expect(config.devServerKeepPublicPath).to.be.true; + }); }); From 5db706ff1f4a9007f0dc2b7dc5aeacccec1358a4 Mon Sep 17 00:00:00 2001 From: David Paz Date: Tue, 18 Jul 2017 00:43:15 +0200 Subject: [PATCH 11/68] Refactor plugin configuration --- lib/config-generator.js | 203 ++++----------------------- lib/plugins/asset-output-display.js | 30 ++++ lib/plugins/clean.js | 35 +++++ lib/plugins/common-chunks.js | 40 ++++++ lib/plugins/define.js | 34 +++++ lib/plugins/delete-unused-entries.js | 25 ++++ lib/plugins/extract-text.js | 42 ++++++ lib/plugins/friendly-errors.js | 40 ++++++ lib/plugins/loader-options.js | 37 +++++ lib/plugins/manifest.js | 34 +++++ lib/plugins/uglify.js | 32 +++++ lib/plugins/variable-provider.js | 23 +++ lib/plugins/versioning.js | 58 ++++++++ 13 files changed, 461 insertions(+), 172 deletions(-) create mode 100644 lib/plugins/asset-output-display.js create mode 100644 lib/plugins/clean.js create mode 100644 lib/plugins/common-chunks.js create mode 100644 lib/plugins/define.js create mode 100644 lib/plugins/delete-unused-entries.js create mode 100644 lib/plugins/extract-text.js create mode 100644 lib/plugins/friendly-errors.js create mode 100644 lib/plugins/loader-options.js create mode 100644 lib/plugins/manifest.js create mode 100644 lib/plugins/uglify.js create mode 100644 lib/plugins/variable-provider.js create mode 100644 lib/plugins/versioning.js diff --git a/lib/config-generator.js b/lib/config-generator.js index b1b3f83d..de5dca41 100644 --- a/lib/config-generator.js +++ b/lib/config-generator.js @@ -9,28 +9,28 @@ 'use strict'; -const webpack = require('webpack'); -const ExtractTextPlugin = require('extract-text-webpack-plugin'); const extractText = require('./loaders/extract-text'); -const ManifestPlugin = require('./webpack/webpack-manifest-plugin'); -const DeleteUnusedEntriesJSPlugin = require('./webpack/delete-unused-entries-js-plugin'); -const AssetOutputDisplayPlugin = require('./friendly-errors/asset-output-display-plugin'); -const CleanWebpackPlugin = require('clean-webpack-plugin'); -const WebpackChunkHash = require('webpack-chunk-hash'); -const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin'); -const missingLoaderTransformer = require('./friendly-errors/transformers/missing-loader'); -const missingLoaderFormatter = require('./friendly-errors/formatters/missing-loader'); -const missingPostCssConfigTransformer = require('./friendly-errors/transformers/missing-postcss-config'); -const missingPostCssConfigFormatter = require('./friendly-errors/formatters/missing-postcss-config'); -const vueUnactivatedLoaderTransformer = require('./friendly-errors/transformers/vue-unactivated-loader-error'); -const vueUnactivatedLoaderFormatter = require('./friendly-errors/formatters/vue-unactivated-loader-error'); const pathUtil = require('./config/path-util'); +// loaders utils const cssLoaderUtil = require('./loaders/css'); const sassLoaderUtil = require('./loaders/sass'); const lessLoaderUtil = require('./loaders/less'); const babelLoaderUtil = require('./loaders/babel'); const tsLoaderUtil = require('./loaders/typescript'); const vueLoaderUtil = require('./loaders/vue'); +// plugins utils +const extractTextPluginUtil = require('./plugins/extract-text'); +const deleteUnusedEntriesPluginUtil = require('./plugins/delete-unused-entries'); +const manifestPluginUtil = require('./plugins/manifest'); +const loaderOptionsPluginUtil = require('./plugins/loader-options'); +const versioningPluginUtil = require('./plugins/versioning'); +const variableProviderPluginUtil = require('./plugins/variable-provider'); +const cleanPluginUtil = require('./plugins/clean'); +const commonChunksPluginUtil = require('./plugins/common-chunks'); +const definePluginUtil = require('./plugins/define'); +const uglifyPluginUtil = require('./plugins/uglify'); +const friendlyErrorPluginUtil = require('./plugins/friendly-errors'); +const assetOutputDisplay = require('./plugins/asset-output-display'); class ConfigGenerator { /** @@ -180,173 +180,32 @@ class ConfigGenerator { buildPluginsConfig() { let plugins = []; - /* - * All CSS/SCSS content (due to the loaders above) will be - * extracted into an [entrypointname].css files. The result - * is that NO css will be inlined, *except* CSS that is required - * in an async way (e.g. via require.ensure()). - * - * This may not be ideal in some cases, but it's at least - * predictable. It means that you must manually add a - * link tag for an entry point's CSS (unless no CSS file - * was imported - in which case no CSS file will be dumped). - */ - plugins.push(new ExtractTextPlugin({ - filename: this.webpackConfig.useVersioning ? '[name].[contenthash].css' : '[name].css', - // if true, async CSS (e.g. loaded via require.ensure()) - // is extracted to the entry point CSS. If false, it's - // inlined in the AJAX-loaded .js file. - allChunks: false - })); + extractTextPluginUtil(plugins, this.webpackConfig); // register the pure-style entries that should be deleted - plugins.push(new DeleteUnusedEntriesJSPlugin( - // transform into an Array - [... this.webpackConfig.styleEntries.keys()] - )); - - /* - * Dump the manifest.json file - */ - let manifestPrefix = this.webpackConfig.manifestKeyPrefix; - if (null === manifestPrefix) { - // by convention, we remove the opening slash on the manifest keys - manifestPrefix = this.webpackConfig.publicPath.replace(/^\//,''); - } - plugins.push(new ManifestPlugin({ - basePath: manifestPrefix, - // guarantee the value uses the public path (or CDN public path) - publicPath: this.webpackConfig.getRealPublicPath(), - // always write a manifest.json file, even with webpack-dev-server - writeToFileEmit: true, - })); - - /* - * This section is a bit mysterious. The "minimize" - * true is read and used to minify the CSS. - * But as soon as this plugin is included - * at all, SASS begins to have errors, until the context - * and output options are specified. At this time, I'm - * not totally sure what's going on here - * https://github.com/jtangelder/sass-loader/issues/285 - */ - plugins.push(new webpack.LoaderOptionsPlugin({ - debug: !this.webpackConfig.isProduction(), - options: { - context: this.webpackConfig.getContext(), - output: { path: this.webpackConfig.outputPath } - } - })); - - /* - * With versioning, the "chunkhash" used in the filenames and - * the module ids (i.e. the internal names of modules that - * are required) become important. Specifically: - * - * 1) If the contents of a module don't change, then you don't want its - * internal module id to change. Otherwise, whatever file holds the - * webpack "manifest" will change because the module id will change. - * Solved by HashedModuleIdsPlugin or NamedModulesPlugin - * - * 2) Similarly, if the final contents of a file don't change, - * then we also don't want that file to have a new filename. - * The WebpackChunkHash() handles this, by making sure that - * the chunkhash is based off of the file contents. - * - * Even in the webpack community, the ideal setup seems to be - * a bit of a mystery: - * * https://github.com/webpack/webpack/issues/1315 - * * https://github.com/webpack/webpack.js.org/issues/652#issuecomment-273324529 - * * https://webpack.js.org/guides/caching/#deterministic-hashes - */ - if (this.webpackConfig.isProduction()) { - // shorter, and obfuscated module ids (versus NamedModulesPlugin) - // makes the final assets *slightly* larger, but prevents contents - // from sometimes changing when nothing really changed - plugins.push(new webpack.HashedModuleIdsPlugin()); - } else { - // human-readable module names, helps debug in HMR - // enable always when not in production for consistency - plugins.push(new webpack.NamedModulesPlugin()); - } + deleteUnusedEntriesPluginUtil(plugins, this.webpackConfig); - if (this.webpackConfig.useVersioning) { - // enables the [chunkhash] ability - plugins.push(new WebpackChunkHash()); - } + // Dump the manifest.json file + manifestPluginUtil(plugins, this.webpackConfig); - if (Object.keys(this.webpackConfig.providedVariables).length > 0) { - plugins = plugins.concat([ - new webpack.ProvidePlugin(this.webpackConfig.providedVariables) - ]); - } + loaderOptionsPluginUtil(plugins, this.webpackConfig); - if (this.webpackConfig.cleanupOutput) { - plugins.push( - new CleanWebpackPlugin(['**/*'], { - root: this.webpackConfig.outputPath, - verbose: false, - }) - ); - } + versioningPluginUtil(plugins, this.webpackConfig); - // if we're extracting a vendor chunk, set it up! - if (this.webpackConfig.sharedCommonsEntryName) { - plugins = plugins.concat([ - new webpack.optimize.CommonsChunkPlugin({ - name: [ - this.webpackConfig.sharedCommonsEntryName, - /* - * Always dump a 2nd file - manifest.json that - * will contain the webpack manifest information. - * This changes frequently, and without this line, - * it would be packaged inside the "shared commons entry" - * file - e.g. vendor.js, which would prevent long-term caching. - */ - 'manifest' - ], - minChunks: Infinity, - }), - ]); - } + variableProviderPluginUtil(plugins, this.webpackConfig); - if (this.webpackConfig.isProduction()) { - plugins = plugins.concat([ - new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: '"production"' - } - }), - - // todo - options here should be configurable - new webpack.optimize.UglifyJsPlugin({ - sourceMap: this.webpackConfig.useSourceMaps - }) - ]); - } + cleanPluginUtil(plugins, this.webpackConfig, ['**/*']); - const friendlyErrorsPlugin = new FriendlyErrorsWebpackPlugin({ - clearConsole: false, - additionalTransformers: [ - missingLoaderTransformer, - missingPostCssConfigTransformer, - vueUnactivatedLoaderTransformer - ], - additionalFormatters: [ - missingLoaderFormatter, - missingPostCssConfigFormatter, - vueUnactivatedLoaderFormatter - ], - compilationSuccessInfo: { - messages: [] - } - }); - plugins.push(friendlyErrorsPlugin); + commonChunksPluginUtil(plugins, this.webpackConfig); - if (!this.webpackConfig.useDevServer()) { - const outputPath = pathUtil.getRelativeOutputPath(this.webpackConfig); - plugins.push(new AssetOutputDisplayPlugin(outputPath, friendlyErrorsPlugin)); - } + // todo - options here should be configurable + definePluginUtil(plugins, this.webpackConfig); + uglifyPluginUtil(plugins, this.webpackConfig); + + let friendlyErrorPlugin = friendlyErrorPluginUtil(); + plugins.push(friendlyErrorPlugin); + + assetOutputDisplay(plugins, this.webpackConfig, friendlyErrorPlugin); this.webpackConfig.plugins.forEach(function(plugin) { plugins.push(plugin); diff --git a/lib/plugins/asset-output-display.js b/lib/plugins/asset-output-display.js new file mode 100644 index 00000000..69a69f56 --- /dev/null +++ b/lib/plugins/asset-output-display.js @@ -0,0 +1,30 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const pathUtil = require('../config/path-util'); +const AssetOutputDisplayPlugin = require('../friendly-errors/asset-output-display-plugin'); + +/** + * Updates plugins array passed adding AssetOutputDisplayPlugin instance + * + * @param {Array} plugins + * @param {WebpackConfig} webpackConfig + * @param {FriendlyErrorsWebpackPlugin} friendlyErrorsPlugin + * @return {void} + */ +module.exports = function(plugins, webpackConfig, friendlyErrorsPlugin) { + if (webpackConfig.useDevServer()) { + return; + } + + const outputPath = pathUtil.getRelativeOutputPath(webpackConfig); + plugins.push(new AssetOutputDisplayPlugin(outputPath, friendlyErrorsPlugin)); +}; diff --git a/lib/plugins/clean.js b/lib/plugins/clean.js new file mode 100644 index 00000000..930812f7 --- /dev/null +++ b/lib/plugins/clean.js @@ -0,0 +1,35 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const CleanWebpackPlugin = require('clean-webpack-plugin'); + +/** + * Updates plugins array passed adding CleanWebpackPlugin instance + * + * @param {Array} plugins to push to + * @param {WebpackConfig} webpackConfig read only variable + * @param {Array} paths to clean + * @param {Object} cleanUpOptions + * @return {void} + */ +module.exports = function(plugins, webpackConfig, paths, cleanUpOptions = {}) { + + if (!webpackConfig.cleanupOutput) { + return; + } + + let config = Object.assign({}, cleanUpOptions, { + root: webpackConfig.outputPath, + verbose: false, + }); + + plugins.push(new CleanWebpackPlugin(paths, config)); +}; diff --git a/lib/plugins/common-chunks.js b/lib/plugins/common-chunks.js new file mode 100644 index 00000000..e1d34d23 --- /dev/null +++ b/lib/plugins/common-chunks.js @@ -0,0 +1,40 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const webpack = require('webpack'); + +/** + * @param {Array} plugins + * @param {WebpackConfig} webpackConfig + * @return {void} + */ +module.exports = function(plugins, webpackConfig) { + + if (!webpackConfig.sharedCommonsEntryName) { + return; + } + + // if we're extracting a vendor chunk, set it up! + plugins.push(new webpack.optimize.CommonsChunkPlugin({ + name: [ + webpackConfig.sharedCommonsEntryName, + /* + * Always dump a 2nd file - manifest.json that + * will contain the webpack manifest information. + * This changes frequently, and without this line, + * it would be packaged inside the "shared commons entry" + * file - e.g. vendor.js, which would prevent long-term caching. + */ + 'manifest' + ], + minChunks: Infinity, + })); +}; diff --git a/lib/plugins/define.js b/lib/plugins/define.js new file mode 100644 index 00000000..9a5556b7 --- /dev/null +++ b/lib/plugins/define.js @@ -0,0 +1,34 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const webpack = require('webpack'); + +/** + * @param {Array} plugins + * @param {WebpackConfig} webpackConfig + * @param {Object} defineOptions + * @return {void} + */ +module.exports = function(plugins, webpackConfig, defineOptions = {}) { + + if (!webpackConfig.isProduction()) { + return; + } + + let defineConfig = Object.assign({}, defineOptions, { + 'process.env': { + NODE_ENV: '"production"' + } + }); + let define = new webpack.DefinePlugin(defineConfig); + + plugins.push(define); +}; diff --git a/lib/plugins/delete-unused-entries.js b/lib/plugins/delete-unused-entries.js new file mode 100644 index 00000000..e77a26bb --- /dev/null +++ b/lib/plugins/delete-unused-entries.js @@ -0,0 +1,25 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const DeleteUnusedEntriesJSPlugin = require('../webpack/delete-unused-entries-js-plugin'); + +/** + * @param {Array} plugins + * @param {WebpackConfig} webpackConfig + * @return {void} + */ +module.exports = function(plugins, webpackConfig) { + + plugins.push(new DeleteUnusedEntriesJSPlugin( + // transform into an Array + [... webpackConfig.styleEntries.keys()] + )); +}; diff --git a/lib/plugins/extract-text.js b/lib/plugins/extract-text.js new file mode 100644 index 00000000..9a43924c --- /dev/null +++ b/lib/plugins/extract-text.js @@ -0,0 +1,42 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const ExtractTextPlugin = require('extract-text-webpack-plugin'); + +/** + * @param {Array} plugins + * @param {WebpackConfig} webpackConfig + * @param {Object} extractTextOptions Options to pass to the plugin + * @return {void} + */ +module.exports = function(plugins, webpackConfig, extractTextOptions = {}) { + + /* + * All CSS/SCSS content (due to the loaders above) will be + * extracted into an [entrypointname].css files. The result + * is that NO css will be inlined, *except* CSS that is required + * in an async way (e.g. via require.ensure()). + * + * This may not be ideal in some cases, but it's at least + * predictable. It means that you must manually add a + * link tag for an entry point's CSS (unless no CSS file + * was imported - in which case no CSS file will be dumped). + */ + let config = Object.assign({}, extractTextOptions, { + filename: webpackConfig.useVersioning ? '[name].[contenthash].css' : '[name].css', + // if true, async CSS (e.g. loaded via require.ensure()) + // is extracted to the entry point CSS. If false, it's + // inlined in the AJAX-loaded .js file. + allChunks: false + }); + + plugins.push(new ExtractTextPlugin(config)); +}; diff --git a/lib/plugins/friendly-errors.js b/lib/plugins/friendly-errors.js new file mode 100644 index 00000000..2962855b --- /dev/null +++ b/lib/plugins/friendly-errors.js @@ -0,0 +1,40 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin'); +const missingLoaderTransformer = require('../friendly-errors/transformers/missing-loader'); +const missingLoaderFormatter = require('../friendly-errors/formatters/missing-loader'); +const missingPostCssConfigTransformer = require('../friendly-errors/transformers/missing-postcss-config'); +const missingPostCssConfigFormatter = require('../friendly-errors/formatters/missing-postcss-config'); +const vueUnactivatedLoaderTransformer = require('../friendly-errors/transformers/vue-unactivated-loader-error'); +const vueUnactivatedLoaderFormatter = require('../friendly-errors/formatters/vue-unactivated-loader-error'); + +/** + * @return {FriendlyErrorsWebpackPlugin} + */ +module.exports = function() { + return new FriendlyErrorsWebpackPlugin({ + clearConsole: false, + additionalTransformers: [ + missingLoaderTransformer, + missingPostCssConfigTransformer, + vueUnactivatedLoaderTransformer + ], + additionalFormatters: [ + missingLoaderFormatter, + missingPostCssConfigFormatter, + vueUnactivatedLoaderFormatter + ], + compilationSuccessInfo: { + messages: [] + } + }); +}; diff --git a/lib/plugins/loader-options.js b/lib/plugins/loader-options.js new file mode 100644 index 00000000..d9ae3dff --- /dev/null +++ b/lib/plugins/loader-options.js @@ -0,0 +1,37 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const webpack = require('webpack'); + +/** + * @param {Array} plugins + * @param {WebpackConfig} webpackConfig + * @return {void} + */ +module.exports = function(plugins, webpackConfig) { + + /* + * This section is a bit mysterious. The "minimize" + * true is read and used to minify the CSS. + * But as soon as this plugin is included + * at all, SASS begins to have errors, until the context + * and output options are specified. At this time, I'm + * not totally sure what's going on here + * https://github.com/jtangelder/sass-loader/issues/285 + */ + plugins.push(new webpack.LoaderOptionsPlugin({ + debug: !webpackConfig.isProduction(), + options: { + context: webpackConfig.getContext(), + output: { path: webpackConfig.outputPath } + } + })); +}; diff --git a/lib/plugins/manifest.js b/lib/plugins/manifest.js new file mode 100644 index 00000000..97f78e31 --- /dev/null +++ b/lib/plugins/manifest.js @@ -0,0 +1,34 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const ManifestPlugin = require('../webpack/webpack-manifest-plugin'); + +/** + * @param {Array} plugins + * @param {WebpackConfig} webpackConfig + * @return {void} + */ +module.exports = function(plugins, webpackConfig) { + + let manifestPrefix = webpackConfig.manifestKeyPrefix; + if (null === manifestPrefix) { + // by convention, we remove the opening slash on the manifest keys + manifestPrefix = webpackConfig.publicPath.replace(/^\//, ''); + } + + plugins.push(new ManifestPlugin({ + basePath: manifestPrefix, + // guarantee the value uses the public path (or CDN public path) + publicPath: webpackConfig.getRealPublicPath(), + // always write a manifest.json file, even with webpack-dev-server + writeToFileEmit: true, + })); +}; diff --git a/lib/plugins/uglify.js b/lib/plugins/uglify.js new file mode 100644 index 00000000..3204fcf3 --- /dev/null +++ b/lib/plugins/uglify.js @@ -0,0 +1,32 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const webpack = require('webpack'); + +/** + * @param {Array} plugins + * @param {WebpackConfig} webpackConfig + * @param {Object} uglifyOptions + * @return {void} + */ +module.exports = function(plugins, webpackConfig, uglifyOptions = {}) { + + if (!webpackConfig.isProduction()) { + return; + } + + let uglifyConfig = Object.assign({}, uglifyOptions, { + sourceMap: webpackConfig.useSourceMaps + }); + let uglify = new webpack.optimize.UglifyJsPlugin(uglifyConfig); + + plugins.push(uglify); +}; diff --git a/lib/plugins/variable-provider.js b/lib/plugins/variable-provider.js new file mode 100644 index 00000000..f844b166 --- /dev/null +++ b/lib/plugins/variable-provider.js @@ -0,0 +1,23 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const webpack = require('webpack'); + +/** + * @param {Array} plugins + * @param {WebpackConfig} webpackConfig + * @return {Array} of plugins to add to webpack + */ +module.exports = function(plugins, webpackConfig) { + if (Object.keys(webpackConfig.providedVariables).length > 0) { + plugins.push(new webpack.ProvidePlugin(webpackConfig.providedVariables)); + } +}; diff --git a/lib/plugins/versioning.js b/lib/plugins/versioning.js new file mode 100644 index 00000000..42b16008 --- /dev/null +++ b/lib/plugins/versioning.js @@ -0,0 +1,58 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const webpack = require('webpack'); +const WebpackChunkHash = require('webpack-chunk-hash'); + +/** + * @param {Array} plugins + * @param {WebpackConfig} webpackConfig + * @return {void} + */ +module.exports = function(plugins, webpackConfig) { + + /* + * With versioning, the "chunkhash" used in the filenames and + * the module ids (i.e. the internal names of modules that + * are required) become important. Specifically: + * + * 1) If the contents of a module don't change, then you don't want its + * internal module id to change. Otherwise, whatever file holds the + * webpack "manifest" will change because the module id will change. + * Solved by HashedModuleIdsPlugin or NamedModulesPlugin + * + * 2) Similarly, if the final contents of a file don't change, + * then we also don't want that file to have a new filename. + * The WebpackChunkHash() handles this, by making sure that + * the chunkhash is based off of the file contents. + * + * Even in the webpack community, the ideal setup seems to be + * a bit of a mystery: + * * https://github.com/webpack/webpack/issues/1315 + * * https://github.com/webpack/webpack.js.org/issues/652#issuecomment-273324529 + * * https://webpack.js.org/guides/caching/#deterministic-hashes + */ + if (webpackConfig.isProduction()) { + // shorter, and obfuscated module ids (versus NamedModulesPlugin) + // makes the final assets *slightly* larger, but prevents contents + // from sometimes changing when nothing really changed + plugins.push(new webpack.HashedModuleIdsPlugin()); + } else { + // human-readable module names, helps debug in HMR + // enable always when not in production for consistency + plugins.push(new webpack.NamedModulesPlugin()); + } + + if (webpackConfig.useVersioning) { + // enables the [chunkhash] ability + plugins.push(new WebpackChunkHash()); + } +}; From c6624644ae9fd847a719c5d55e091f465de2e144 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 15 Jul 2017 10:35:00 -0400 Subject: [PATCH 12/68] Allowing an absolute publicPath with dev-server, but showing a warning --- bin/encore.js | 1 - index.js | 4 +++ lib/WebpackConfig.js | 39 +++++++++++------------- lib/config-generator.js | 2 +- lib/config/RuntimeConfig.js | 1 + lib/config/parse-runtime.js | 4 +++ lib/config/validator.js | 18 ++++++++++- lib/logger.js | 59 +++++++++++++++++++++++++++++++++++++ test/WebpackConfig.js | 12 +------- test/config/validator.js | 18 +++++++++++ 10 files changed, 122 insertions(+), 36 deletions(-) create mode 100644 lib/logger.js diff --git a/bin/encore.js b/bin/encore.js index 6ab9613a..b8ba29e2 100755 --- a/bin/encore.js +++ b/bin/encore.js @@ -10,7 +10,6 @@ 'use strict'; -const path = require('path'); const parseRuntime = require('../lib/config/parse-runtime'); const context = require('../lib/context'); const chalk = require('chalk'); diff --git a/index.js b/index.js index 2a00eef1..2f1b7513 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,7 @@ 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'); // at this time, the encore executable should have set the runtimeConfig if (!runtimeConfig) { @@ -21,6 +22,9 @@ if (!runtimeConfig) { } let webpackConfig = new WebpackConfig(runtimeConfig); +if (runtimeConfig.verbose) { + logger.verbose(); +} module.exports = { /** diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index cdcfc6c8..392eb161 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -88,23 +88,11 @@ class WebpackConfig { } setPublicPath(publicPath) { - /* - * Do not allow absolute URLs *and* the webpackDevServer - * to be used at the same time. The webpackDevServer basically - * provides the publicPath (and so in those cases, publicPath) - * is simply used as the default manifestKeyPrefix. - */ - if (publicPath.includes('://')) { - if (this.useDevServer() && false === this.runtimeConfig.devServerKeepPublicPath) { - throw new Error('You cannot pass an absolute URL to setPublicPath() and use the dev-server at the same time. This is because the public path is automatically set to point to the dev server. Try using Encore.isProduction() to only configure your absolute publicPath for production. Or, if you want to override this behavior, pass the --keep-public-path option to allow this.'); - } - } else { - if (publicPath.indexOf('/') !== 0) { - // technically, not starting with "/" is legal, but not - // what you want in most cases. Let's not let the user make - // a mistake (and we can always change this later). - throw new Error('The value passed to setPublicPath() must start with "/" or be a full URL (http://...)'); - } + if (publicPath.includes('://') === false && publicPath.indexOf('/') !== 0) { + // technically, not starting with "/" is legal, but not + // what you want in most cases. Let's not let the user make + // a mistake (and we can always change this later). + throw new Error('The value passed to setPublicPath() must start with "/" or be a full URL (http://...)'); } // guarantee a single trailing slash @@ -147,13 +135,20 @@ class WebpackConfig { * @returns {string} */ getRealPublicPath() { - // if we're using webpack-dev-server, use it & add the publicPath - if (this.useDevServer() && false === this.runtimeConfig.devServerKeepPublicPath) { - // avoid 2 middle slashes - return this.runtimeConfig.devServerUrl.replace(/\/$/,'') + this.publicPath; + if (!this.useDevServer()) { + return this.publicPath; + } + + if (this.runtimeConfig.devServerKeepPublicPath) { + return this.publicPath; + } + + if (this.publicPath.includes('://')) { + return this.publicPath; } - return this.publicPath; + // if using dev-server, prefix the publicPath with the dev server URL + return this.runtimeConfig.devServerUrl.replace(/\/$/,'') + this.publicPath; } getFontsPublicPath() { diff --git a/lib/config-generator.js b/lib/config-generator.js index b1b3f83d..556b7d67 100644 --- a/lib/config-generator.js +++ b/lib/config-generator.js @@ -384,8 +384,8 @@ class ConfigGenerator { publicPath: this.webpackConfig.publicPath, // avoid CORS concerns trying to load things like fonts from the dev server headers: { 'Access-Control-Allow-Origin': '*' }, - // required by FriendlyErrorsWebpackPlugin hot: this.webpackConfig.useHotModuleReplacementPlugin(), + // required by FriendlyErrorsWebpackPlugin quiet: true, compress: true, historyApiFallback: true, diff --git a/lib/config/RuntimeConfig.js b/lib/config/RuntimeConfig.js index 4b035ac5..f2bd95b9 100644 --- a/lib/config/RuntimeConfig.js +++ b/lib/config/RuntimeConfig.js @@ -25,6 +25,7 @@ class RuntimeConfig { this.babelRcFileExists = null; this.helpRequested = false; + this.verbose = false; } } diff --git a/lib/config/parse-runtime.js b/lib/config/parse-runtime.js index 99cde4a9..36aa9073 100644 --- a/lib/config/parse-runtime.js +++ b/lib/config/parse-runtime.js @@ -29,14 +29,18 @@ module.exports = function(argv, cwd) { case 'dev': runtimeConfig.isValidCommand = true; runtimeConfig.environment = 'dev'; + runtimeConfig.verbose = true; break; case 'production': runtimeConfig.isValidCommand = true; runtimeConfig.environment = 'production'; + runtimeConfig.verbose = false; break; case 'dev-server': runtimeConfig.isValidCommand = true; runtimeConfig.environment = 'dev'; + runtimeConfig.verbose = true; + runtimeConfig.useDevServer = true; runtimeConfig.devServerHttps = argv.https; runtimeConfig.useHotModuleReplacement = argv.hot || false; diff --git a/lib/config/validator.js b/lib/config/validator.js index 14f6952c..a7eda7cc 100644 --- a/lib/config/validator.js +++ b/lib/config/validator.js @@ -10,6 +10,7 @@ 'use strict'; const pathUtil = require('./path-util'); +const logger = require('./../logger'); class Validator { /** @@ -46,9 +47,24 @@ class Validator { } _validateDevServer() { - if (this.webpackConfig.useVersioning && this.webpackConfig.useDevServer()) { + if (!this.webpackConfig.useDevServer()) { + return; + } + + if (this.webpackConfig.useVersioning) { throw new Error('Don\'t enable versioning with the dev-server. A good setting is Encore.enableVersioning(Encore.isProduction()).'); } + + /* + * An absolute publicPath is incompatible with webpackDevServer. + * This is because we want to *change* the publicPath to point + * to the webpackDevServer URL (e.g. http://localhost:8080/). + * There are some valid use-cases for not wanting this behavior + * (see #59), but we want to warn the user. + */ + if (this.webpackConfig.publicPath.includes('://')) { + logger.warning(`Passing an absolute URL to setPublicPath() *and* using the dev-server can cause issues. Your assets will load from the publicPath (${this.webpackConfig.publicPath}) instead of from the dev server URL (${this.webpackConfig.runtimeConfig.devServerUrl}).`); + } } } diff --git a/lib/logger.js b/lib/logger.js new file mode 100644 index 00000000..f51f1500 --- /dev/null +++ b/lib/logger.js @@ -0,0 +1,59 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const chalk = require('chalk'); +let isVerbose = false; +let quiet = false; +let messages = { + debug: [], + warning: [], +}; + +function log(message) { + if (quiet) { + return; + } + + console.log(message); +} + +module.exports = { + debug(message) { + messages.debug.push(message); + + if (isVerbose) { + log(`${chalk.bgBlack.white(' DEBUG ')} ${message}`); + } + }, + + warning(message) { + messages.warning.push(message); + + log(`${chalk.bgYellow.black(' WARNING ')} ${chalk.yellow(message)}`); + }, + + clearMessages() { + messages.debug = []; + messages.warning = []; + }, + + getMessages() { + return messages; + }, + + quiet() { + quiet = true; + }, + + verbose() { + isVerbose = true; + } +}; diff --git a/test/WebpackConfig.js b/test/WebpackConfig.js index 27a31641..06bdc71c 100644 --- a/test/WebpackConfig.js +++ b/test/WebpackConfig.js @@ -104,15 +104,6 @@ describe('WebpackConfig object', () => { config.setPublicPath('foo/'); }).to.throw('The value passed to setPublicPath() must start with "/"'); }); - - it('Setting to a URL when using devServer throws an error', () => { - const config = createConfig(); - config.runtimeConfig.useDevServer = true; - - expect(() => { - config.setPublicPath('https://examplecdn.com'); - }).to.throw('You cannot pass an absolute URL to setPublicPath() and use the dev-server'); - }); }); describe('getRealPublicPath', () => { @@ -142,11 +133,10 @@ describe('WebpackConfig object', () => { expect(config.getRealPublicPath()).to.equal('/public/'); }); - it('devServer & devServerKeepPublicPath option allows absolute publicPath', () => { + it('devServer does not prefix if publicPath is absolute', () => { const config = createConfig(); config.runtimeConfig.useDevServer = true; config.runtimeConfig.devServerUrl = 'http://localhost:8080/'; - config.runtimeConfig.devServerKeepPublicPath = true; config.setPublicPath('http://coolcdn.com/public'); config.setManifestKeyPrefix('/public/'); diff --git a/test/config/validator.js b/test/config/validator.js index 9ab2810c..8f4f8555 100644 --- a/test/config/validator.js +++ b/test/config/validator.js @@ -13,6 +13,9 @@ const expect = require('chai').expect; const WebpackConfig = require('../../lib/WebpackConfig'); const RuntimeConfig = require('../../lib/config/RuntimeConfig'); const validator = require('../../lib/config/validator'); +const logger = require('../../lib/logger'); + +logger.quiet(); function createConfig() { const runtimeConfig = new RuntimeConfig(); @@ -65,4 +68,19 @@ describe('The validator function', () => { validator(config); }).to.throw('Don\'t enable versioning with the dev-server'); }); + + it('warning with dev-server and absolute publicPath', () => { + const config = createConfig(); + config.outputPath = '/tmp/public/build'; + config.setPublicPath('https://absoluteurl.com/build'); + config.setManifestKeyPrefix('build/'); + config.addEntry('main', './main'); + config.runtimeConfig.useDevServer = true; + + logger.clearMessages(); + validator(config); + + expect(logger.getMessages().warning).to.have.lengthOf(1); + expect(logger.getMessages().warning[0]).to.include('Passing an absolute URL to setPublicPath() *and* using the dev-server can cause issues'); + }); }); From b8dc43e5d03bb6a9e148c9eeeed3c2c229ca4cae Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 20 Jul 2017 20:50:22 -0400 Subject: [PATCH 13/68] Using real public path, though it doesn't look like it matters --- lib/config-generator.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/config-generator.js b/lib/config-generator.js index 556b7d67..3e7389e8 100644 --- a/lib/config-generator.js +++ b/lib/config-generator.js @@ -381,7 +381,8 @@ class ConfigGenerator { return { contentBase: contentBase, - publicPath: this.webpackConfig.publicPath, + // this doesn't appear to be necessary, but here in case + publicPath: this.webpackConfig.getRealPublicPath(), // avoid CORS concerns trying to load things like fonts from the dev server headers: { 'Access-Control-Allow-Origin': '*' }, hot: this.webpackConfig.useHotModuleReplacementPlugin(), From 7878a144424a525bbaea8f0e573a55b55b87ad48 Mon Sep 17 00:00:00 2001 From: Lyrkan Date: Thu, 20 Jul 2017 20:06:21 +0200 Subject: [PATCH 14/68] Add EditorConfig file --- .editorconfig | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..98295bdd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +; top-most EditorConfig file +root = true + +; Unix-style newlines +[*] +end_of_line = LF + +[*.js] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true \ No newline at end of file From f109b7c468268ae27110929f7b76399c98110452 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 20 Jul 2017 20:10:28 -0400 Subject: [PATCH 15/68] fixing changelog typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d58b258..e4493d7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 0.11.0 - * The `webpack` page was upgraded from version 2.2 to 3.1 #53. The + * The `webpack` package was upgraded from version 2.2 to 3.1 #53. The `extract-text-webpack-plugin` package was also upgraded from 2.1 to 3.0. From ab801cfda7cfc2ef7154edc98425733997f35984 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 26 Jul 2017 11:50:49 -0400 Subject: [PATCH 16/68] Fixing a bug with webpack 3.4.0 with extra arg as an entry --- bin/encore.js | 2 +- test/bin/encore.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 test/bin/encore.js diff --git a/bin/encore.js b/bin/encore.js index b8ba29e2..c8b96b41 100755 --- a/bin/encore.js +++ b/bin/encore.js @@ -15,7 +15,7 @@ const context = require('../lib/context'); const chalk = require('chalk'); const runtimeConfig = parseRuntime( - require('yargs').argv, + require('yargs/yargs')(process.argv.slice(2)).argv, process.cwd() ); context.runtimeConfig = runtimeConfig; diff --git a/test/bin/encore.js b/test/bin/encore.js new file mode 100644 index 00000000..acdcc831 --- /dev/null +++ b/test/bin/encore.js @@ -0,0 +1,50 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const chai = require('chai'); +chai.use(require('chai-fs')); +const path = require('path'); +const testSetup = require('../../lib/test/setup'); +const fs = require('fs-extra'); +const exec = require('child_process').exec; + +describe('bin/encore.js', function() { + // being functional tests, these can take quite long + this.timeout(8000); + + it('Basic smoke test', (done) => { + testSetup.emptyTmpDir(); + const testDir = testSetup.createTestAppDir(); + + fs.writeFileSync( + path.join(testDir, 'webpack.config.js'), + ` +const Encore = require('../../index.js'); +Encore + .setOutputPath('build/') + .setPublicPath('/build') + .addEntry('main', './js/no_require') +; + +module.exports = Encore.getWebpackConfig(); + ` + ); + + const binPath = path.resolve(__dirname, '../', '../', 'bin', 'encore.js'); + exec(`node ${binPath} dev --context=${testDir}`, { cwd: testDir }, (err, stdout, stderr) => { + if (err) { + throw new Error(`Error executing encore: ${err} ${stderr} ${stdout}`); + } + + done(); + }); + }); +}); From 8f97face6419cf9d906bea4df6f3571dbe99357e Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 26 Jul 2017 10:12:55 -0400 Subject: [PATCH 17/68] upgrading locked deps --- yarn.lock | 290 +++++++++++++++++++++++++++--------------------------- 1 file changed, 147 insertions(+), 143 deletions(-) diff --git a/yarn.lock b/yarn.lock index 737d6319..22a39f78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -131,10 +131,10 @@ ansi-styles@^2.2.1: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" ansi-styles@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.1.0.tgz#09c202d5c917ec23188caa5c9cb9179cd9547750" + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" dependencies: - color-convert "^1.0.0" + color-convert "^1.9.0" anymatch@^1.3.0: version "1.3.0" @@ -850,8 +850,8 @@ big.js@^3.1.3: resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.1.3.tgz#4cada2193652eb3ca9ec8e55c9015669c9806978" binary-extensions@^1.0.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774" + version "1.9.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.9.0.tgz#66506c16ce6f4d6928a5b3cd6a33ca41e941e37b" bit-mask@0.0.2-alpha: version "0.0.2-alpha" @@ -976,11 +976,11 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: electron-to-chromium "^1.2.7" browserslist@^2.1.2: - version "2.1.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.1.5.tgz#e882550df3d1cd6d481c1a3e0038f2baf13a4711" + version "2.2.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.2.2.tgz#e9b4618b8a01c193f9786beea09f6fd10dbe31c3" dependencies: - caniuse-lite "^1.0.30000684" - electron-to-chromium "^1.3.14" + caniuse-lite "^1.0.30000704" + electron-to-chromium "^1.3.16" buffer-indexof@^1.0.0: version "1.1.0" @@ -1006,9 +1006,9 @@ builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" -bytes@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.3.0.tgz#d5b680a165b6201739acb611542aabc2d8ceb070" +bytes@2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.5.0.tgz#4c9423ea2d252c270c41b2bdefeff9bb6b62c06a" call-me-maybe@^1.0.1: version "1.0.1" @@ -1057,12 +1057,12 @@ caniuse-api@^1.5.2: lodash.uniq "^4.5.0" caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000698" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000698.tgz#623a2de3458ceca379846a8f170e7b1771c7c3a3" + version "1.0.30000706" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000706.tgz#e2b5f0460573cbcc88a0985f5cced08f1617c6f5" -caniuse-lite@^1.0.30000684: - version "1.0.30000698" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000698.tgz#8102e8978b1f36962f2a102432e4bf4eac7b6cbe" +caniuse-lite@^1.0.30000704: + version "1.0.30000706" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000706.tgz#bc59abc41ba7d4a3634dda95befded6114e1f24e" caseless@~0.12.0: version "0.12.0" @@ -1108,7 +1108,7 @@ chalk@^2.0.1: escape-string-regexp "^1.0.5" supports-color "^4.0.0" -chokidar@^1.4.3, chokidar@^1.6.0: +chokidar@^1.6.0, chokidar@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" dependencies: @@ -1131,8 +1131,8 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: safe-buffer "^5.0.1" circular-json@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" + version "0.3.3" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" clap@^1.0.9: version "1.2.0" @@ -1213,15 +1213,15 @@ code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" -color-convert@^1.0.0, color-convert@^1.3.0: +color-convert@^1.3.0, color-convert@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" dependencies: color-name "^1.1.1" color-name@^1.0.0, color-name@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.2.tgz#5c8ab72b64bd2215d617ae9559ebb148475cf98d" + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" color-string@^0.3.0: version "0.3.0" @@ -1249,7 +1249,7 @@ colors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" -colors@^1.0.3, colors@~1.1.2: +colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" @@ -1273,22 +1273,23 @@ commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" -compressible@~2.0.8: +compressible@~2.0.10: version "2.0.10" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.10.tgz#feda1c7f7617912732b29bf8cf26252a20b9eecd" dependencies: mime-db ">= 1.27.0 < 2" compression@^1.5.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.6.2.tgz#cceb121ecc9d09c52d7ad0c3350ea93ddd402bc3" + version "1.7.0" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.0.tgz#030c9f198f1643a057d776a738e922da4373012d" dependencies: accepts "~1.3.3" - bytes "2.3.0" - compressible "~2.0.8" - debug "~2.2.0" + bytes "2.5.0" + compressible "~2.0.10" + debug "2.6.8" on-headers "~1.0.1" - vary "~1.1.0" + safe-buffer "5.1.1" + vary "~1.1.1" concat-map@0.0.1: version "0.0.1" @@ -1374,8 +1375,8 @@ corser@~2.0.0: resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87" cosmiconfig@^2.1.0, cosmiconfig@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.1.3.tgz#952771eb0dddc1cb3fa2f6fbe51a522e93b3ee0a" + version "2.2.2" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.2.2.tgz#6173cebd56fac042c1f4390edf7af6c07c7cb892" dependencies: is-directory "^0.3.1" js-yaml "^3.4.3" @@ -1419,11 +1420,12 @@ cross-spawn@^3.0.0: lru-cache "^4.0.1" which "^1.2.9" -cross-spawn@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" dependencies: lru-cache "^4.0.1" + shebang-command "^1.2.0" which "^1.2.9" cryptiles@2.x.x: @@ -1433,8 +1435,8 @@ cryptiles@2.x.x: boom "2.x.x" crypto-browserify@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.0.tgz#3652a0906ab9b2a7e0c3ce66a408e957a2485522" + version "3.11.1" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.1.tgz#948945efc6757a400d6e5e5af47194d10064279f" dependencies: browserify-cipher "^1.0.0" browserify-sign "^4.0.0" @@ -1586,7 +1588,7 @@ de-indent@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" -debug@2, debug@2.6.8, debug@^2.1.1, debug@^2.1.3, debug@^2.2, debug@^2.2.0, debug@^2.6.8: +debug@2, debug@2.6.8, debug@^2.1.1, debug@^2.1.3, debug@^2.2, debug@^2.2.0, debug@^2.6.6, debug@^2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" dependencies: @@ -1604,12 +1606,6 @@ debug@2.6.7: dependencies: ms "2.0.0" -debug@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" - dependencies: - ms "0.7.1" - decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1804,9 +1800,9 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" -electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.14: - version "1.3.15" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.15.tgz#08397934891cbcfaebbd18b82a95b5a481138369" +electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.16: + version "1.3.16" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.16.tgz#d0e026735754770901ae301a21664cba45d92f7d" elliptic@^6.0.0: version "6.4.0" @@ -1828,14 +1824,14 @@ encodeurl@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" -enhanced-resolve@^3.0.0, enhanced-resolve@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.3.0.tgz#950964ecc7f0332a42321b673b38dc8ff15535b3" +enhanced-resolve@^3.0.0, enhanced-resolve@^3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" dependencies: graceful-fs "^4.1.2" memory-fs "^0.4.0" object-assign "^4.0.1" - tapable "^0.2.5" + tapable "^0.2.7" entities@~1.1.1: version "1.1.1" @@ -1948,8 +1944,8 @@ eslint-plugin-header@^1.0.0: resolved "https://registry.yarnpkg.com/eslint-plugin-header/-/eslint-plugin-header-1.0.0.tgz#3990ec8be67e30694fcbee672c5f8a5ae4c80cf8" eslint-plugin-node@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-4.2.2.tgz#82959ca9aed79fcbd28bb1b188d05cac04fb3363" + version "4.2.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-4.2.3.tgz#c04390ab8dbcbb6887174023d6f3a72769e63b97" dependencies: ignore "^3.0.11" minimatch "^3.0.2" @@ -2068,12 +2064,12 @@ evp_bytestokey@^1.0.0: dependencies: create-hash "^1.1.1" -execa@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.5.1.tgz#de3fb85cb8d6e91c85bcbceb164581785cb57b36" +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" dependencies: - cross-spawn "^4.0.0" - get-stream "^2.2.0" + cross-spawn "^5.0.1" + get-stream "^3.0.0" is-stream "^1.1.0" npm-run-path "^2.0.0" p-finally "^1.0.0" @@ -2386,12 +2382,9 @@ get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" -get-stream@^2.2.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" - dependencies: - object-assign "^4.0.1" - pinkie-promise "^2.0.0" +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" getpass@^0.1.1: version "0.1.7" @@ -2770,9 +2763,9 @@ ip@^1.1.0: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" -ipaddr.js@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec" +ipaddr.js@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0" is-absolute-url@^2.0.0: version "2.1.0" @@ -2894,10 +2887,10 @@ is-plain-obj@^1.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" is-plain-object@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.3.tgz#c15bf3e4b66b62d72efaf2925848663ecbc619b6" + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" dependencies: - isobject "^3.0.0" + isobject "^3.0.1" is-posix-bracket@^0.1.0: version "0.1.1" @@ -2957,7 +2950,7 @@ isobject@^2.0.0: dependencies: isarray "1.0.0" -isobject@^3.0.0: +isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" @@ -3038,8 +3031,8 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" json-loader@^0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de" + version "0.5.7" + resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" json-schema-traverse@^0.3.0: version "0.3.1" @@ -3306,6 +3299,10 @@ lodash@^3.10.1: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" +loglevel@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.4.1.tgz#95b383f91a3c2756fd4ab093667e4309161f2bcd" + lolex@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6" @@ -3423,19 +3420,15 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -"mime-db@>= 1.27.0 < 2": - version "1.28.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.28.0.tgz#fedd349be06d2865b7fc57d837c6de4f17d7ac3c" - -mime-db@~1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" +"mime-db@>= 1.27.0 < 2", mime-db@~1.29.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.29.0.tgz#48d26d235589651704ac5916ca06001914266878" mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7: - version "2.1.15" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" + version "2.1.16" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.16.tgz#2b858a52e5ecd516db897ac2be87487830698e23" dependencies: - mime-db "~1.27.0" + mime-db "~1.29.0" mime@1.3.4: version "1.3.4" @@ -3508,10 +3501,6 @@ moment@2.x.x: version "2.18.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f" -ms@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" - ms@0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" @@ -3833,10 +3822,10 @@ os-locale@^1.4.0: lcid "^1.0.0" os-locale@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.0.0.tgz#15918ded510522b81ee7ae5a309d54f639fc39a4" + version "2.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" dependencies: - execa "^0.5.0" + execa "^0.7.0" lcid "^1.0.0" mem "^1.1.0" @@ -4294,12 +4283,12 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0 supports-color "^3.2.3" postcss@^6.0.1: - version "6.0.6" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.6.tgz#bba4d58e884fc78c840d1539e10eddaabb8f73bd" + version "6.0.8" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.8.tgz#89067a9ce8b11f8a84cbc5117efc30419a0857b3" dependencies: chalk "^2.0.1" source-map "^0.5.6" - supports-color "^4.1.0" + supports-color "^4.2.0" prelude-ls@~1.1.2: version "1.1.2" @@ -4351,11 +4340,11 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" proxy-addr@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.4.tgz#27e545f6960a44a627d9b44467e35c1b6b4ce2f3" + version "1.1.5" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.5.tgz#71c0ee3b102de3f202f3b64f608d173fcba1a918" dependencies: forwarded "~0.1.0" - ipaddr.js "1.3.0" + ipaddr.js "1.4.0" prr@~0.0.0: version "0.0.0" @@ -4759,7 +4748,7 @@ rx-lite@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -4813,7 +4802,11 @@ selfsigned@^1.9.1: dependencies: node-forge "0.6.33" -"semver@2 || 3 || 4 || 5", semver@5.3.0, semver@^5.0.1, semver@^5.0.3, semver@^5.3.0, semver@~5.3.0: +"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.3.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" + +semver@5.3.0, semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -4891,6 +4884,16 @@ shallow-clone@^0.1.2: lazy-cache "^0.2.3" mixin-object "^2.0.1" +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + shelljs@^0.7.5: version "0.7.8" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" @@ -4908,8 +4911,8 @@ signal-exit@^3.0.0: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" sinon@^2.3.4: - version "2.3.7" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.3.7.tgz#1451614a2eaab05bb4d876c1335cd40132ec5127" + version "2.4.0" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.4.0.tgz#398de1bd15c9c6d671b5ed708c8a121a213ae8b7" dependencies: diff "^3.1.0" formatio "1.2.0" @@ -4934,16 +4937,16 @@ sntp@1.x.x: dependencies: hoek "2.x.x" -sockjs-client@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.2.tgz#f0212a8550e4c9468c8cceaeefd2e3493c033ad5" +sockjs-client@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12" dependencies: - debug "^2.2.0" + debug "^2.6.6" eventsource "0.1.6" faye-websocket "~0.11.0" inherits "^2.0.1" json3 "^3.3.2" - url-parse "^1.1.1" + url-parse "^1.1.8" sockjs@0.3.18: version "0.3.18" @@ -5112,8 +5115,8 @@ string-width@^1.0.1, string-width@^1.0.2: strip-ansi "^3.0.0" string-width@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.0.tgz#030664561fc146c9423ec7d978fe2457437fe6d0" + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" dependencies: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" @@ -5193,15 +5196,15 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.2.3: +supports-color@^3.1.1, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" dependencies: has-flag "^1.0.0" -supports-color@^4.0.0, supports-color@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.0.tgz#ad986dc7eb2315d009b4d77c8169c2231a684037" +supports-color@^4.0.0, supports-color@^4.2.0, supports-color@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.1.tgz#65a4bb2631e90e02420dba5554c375a4754bb836" dependencies: has-flag "^2.0.0" @@ -5232,9 +5235,9 @@ table@^3.7.8: slice-ansi "0.0.4" string-width "^2.0.0" -tapable@^0.2.5, tapable@~0.2.5: - version "0.2.6" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d" +tapable@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.7.tgz#e46c0daacbb2b8a98b9b0cea0f4052105817ed5c" tar-pack@^3.4.0: version "3.4.0" @@ -5316,10 +5319,10 @@ tryit@^1.0.1: resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" ts-loader@^2.1.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-2.2.2.tgz#6ea98ebe384ee9ba4c891fea6de6d4e7d8134730" + version "2.3.1" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-2.3.1.tgz#6edc603393c2775c40ad84e3420007f1c097eab0" dependencies: - colors "^1.0.3" + chalk "^2.0.1" enhanced-resolve "^3.0.0" loader-utils "^1.0.2" semver "^5.0.1" @@ -5368,8 +5371,8 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" typescript@^2.3.4: - version "2.4.1" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc" + version "2.4.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.2.tgz#f8395f85d459276067c988aa41837a8f82870844" uglify-js@^2.8.29: version "2.8.29" @@ -5439,7 +5442,7 @@ url-parse@1.0.x: querystringify "0.0.x" requires-port "1.0.x" -url-parse@^1.1.1: +url-parse@^1.1.8: version "1.1.9" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.9.tgz#c67f1d775d51f0a18911dd7b3ffad27bb9e5bd19" dependencies: @@ -5496,7 +5499,7 @@ validate-npm-package-license@^3.0.1: spdx-correct "~1.0.0" spdx-expression-parse "~1.0.0" -vary@~1.1.0, vary@~1.1.1: +vary@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37" @@ -5521,8 +5524,8 @@ vue-hot-reload-api@^2.1.0: resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.1.0.tgz#9ca58a6e0df9078554ce1708688b6578754d86de" vue-loader@^12.2.1: - version "12.2.1" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-12.2.1.tgz#53f27c0973d386768f5a75156f4129b5efc6ba55" + version "12.2.2" + resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-12.2.2.tgz#2b3a764f27018f975bc78cb8b1f55137548ee2d7" dependencies: consolidate "^0.14.0" hash-sum "^1.0.2" @@ -5546,8 +5549,8 @@ vue-style-loader@^3.0.0: loader-utils "^1.0.2" vue-template-compiler@^2.3.4: - version "2.3.4" - resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.3.4.tgz#5a88ac2c5e4d5d6218e6aa80e7e221fb7e67894c" + version "2.4.2" + resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.4.2.tgz#5a45d843f148b098f6c1d1e35ac20c4956d30ad1" dependencies: de-indent "^1.0.2" he "^1.1.0" @@ -5557,15 +5560,15 @@ vue-template-es2015-compiler@^1.2.2: resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.5.3.tgz#22787de4e37ebd9339b74223bc467d1adee30545" vue@^2.3.4: - version "2.3.4" - resolved "https://registry.yarnpkg.com/vue/-/vue-2.3.4.tgz#5ec3b87a191da8090bbef56b7cfabd4158038171" + version "2.4.2" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.4.2.tgz#a9855261f191c978cc0dc1150531b8d08149b58c" -watchpack@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.3.1.tgz#7d8693907b28ce6013e7f3610aa2a1acf07dad87" +watchpack@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac" dependencies: async "^2.1.2" - chokidar "^1.4.3" + chokidar "^1.7.0" graceful-fs "^4.1.2" wbuf@^1.1.0, wbuf@^1.7.2: @@ -5592,8 +5595,8 @@ webpack-dev-middleware@^1.11.0: range-parser "^1.0.3" webpack-dev-server@^2.4.5: - version "2.5.1" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.5.1.tgz#a02e726a87bb603db5d71abb7d6d2649bf10c769" + version "2.6.1" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.6.1.tgz#0b292a9da96daf80a65988f69f87b4166e5defe7" dependencies: ansi-html "0.0.7" bonjour "^3.5.0" @@ -5605,12 +5608,13 @@ webpack-dev-server@^2.4.5: html-entities "^1.2.0" http-proxy-middleware "~0.17.4" internal-ip "^1.2.0" + loglevel "^1.4.1" opn "4.0.2" portfinder "^1.0.9" selfsigned "^1.9.1" serve-index "^1.7.2" sockjs "0.3.18" - sockjs-client "1.1.2" + sockjs-client "1.1.4" spdy "^3.4.1" strip-ansi "^3.0.0" supports-color "^3.1.1" @@ -5625,15 +5629,15 @@ webpack-sources@^1.0.1: source-map "~0.5.3" "webpack@>=2.2.0 <4": - version "3.1.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.1.0.tgz#ac0675e500db835f9ab2369d29ba096f51ad0731" + version "3.4.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.4.1.tgz#4c3f4f3fb318155a4db0cb6a36ff05c5697418f4" dependencies: acorn "^5.0.0" acorn-dynamic-import "^2.0.0" ajv "^5.1.5" ajv-keywords "^2.0.0" async "^2.1.2" - enhanced-resolve "^3.3.0" + enhanced-resolve "^3.4.0" escope "^3.6.0" interpret "^1.0.0" json-loader "^0.5.4" @@ -5644,12 +5648,12 @@ webpack-sources@^1.0.1: mkdirp "~0.5.0" node-libs-browser "^2.0.0" source-map "^0.5.3" - supports-color "^3.1.0" - tapable "~0.2.5" + supports-color "^4.2.1" + tapable "^0.2.7" uglifyjs-webpack-plugin "^0.4.6" - watchpack "^1.3.1" + watchpack "^1.4.0" webpack-sources "^1.0.1" - yargs "^6.0.0" + yargs "^8.0.2" websocket-driver@>=0.5.1: version "0.6.5" @@ -5808,7 +5812,7 @@ yargs@^7.0.0: y18n "^3.2.1" yargs-parser "^5.0.0" -yargs@^8.0.1: +yargs@^8.0.1, yargs@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" dependencies: From 0b3af039ec24495a5a4059a79f30a28231e2d993 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 26 Jul 2017 12:49:40 -0400 Subject: [PATCH 18/68] Adding quotes... just in case there are spaces This isn't a perfect escape, but it's just for test paths --- test/bin/encore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bin/encore.js b/test/bin/encore.js index acdcc831..9a3f5a5d 100644 --- a/test/bin/encore.js +++ b/test/bin/encore.js @@ -39,7 +39,7 @@ module.exports = Encore.getWebpackConfig(); ); const binPath = path.resolve(__dirname, '../', '../', 'bin', 'encore.js'); - exec(`node ${binPath} dev --context=${testDir}`, { cwd: testDir }, (err, stdout, stderr) => { + exec(`node '${binPath}' dev --context='${testDir}'`, { cwd: testDir }, (err, stdout, stderr) => { if (err) { throw new Error(`Error executing encore: ${err} ${stderr} ${stdout}`); } From cd7ca2d293726b48036a1f24af4a581489a4f006 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 26 Jul 2017 12:52:53 -0400 Subject: [PATCH 19/68] prepping changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4493d7e..d7218b4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # CHANGELOG +## 0.12.0 + + * Fixed a bug with webpack 3.4.0 ("Can't resolve dev") - #114. + + * Added `--keep-public-path` option to `dev-server` that allows + you to specify that you do *not* want your `publicPath` to + automatically point at the dev-server URL. Also relaxed the + requirements when using `dev-server` so that you *can* now + specify a custom, fully-qualified `publicPath` URL - #96 + + * Fixed bug where `@import` CSS wouldn't use postcss - #108 + ## 0.11.0 * The `webpack` package was upgraded from version 2.2 to 3.1 #53. The From 119654c753d18939cce615463238dac6c75b7ca9 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 26 Jul 2017 12:53:54 -0400 Subject: [PATCH 20/68] Tagging 0.12.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index adb95c49..bfddfcdc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@symfony/webpack-encore", - "version": "0.11.0", + "version": "0.12.0", "description": "", "main": "index.js", "scripts": { From dab83858f9769dbd5acafb7abc9e6824eaff2dea Mon Sep 17 00:00:00 2001 From: Lyrkan Date: Sat, 22 Jul 2017 22:23:48 +0200 Subject: [PATCH 21/68] Modify images/fonts loaders so a hash is always added to the name of output files --- fixtures/css/same_filename.css | 17 ++++ fixtures/fonts/same_filename/Roboto.woff2 | Bin 0 -> 64268 bytes .../images/same_filename/symfony_logo.png | Bin 0 -> 15991 bytes test/functional.js | 76 ++++++++++++++---- 4 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 fixtures/css/same_filename.css create mode 100644 fixtures/fonts/same_filename/Roboto.woff2 create mode 100644 fixtures/images/same_filename/symfony_logo.png diff --git a/fixtures/css/same_filename.css b/fixtures/css/same_filename.css new file mode 100644 index 00000000..78b2927b --- /dev/null +++ b/fixtures/css/same_filename.css @@ -0,0 +1,17 @@ +h4 { + background: top left url('./../images/symfony_logo.png') no-repeat; +} + +h5 { + background: top left url('./../images/same_filename/symfony_logo.png') no-repeat; +} + +@font-face { + font-family: 'Roboto'; + src: url('./../fonts/Roboto.woff2') format('woff2'); +} + +@font-face { + font-family: 'Roboto2'; + src: url('./../fonts/same_filename/Roboto.woff2') format('woff2'); +} \ No newline at end of file diff --git a/fixtures/fonts/same_filename/Roboto.woff2 b/fixtures/fonts/same_filename/Roboto.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..0707d9ab13144fa7fc48e4b81df5cd75e4d7b32c GIT binary patch literal 64268 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�H6diG}Ai0IpO z-I>7wG0=n_hN#qp6wImZp*v(s_%tiE8gK391<(e9gJY>w5(r=JhXAwIUXW9YuTHRb zoBAWu!{+Ud=_{`z0I&fFULa7=A?c9wSc0s0e^~2rD6BGt2M|yVmI%$5x=<#>DnA1^-jp0_WYO^pNcJif;uXH-)sDzH02DB*m!r#UwR!)zGj zULKH5!hBH9?X6*=5qn){;Q{3Sq-7Ez+|AtB5}QpVaC3g70Ek8Qs6e6%)qp24y%(~a zYZ!#is5B;p(F!A-86MJsTEf7}5t8h`&R>4`vw!kaZ-AEog!M2GUx!o1_5cySx$q${ zu8&?`FO0O;jj!$~D)&roojQZ+QQd3|?(vq4Otp((sZAX@CPRF+CQKRO<_shw^a~qr zMh(ti$2ASfv$BF+}Bfw|eb;0fZ9b_k*|lSvH0TKglcFuz-A z2TK9~7ITCu5fn-p_vS8RS(CRiZUHs`46H^iST~#EG9pB^2y|#q`!FSMi+kb>1?d3* zQx!*YLqeX2Umi9@j%K2T9;8-)s^rQRkb=CjdiVfm)X$qlMk!^`ZwSgr=&dIxnCh0O z-)Rcz?0QWLvO$1S1c9xf%upo~%`?fjB(c~FA8jfjtFlMZFE4$B)i|PRHHK|)o@>M| zF))s@*3l|Zk0)16gV7rUe02Bd%MYI-GirGQT!ZbnbYEqEq~)&R4Ew~u@${RHLpq%( z5$|#xgeZ2H5)~otjdc{--3AS9luaxKaYo+4oe&CCMqS(^>J)2||DfEc;dR3d!`rJ)#^%d>o$dJPL<1WpLP`(XanFWqMSd=arw0}}O_wV(}{&-@{^?GFtLyEgpf1T~}I zk%lK0qqGjj?#$fft)^L;kx_zuNCGhhsZwmT7qZ5tx@D0|r+Q#9?pq+%MM0i5g_5~d z^swL>#d0kuXb#gqiiSaK^67$r6e52^6pc=EQEILBP=1~P>dF`; zpD8yG)c|QIk`_c-ET$eB?B<{`O{-{v?V{vokTWwcKu$bAK|ArQS7KyciPV);+BPFo|$Zl5v!7i*1YT|+kv z$pP%44tk#Qx+=)9CBKOTby*=$SA6)_Oolfw(M7 z+{a%~Zj_kPDO0q|%&nbGC!HCKz2+PfPz~vZ%*$FRu7d_9mq`~+OGmF-W^0Wk^t83} zIM?THlYpPWnZO#j2t>QArAca6BOro=wBk=A_sY>WFstrT2{J0<8QBG#$CS(@wt0VW z(U+!6dTNA+Ar6G|e!FSksS_CxqEn?-?M21GkSuy_2Gl`y4GGmzWCrqZxZiMQmb} zS+96Z`!GTCp)`|<)GIF9b0*9Vag=e~D3S4+^KdlZ4W>RvERV$&XeCE zsb#vmEL^?JU&HcnM2q$g(Nqsc2_5qZ)vQbvQ|)g-NZp6Uop)=z8@XIHx`-mjviAoq zxLJGwcFGP{#JgVffEBPL80nr24{t;_4%xy|Z^hz^AZZF!1@NGS93OX~jKJsd7@H2Q zF}1sAgQ@_)zo4{kkgEh@OL|tE5Se*i9K_c}A~asAO9|618%r#sbC^omlG;uC!3dXV zc%&v=(VD8%o7ni9&!)QFrGfOGk_3fpbZMiG)V{w32~QCrMrz+wPR$_Sb|=Ug4kyJs z2ch1`9yLQm;Vzv>5MP(q%1T57Uh6=OnvKZLBh`ScpV9!n16|@CQH=WGw=p9s-5V2Q zK2zuUG8yfAf2=E-wMN2RH>n2h@iv(Q zmsW<@0=kt^=(ev-7($cLi82E6*70CGQ3`^RZ|0uUc*NrnJDt zeocF-J^^SWz2uE%NP-MqkqRI)8i*VuWH`6g&|-@0q_; zUV?Z{0vwF7TZPBi(UB&i>Ov!(D3dQfesO&A^ga7MIht0K4oq|@jLP~gD2D2-;)T$f2gPWtcXuqU2Mi`}M=Gg$$F_ST)trUkr@ z8XL!*BeWCyfglw+WXEw1M+)TMb=@uKdcE*u9zDjA`#Kfnkb?X?+s9_#AgkB#39Sw#P6Mh z*Numq6oMryjtW^}q;@;szndKGd*utAqJwd*>FuOuD5^Dm`GcMJ3*NJCSJPHm4Ntqf zH|O61)!d|oD?zu}H5Ur3`%9!5Dj6h*v+B{^%j~X$*hJ?3QWJJt#VIo4FfV9g?X%J1 zNW5S!&tw<4c4h)W)d_g1928R}^mK0L@r0e0f4fW4iY>bdJ8BZ#dy%FNvceaoqOz9- z6ltMuw&L?)brn<5rH`(kE+>=aJR`!b2nn+O<+4IG!d6=<6cp5=;lpwpmP6jcE1ic%q6uSM{VDkpe_W@plr~KaxNh^08;t3rVgc zyORu2NzktsJN)p-OF~qj)RoMJ4m%%o$`Tdb9~w?mulWrr$^2-Rm`&aXfmS?&Pa1i~ zBK43Vs7oXC?4Yu6!bQHr-KWiK^HVcK<1}8*!H|>~B&0}?R>yfvDChE%LbFVK22mx! zEi6HeISfz9HK1}+6f_Ye8IV(DM`xE1dTOpchP9h*2hxIH66q_F-ynwx)XdyJa`(033;`!HxjQcnjKOnAvKHyzO~6M| zV*g4$L2mmqac?rX&rf?GyzF0@waVY<$Ns3mTr8ksd?YI&eFQ-*XI;PIoR2muUXy|U z#Jt#Nrm3;V0!FlXn{ECnyY>%rjq(O|)ANV->)>F|-B1t^Gu`epRZoOz)z0;1Qrh=? zS|Q14sM@e4$g1CeUIh#NqVEH!^CJP}WiT(3s;Z~v^VqGZnZ`iGeOCzTGwPX_m&Ha{ zqTYk6CRM8zMG_I6+~sSVcUzQ$_snpCfI2}0zN1wn+mG2s!f8_~vaaWhGi!(~$gJ+b z>}pe1KWgA?KNZnU2FZ?VZ6`$5kR^KCI=ipN8G5))$nQ^C*dxH)>1_dJ2wfurZmGoC zw4L`17kyY3R=+$uGKkM5bJoOjsNBIm6_{D2r z-uIiIy}24_jDoyU*F~UJQMau|Dbwhx8@d2xVD;6FWqBwt&K2gaDMIL&6BLghaoN1g zI?zC0zUb6@Q4|G@PFmo6}y4h_xnpaSVC7PRodB}w088M;lG z3=Xh{cUJPPrJSkPM95A{LI2yz({IK8`FMv^q_8MPvCEgq2DnZ`Qz3SZHHbYxs!N#+ zEziu-a8#jt@{%Ke6eAj9*2f$!$E3{Sb3Z7eUuxnx{bD)ZA4WJThPO?fg&Q$FiH`Vy8MkAZ&rYT&J4GsV8Fosny_3;S z2Q)hrXP)Rm9DlXY75jwQ7n<^c!_ow@o46)MwqbGJ>fxg~wJ(1Jre#5B2vBmOCntM%P6 z?_xxo4R1NCcf3XoeyKh9SSx)DZ+oa2ykMVeof$|=#42uuGSfIUX>5eSCmQyJT z;spcr)kH|?CZ!oQ)D{_{;0aY0cP}Fsa%ah!&G|T@d@m$_^S)$sYf5=wswb)zq9s>E zZyZc;ZC>56nxT%NNV&VQbU~3oRCK6VCSubc# zYj*kp+#_NjZ$`y2X^;a^9xt}CgY54Ajdw8BC)KU1sC%1LHn$&3XOIJ4c?eLnxm(;J zSsovFsqQDIy!|AO&lis?J;((^{fE4MhsSz8K7!YWvn9vjwteLAuyGR<5h+HngdSEgTJjEQ_mGh<$hvEak!;7V$ z2pZZVhm!Q7RlSnkig}e|d)j4t(eO6kCOh%b_RihIx*ih7>uuQXW2uzyEF-d<6$BwS zaz8(PqXsTK_`x-?fy4G^Ztr=GM{Eq^siA*c-_ZFp<%q79pij<5*d8*r@}(&`w`tpp zJD@kKV|!9@m9VW02YgT^vt6?L8h%y&!td_O%rB)k51P-3cM^MZOMLax1gecq<*oOi zzEY#t2+T~`j1C0tVo8~@n|3j_&#yssiYRDT6Cw!IEuKY`BWyf$EDN}cp%ID5r3>u* zrzxJLinZ(vLwL&xPoMT_2ojXKiHKZ+T(RLw2x7sSkCh+ud(iSD{`lG$!S6?}_B3V& zi6_T=)n6u#6j3tbP#SGka+z1tkZ(3%Wd{DQX2`qdheR6`0Ca%jK~A9eenu+7?XT!V z6=ULgBfd8=wBR%=K6ATvqL>53f~AZS>Nrm&p$OC!@*wo9S#jf<5;a9m2^$D{*SHS% zy6&<+!r3V<60+M88kq!5$)4`iUE$Q-sb~5Pe`gQ-+}o4dQ~%=f4-Wa{{6qgdzwdo6 z_}YKe=wL>&^J=Y`arf{+@I$j_hEdU0m7xIFFG?Gy47p=F6|5 zH4C7+I5m-p7CFct#ilE0&}j|?UU(zMu-^>3_Ff<+_}Zs}`m}<(mmqc9cEfND5KoFS z0o7prXL5v~pcarE2uICQN(XG~)Pv}wo7Q_PNal}fk&Zlb=Quab=}KDNqVi)>E@NwU zT)ku`&M--rLMVCB3wL{L<*h_R`z|y)^l6WkHxJ>iJmj@CoViEF!_-jKf)m}B`~1w* zCrCpy!~s0W$Kq&}t(&;e3zKu6dd=?huqPk2Bt4v;_8iyUt3fWCr)tQnS+~JgFDq&Z zQc#-8<+W9?l{rcMgXS?WmvqQ?LCr9SklSszS6uuZloZ$)i$Ey7wn!(OQuw?o3TKYU zm5>m)O|Xs(s-%VmB}BysOH*$mBW05z3r(w44l{7FBb0PZs#;J-%KaMS7$(%0S=5|z zL0;%fIzc*;mqL+IDl!skq>2O$#G+GJKb@(@$r#A?A?{w7nK{td#JrhkpYk{AOf_X9 z)yrjjlULd|BZiFuOZCzgUv3*8% zeS?F1kngqU`{NmWoTXfJGF^K@q6{0~ywL;56$d5(DId1BVf)ce zrhK*if9>C&)J(RK(1a?p#(QvRI844`GZpQhr;qU~;7unYCP6aHnpH8T_uW~oIq2vp zHaT^vVPaMt(iRe_+w`C!8QRSgX^tQw;qQhvt+5S4ImR^Ox)MnIowO&7I)DFp^1_yt z_d!iH+)fJ%Q*wL4PPN*nQ1nQ0>M&V$WzFC$DJ3a<@ZKwXybbuqJ{NjNJ%RgeKcSY~ zpS&n+Z`U~~%>BZ<>gnNrG#`7bv z6||v>ax#Xt(yk+|a~Hyz*9-Dok@+&uX|DKrX&IQW=cvxmX5k2#jjDSUEwY-(@@ofD zfLp~`1$sQ8Us>fVePJatpG2yJ(ww61)6qrcCd|Vb8dhAIP!-CC2+6)fM9{1q$b2V; zTya=NY_I1CEBoqDWu@vvylPfWgXj->bNh**=68m;o@gI#ZK#{EzYGs zi^zr#VX&Z5LW(qx9+68BG$#epu`GHhmyX}l*Ojgd>!4GRR&0+<&KTqn4J|@~v}hNQ z@Q3Qv(s_Oim1l-apW{IvOB$i@`5Pm&?XK~;>o~GM^wZ#%G(IZ>dP5G z-mbtop~bo7blM=1U2MH;D!YgpW$MZhf9eZE{OD>{#5B4Ib^4~^ zAGCel7`Ne2J<` zkE*jpW=Rh$V?pOxn1emuBctTC`3Yj}tjFqN{bn=U^rf^30WffZ<2Q3VYsg+|k zM4JvsMi%>R@F@f~R1HDYW^lRD$4Qis?$oOaCClW#%Ubgdc#aZ`YcT{N9 za;;ao&O$MljAe5xELr;q5n-cKsyYg|Tvp$DoUS%{u0?P&Qp8NusAp@7(~Yuq?b4&j zL>3vgMrZ3v?Dg=w%jRim@MlVQXxTA^;IJQ_RM0(Yhjaoj6S}(%8rRD#VNa}rKcj-~ z32oY*{F#c4q{jS47&M~hQ`tjmjsT^CR! zmryzLdO^gL>Pg4~r~rYOCR2+#jNPGK*^;pz$eqI<+dX+~VSLzrwqEN^qc%5gQM6TS z-#*UgH~%whl>4^NyA+C46Cw8YL@0Ej_;6v))j(Mls4Y1FU0=9{aEBBebWFwDT*B|^ zoU813p;2~WL)1gGQVohZ?$0X@Lsx^no~p2=9rOOG3Ie^H$GUQo?!je+v?3T7n?s1V zgoh!)!Mpbk!GV3`*m~Hvhk@!Yb8h5-afs51;bPvP(8nu=;o3F*A#F0<=mt?Vex!xF zaQBb?(+|S93k>^T4IP=`_zc+EMF5S%4(D_|mwV_3o6rQd+39c#Y#jF{o7rd``iXV~ zH^1zlT3cTha(r@2r-2_CGv8)1H-ZO2sWPNaGImG1`v#H{_R-qs%cpgHbj zlrZc4{5$KprTft~?f@ z=2;Y*c}OW#W4@s%bQPI6((F`&Fbf^6TmZ6$dDE}SkIoh`4)Xnc8TJc2)(a3ox287Q zAeFS6<*cp9M#Tw@lg5_**56m2eQ-@s11np8G>FZ@eQQz}FBF8`V)r}p>94`sLL^US zyGsfCn+z5`Lsqy(gP}%pesjlRcyex=m52A)RtH5}DT~&8BH#9Sm|ED`wOf7i{r*BW zyFL#)p2krXE5b5(|MRCQ-uEYZpRP;)=->Ev!b=C!!Zs~|q^zM8R~JFS$S_>!MV-i2 z<%HAjk3S^S_BNJu&HSBz@K1C}bb+Uy6@mYv7me2h^u~YBFnp-7kfeu~o0W!*rFmYSpPjpaK-#h@NK;5ezlqxd z2;XaCqJ=c~Os;d?h%Sf#Q)L{@kEkS`#wFHBTauVGddfPcPqpKDoHE5ZdM#MW%tN-; z5vvM+_!=b)&B1aIKpO*u7-&*WRcag%g~Utmm!^!PMEN9G!wYQm!pEnq3p(TwK!buh zy0Ugk5371XHE5L8=VUA#s@^b_*k)S-J;wTPbS26>@-z`tnh%?W401EnnG8VfUC#b9 zZKTt=*|IQRaKV%dB-R*qB(ohP;!u0&*%|k?D6~(}Ku_pWqZAy<8~pkX8iSJFr8G%cu?m(BuX-RL$G$U`q9ZCX);Tz>Y_+c% zILs&kq_&SST|-gQDeV#t<#Y`Jn@}cO-Qm&-rWFT~32+NghI`b(M4uOgV6O99p6&p5 z>2*!TMp?doe-4VuGc_zXanfuwKMA=OiW>Df(dY*eQY)AuJ7N>sh+exEviFP_jyYk# z7}un_%2Y3s5K%F|X^ z3rn%c^e+)H?Ve^e^Q1M=x8i1Tk!sUIdYjzi(kZ3!5J|JBXV~{C$7zlN2<>f<<TgH%!f#anh944ca8rZdC>>E*HgK&8zalaYhTkrG_%@@?;k%bF^iJJ1u5?p=0qMf{ zzkjAEZU#;__e;P_1ql%;RcSef|j@fV%kYo3f2*MeS6*flWKZRzXrd@`A|!>6T=%O{le}xCItjd1+T=?4qfnwC@*KH$ zlt_oxQy5ph9n1OYipr%-YDu|OOgW==OS!a(3X)EI!FGpZuDp9bJ>LWrKB9BpJwqwU zJ!+DdW5=TqIHqD5hMQC~vKB)tl z;6%?cO6xVn^XS}cG3uTxs?;MS?1;#5oOz)d(8B1kx%I_!?sb_WH#CY9Re}}X?cM!% zu;J}`^EWuS;}%K zPT`zP#D$K$s#bOj3;Wb4E{_H*FJCINHklzsQ$oM$QIki=^x>IXizV# zU9x5xBU@1i^v>G12LapDJ{?$=ET~-5-M!|WJBZn{pHXKYf1SMu#49Cq1ax~#)W@?Y zUU%=T^M0zy-$B-hl4d2P;9eX}R1YGMD|4sJcZXGT=0@LMv1vxEtdju<;=|E3B()=L ziHxUfO{6OjMeX%XChSq&IZ3RgliB5fhk%wc5H*H$o-(XjS*-5nm>ydFLYwCY_882A+DAW^h-Up9ps0haL@ zLm(fbf;0Y4s#nh0UtGLa$Snf^?qQ@MO!1j(=xKMDAEz|k(pC5RY12?w;^(z1CG8+e z?^)_Fu4nkPq1WvWLM7d{F6wpHvoZIn7L_&7XInYu{?K4YSTVk z(;PjEeRw=k^{MS?hv^a{xn1o|GtHCA1+k*-LJhoP?{=+5-IQKGcS!6rddYPrXeFsN z@q9i{#`+JRNBUVNj3S6w+#xr?(6cu)U8HyL=1dbX}zM8CD;*EHe z4YKR!)jP%JKsEd!;f5Wsb`_!+J9Li0bR>v+_I^{YJw@eNsmK;hSgH6(RAk%~%sH7Y zMAT-w0V;x{@6R8{ODEi}5NHMhVhd6TSR~18j)izaU6hwfT!WOfhhDnQqi-~MQlu~b zvROaxKl=1{8jhY}I+)bG(%#z1A)SdDSMHOoVxbJ<#nasFdn&eu=LY+a>zwzlGk+KWGMYZRK-V4Y!4 z;K{popZ#)^Qi^t@M-sK>)g5u7VE5RfY_oIEksa2{jzLD)tiQN(4Fh9?I7vt-U)fw0 zJ9Qw{#Vwn{eyZ{kHJ2ll75p?I%Dt0{fD>FRdEha)Cyxq(sz?^jav`A`GsqdY(DdM4%d3 zbfpVXSQr4vAg)uB6spM<3xt^)9&ny|(NBZn)|J7p=xqVUW_oxOp#=aL5h@J9OK@u@tV z_A{yr>Gg(aMFX1M1e^7WLtCV4R(N(~9Yc?s_MCCf4jOu*xIv1rdgy~#vSB32rqH0I zxDR4G%PCtlixAbWs|cZMMPN(exM!@e6uCJCca%6j#yUayIhq;Gqcs{S&@NdzsyQ0J zWbtv-lI?lO`a$reKAR3RA><3OGV0ijJAyD&ha+Kg1@u`L*V_CD%ZEIE7JY5T+0qHN zg1IBI`g1K*q?!R+-5d2W{7o+T~mnC>Deswe8ZT;sZL z2%so(m%ElPOu=V@y0VV(f&%)+0Gk`mTO;%hU2`?$7pc#4M7yh3?@IUc`NY_E!{>GC z64>aw(BJLJW3rc`&e~>0Mx}DH5)^`gSYqrr3c96s)rReAt^TOp?b95l&?^t6=5IEf zGRODx3k(Gr(?)Zx0h+ys#xcUV*_<=RJK>lOQPn_1Eyz?2_LmD{sbd$b1-$v{^uhxN zq)FkpyB`gE;=P36l_(MOt&3RFis4Vg$k=0kTW5leLgMtu0Z=zBTgj^>33bR;)+RV!9gA2zUlH9% zzjaASw$-z9PA8Gk>)z09_UWAP!^N_n!&KLuX1P(#tsDy3!&)q9qxKFrI~ z-pZrvcz9DzjY&5TYybL?yi80vI)}?sXL`z>GFbv7n>{3?MQjz~l|R5J(}~|9-;1PD zApuu7N%Y01B$_&hOzo2!ds$|Klm7ws#m65%3d0A_{K2)s=znL?I%bwka(TdrcdB)A zlm?da4h5HcUW$ngwiVq6v&uUE_$7TV7J9-b8{64C**{pY= zc}W`F&7_1F88^L6+=7O&?egFoIs<>2E>yw$TNn9wkJ#lbP22?wN>mt)~)t`IlynHSVVc-ejGUCv7 zmC1pmvc?mx^T~Z09vIKkV8f+h&I)A6kbG*0J5r>OR^Vzd5ue*d?=y4 z@O}J$Z7AA?S*|W>w~OQ~B7{GX4<3UBw0P>)s&kpoNfgZ(NN>1d&aUBIgzu0B8j!m_ zaXrn5G(^Zd*>Vv0GI_(7jKPm|(m+JU3gl#&j4OnnL!fy9_*qU#dz$v2V%sjOGP)uM7C z-$y)gFtlfWz6`5jpKEw=lNqR{l|;I(O^Q$Ll~E8iT0Jo5Y_D9D;qCKcuMGFGDW{T> zW_+M3i4z}w%(+%&$S~ZdYaXSi7H{m@(iWfgtjzr33pri^0L-?#e7DTFn`y5b7Wd;H z#6g-rJ$6|o6<#{2GaKcT{QV(jjD5tLR#ppMiERmpf6u;){DOrwpju}fy+~w=uV(b~ zrK`B-HE<#VqRMQhonEP%;b@mGvVD-Ip>}&!WS5@#>u%YW%NngGGnoX(x)gP!5Hc?v zwAo^|@8XEbCOVAaFf-*mh2%zVvYxMjiC|3y!Y$?-aBH*~^Iyvcv*_n0-vBzZeq0ve zF;J-06+{_3@9tKHkF3D^-a`;$ET)8aj1^@&HYpkX361GjU!9uwjFr4dsz%?OQ-rGr z2pPqlkPqIrfv;&9={Nc_zB3z@KuDgwgoToxjyBoq(@X0$kD3bNPoWZ`9r3!F^-P|+Y1odc(x3x6B0yg{gR^3p|Gm?cGqdhe)X6l*?vWbHd@bCa+OZ5)){oH+YA7jMewg)Y*f@9b z&6^A7BE#v=M)kC&p_=8POgXG-0Ay0Od8BbeuJrtz#q5_^ zyZeTi6e6@cmI-qin+$nQ&qga_@hwH>lu~?hN#{ewQa*%&@~L~WDk!oAj#L)y4t{PL zS~W|64EBv}?0D)xl`i&)h)KkdKb;faC3#uek3ns>ac;GnDZ5n-a_Tr+9|^aqCob%z z4|U@x|FC6?s^;M2Td84&;|XEwvG7^>aBT?<2T4k)UM~0v*93Uiv*y9A$6JBtXmLn2 z9W(G%)@Wmb*q(|{sD3M>13pqw7san<50o&}d>WO7YEDe;h+-&wo{%^O;t_2?-G_>M zW@_Ic|NlsnG8T*B&%+Jemv&u^A4Kia&#$d+hb4hWrl7U(d|GoP1+ZBBT1}4FTAFxT z>;0(hcXk13y}dMFy}RtuBlPgK#YL2^k7Zf!;m<{24};l+qZND)NQ3t_F9ze8ig~@q zg4M>Y*KaQMpQVMtj#d2WuGd6{^if#P-U&AT3}zuU(s z@mhj%j*6yMD(br)OOH*Dt-qS|PnKr}nO%|gI9CEG$koa3)WGE@(cItL{gq^ztaF&6 zE-+q6lYwLuMuhAR5ymJc&8^mX%#ImxA?TdPX=*w@eZ(_epJpHrfoDQzvzxj(u@TQK zTV|8pnA@k3Jq%s=wP)5{0JZ8>w*Ns1vV-2kqUNQS;enXNW{jV*aTp)(ZEXCN5IQ%& z$kn-+aT$lZitU~oCC*=f*>N5{RwLLZgUWO)ja&T?qKGH)dYHwCSWY95#Tz?xpjv2Y z;?5V(D^G+s*4nj4nBi?{R=u5n_KYntF07MoCAI8n+FawTq$+m6@7xh&fk3CRX+$Fa zg^Yho#D7LaxapZEfchv=MFYwOvED;CtDsP5%s`bOe(KDE0stNe#X6)SqBU8RPH^3?~;5QyO zBiW?k6&G}{`e_gUGFB~tROMwL+p%Ru3Dp}#4v3%gE8u(0Qg7$0_jBSj`&j}XZ&q!h zgz`)l39;>=GvRA}?^AI^cSC{t4i&_L{muL1n~+-rYj=_{fDFlIIdD)U#4&|~Y!+Lp zPkTS9sC(1WHEpC}HA+_(Hj%7-y>dpy_6&2E&=fZar$+e2++nV`?JangN`&|cfBv0* z*j9cGcm6;>50ww}0as;D+kb7VcVhxX7O z+C!n^3AR_vn=3Z!SPaF3gF5!rvWFw{)m}=voR{?k+K;h5D_9l6j2X!+v!Q5w)r5?F zb9G6!TqbP{f9nR=tl89mWiMDocHJA>ja|j1<5t8jSuHX*2JfN1+6R{`?=*h4boB~b z>En=%$DhrGv5K%kt327VO7FM%;#%Zk1`er@oOaIhsY&X zZb43t+Mb(IU_AHw`ENhiaXF+Qm0uC46scei9P7T=OI9eeA{0-BzL*}C6*)5@XVkEh zH?44&fN@J{%ywR3h@dpneeAtWX;IrMi)?}P-a#T9}vmzt* z?=+W+!sUo4kBhKjaIv;Y-rptms_&%`llSYL@WOUVvORLTseYbSmlg`WcbX^2J)uH3#k+ZMs3D`HPGfc^s?klMJsl>FrKHd*^9by*`my-f>;J>fW}=i+ z^o}+W=Ii7*bN&zPkkaI;ytCkkc=cB{^{Kg>(}JleCP6x-ws%49YqJ26Yc{W|~-)W%%tcrTx>IHTe! zL*+VkVP_W!pvtTMwlCa>6??nNwkEYf5xYwE?6!yvJ6lnytr#y)Q7km{niMu^R82O! z`5s~j4#bPMHeLgjEf3=vJ&nfdu4gy6PjyDr#_~i}+rlhSvEN^i`{zrj9_>;#f83tN z2`BFqK4!=8#4$LXPmz6kXY57gdm1xD?}^TDBHVI$=)ybZ#v^yl0U}XiWy}8(GAggBw+1V3_2A74_TX*A0>Y6^E4DBY~{jSKO z?gi1^^*Ze-6RJaS-W`*v_~4C@Ihp+AebSj|Cn`6GQnG1m_MYAO|Myc~ayqWw?Nyg{ zYRj%N6@tLjow@XJpqKTYtJ5**sbV=Zo4%ZSX)sZ{?@Zsm+e2P)(vPfNmK$}fGO5Tw zfqyG~eSyOF{g7E+(gcV-l(sUkRg1@kqS3IS1%HWzAU_K<-bbfK~e?}rES1b$H?JK z&*O#zd(<-7!rz-<7NmSGVt~B1meDDjxERG0p@6qx;GgQu@_iw&y+~V5&>@<9o zm20BjWgyzOAbN_~dZ1n$W2|L|pNt`^12};u;k2sigVHutA`(@_S_oQhfj*!li5XcU zSm7_4jrIR3{X_r=AZGJVOm2^THT!>eoBaSFB2T~Qbp;Ul@I^ZF+dR|rGyh2ik_!ZY zh$ui({sjZ%`u=Ag@lOH5{f^WoH!0R(eSFc>o?mHeye*7)k0dpDC4sl%E7XeS+0-3G zPG^N8aaAVBJb36Ubv9z%r<%U5#F$cr78%hfL0Jr!N@~J}su2X)^XE^{g|@Zsm-Yy% z_@@ak%*0$%98AC?dG!diEu#yq?k1Ls%7Ly}KQV9BXX2f)*75H8V_g*~Cpj&Hi!6vX zzz3>{p#C@1MoBnPJ={@rMN35$zD|DlA;_9nLR8N$@}h6_SIEQ zwRX9F^L75%lhqCVEpTP#v~=Y?S%02`P}22GK)1{Hz=Ifs`ctNUjam!?dyu@AOf??8 z+O~MAs|c}zdO5i)0X3$&pqKwK%+fw|%UofGGop(s(WFt3 zqnAeIDP*db93jvENJb{XjO|X6X8+4j{-Wg8EhA~Ow!2S|9h%9cNQwdYcGdblL{+wv z^%-l68K!6@q^S|Ad`t^L^& zYcdVQq*)#lax&Cr)HE4FIU^(0*2KX|@+mn@uEn4un&cyQLXFhb(^0CNr`b|wzJ!H^igh7)laBm-& z;Jje|w9llQ6NSN-yW%tLcu0=CXs2FcFNHedX z$rJ~nkX%D(8zi5Y_l#owSnxHvT$MyM`_U?gY-r;2WFOcDfl1udaFmnpD}&?^btvw< zZ0^tsPYuF8TH;sw6pT)@{ou}i4sP&?16-pG8)k_J{1F)VG_=^C5k23K>q(grRMh%0 z$|&wi2?g&c`7SY)wD);XqZ6sK?A|~%VnWHbGC?|+Wka8-EeHF5?7KQ~${89AN%#wl zHk_cyUT8GV(?hpO|I+h(nFLFwB%3DFATOQ3*TAGp366|MOi*sl(k63LQ2j(}Ggg3mGReD?#h59}~=YA!Z+%n&Cu5_7=RtbEf zw9dUG8n1A*5RKr>&#LGsMIA%lKAgd{hyrQdY`gm$h&tz0u&j6ep^i}gKR|(-h1gSe zPO*l1`J=-&RGR!KP7aK1P_UOIsmW&RFCm~vOq8-{u;ml!o|X0K((uu;Na`9@Me{)3 zECDChGU}8C?HJucQEM2AurJMu7M*`R1xohFvm+}4xE1UJUlWCcjaOGMWN?8K@T3S4 z8hS8eF%HR6A`r=Olkr)~?j3kU$r*tFFvnt}>~O?jb1Zc1kpJxl)Wo652)0~;4o_g1 zqEs8|Tp=eGnq7=4!Py=vrEFcy7ku~k2;#X{+3BW99@HySRvMJ^=_26l{fvB)%2D2W z9Pp^NuJj_}Uj#36|DoUpPUQcvg z?!1{ids>~b=jS6a(G@N3(IH63O_6Q({Q_~Qa7JD@X^#tZIQzN%R3JO?s2mYjJrk-v zeGN37Y1%3E-iW8NKob-?)E68PrvziM?$K3^HqUXTbW+u=dqlDzsbr}}Ie%*vmD0fj zeYWpmZ9M0aBQLvkI%(ms@7DPzKP1|Vp?osr!BH!aN&1=WT{P;zl+3Mfsn`U%AH~?y zChyGFi3gnK3oC~;YZ*fHXi)&{P;2$HEmkMOcFn?5thG8V^V#%m52ox^6j-eQ06W!* zuvoMb&}v<}u_-;%rx-L_t0=Hl0RTWgWuq4@QL7VSHQR_md8n1RBjG7U=5WGixC3iLcg^`L%1$2Obx zY5U8L9pHAs@qXJhv1gvGm_&Ap61yS*JrN*#Q?RFK$I*(KI9BCvcy}hFM@0QXngi%| z==cy4@88G%j1^!s-jjWWLNCT#Fj9OMn!yVjj`hYaVY#u@_zC6`--1`dCnf#N)rfIn zxv7WUXAU9Cu`cWdfUQ<(q*Od9)fq`+qs|-D@BAgwkcBp|&RAY*dH*L~tu&20>wV%N zlrWlv2ZZ_T=UA`UT;rYb)U7-1UTrKEa`29-Q6kezz_;Kv#6CaF<3`K;SzG5Lu>M}N zD37P17x)ak0k5d``StPIg*C;C@$flX775gsOLapmy(c^hK7yXpV)Vk>;6Ly*_zvf% zRnq3yxaD3c?yKEa{!Tid9%yx!^%HHnGg(*h+U|gNGGz6>eJHMtHsw9}P|b|uM6L3f z#sm7^vj)tr_b=?Emgl$iNPFx0G*Ehr5c7YZC02sDcWZhgkC{h^pLOmn)J==^wk3m2 zdrx=JbK@A}cnk2~Lt7c>U z(PHrl>>XZiN0>>N9AbVCV9m93Gg-cQS#tb=U8;eBEHM&OBcYE{SU8?Ns1Oz+_wtz7OVF*N|2pHlSAOe9jWJ&CnNH_5MrIe66 z!x6ZS1(1P-4dguuR)JiM2Ko3lw%{$>u8^A{iLZ+rK*GuUOZ{fZG?@t6sa}$o0-8hW z%a8*&!m@}*n0EvW%O~VeYg)34r7f+!bP4=q)A|_#I|DP<#=Z=s?R{8#YEVl!@SyrU1 zaPKhYR=`_ol$9<~=<6(a!{MreBCyUf?JIziIx`t`UNBjJAl(woDo6Fyxc@JJ&tFLX z23RCXvJ5F=FhxtF#*>uCJ!VBoYUgx3`sx^oA{l4lk%-?~tdOUAGWv5umm;0BsH(|g zNob=;E7ZgzNgQWlix&YA5T{5LlP8)MZja0O2qf~2zIq;HG_5}`C6N_~tai*IcubdI zU_Xu;z8S-!Z!q?p7AcBOCcuc6qBBdgc?3CQjtG<01Vkl^jPgY5CsEYYEh0*!JX|Xp zowG-hMTlISh8KokmW5nu;qf%dklgQb#m^fn(DVNqQ%b>PqFmousZ7Lkhv9;NWStRA zG4^U6w*=i2Lqwe}&%CZ&)!=Fjg@h}Por(5$7(X*W>TInK+^fMjRwyDotv)BliSi2?g`RKR|M3z%)s%?_~L;;3D~Oxmr6`530*`W1;48HxIlG;9jjAOY)T77Vl$k y9u_2MuMX-6dOFredP?iJx1sw$z=CS+Sf01V``Vk<mJxJDVxd_D4=w0{{STsQtqL literal 0 HcmV?d00001 diff --git a/fixtures/images/same_filename/symfony_logo.png b/fixtures/images/same_filename/symfony_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c065647d5261f678e31176065787164cdf36728a GIT binary patch literal 15991 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 NI~f&7`CF6E{|}KMPMZJ# literal 0 HcmV?d00001 diff --git a/test/functional.js b/test/functional.js index 7d9f006e..1beba803 100644 --- a/test/functional.js +++ b/test/functional.js @@ -62,8 +62,8 @@ describe('Functional tests using webpack', function() { 'main.js', 'font.css', 'bg.css', - 'fonts/Roboto.woff2', - 'images/symfony_logo.png', + 'fonts/Roboto.9896f773628188b649ed5824fa363290.woff2', + 'images/symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png', 'manifest.json' ]); @@ -87,11 +87,11 @@ describe('Functional tests using webpack', function() { ); webpackAssert.assertManifestPath( 'build/fonts/Roboto.woff2', - '/build/fonts/Roboto.woff2' + '/build/fonts/Roboto.9896f773628188b649ed5824fa363290.woff2' ); webpackAssert.assertManifestPath( 'build/images/symfony_logo.png', - '/build/images/symfony_logo.png' + '/build/images/symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png' ); done(); @@ -119,11 +119,11 @@ describe('Functional tests using webpack', function() { webpackAssert.assertOutputFileContains( 'bg.css', - 'http://localhost:8090/assets/images/symfony_logo.png' + 'http://localhost:8090/assets/images/symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png' ); webpackAssert.assertOutputFileContains( 'font.css', - 'http://localhost:8090/assets/fonts/Roboto.woff2' + 'http://localhost:8090/assets/fonts/Roboto.9896f773628188b649ed5824fa363290.woff2' ); // manifest file has CDN in value webpackAssert.assertManifestPath( @@ -172,7 +172,7 @@ describe('Functional tests using webpack', function() { webpackAssert.assertOutputFileContains( 'bg.css', - 'http://localhost:8090/assets/images/symfony_logo.png' + 'http://localhost:8090/assets/images/symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png' ); // manifest file has CDN in value webpackAssert.assertManifestPath( @@ -323,28 +323,76 @@ describe('Functional tests using webpack', function() { expect(path.join(config.outputPath, 'images')).to.be.a.directory() .with.files([ - 'symfony_logo.png' + 'symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png' ] ); expect(path.join(config.outputPath, 'fonts')).to.be.a.directory() .with.files([ - 'Roboto.woff2' + 'Roboto.9896f773628188b649ed5824fa363290.woff2' ] ); webpackAssert.assertOutputFileContains( 'bg.css', - '/build/images/symfony_logo.png' + '/build/images/symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png' ); + webpackAssert.assertOutputFileContains( 'font.css', - '/build/fonts/Roboto.woff2' + '/build/fonts/Roboto.9896f773628188b649ed5824fa363290.woff2' + ); + + done(); + }); + }); + + it('two fonts or images with the same filename should not output a single file', (done) => { + const config = createWebpackConfig('www/build', 'dev'); + config.setPublicPath('/build'); + config.addStyleEntry('styles', './css/same_filename.css'); + config.enableSassLoader(); + + testSetup.runWebpack(config, (webpackAssert) => { + expect(config.outputPath).to.be.a.directory() + .with.files([ + 'styles.css', + 'manifest.json' + ] + ); + + expect(path.join(config.outputPath, 'images')).to.be.a.directory() + .with.files([ + 'symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png', + 'symfony_logo.f27119c20951b473f2de80c000d60fa8.png' + ] + ); + + expect(path.join(config.outputPath, 'fonts')).to.be.a.directory() + .with.files([ + 'Roboto.9896f773628188b649ed5824fa363290.woff2', + 'Roboto.3c37aa69cd77e6a53a067170fa8fe2e9.woff2' + ] ); webpackAssert.assertOutputFileContains( - 'font.css', - '/build/fonts/Roboto.woff2' + 'styles.css', + '/build/images/symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png' + ); + + webpackAssert.assertOutputFileContains( + 'styles.css', + '/build/images/symfony_logo.f27119c20951b473f2de80c000d60fa8.png' + ); + + webpackAssert.assertOutputFileContains( + 'styles.css', + '/build/fonts/Roboto.9896f773628188b649ed5824fa363290.woff2' + ); + + webpackAssert.assertOutputFileContains( + 'styles.css', + '/build/fonts/Roboto.3c37aa69cd77e6a53a067170fa8fe2e9.woff2' ); done(); @@ -732,7 +780,7 @@ module.exports = { expect(config.outputPath).to.be.a.directory().with.deep.files([ 'main.js', 'main.css', - 'images/logo.png', + 'images/logo.82b9c7a5a3f405032b1db71a25f67021.png', 'manifest.json' ]); From 34a354f3f036f2582b26be95f67c0b6cd604e625 Mon Sep 17 00:00:00 2001 From: Lyrkan Date: Wed, 26 Jul 2017 18:56:13 +0200 Subject: [PATCH 22/68] Use [hash:8] for images and fonts filenames instead of [hash] --- lib/config-generator.js | 8 +++---- test/functional.js | 48 ++++++++++++++++++++--------------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/config-generator.js b/lib/config-generator.js index 3e7389e8..8d55b4c9 100644 --- a/lib/config-generator.js +++ b/lib/config-generator.js @@ -127,16 +127,16 @@ class ConfigGenerator { test: /\.(png|jpg|jpeg|gif|ico|svg)$/, loader: 'file-loader', options: { - name: `images/[name]${this.webpackConfig.useVersioning ? '.[hash]' : ''}.[ext]`, - publicPath: this.webpackConfig.getImagesPublicPath() + name: 'images/[name].[hash:8].[ext]', + publicPath: this.webpackConfig.getRealPublicPath() } }, { test: /\.(woff|woff2|ttf|eot|otf)$/, loader: 'file-loader', options: { - name: `fonts/[name]${this.webpackConfig.useVersioning ? '.[hash]' : ''}.[ext]`, - publicPath: this.webpackConfig.getFontsPublicPath() + name: 'fonts/[name].[hash:8].[ext]', + publicPath: this.webpackConfig.getRealPublicPath() } }, ]; diff --git a/test/functional.js b/test/functional.js index 1beba803..d04e5843 100644 --- a/test/functional.js +++ b/test/functional.js @@ -62,8 +62,8 @@ describe('Functional tests using webpack', function() { 'main.js', 'font.css', 'bg.css', - 'fonts/Roboto.9896f773628188b649ed5824fa363290.woff2', - 'images/symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png', + 'fonts/Roboto.9896f773.woff2', + 'images/symfony_logo.ea1ca6f7.png', 'manifest.json' ]); @@ -87,11 +87,11 @@ describe('Functional tests using webpack', function() { ); webpackAssert.assertManifestPath( 'build/fonts/Roboto.woff2', - '/build/fonts/Roboto.9896f773628188b649ed5824fa363290.woff2' + '/build/fonts/Roboto.9896f773.woff2' ); webpackAssert.assertManifestPath( 'build/images/symfony_logo.png', - '/build/images/symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png' + '/build/images/symfony_logo.ea1ca6f7.png' ); done(); @@ -119,11 +119,11 @@ describe('Functional tests using webpack', function() { webpackAssert.assertOutputFileContains( 'bg.css', - 'http://localhost:8090/assets/images/symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png' + 'http://localhost:8090/assets/images/symfony_logo.ea1ca6f7.png' ); webpackAssert.assertOutputFileContains( 'font.css', - 'http://localhost:8090/assets/fonts/Roboto.9896f773628188b649ed5824fa363290.woff2' + 'http://localhost:8090/assets/fonts/Roboto.9896f773.woff2' ); // manifest file has CDN in value webpackAssert.assertManifestPath( @@ -172,7 +172,7 @@ describe('Functional tests using webpack', function() { webpackAssert.assertOutputFileContains( 'bg.css', - 'http://localhost:8090/assets/images/symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png' + 'http://localhost:8090/assets/images/symfony_logo.ea1ca6f7.png' ); // manifest file has CDN in value webpackAssert.assertManifestPath( @@ -285,20 +285,20 @@ describe('Functional tests using webpack', function() { '0.d002be21e9bcf76057e9.js', // chunks are also versioned 'main.292c0347ed1240663cb1.js', 'h1.c84caea6dd12bba7955dee9fedd5fd03.css', - 'bg.42ced8eae2254268bb3c65f1e65bd041.css', + 'bg.483832e48e67e6a3b7f0ae064eadca51.css', 'manifest.json' ] ); expect(path.join(config.outputPath, 'images')).to.be.a.directory() .with.files([ - 'symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png' + 'symfony_logo.ea1ca6f7.png' ] ); webpackAssert.assertOutputFileContains( - 'bg.42ced8eae2254268bb3c65f1e65bd041.css', - '/build/images/symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png' + 'bg.483832e48e67e6a3b7f0ae064eadca51.css', + '/build/images/symfony_logo.ea1ca6f7.png' ); done(); @@ -323,24 +323,24 @@ describe('Functional tests using webpack', function() { expect(path.join(config.outputPath, 'images')).to.be.a.directory() .with.files([ - 'symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png' + 'symfony_logo.ea1ca6f7.png' ] ); expect(path.join(config.outputPath, 'fonts')).to.be.a.directory() .with.files([ - 'Roboto.9896f773628188b649ed5824fa363290.woff2' + 'Roboto.9896f773.woff2' ] ); webpackAssert.assertOutputFileContains( 'bg.css', - '/build/images/symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png' + '/build/images/symfony_logo.ea1ca6f7.png' ); webpackAssert.assertOutputFileContains( 'font.css', - '/build/fonts/Roboto.9896f773628188b649ed5824fa363290.woff2' + '/build/fonts/Roboto.9896f773.woff2' ); done(); @@ -363,36 +363,36 @@ describe('Functional tests using webpack', function() { expect(path.join(config.outputPath, 'images')).to.be.a.directory() .with.files([ - 'symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png', - 'symfony_logo.f27119c20951b473f2de80c000d60fa8.png' + 'symfony_logo.ea1ca6f7.png', + 'symfony_logo.f27119c2.png' ] ); expect(path.join(config.outputPath, 'fonts')).to.be.a.directory() .with.files([ - 'Roboto.9896f773628188b649ed5824fa363290.woff2', - 'Roboto.3c37aa69cd77e6a53a067170fa8fe2e9.woff2' + 'Roboto.9896f773.woff2', + 'Roboto.3c37aa69.woff2' ] ); webpackAssert.assertOutputFileContains( 'styles.css', - '/build/images/symfony_logo.ea1ca6f7f3719118f301a5cfcb1df3c0.png' + '/build/images/symfony_logo.ea1ca6f7.png' ); webpackAssert.assertOutputFileContains( 'styles.css', - '/build/images/symfony_logo.f27119c20951b473f2de80c000d60fa8.png' + '/build/images/symfony_logo.f27119c2.png' ); webpackAssert.assertOutputFileContains( 'styles.css', - '/build/fonts/Roboto.9896f773628188b649ed5824fa363290.woff2' + '/build/fonts/Roboto.9896f773.woff2' ); webpackAssert.assertOutputFileContains( 'styles.css', - '/build/fonts/Roboto.3c37aa69cd77e6a53a067170fa8fe2e9.woff2' + '/build/fonts/Roboto.3c37aa69.woff2' ); done(); @@ -780,7 +780,7 @@ module.exports = { expect(config.outputPath).to.be.a.directory().with.deep.files([ 'main.js', 'main.css', - 'images/logo.82b9c7a5a3f405032b1db71a25f67021.png', + 'images/logo.82b9c7a5.png', 'manifest.json' ]); From 04e9b9b04507b0bb1ed8548e2661d207456ad0ca Mon Sep 17 00:00:00 2001 From: Lyrkan Date: Fri, 4 Aug 2017 22:00:41 +0200 Subject: [PATCH 23/68] Add options callback to Encore.enablePostCssLoader() --- index.js | 14 ++++++++++++-- lib/WebpackConfig.js | 9 ++++++++- lib/loaders/css.js | 15 ++++++++++++--- test/WebpackConfig.js | 24 ++++++++++++++++++++++++ test/loaders/css.js | 34 ++++++++++++++++++++++++++-------- 5 files changed, 82 insertions(+), 14 deletions(-) diff --git a/index.js b/index.js index 2f1b7513..3bccfe7e 100644 --- a/index.js +++ b/index.js @@ -287,10 +287,20 @@ module.exports = { * * https://github.com/postcss/postcss-loader * + * Encore.enablePostCssLoader(); + * + * Or pass options to the loader + * + * Encore.enablePostCssLoader(function(options) { + * // https://github.com/postcss/postcss-loader#options + * // options.config = {...} + * }) + * + * @param {function} postCssLoaderOptionsCallback * @return {exports} */ - enablePostCssLoader() { - webpackConfig.enablePostCssLoader(); + enablePostCssLoader(postCssLoaderOptionsCallback = () => {}) { + webpackConfig.enablePostCssLoader(postCssLoaderOptionsCallback); return this; }, diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index 392eb161..23f5ac26 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -42,6 +42,7 @@ class WebpackConfig { this.useVersioning = false; this.useSourceMaps = false; this.usePostCssLoader = false; + this.postCssLoaderOptionsCallback = function() {}; this.useSassLoader = false; this.sassLoaderOptionsCallback = function() {}; this.sassOptions = { @@ -236,8 +237,14 @@ class WebpackConfig { this.addEntry(name, files); } - enablePostCssLoader() { + enablePostCssLoader(postCssLoaderOptionsCallback = () => {}) { this.usePostCssLoader = true; + + if (typeof postCssLoaderOptionsCallback !== 'function') { + throw new Error('Argument 1 to enablePostCssLoader() must be a callback function.'); + } + + this.postCssLoaderOptionsCallback = postCssLoaderOptionsCallback; } enableSassLoader(sassLoaderOptionsCallback = () => {}, options = {}) { diff --git a/lib/loaders/css.js b/lib/loaders/css.js index 231f16d2..cc18ba98 100644 --- a/lib/loaders/css.js +++ b/lib/loaders/css.js @@ -38,11 +38,20 @@ module.exports = { if (usePostCssLoader) { loaderFeatures.ensureLoaderPackagesExist('postcss'); + const postCssLoaderOptions = { + sourceMap: webpackConfig.useSourceMaps + }; + + // allow options to be configured + webpackConfig.postCssLoaderOptionsCallback.apply( + // use config as the this variable + postCssLoaderOptions, + [postCssLoaderOptions] + ); + cssLoaders.push({ loader: 'postcss-loader', - options: { - sourceMap: webpackConfig.useSourceMaps - } + options: postCssLoaderOptions }); } diff --git a/test/WebpackConfig.js b/test/WebpackConfig.js index 06bdc71c..d65081ed 100644 --- a/test/WebpackConfig.js +++ b/test/WebpackConfig.js @@ -282,6 +282,30 @@ describe('WebpackConfig object', () => { }); }); + describe('enablePostCssLoader', () => { + it('Call with no config', () => { + const config = createConfig(); + config.enablePostCssLoader(); + + expect(config.usePostCssLoader).to.be.true; + }); + + it('Pass options callback', () => { + const config = createConfig(); + const callback = () => {}; + config.enablePostCssLoader(callback); + + expect(config.usePostCssLoader).to.be.true; + expect(config.postCssLoaderOptionsCallback).to.equal(callback); + }); + + it('Pass invalid options callback', () => { + const config = createConfig(); + + expect(() => config.enablePostCssLoader('FOO')).to.throw('must be a callback function'); + }); + }); + describe('enableSassLoader', () => { it('Call with no config', () => { const config = createConfig(); diff --git a/test/loaders/css.js b/test/loaders/css.js index 579b16b5..465c581d 100644 --- a/test/loaders/css.js +++ b/test/loaders/css.js @@ -44,14 +44,32 @@ describe('loaders/css', () => { expect(actualLoaders[0].options.minimize).to.be.true; }); - it('getLoaders() with PostCSS', () => { - const config = createConfig(); - config.enableSourceMaps(); - config.enablePostCssLoader(); + describe('getLoaders() with PostCSS', () => { + it('without options callback', () => { + const config = createConfig(); + config.enableSourceMaps(); + config.enablePostCssLoader(); - const actualLoaders = cssLoader.getLoaders(config); - // css-loader & postcss-loader - expect(actualLoaders).to.have.lengthOf(2); - expect(actualLoaders[1].options.sourceMap).to.be.true; + const actualLoaders = cssLoader.getLoaders(config); + // css-loader & postcss-loader + expect(actualLoaders).to.have.lengthOf(2); + expect(actualLoaders[1].options.sourceMap).to.be.true; + }); + + it('with options callback', () => { + const config = createConfig(); + config.enableSourceMaps(); + config.enablePostCssLoader((options) => { + options.config = { + path: 'config/postcss.config.js' + }; + }); + + const actualLoaders = cssLoader.getLoaders(config); + // css-loader & postcss-loader + expect(actualLoaders).to.have.lengthOf(2); + expect(actualLoaders[1].options.sourceMap).to.be.true; + expect(actualLoaders[1].options.config.path).to.equal('config/postcss.config.js'); + }); }); }); From 13cfa28ab46d8dafe6856f8fab87db10c4563d9a Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 26 Jul 2017 21:38:53 -0400 Subject: [PATCH 24/68] Removing quotes to get Windows tests passing These paths still need to be escaped - will likely fail if tests are run when Encore is installed in a directory with spaces - but that's a chore for later --- test/bin/encore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bin/encore.js b/test/bin/encore.js index 9a3f5a5d..acdcc831 100644 --- a/test/bin/encore.js +++ b/test/bin/encore.js @@ -39,7 +39,7 @@ module.exports = Encore.getWebpackConfig(); ); const binPath = path.resolve(__dirname, '../', '../', 'bin', 'encore.js'); - exec(`node '${binPath}' dev --context='${testDir}'`, { cwd: testDir }, (err, stdout, stderr) => { + exec(`node ${binPath} dev --context=${testDir}`, { cwd: testDir }, (err, stdout, stderr) => { if (err) { throw new Error(`Error executing encore: ${err} ${stderr} ${stdout}`); } From 1907d5c1b3f1a25d46f8626ef43344f0207e9d23 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 26 Jul 2017 22:15:34 -0400 Subject: [PATCH 25/68] Adding tests for the logger and cleaning up a bit internally Also added the recommendation method --- lib/logger.js | 57 ++++++++++++++++++++++++++------------ test/config/validator.js | 5 ++-- test/logger.js | 60 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 21 deletions(-) create mode 100644 test/logger.js diff --git a/lib/logger.js b/lib/logger.js index f51f1500..a59a1178 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -10,15 +10,31 @@ 'use strict'; const chalk = require('chalk'); -let isVerbose = false; -let quiet = false; -let messages = { - debug: [], - warning: [], + +const messagesKeys = [ + 'debug', + 'recommendation', + 'warning', +]; +const defaultConfig = { + isVerbose: false, + quiet: false +}; + +let messages = {}; +let config = {}; + +const reset = function() { + messages = {}; + for (let messageKey of messagesKeys) { + messages[messageKey] = []; + } + config = Object.assign({}, defaultConfig); }; +reset(); function log(message) { - if (quiet) { + if (config.quiet) { return; } @@ -29,31 +45,36 @@ module.exports = { debug(message) { messages.debug.push(message); - if (isVerbose) { - log(`${chalk.bgBlack.white(' DEBUG ')} ${message}`); + if (config.isVerbose) { + log(`${chalk.bgBlack.white(' DEBUG ')} ${message}`); } }, - warning(message) { - messages.warning.push(message); + recommendation(message) { + messages.recommendation.push(message); - log(`${chalk.bgYellow.black(' WARNING ')} ${chalk.yellow(message)}`); + log(`${chalk.bgBlue.white(' RECOMMEND ')} ${message}`); }, - clearMessages() { - messages.debug = []; - messages.warning = []; + warning(message) { + messages.warning.push(message); + + log(`${chalk.bgYellow.black(' WARNING ')} ${chalk.yellow(message)}`); }, getMessages() { return messages; }, - quiet() { - quiet = true; + quiet(setQuiet = true) { + config.quiet = setQuiet; + }, + + verbose(setVerbose = true) { + config.isVerbose = setVerbose; }, - verbose() { - isVerbose = true; + reset() { + reset(); } }; diff --git a/test/config/validator.js b/test/config/validator.js index 8f4f8555..d86df458 100644 --- a/test/config/validator.js +++ b/test/config/validator.js @@ -15,8 +15,6 @@ const RuntimeConfig = require('../../lib/config/RuntimeConfig'); const validator = require('../../lib/config/validator'); const logger = require('../../lib/logger'); -logger.quiet(); - function createConfig() { const runtimeConfig = new RuntimeConfig(); runtimeConfig.context = __dirname; @@ -77,7 +75,8 @@ describe('The validator function', () => { config.addEntry('main', './main'); config.runtimeConfig.useDevServer = true; - logger.clearMessages(); + logger.reset(); + logger.quiet(); validator(config); expect(logger.getMessages().warning).to.have.lengthOf(1); diff --git a/test/logger.js b/test/logger.js new file mode 100644 index 00000000..c7716a73 --- /dev/null +++ b/test/logger.js @@ -0,0 +1,60 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const expect = require('chai').expect; +require('../lib/context').runtimeConfig = {}; +const logger = require('../lib/logger'); + +describe('logger', () => { + beforeEach(() => { + logger.reset(); + }); + + afterEach(() => { + logger.reset(); + }); + + it('Smoke test for log methods', () => { + + const methods = [ + 'debug', + 'recommendation', + 'warning', + ]; + const testString = 'TEST MESSAGE'; + const expectedMessages = { + debug: [testString], + recommendation: [testString], + warning: [testString], + }; + + logger.quiet(); + logger.verbose(); + + for (let loggerMethod of methods) { + logger[loggerMethod](testString); + } + + // clone the object so the afterEach doesn't clear out before + // a failure message is shown + const actualMessages = Object.assign({}, logger.getMessages()); + expect(actualMessages).to.deep.equal(expectedMessages); + }); + + it('test reset()', () => { + logger.debug('DEBUG!'); + logger.reset(); + + const actualMessages = Object.assign({}, logger.getMessages()); + + expect(actualMessages.debug).to.have.lengthOf(0); + }); +}); From 8e5b721d56cf6d6cee210f226665b5167b1c2bc8 Mon Sep 17 00:00:00 2001 From: David Paz Date: Tue, 18 Jul 2017 17:37:06 +0200 Subject: [PATCH 26/68] Forked typescript type checking --- index.js | 17 +++++++ lib/WebpackConfig.js | 13 ++++++ lib/loader-features.js | 5 ++ lib/loaders/typescript.js | 11 +++++ lib/plugins/forked-ts-types.js | 29 ++++++++++++ package.json | 1 + test/WebpackConfig.js | 19 ++++++++ test/functional.js | 38 +++++++++++---- test/index.js | 9 ++++ test/plugins/forked-ts-types.js | 41 +++++++++++++++++ yarn.lock | 82 +++++++++++++++++++++++---------- 11 files changed, 231 insertions(+), 34 deletions(-) create mode 100644 lib/plugins/forked-ts-types.js create mode 100644 test/plugins/forked-ts-types.js diff --git a/index.js b/index.js index 2f1b7513..c97f31a0 100644 --- a/index.js +++ b/index.js @@ -392,6 +392,23 @@ module.exports = { return this; }, + /** + * Call this to enable forked type checking for TypeScript loader + * https://github.com/TypeStrong/ts-loader/blob/v2.3.0/README.md#faster-builds + * + * This is a build optimization API to reduce build times. + * + * @param {function} forkedTypeScriptTypesCheckOptionsCallback + * @return {exports} + */ + enableForkedTypeScriptTypesChecking(forkedTypeScriptTypesCheckOptionsCallback = () => {}) { + webpackConfig.enableForkedTypeScriptTypesChecking( + forkedTypeScriptTypesCheckOptionsCallback + ); + + return this; + }, + /** * If enabled, the Vue.js loader is enabled. * diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index 392eb161..58ab8ef3 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -58,6 +58,8 @@ class WebpackConfig { this.loaders = []; this.useTypeScriptLoader = false; this.tsConfigurationCallback = function() {}; + this.useForkedTypeScriptTypeChecking = false; + this.forkedTypeScriptTypesCheckOptionsCallback = () => {}; } getContext() { @@ -276,6 +278,17 @@ class WebpackConfig { this.tsConfigurationCallback = callback; } + enableForkedTypeScriptTypesChecking(forkedTypeScriptTypesCheckOptionsCallback = () => {}) { + + if (typeof forkedTypeScriptTypesCheckOptionsCallback !== 'function') { + throw new Error('Argument 1 to enableForkedTypeScriptTypesChecking() must be a callback function.'); + } + + this.useForkedTypeScriptTypeChecking = true; + this.forkedTypeScriptTypesCheckOptionsCallback = + forkedTypeScriptTypesCheckOptionsCallback; + } + enableVueLoader(vueLoaderOptionsCallback = () => {}) { this.useVueLoader = true; diff --git a/lib/loader-features.js b/lib/loader-features.js index e5cf87e7..d1746cc3 100644 --- a/lib/loader-features.js +++ b/lib/loader-features.js @@ -41,6 +41,11 @@ const loaderFeatures = { packages: ['typescript', 'ts-loader'], description: 'process TypeScript files' }, + forkedtypecheck: { + method: 'enableForkedTypeScriptTypesChecking()', + packages: ['typescript', 'ts-loader', 'fork-ts-checker-webpack-plugin'], + description: 'check TypeScript types in a separate process' + }, vue: { method: 'enableVueLoader()', // vue is needed so the end-user can do things diff --git a/lib/loaders/typescript.js b/lib/loaders/typescript.js index 53d9f17a..939e1079 100644 --- a/lib/loaders/typescript.js +++ b/lib/loaders/typescript.js @@ -32,6 +32,17 @@ module.exports = { [config] ); + // fork-ts-checker-webpack-plugin integration + if (webpackConfig.useForkedTypeScriptTypeChecking) { + loaderFeatures.ensureLoaderPackagesExist('forkedtypecheck'); + // force transpileOnly to speed up + config.transpileOnly = true; + + // add forked ts types plugin to the stack + const forkedTypesPluginUtil = require('../plugins/forked-ts-types'); // eslint-disable-line + forkedTypesPluginUtil(webpackConfig); + } + // use ts alongside with babel // @see https://github.com/TypeStrong/ts-loader/blob/master/README.md#babel let loaders = babelLoader.getLoaders(webpackConfig); diff --git a/lib/plugins/forked-ts-types.js b/lib/plugins/forked-ts-types.js new file mode 100644 index 00000000..83074e8b --- /dev/null +++ b/lib/plugins/forked-ts-types.js @@ -0,0 +1,29 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); // eslint-disable-line + +/** + * @param {WebpackConfig} webpackConfig + * @return {void} + */ +module.exports = function(webpackConfig) { + let config = {}; + + // allow for ts-loader config to be controlled + webpackConfig.forkedTypeScriptTypesCheckOptionsCallback.apply( + // use config as the this variable + config, + [config] + ); + + webpackConfig.addPlugin(new ForkTsCheckerWebpackPlugin(config)); +}; diff --git a/package.json b/package.json index bfddfcdc..c4c71482 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "eslint": "^3.19.0", "eslint-plugin-header": "^1.0.0", "eslint-plugin-node": "^4.2.2", + "fork-ts-checker-webpack-plugin": "^0.2.7", "http-server": "^0.9.0", "less": "^2.7.2", "less-loader": "^4.0.2", diff --git a/test/WebpackConfig.js b/test/WebpackConfig.js index 06bdc71c..987bd134 100644 --- a/test/WebpackConfig.js +++ b/test/WebpackConfig.js @@ -332,6 +332,25 @@ describe('WebpackConfig object', () => { }); }); + describe('enableForkedTypeScriptTypesChecking', () => { + it('Calling method sets it', () => { + const config = createConfig(); + config.enableTypeScriptLoader(); + const testCallback = () => {}; + config.enableForkedTypeScriptTypesChecking(testCallback); + expect(config.forkedTypeScriptTypesCheckOptionsCallback) + .to.equal(testCallback); + }); + + it('Calling with non-callback throws an error', () => { + const config = createConfig(); + + expect(() => { + config.enableForkedTypeScriptTypesChecking('FOO'); + }).to.throw('must be a callback function'); + }); + }); + describe('addPlugin', () => { it('extends the current registered plugins', () => { const config = createConfig(); diff --git a/test/functional.js b/test/functional.js index 9f57eacc..2409e05e 100644 --- a/test/functional.js +++ b/test/functional.js @@ -288,13 +288,13 @@ describe('Functional tests using webpack', function() { 'bg.483832e48e67e6a3b7f0ae064eadca51.css', 'manifest.json' ] - ); + ); expect(path.join(config.outputPath, 'images')).to.be.a.directory() .with.files([ 'symfony_logo.ea1ca6f7.png' ] - ); + ); webpackAssert.assertOutputFileContains( 'bg.483832e48e67e6a3b7f0ae064eadca51.css', @@ -319,19 +319,19 @@ describe('Functional tests using webpack', function() { 'font.css', 'manifest.json' ] - ); + ); expect(path.join(config.outputPath, 'images')).to.be.a.directory() .with.files([ 'symfony_logo.ea1ca6f7.png' ] - ); + ); expect(path.join(config.outputPath, 'fonts')).to.be.a.directory() .with.files([ 'Roboto.9896f773.woff2' ] - ); + ); webpackAssert.assertOutputFileContains( 'bg.css', @@ -359,21 +359,21 @@ describe('Functional tests using webpack', function() { 'styles.css', 'manifest.json' ] - ); + ); expect(path.join(config.outputPath, 'images')).to.be.a.directory() .with.files([ 'symfony_logo.ea1ca6f7.png', 'symfony_logo.f27119c2.png' ] - ); + ); expect(path.join(config.outputPath, 'fonts')).to.be.a.directory() .with.files([ 'Roboto.9896f773.woff2', 'Roboto.3c37aa69.woff2' ] - ); + ); webpackAssert.assertOutputFileContains( 'styles.css', @@ -632,7 +632,7 @@ module.exports = { fs.writeFileSync( path.join(appDir, '.babelrc'), -` + ` { "presets": [ ["env", { @@ -712,6 +712,26 @@ module.exports = { }); }); + it('TypeScript is compiled and type checking is done in a separate process!', (done) => { + this.timeout(8000); + setTimeout(done, 7000); + + const config = createWebpackConfig('www/build', 'dev'); + config.setPublicPath('/build'); + config.addEntry('main', ['./js/render.ts', './js/index.ts']); + config.enableTypeScriptLoader(); + // test should fail if `config.tsconfig` is not set up properly + config.enableForkedTypeScriptTypesChecking((config) => { + config.silent = true; // remove to get output on terminal + }); + + expect(function() { + testSetup.runWebpack(config, (webpackAssert) => { + done(); + }); + }).to.throw('wrong `tsconfig` path in fork plugin configuration (should be a relative or absolute path)'); + }); + it('The output directory is cleaned between builds', (done) => { const config = createWebpackConfig('www/build', 'dev'); config.setPublicPath('/build'); diff --git a/test/index.js b/test/index.js index d1d2b444..5c4c8687 100644 --- a/test/index.js +++ b/test/index.js @@ -186,6 +186,15 @@ describe('Public API', () => { }); + describe('enableForkedTypeScriptTypesChecking', () => { + + it('must return the API object', () => { + const returnedValue = api.enableForkedTypeScriptTypesChecking(); + expect(returnedValue).to.equal(api); + }); + + }); + describe('enableVueLoader', () => { it('must return the API object', () => { diff --git a/test/plugins/forked-ts-types.js b/test/plugins/forked-ts-types.js new file mode 100644 index 00000000..194e75d1 --- /dev/null +++ b/test/plugins/forked-ts-types.js @@ -0,0 +1,41 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +'use strict'; + +const expect = require('chai').expect; +const WebpackConfig = require('../../lib/WebpackConfig'); +const RuntimeConfig = require('../../lib/config/RuntimeConfig'); +const tsLoader = require('../../lib/loaders/typescript'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); + +function createConfig() { + const runtimeConfig = new RuntimeConfig(); + runtimeConfig.context = __dirname; + runtimeConfig.babelRcFileExists = false; + + return new WebpackConfig(runtimeConfig); +} + +describe('plugins/forkedtypecheck', () => { + it('getPlugins() basic usage', () => { + const config = createConfig(); + config.enableTypeScriptLoader(); + config.enableForkedTypeScriptTypesChecking(); + + expect(config.plugins).to.have.lengthOf(0); + const tsTypeChecker = require('../../lib/plugins/forked-ts-types'); + tsTypeChecker(config); + expect(config.plugins).to.have.lengthOf(1); + expect(config.plugins[0]).to.be.an.instanceof(ForkTsCheckerWebpackPlugin); + // after enabling plugin, check typescript loader has right config + const actualLoaders = tsLoader.getLoaders(config); + expect(actualLoaders[1].options.transpileOnly).to.be.true; + }); +}); diff --git a/yarn.lock b/yarn.lock index 22a39f78..24cfaec8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -137,11 +137,11 @@ ansi-styles@^3.1.0: color-convert "^1.9.0" anymatch@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" + version "1.3.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" dependencies: - arrify "^1.0.0" micromatch "^2.1.5" + normalize-path "^2.0.0" aproba@^1.0.3: version "1.1.2" @@ -780,8 +780,8 @@ babel-runtime@5.8.29: core-js "^1.0.0" babel-runtime@^6.18.0, babel-runtime@^6.22.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.25.0.tgz#33b98eaa5d482bb01a8d1aa6b437ad2b01aec41c" dependencies: core-js "^2.4.0" regenerator-runtime "^0.10.0" @@ -1057,12 +1057,12 @@ caniuse-api@^1.5.2: lodash.uniq "^4.5.0" caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000706" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000706.tgz#e2b5f0460573cbcc88a0985f5cced08f1617c6f5" + version "1.0.30000708" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000708.tgz#c2e736bd3b7fc5f6c14e4c6dfe62b98ed15e8a5b" caniuse-lite@^1.0.30000704: - version "1.0.30000706" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000706.tgz#bc59abc41ba7d4a3634dda95befded6114e1f24e" + version "1.0.30000708" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000708.tgz#71dbf388c57f379b1bb66c89a890edc04c2509b6" caseless@~0.12.0: version "0.12.0" @@ -1274,10 +1274,10 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" compressible@~2.0.10: - version "2.0.10" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.10.tgz#feda1c7f7617912732b29bf8cf26252a20b9eecd" + version "2.0.11" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.11.tgz#16718a75de283ed8e604041625a2064586797d8a" dependencies: - mime-db ">= 1.27.0 < 2" + mime-db ">= 1.29.0 < 2" compression@^1.5.2: version "1.7.0" @@ -1663,10 +1663,14 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" -depd@1.1.0, depd@~1.1.0: +depd@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" +depd@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" + des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" @@ -2276,6 +2280,18 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" +fork-ts-checker-webpack-plugin@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-0.2.7.tgz#4fdcf245abd1e7f971658c816ecd5e28d3cf4670" + dependencies: + babel-code-frame "^6.22.0" + chalk "^1.1.3" + chokidar "^1.7.0" + lodash.endswith "^4.2.1" + lodash.isfunction "^3.0.8" + lodash.isstring "^4.0.1" + lodash.startswith "^4.2.1" + form-data@~2.1.1: version "2.1.4" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" @@ -2985,8 +3001,8 @@ js-tokens@^3.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" js-yaml@^3.4.3, js-yaml@^3.5.1: - version "3.9.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.0.tgz#4ffbbf25c2ac963b8299dc74da7e3740de1c18ce" + version "3.9.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.1.tgz#08775cebdfdd359209f0d2acd383c8f86a6904a0" dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -3255,6 +3271,10 @@ lodash.defaults@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" +lodash.endswith@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.endswith/-/lodash.endswith-4.2.1.tgz#fed59ac1738ed3e236edd7064ec456448b37bc09" + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -3263,6 +3283,14 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" +lodash.isfunction@^3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.8.tgz#4db709fc81bc4a8fd7127a458a5346c5cdce2c6b" + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + lodash.keys@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" @@ -3283,6 +3311,10 @@ lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" +lodash.startswith@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.startswith/-/lodash.startswith-4.2.1.tgz#c598c4adce188a27e53145731cdc6c0e7177600c" + lodash.tail@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" @@ -3420,7 +3452,7 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -"mime-db@>= 1.27.0 < 2", mime-db@~1.29.0: +"mime-db@>= 1.29.0 < 2", mime-db@~1.29.0: version "1.29.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.29.0.tgz#48d26d235589651704ac5916ca06001914266878" @@ -3657,7 +3689,7 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.1: +normalize-path@^2.0.0, normalize-path@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" dependencies: @@ -3692,8 +3724,8 @@ npm-run-path@^2.0.0: set-blocking "~2.0.0" nsp@^2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/nsp/-/nsp-2.6.3.tgz#db05035953cda2ab3a571ee82fab84f4cb081d17" + version "2.7.0" + resolved "https://registry.yarnpkg.com/nsp/-/nsp-2.7.0.tgz#460b9abc716f5f6cc588b6f787990c9fc9b3097c" dependencies: chalk "^1.1.1" cli-table "^0.3.1" @@ -4690,8 +4722,8 @@ resolve-url@~0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" + version "1.4.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" dependencies: path-parse "^1.0.5" @@ -4911,8 +4943,8 @@ signal-exit@^3.0.0: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" sinon@^2.3.4: - version "2.4.0" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.4.0.tgz#398de1bd15c9c6d671b5ed708c8a121a213ae8b7" + version "2.4.1" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.4.1.tgz#021fd64b54cb77d9d2fb0d43cdedfae7629c3a36" dependencies: diff "^3.1.0" formatio "1.2.0" @@ -5319,8 +5351,8 @@ tryit@^1.0.1: resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" ts-loader@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-2.3.1.tgz#6edc603393c2775c40ad84e3420007f1c097eab0" + version "2.3.2" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-2.3.2.tgz#b71b9f0d0062c791a654d462140718f9f7817665" dependencies: chalk "^2.0.1" enhanced-resolve "^3.0.0" From 69530ea1c928ac08600bc636ce19ee6aa43152be Mon Sep 17 00:00:00 2001 From: Lyrkan Date: Fri, 4 Aug 2017 22:09:34 +0200 Subject: [PATCH 27/68] Remove duplicate addPlugin test case --- test/WebpackConfig.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/test/WebpackConfig.js b/test/WebpackConfig.js index aac8d59c..d555c079 100644 --- a/test/WebpackConfig.js +++ b/test/WebpackConfig.js @@ -375,19 +375,6 @@ describe('WebpackConfig object', () => { }); }); - describe('addPlugin', () => { - it('extends the current registered plugins', () => { - const config = createConfig(); - const nbOfPlugins = config.plugins.length; - - expect(nbOfPlugins).to.equal(0); - - config.addPlugin(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)); - - expect(config.plugins.length).to.equal(1); - }); - }); - describe('enableVueLoader', () => { it('Call with no config', () => { const config = createConfig(); From 860b5ff1c76e0fd8934ac6cecb5964e890728fd3 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 5 Aug 2017 08:16:31 -0400 Subject: [PATCH 28/68] reversing pathinfo logic This creates extra comments in the final .js files, to help debugging. This was a mistake when originally added - the logic was reversed. --- lib/config-generator.js | 2 +- test/functional.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/config-generator.js b/lib/config-generator.js index 11e8ab2f..bf672545 100644 --- a/lib/config-generator.js +++ b/lib/config-generator.js @@ -107,7 +107,7 @@ class ConfigGenerator { // will use the CDN path (if one is available) so that split // chunks load internally through the CDN. publicPath: this.webpackConfig.getRealPublicPath(), - pathinfo: this.webpackConfig.isProduction() + pathinfo: !this.webpackConfig.isProduction() }; } diff --git a/test/functional.js b/test/functional.js index 2409e05e..a94e9bea 100644 --- a/test/functional.js +++ b/test/functional.js @@ -550,6 +550,11 @@ describe('Functional tests using webpack', function() { 'main.js', '// comments in no_require.js' ); + // check for any webpack-added comments + webpackAssert.assertOutputFileDoesNotContain( + 'main.js', + '/*!' + ); // extra spaces should not live in the CSS file webpackAssert.assertOutputFileDoesNotContain( 'styles.css', From 2ec4e18768b62d615b87d6db5bab851427009172 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 20 Jul 2017 23:05:45 -0400 Subject: [PATCH 29/68] Initializing the plugins key on babel, so you can call babelConfig.plugins.push() if you want --- lib/loaders/babel.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/loaders/babel.js b/lib/loaders/babel.js index 5aad229e..ce7a0c30 100644 --- a/lib/loaders/babel.js +++ b/lib/loaders/babel.js @@ -41,6 +41,7 @@ module.exports = { useBuiltIns: true }] ], + plugins: [] }); if (webpackConfig.useReact) { From a6f1b3175558aaf9d51954eca4bd4ec6f7eb0f41 Mon Sep 17 00:00:00 2001 From: Lyrkan Date: Thu, 20 Jul 2017 19:31:48 +0200 Subject: [PATCH 30/68] Allow to disable the default image and font loaders --- index.js | 24 ++++++++++++++++ lib/WebpackConfig.js | 10 +++++++ lib/config-generator.js | 18 ++++++++---- test/WebpackConfig.js | 18 ++++++++++++ test/config-generator.js | 60 ++++++++++++++++++++++++++++++++++++++++ test/index.js | 18 ++++++++++++ 6 files changed, 142 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 9fce38c1..bd3cba9f 100644 --- a/index.js +++ b/index.js @@ -441,6 +441,30 @@ module.exports = { return this; }, + /** + * Call this if you wish to disable the default + * images loader. + * + * @returns {exports} + */ + disableImagesLoader() { + webpackConfig.disableImagesLoader(); + + return this; + }, + + /** + * Call this if you wish to disable the default + * fonts loader. + * + * @returns {exports} + */ + disableFontsLoader() { + webpackConfig.disableFontsLoader(); + + return this; + }, + /** * If enabled, the output directory is emptied between * each build (to remove old files). diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index addeb7ea..8fec7dc5 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -61,6 +61,8 @@ class WebpackConfig { this.tsConfigurationCallback = function() {}; this.useForkedTypeScriptTypeChecking = false; this.forkedTypeScriptTypesCheckOptionsCallback = () => {}; + this.useImagesLoader = true; + this.useFontsLoader = true; } getContext() { @@ -306,6 +308,14 @@ class WebpackConfig { this.vueLoaderOptionsCallback = vueLoaderOptionsCallback; } + disableImagesLoader() { + this.useImagesLoader = false; + } + + disableFontsLoader() { + this.useFontsLoader = false; + } + cleanupOutputBeforeBuild() { this.cleanupOutput = true; } diff --git a/lib/config-generator.js b/lib/config-generator.js index 11e8ab2f..40e3dc36 100644 --- a/lib/config-generator.js +++ b/lib/config-generator.js @@ -122,24 +122,30 @@ class ConfigGenerator { { test: /\.css$/, use: extractText.extract(this.webpackConfig, cssLoaderUtil.getLoaders(this.webpackConfig, false)) - }, - { + } + ]; + + if (this.webpackConfig.useImagesLoader) { + rules.push({ test: /\.(png|jpg|jpeg|gif|ico|svg)$/, loader: 'file-loader', options: { name: 'images/[name].[hash:8].[ext]', publicPath: this.webpackConfig.getRealPublicPath() } - }, - { + }); + } + + if (this.webpackConfig.useFontsLoader) { + rules.push({ test: /\.(woff|woff2|ttf|eot|otf)$/, loader: 'file-loader', options: { name: 'fonts/[name].[hash:8].[ext]', publicPath: this.webpackConfig.getRealPublicPath() } - }, - ]; + }); + } if (this.webpackConfig.useSassLoader) { rules.push({ diff --git a/test/WebpackConfig.js b/test/WebpackConfig.js index aac8d59c..7b1abd42 100644 --- a/test/WebpackConfig.js +++ b/test/WebpackConfig.js @@ -430,4 +430,22 @@ describe('WebpackConfig object', () => { expect(config.loaders).to.deep.equals([{ 'test': /\.custom$/, 'loader': 'custom-loader' }]); }); }); + + describe('disableImagesLoader', () => { + it('Disable default images loader', () => { + const config = createConfig(); + config.disableImagesLoader(); + + expect(config.useImagesLoader).to.be.false; + }); + }); + + describe('disableFontsLoader', () => { + it('Disable default fonts loader', () => { + const config = createConfig(); + config.disableFontsLoader(); + + expect(config.useFontsLoader).to.be.false; + }); + }); }); diff --git a/test/config-generator.js b/test/config-generator.js index 9d5b739e..9b4dd2bd 100644 --- a/test/config-generator.js +++ b/test/config-generator.js @@ -416,4 +416,64 @@ describe('The config-generator function', () => { expect(ignorePlugin).to.not.be.undefined; }); }); + + describe('disableImagesLoader() removes the default images loader', () => { + it('without disableImagesLoader()', () => { + const config = createConfig(); + config.outputPath = '/tmp/output/public-path'; + config.publicPath = '/public-path'; + config.addEntry('main', './main'); + // do not call disableImagesLoader + + const actualConfig = configGenerator(config); + + expect(function() { + findRule(/\.(png|jpg|jpeg|gif|ico|svg)$/, actualConfig.module.rules); + }).to.not.throw(); + }); + + it('with disableImagesLoader()', () => { + const config = createConfig(); + config.outputPath = '/tmp/output/public-path'; + config.publicPath = '/public-path'; + config.addEntry('main', './main'); + config.disableImagesLoader(); + + const actualConfig = configGenerator(config); + + expect(function() { + findRule(/\.(png|jpg|jpeg|gif|ico|svg)$/, actualConfig.module.rules); + }).to.throw(); + }); + }); + + describe('disableFontsLoader() removes the default fonts loader', () => { + it('without disableFontsLoader()', () => { + const config = createConfig(); + config.outputPath = '/tmp/output/public-path'; + config.publicPath = '/public-path'; + config.addEntry('main', './main'); + // do not call disableFontsLoader + + const actualConfig = configGenerator(config); + + expect(function() { + findRule(/\.(woff|woff2|ttf|eot|otf)$/, actualConfig.module.rules); + }).to.not.throw(); + }); + + it('with disableFontsLoader()', () => { + const config = createConfig(); + config.outputPath = '/tmp/output/public-path'; + config.publicPath = '/public-path'; + config.addEntry('main', './main'); + config.disableFontsLoader(); + + const actualConfig = configGenerator(config); + + expect(function() { + findRule(/\.(woff|woff2|ttf|eot|otf)$/, actualConfig.module.rules); + }).to.throw(); + }); + }); }); diff --git a/test/index.js b/test/index.js index 5c4c8687..6ab715d9 100644 --- a/test/index.js +++ b/test/index.js @@ -204,6 +204,24 @@ describe('Public API', () => { }); + describe('disableImagesLoader', () => { + + it('must return the API object', () => { + const returnedValue = api.disableImagesLoader(); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('disableFontsLoader', () => { + + it('must return the API object', () => { + const returnedValue = api.disableFontsLoader(); + expect(returnedValue).to.equal(api); + }); + + }); + describe('cleanupOutputBeforeBuild', () => { it('must return the API object', () => { From 9680da575ef156f2196f9a21fbe6a9d656fca21c Mon Sep 17 00:00:00 2001 From: Lyrkan Date: Sat, 5 Aug 2017 23:03:38 +0200 Subject: [PATCH 31/68] Add options callback to Encore.enableLessLoader() --- index.js | 15 +++++++++++++-- lib/WebpackConfig.js | 9 ++++++++- lib/loaders/less.js | 15 ++++++++++++--- test/WebpackConfig.js | 25 +++++++++++++++++++++++++ test/loaders/less.js | 22 ++++++++++++++++++++++ 5 files changed, 80 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index bd3cba9f..2395ccf0 100644 --- a/index.js +++ b/index.js @@ -342,10 +342,21 @@ module.exports = { /** * Call this if you plan on loading less files. * + * Encore.enableLessLoader(); + * + * Or pass options to the loader + * + * Encore.enableLessLoader(function(options) { + * // https://github.com/webpack-contrib/less-loader#examples + * // http://lesscss.org/usage/#command-line-usage-options + * // options.relativeUrls = false; + * }); + * + * @param {function} lessLoaderOptionsCallback * @return {exports} */ - enableLessLoader() { - webpackConfig.enableLessLoader(); + enableLessLoader(lessLoaderOptionsCallback = () => {}) { + webpackConfig.enableLessLoader(lessLoaderOptionsCallback); return this; }, diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index 8fec7dc5..add790e6 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -49,6 +49,7 @@ class WebpackConfig { resolve_url_loader: true }; this.useLessLoader = false; + this.lessLoaderOptionsCallback = function() {}; this.cleanupOutput = false; this.sharedCommonsEntryName = null; this.providedVariables = {}; @@ -269,8 +270,14 @@ class WebpackConfig { } } - enableLessLoader() { + enableLessLoader(lessLoaderOptionsCallback = () => {}) { this.useLessLoader = true; + + if (typeof lessLoaderOptionsCallback !== 'function') { + throw new Error('Argument 1 to enableLessLoader() must be a callback function.'); + } + + this.lessLoaderOptionsCallback = lessLoaderOptionsCallback; } enableReactPreset() { diff --git a/lib/loaders/less.js b/lib/loaders/less.js index 1324f1a7..e3986a7a 100644 --- a/lib/loaders/less.js +++ b/lib/loaders/less.js @@ -21,13 +21,22 @@ module.exports = { getLoaders(webpackConfig, ignorePostCssLoader = false) { loaderFeatures.ensureLoaderPackagesExist('less'); + const config = { + sourceMap: webpackConfig.useSourceMaps + }; + + // allow options to be configured + webpackConfig.lessLoaderOptionsCallback.apply( + // use config as the this variable + config, + [config] + ); + return [ ...cssLoader.getLoaders(webpackConfig, ignorePostCssLoader), { loader: 'less-loader', - options: { - sourceMap: webpackConfig.useSourceMaps - } + options: config }, ]; } diff --git a/test/WebpackConfig.js b/test/WebpackConfig.js index 016d0636..8cfdccea 100644 --- a/test/WebpackConfig.js +++ b/test/WebpackConfig.js @@ -339,6 +339,31 @@ describe('WebpackConfig object', () => { }); }); + describe('enableLessLoader', () => { + it('Calling method sets it', () => { + const config = createConfig(); + config.enableLessLoader(); + + expect(config.useLessLoader).to.be.true; + }); + + it('Calling with callback', () => { + const config = createConfig(); + const callback = (lessOptions) => {}; + config.enableLessLoader(callback); + + expect(config.lessLoaderOptionsCallback).to.equal(callback); + }); + + it('Calling with non-callback throws an error', () => { + const config = createConfig(); + + expect(() => { + config.enableLessLoader('FOO'); + }).to.throw('must be a callback function'); + }); + }); + describe('enableTypeScriptLoader', () => { it('Calling method sets it', () => { const config = createConfig(); diff --git a/test/loaders/less.js b/test/loaders/less.js index 403da23c..ec7826fc 100644 --- a/test/loaders/less.js +++ b/test/loaders/less.js @@ -39,4 +39,26 @@ describe('loaders/less', () => { cssLoader.getLoaders.restore(); }); + + it('getLoaders() with options callback', () => { + const config = createConfig(); + config.enableSourceMaps(true); + + // make the cssLoader return nothing + sinon.stub(cssLoader, 'getLoaders') + .callsFake(() => []); + + config.enableLessLoader(function(lessOptions) { + lessOptions.custom_option = 'foo'; + lessOptions.other_option = true; + }); + + const actualLoaders = lessLoader.getLoaders(config); + expect(actualLoaders[0].options).to.deep.equals({ + sourceMap: true, + custom_option: 'foo', + other_option: true + }); + cssLoader.getLoaders.restore(); + }); }); From b4a4d118ebd14fbda13b46da5f0165aa4de4d592 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 20 Jul 2017 22:43:39 -0400 Subject: [PATCH 32/68] Giving the user a nice, default postcss.config.js file --- .../formatters/missing-postcss-config.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/friendly-errors/formatters/missing-postcss-config.js b/lib/friendly-errors/formatters/missing-postcss-config.js index 3cfaf9fc..220a5694 100644 --- a/lib/friendly-errors/formatters/missing-postcss-config.js +++ b/lib/friendly-errors/formatters/missing-postcss-config.js @@ -25,8 +25,19 @@ function formatErrors(errors) { ); messages.push(''); messages.push(`${chalk.bgGreen.black('', 'FIX', '')} Create a ${chalk.yellow('postcss.config.js')} file at the root of your project.`); + messages.push(''); + messages.push('Here is an example to get you started!') + messages.push(chalk.yellow(` +// postcss.config.js +module.exports = { + plugins: { + 'autoprefixer': {}, + } +} + `)); messages.push(''); + messages.push('') return messages; } From 18c6a3480c10af757f0ba36adbc8aba7e7a3d0c3 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 9 Aug 2017 10:32:40 -0400 Subject: [PATCH 33/68] updating changelog for 0.13.0 --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7218b4d..e72dc49a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # CHANGELOG +## 0.13.0 + + * [BEHAVIOR CHANGE] Image and font files now *always* include + a hash in their filename, and the hash is shorter - #110 via @Lyrkan + + * Fixed a bug that caused extra comments to be in the final production + compiled JavaScript - #132 via @weaverryan + + * `Encore.enablePostCssLoader()` now accepts an options callback - + #130 via @Lyrkan + + * `Encore.enableLessLoader()` now accepts an options callback - + #134 via @Lyrkan + + * Added `Encore.enableForkedTypeScriptTypesChecking()` to enable + [fork-ts-checker-webpack-plugin](https://github.com/Realytics/fork-ts-checker-webpack-plugin) + for faster typescript type checking - #101 via @davidmpaz + + * Added `Encore.disableImagesLoader()` and `Encore.disableFontsLoader()` + to totally disable the `file-loader` rules for images and fonts - + #103 via @Lyrkan + ## 0.12.0 * Fixed a bug with webpack 3.4.0 ("Can't resolve dev") - #114. From b459ac9066f843b2f5a0ff5eb9984fccdd076f18 Mon Sep 17 00:00:00 2001 From: David Paz Date: Mon, 17 Jul 2017 22:41:42 +0200 Subject: [PATCH 34/68] Loader features refactor --- lib/{loader-features.js => features.js} | 26 +++++++++---------- .../formatters/missing-loader.js | 8 +++--- lib/loaders/babel.js | 4 +-- lib/loaders/css.js | 4 +-- lib/loaders/less.js | 4 +-- lib/loaders/sass.js | 4 +-- lib/loaders/typescript.js | 6 ++--- lib/loaders/vue.js | 10 +++---- test/functional.js | 24 ++++++----------- 9 files changed, 41 insertions(+), 49 deletions(-) rename lib/{loader-features.js => features.js} (74%) diff --git a/lib/loader-features.js b/lib/features.js similarity index 74% rename from lib/loader-features.js rename to lib/features.js index d1746cc3..34a68d6a 100644 --- a/lib/loader-features.js +++ b/lib/features.js @@ -13,9 +13,9 @@ const packageHelper = require('./package-helper'); /** * An object that holds internal configuration about different - * "loaders" that can be enabled. + * "loaders"/"plugins" that can be enabled/used. */ -const loaderFeatures = { +const features = { sass: { method: 'enableSassLoader()', packages: ['sass-loader', 'node-sass'], @@ -55,19 +55,19 @@ const loaderFeatures = { } }; -function getLoaderFeatureConfig(loaderName) { - if (!loaderFeatures[loaderName]) { - throw new Error(`Unknown loader feature ${loaderName}`); +function getFeatureConfig(featureName) { + if (!features[featureName]) { + throw new Error(`Unknown feature ${featureName}`); } - return loaderFeatures[loaderName]; + return features[featureName]; } module.exports = { - getLoaderFeatureConfig, + getFeatureConfig, - ensureLoaderPackagesExist: function(loaderName) { - const config = getLoaderFeatureConfig(loaderName); + ensurePackagesExist: function(featureName) { + const config = getFeatureConfig(featureName); packageHelper.ensurePackagesExist( config.packages, @@ -75,11 +75,11 @@ module.exports = { ); }, - getLoaderFeatureMethod: function(loaderName) { - return getLoaderFeatureConfig(loaderName).method; + getFeatureMethod: function(featureName) { + return getFeatureConfig(featureName).method; }, - getLoaderFeatureDescription: function(loaderName) { - return getLoaderFeatureConfig(loaderName).description; + getFeatureDescription: function(featureName) { + return getFeatureConfig(featureName).description; } }; diff --git a/lib/friendly-errors/formatters/missing-loader.js b/lib/friendly-errors/formatters/missing-loader.js index bebf16a9..86f67a62 100644 --- a/lib/friendly-errors/formatters/missing-loader.js +++ b/lib/friendly-errors/formatters/missing-loader.js @@ -10,7 +10,7 @@ 'use strict'; const chalk = require('chalk'); -const loaderFeatures = require('../../loader-features'); +const loaderFeatures = require('../../features'); const packageHelper = require('../../package-helper'); function formatErrors(errors) { @@ -23,10 +23,10 @@ function formatErrors(errors) { const fixes = []; if (error.loaderName) { - let neededCode = `Encore.${loaderFeatures.getLoaderFeatureMethod(error.loaderName)}`; + let neededCode = `Encore.${loaderFeatures.getFeatureMethod(error.loaderName)}`; fixes.push(`Add ${chalk.green(neededCode)} to your webpack.config.js file.`); - const loaderFeatureConfig = loaderFeatures.getLoaderFeatureConfig(error.loaderName); + const loaderFeatureConfig = loaderFeatures.getFeatureConfig(error.loaderName); const packageRecommendations = packageHelper.getPackageRecommendations( loaderFeatureConfig.packages ); @@ -44,7 +44,7 @@ function formatErrors(errors) { ]); if (error.loaderName) { - messages.push(`${chalk.bgGreen.black('', 'FIX', '')} To ${loaderFeatures.getLoaderFeatureDescription(error.loaderName)}:`); + messages.push(`${chalk.bgGreen.black('', 'FIX', '')} To ${loaderFeatures.getFeatureDescription(error.loaderName)}:`); } else { messages.push(`${chalk.bgGreen.black('', 'FIX', '')} To load ${error.file}:`); } diff --git a/lib/loaders/babel.js b/lib/loaders/babel.js index ce7a0c30..e633f134 100644 --- a/lib/loaders/babel.js +++ b/lib/loaders/babel.js @@ -9,7 +9,7 @@ 'use strict'; -const loaderFeatures = require('../loader-features'); +const loaderFeatures = require('../features'); /** * @param {WebpackConfig} webpackConfig @@ -45,7 +45,7 @@ module.exports = { }); if (webpackConfig.useReact) { - loaderFeatures.ensureLoaderPackagesExist('react'); + loaderFeatures.ensurePackagesExist('react'); babelConfig.presets.push('react'); } diff --git a/lib/loaders/css.js b/lib/loaders/css.js index cc18ba98..03308b8b 100644 --- a/lib/loaders/css.js +++ b/lib/loaders/css.js @@ -9,7 +9,7 @@ 'use strict'; -const loaderFeatures = require('../loader-features'); +const loaderFeatures = require('../features'); /** * @param {WebpackConfig} webpackConfig @@ -36,7 +36,7 @@ module.exports = { ]; if (usePostCssLoader) { - loaderFeatures.ensureLoaderPackagesExist('postcss'); + loaderFeatures.ensurePackagesExist('postcss'); const postCssLoaderOptions = { sourceMap: webpackConfig.useSourceMaps diff --git a/lib/loaders/less.js b/lib/loaders/less.js index e3986a7a..9df07dd4 100644 --- a/lib/loaders/less.js +++ b/lib/loaders/less.js @@ -9,7 +9,7 @@ 'use strict'; -const loaderFeatures = require('../loader-features'); +const loaderFeatures = require('../features'); const cssLoader = require('./css'); /** @@ -19,7 +19,7 @@ const cssLoader = require('./css'); */ module.exports = { getLoaders(webpackConfig, ignorePostCssLoader = false) { - loaderFeatures.ensureLoaderPackagesExist('less'); + loaderFeatures.ensurePackagesExist('less'); const config = { sourceMap: webpackConfig.useSourceMaps diff --git a/lib/loaders/sass.js b/lib/loaders/sass.js index 352f578d..d1ab26c7 100644 --- a/lib/loaders/sass.js +++ b/lib/loaders/sass.js @@ -9,7 +9,7 @@ 'use strict'; -const loaderFeatures = require('../loader-features'); +const loaderFeatures = require('../features'); const cssLoader = require('./css'); /** @@ -20,7 +20,7 @@ const cssLoader = require('./css'); */ module.exports = { getLoaders(webpackConfig, sassOptions = {}, ignorePostCssLoader = false) { - loaderFeatures.ensureLoaderPackagesExist('sass'); + loaderFeatures.ensurePackagesExist('sass'); const sassLoaders = [...cssLoader.getLoaders(webpackConfig, ignorePostCssLoader)]; if (true === webpackConfig.sassOptions.resolve_url_loader) { diff --git a/lib/loaders/typescript.js b/lib/loaders/typescript.js index 939e1079..55c94ff8 100644 --- a/lib/loaders/typescript.js +++ b/lib/loaders/typescript.js @@ -9,7 +9,7 @@ 'use strict'; -const loaderFeatures = require('../loader-features'); +const loaderFeatures = require('../features'); const babelLoader = require('./babel'); /** @@ -18,7 +18,7 @@ const babelLoader = require('./babel'); */ module.exports = { getLoaders(webpackConfig) { - loaderFeatures.ensureLoaderPackagesExist('typescript'); + loaderFeatures.ensurePackagesExist('typescript'); // some defaults let config = { @@ -34,7 +34,7 @@ module.exports = { // fork-ts-checker-webpack-plugin integration if (webpackConfig.useForkedTypeScriptTypeChecking) { - loaderFeatures.ensureLoaderPackagesExist('forkedtypecheck'); + loaderFeatures.ensurePackagesExist('forkedtypecheck'); // force transpileOnly to speed up config.transpileOnly = true; diff --git a/lib/loaders/vue.js b/lib/loaders/vue.js index 48d46607..853b55b1 100644 --- a/lib/loaders/vue.js +++ b/lib/loaders/vue.js @@ -9,7 +9,7 @@ 'use strict'; -const loaderFeatures = require('../loader-features'); +const loaderFeatures = require('../features'); const cssLoaderUtil = require('./css'); const sassLoaderUtil = require('./sass'); const lessLoaderUtil = require('./less'); @@ -23,7 +23,7 @@ const extractText = require('./extract-text'); */ module.exports = { getLoaders(webpackConfig, vueLoaderOptionsCallback) { - loaderFeatures.ensureLoaderPackagesExist('vue'); + loaderFeatures.ensurePackagesExist('vue'); /* * The vue-loader passes the contents of