diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..55061422 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +lib/webpack/webpack-manifest-plugin.js \ No newline at end of file diff --git a/index.js b/index.js index ee6232f7..ee431fd3 100644 --- a/index.js +++ b/index.js @@ -913,6 +913,34 @@ class Encore { return this; } + /** + * Configure the mini-css-extract-plugin. + * + * https://github.com/webpack-contrib/mini-css-extract-plugin#configuration + * + * ``` + * Encore.configureMiniCssExtractPlugin( + * function(loaderConfig) { + * // change the loader's config + * // loaderConfig.reloadAll = true; + * }, + * function(pluginConfig) { + * // change the plugin's config + * // pluginConfig.chunkFilename = '[id].css'; + * } + * ); + * ``` + * + * @param {function} loaderOptionsCallback + * @param {function} pluginOptionsCallback + * @returns {Encore} + */ + configureMiniCssExtractPlugin(loaderOptionsCallback, pluginOptionsCallback = () => {}) { + webpackConfig.configureMiniCssExtractPlugin(loaderOptionsCallback, pluginOptionsCallback); + + return this; + } + /** * If enabled, the react preset is added to Babel. * diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index 0455718d..5fb2b7e2 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -146,6 +146,8 @@ class WebpackConfig { this.eslintLoaderOptionsCallback = () => {}; this.tsConfigurationCallback = () => {}; this.handlebarsConfigurationCallback = () => {}; + this.miniCssExtractLoaderConfigurationCallback = () => {}; + this.miniCssExtractPluginConfigurationCallback = () => {}; this.loaderConfigurationCallbacks = { javascript: () => {}, css: () => {}, @@ -453,6 +455,19 @@ class WebpackConfig { this.cssLoaderConfigurationCallback = callback; } + configureMiniCssExtractPlugin(loaderOptionsCallback, pluginOptionsCallback = () => {}) { + if (typeof loaderOptionsCallback !== 'function') { + throw new Error('Argument 1 to configureMiniCssExtractPluginLoader() must be a callback function.'); + } + + if (typeof pluginOptionsCallback !== 'function') { + throw new Error('Argument 2 to configureMiniCssExtractPluginLoader() must be a callback function.'); + } + + this.miniCssExtractLoaderConfigurationCallback = loaderOptionsCallback; + this.miniCssExtractPluginConfigurationCallback = pluginOptionsCallback; + } + enableSingleRuntimeChunk() { this.shouldUseSingleRuntimeChunk = true; } diff --git a/lib/config-generator.js b/lib/config-generator.js index df8176a2..fe6641f5 100644 --- a/lib/config-generator.js +++ b/lib/config-generator.js @@ -414,9 +414,7 @@ class ConfigGenerator { buildPluginsConfig() { const plugins = []; - if (this.webpackConfig.extractCss) { - miniCssExtractPluginUtil(plugins, this.webpackConfig); - } + miniCssExtractPluginUtil(plugins, this.webpackConfig); // register the pure-style entries that should be deleted deleteUnusedEntriesPluginUtil(plugins, this.webpackConfig); diff --git a/lib/loaders/css-extract.js b/lib/loaders/css-extract.js index bbd83304..6eedff34 100644 --- a/lib/loaders/css-extract.js +++ b/lib/loaders/css-extract.js @@ -11,6 +11,7 @@ const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const applyOptionsCallback = require('../utils/apply-options-callback'); module.exports = { /** @@ -32,6 +33,17 @@ module.exports = { }, ...loaders]; } - return [MiniCssExtractPlugin.loader, ...loaders]; + // Default options + const options = { + hmr: webpackConfig.useHotModuleReplacementPlugin(), + }; + + return [{ + loader: MiniCssExtractPlugin.loader, + options: applyOptionsCallback( + webpackConfig.miniCssExtractLoaderConfigurationCallback, + options + ), + }, ...loaders]; } }; diff --git a/lib/plugins/manifest.js b/lib/plugins/manifest.js index b8465ba4..fe21b45c 100644 --- a/lib/plugins/manifest.js +++ b/lib/plugins/manifest.js @@ -10,7 +10,7 @@ 'use strict'; const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars -const ManifestPlugin = require('webpack-manifest-plugin'); +const ManifestPlugin = require('../webpack/webpack-manifest-plugin'); const PluginPriorities = require('./plugin-priorities'); const applyOptionsCallback = require('../utils/apply-options-callback'); const sharedEntryTmpName = require('../utils/sharedEntryTmpName'); diff --git a/lib/plugins/mini-css-extract.js b/lib/plugins/mini-css-extract.js index 68193323..67c44f6d 100644 --- a/lib/plugins/mini-css-extract.js +++ b/lib/plugins/mini-css-extract.js @@ -12,6 +12,7 @@ const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const PluginPriorities = require('./plugin-priorities'); +const applyOptionsCallback = require('../utils/apply-options-callback'); /** * @param {Array} plugins @@ -19,6 +20,11 @@ const PluginPriorities = require('./plugin-priorities'); * @return {void} */ module.exports = function(plugins, webpackConfig) { + // Don't add the plugin if CSS extraction is disabled + if (!webpackConfig.extractCss) { + return; + } + // Default filename can be overridden using Encore.configureFilenames({ css: '...' }) let filename = webpackConfig.useVersioning ? '[name].[contenthash:8].css' : '[name].css'; // the chunk filename should use [id], not [name]. But, due @@ -45,7 +51,12 @@ module.exports = function(plugins, webpackConfig) { }; plugins.push({ - plugin: new MiniCssExtractPlugin(miniCssPluginOptions), + plugin: new MiniCssExtractPlugin( + applyOptionsCallback( + webpackConfig.miniCssExtractPluginConfigurationCallback, + miniCssPluginOptions + ) + ), priority: PluginPriorities.MiniCssExtractPlugin }); }; diff --git a/lib/webpack/webpack-manifest-plugin.js b/lib/webpack/webpack-manifest-plugin.js index 8ccca5da..cbaf8473 100644 --- a/lib/webpack/webpack-manifest-plugin.js +++ b/lib/webpack/webpack-manifest-plugin.js @@ -1,15 +1,257 @@ /* - * This file is part of the Symfony Webpack Encore package. + * This code is based on the "lib/plugin.js" file provided by the + * webpack-manifest-plugin@2.0.4 (https://github.com/danethurber/webpack-manifest-plugin) * - * (c) Fabien Potencier + * It adds the changes made by @karlvr in pull request #176 that + * allow the plugin to work with mini-css-extract-plugin > 0.4.2 * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * The library is licensed as MIT. */ -'use strict'; +var path = require('path'); +var fse = require('fs-extra'); +var _ = require('lodash'); -const logger = require('../logger'); -logger.deprecation('The lib/webpack/webpack-manifest-plugin.js module is deprecated: require the library directly now: require(\'webpack-manifest-plugin\').'); +const emitCountMap = new Map(); -module.exports = require('webpack-manifest-plugin'); +function ManifestPlugin(opts) { + this.opts = _.assign({ + publicPath: null, + basePath: '', + fileName: 'manifest.json', + transformExtensions: /^(gz|map)$/i, + writeToFileEmit: false, + seed: null, + filter: null, + map: null, + generate: null, + sort: null, + serialize: function(manifest) { + return JSON.stringify(manifest, null, 2); + }, + }, opts || {}); +} + +ManifestPlugin.prototype.getFileType = function(str) { + str = str.replace(/\?.*/, ''); + var split = str.split('.'); + var ext = split.pop(); + if (this.opts.transformExtensions.test(ext)) { + ext = split.pop() + '.' + ext; + } + return ext; +}; + +ManifestPlugin.prototype.apply = function(compiler) { + var moduleAssets = {}; + + var outputFolder = compiler.options.output.path; + var outputFile = path.resolve(outputFolder, this.opts.fileName); + var outputName = path.relative(outputFolder, outputFile); + + var moduleAsset = function (module, file) { + if (module.userRequest) { + moduleAssets[file] = path.join( + path.dirname(file), + path.basename(module.userRequest) + ); + } + }; + + var normalModuleLoader = function (loaderContext, module) { + const { emitFile } = loaderContext; + + loaderContext.emitFile = (file, content, sourceMap) => { + if (module.userRequest && !moduleAssets[file]) { + moduleAssets[file] = path.join( + path.dirname(file), + path.basename(module.userRequest) + ); + } + + return emitFile.call(module, file, content, sourceMap); + }; + }; + + var emit = function(compilation, compileCallback) { + const emitCount = emitCountMap.get(outputFile) - 1 + emitCountMap.set(outputFile, emitCount); + + var seed = this.opts.seed || {}; + + var publicPath = this.opts.publicPath != null ? this.opts.publicPath : compilation.options.output.publicPath; + var stats = compilation.getStats().toJson(); + + var files = compilation.chunks.reduce(function(files, chunk) { + return chunk.files.reduce(function (files, path) { + var name = chunk.name ? chunk.name : null; + + if (name) { + name = name + '.' + this.getFileType(path); + } else { + // For nameless chunks, just map the files directly. + name = path; + } + + // Webpack 4: .isOnlyInitial() + // Webpack 3: .isInitial() + // Webpack 1/2: .initial + return files.concat({ + path: path, + chunk: chunk, + name: name, + isInitial: chunk.isOnlyInitial ? chunk.isOnlyInitial() : (chunk.isInitial ? chunk.isInitial() : chunk.initial), + isChunk: true, + isAsset: false, + isModuleAsset: false + }); + }.bind(this), files); + }.bind(this), []); + + // module assets don't show up in assetsByChunkName. + // we're getting them this way; + files = stats.assets.reduce(function (files, asset) { + var name = moduleAssets[asset.name]; + if (name) { + return files.concat({ + path: asset.name, + name: name, + isInitial: false, + isChunk: false, + isAsset: true, + isModuleAsset: true + }); + } + + var isEntryAsset = asset.chunks.length > 0; + if (isEntryAsset) { + return files; + } + + return files.concat({ + path: asset.name, + name: asset.name, + isInitial: false, + isChunk: false, + isAsset: true, + isModuleAsset: false + }); + }, files); + + files = files.filter(function (file) { + // Don't add hot updates to manifest + var isUpdateChunk = file.path.indexOf('hot-update') >= 0; + // Don't add manifest from another instance + var isManifest = emitCountMap.get(path.join(outputFolder, file.name)) !== undefined; + + return !isUpdateChunk && !isManifest; + }); + + // Append optional basepath onto all references. + // This allows output path to be reflected in the manifest. + if (this.opts.basePath) { + files = files.map(function(file) { + file.name = this.opts.basePath + file.name; + return file; + }.bind(this)); + } + + if (publicPath) { + // Similar to basePath but only affects the value (similar to how + // output.publicPath turns require('foo/bar') into '/public/foo/bar', see + // https://github.com/webpack/docs/wiki/configuration#outputpublicpath + files = files.map(function(file) { + file.path = publicPath + file.path; + return file; + }.bind(this)); + } + + files = files.map(file => { + file.name = file.name.replace(/\\/g, '/'); + file.path = file.path.replace(/\\/g, '/'); + return file; + }); + + if (this.opts.filter) { + files = files.filter(this.opts.filter); + } + + if (this.opts.map) { + files = files.map(this.opts.map); + } + + if (this.opts.sort) { + files = files.sort(this.opts.sort); + } + + var manifest; + if (this.opts.generate) { + manifest = this.opts.generate(seed, files); + } else { + manifest = files.reduce(function (manifest, file) { + manifest[file.name] = file.path; + return manifest; + }, seed); + } + + const isLastEmit = emitCount === 0 + if (isLastEmit) { + var output = this.opts.serialize(manifest); + + compilation.assets[outputName] = { + source: function() { + return output; + }, + size: function() { + return output.length; + } + }; + + if (this.opts.writeToFileEmit) { + fse.outputFileSync(outputFile, output); + } + } + + if (compiler.hooks) { + compiler.hooks.webpackManifestPluginAfterEmit.call(manifest); + } else { + compilation.applyPluginsAsync('webpack-manifest-plugin-after-emit', manifest, compileCallback); + } + }.bind(this); + + function beforeRun (compiler, callback) { + let emitCount = emitCountMap.get(outputFile) || 0; + emitCountMap.set(outputFile, emitCount + 1); + + if (callback) { + callback(); + } + } + + if (compiler.hooks) { + const SyncWaterfallHook = require('tapable').SyncWaterfallHook; + const pluginOptions = { + name: 'ManifestPlugin', + stage: Infinity + }; + compiler.hooks.webpackManifestPluginAfterEmit = new SyncWaterfallHook(['manifest']); + + compiler.hooks.compilation.tap(pluginOptions, function (compilation) { + compilation.hooks.normalModuleLoader.tap(pluginOptions, normalModuleLoader); + }); + compiler.hooks.emit.tap(pluginOptions, emit); + + compiler.hooks.run.tap(pluginOptions, beforeRun); + compiler.hooks.watchRun.tap(pluginOptions, beforeRun); + } else { + compiler.plugin('compilation', function (compilation) { + compilation.plugin('module-asset', moduleAsset); + }); + compiler.plugin('emit', emit); + + compiler.plugin('before-run', beforeRun); + compiler.plugin('watch-run', beforeRun); + } +}; + +module.exports = ManifestPlugin; diff --git a/package.json b/package.json index 47ffb33b..ff8f4a0d 100644 --- a/package.json +++ b/package.json @@ -39,20 +39,20 @@ "fs-extra": "^2.0.0", "loader-utils": "^1.1.0", "lodash": ">=3.5 <5", - "mini-css-extract-plugin": ">=0.4.0 <0.4.3", + "mini-css-extract-plugin": "^0.7.0", "optimize-css-assets-webpack-plugin": "^5.0.1", "pkg-up": "^1.0.0", "pretty-error": "^2.1.1", "resolve-url-loader": "^3.0.1", "semver": "^5.5.0", "style-loader": "^0.21.0", + "tapable": "^1.0.0", "terser-webpack-plugin": "^1.1.0", "tmp": "^0.0.33", "webpack": "^4.20.0", "webpack-chunk-hash": "^0.6.0", "webpack-cli": "^3.0.0", "webpack-dev-server": "^3.1.14", - "webpack-manifest-plugin": "^2.0.2", "webpack-sources": "^1.3.0", "yargs-parser": "^12.0.0" }, diff --git a/test/WebpackConfig.js b/test/WebpackConfig.js index 1fc1b4b1..78506acd 100644 --- a/test/WebpackConfig.js +++ b/test/WebpackConfig.js @@ -660,6 +660,40 @@ describe('WebpackConfig object', () => { }); }); + describe('configureMiniCssExtractPlugin', () => { + it('Calling method with its first parameter sets the loader\'s options', () => { + const config = createConfig(); + const testCallback = () => {}; + config.configureMiniCssExtractPlugin(testCallback); + expect(config.miniCssExtractLoaderConfigurationCallback).to.equal(testCallback); + }); + + it('Calling method with its second parameter sets the plugin\'s options', () => { + const config = createConfig(); + const testCallbackLoader = () => {}; + const testCallbackPlugin = () => {}; + config.configureMiniCssExtractPlugin(testCallbackLoader, testCallbackPlugin); + expect(config.miniCssExtractLoaderConfigurationCallback).to.equal(testCallbackLoader); + expect(config.miniCssExtractPluginConfigurationCallback).to.equal(testCallbackPlugin); + }); + + it('Calling with non-callback as 1st parameter throws an error', () => { + const config = createConfig(); + + expect(() => { + config.configureMiniCssExtractPlugin('FOO'); + }).to.throw('must be a callback function'); + }); + + it('Calling with non-callback as 2nd parameter throws an error', () => { + const config = createConfig(); + + expect(() => { + config.configureMiniCssExtractPlugin(() => {}, 'FOO'); + }).to.throw('must be a callback function'); + }); + }); + describe('configureSplitChunks', () => { it('Calling method sets it', () => { const config = createConfig(); diff --git a/test/config-generator.js b/test/config-generator.js index b9aa8bf3..3cc0ffaa 100644 --- a/test/config-generator.js +++ b/test/config-generator.js @@ -14,7 +14,7 @@ const WebpackConfig = require('../lib/WebpackConfig'); const RuntimeConfig = require('../lib/config/RuntimeConfig'); const configGenerator = require('../lib/config-generator'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const ManifestPlugin = require('webpack-manifest-plugin'); +const ManifestPlugin = require('../lib/webpack/webpack-manifest-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const webpack = require('webpack'); const path = require('path'); diff --git a/test/functional.js b/test/functional.js index b5ad9c52..a7de20bd 100644 --- a/test/functional.js +++ b/test/functional.js @@ -401,7 +401,7 @@ describe('Functional tests using webpack', function() { expect(config.outputPath).to.be.a.directory() .with.files([ 'main.89eb104b.js', - 'styles.8ec31654.css', + 'styles.d8f0e981.css', 'manifest.json', 'entrypoints.json', 'runtime.191f859b.js', @@ -409,7 +409,7 @@ describe('Functional tests using webpack', function() { } webpackAssert.assertOutputFileContains( - 'styles.8ec31654.css', + 'styles.d8f0e981.css', 'font-size: 50px;' ); webpackAssert.assertManifestPathDoesNotExist( @@ -417,7 +417,7 @@ describe('Functional tests using webpack', function() { ); webpackAssert.assertManifestPath( 'styles.css', - '/styles.8ec31654.css' + '/styles.d8f0e981.css' ); done(); @@ -448,7 +448,7 @@ describe('Functional tests using webpack', function() { ); webpackAssert.assertManifestPath( 'styles.css', - '/styles.css?91597a40238e0e66' + '/styles.css?05d43eef7455a252' ); done(); @@ -500,19 +500,40 @@ describe('Functional tests using webpack', function() { config.enableVersioning(true); testSetup.runWebpack(config, (webpackAssert) => { - if (!process.env.DISABLE_UNSTABLE_CHECKS) { - expect(config.outputPath).to.be.a.directory() - .with.files([ - '0.590a68c7.js', // chunks are also versioned - '0.8ec31654.css', - 'main.4a5effdb.js', - 'h1.8ec31654.css', - 'bg.0ec2735b.css', - 'manifest.json', - 'entrypoints.json', - 'runtime.a59dcd0a.js', - ]); - } + const actualFilesMap = {}; + const expectedFiles = [ + '0.[hash].js', // chunks are also versioned + '0.[hash].css', + 'main.[hash].js', + 'h1.[hash].css', + 'bg.[hash].css', + 'manifest.json', + 'entrypoints.json', + 'runtime.[hash].js', + ]; + + expect(config.outputPath).to.be.a.directory() + .with.files.that.satisfy(files => { + for (const expectedFile of expectedFiles) { + const expectedFileRegexp = new RegExp(`^${ + expectedFile + .replace('.', '\\.') + .replace('[hash]', '[0-9a-f]+') + }$`); + + const matchingFile = files.find(file => { + return expectedFileRegexp.test(file); + }); + + if (!matchingFile) { + return false; + } + + actualFilesMap[expectedFile] = matchingFile; + } + + return true; + }); expect(path.join(config.outputPath, 'images')).to.be.a.directory() .with.files([ @@ -520,7 +541,7 @@ describe('Functional tests using webpack', function() { ]); webpackAssert.assertOutputFileContains( - 'bg.0ec2735b.css', + actualFilesMap['bg.[hash].css'], '/build/images/symfony_logo.ea1ca6f7.png' ); @@ -2502,7 +2523,7 @@ module.exports = { '/build/main.js': 'sha384-ymG7OyjISWrOpH9jsGvajKMDEOP/mKJq8bHC0XdjQA6P8sg2nu+2RLQxcNNwE/3J', '/build/main~other.js': 'sha384-4g+Zv0iELStVvA4/B27g4TQHUMwZttA5TEojjUyB8Gl5p7sarU4y+VTSGMrNab8n', '/build/main~other.css': 'sha384-hfZmq9+2oI5Cst4/F4YyS2tJAAYdGz7vqSMP8cJoa8bVOr2kxNRLxSw6P8UZjwUn', - '/build/other.js': 'sha384-ZU3hiTN/+Va9WVImPi+cI0/j/Q7SzAVezqL1aEXha8sVgE5HU6/0wKUxj1LEnkC9', + '/build/other.js': 'sha384-iQlk+cKfriMc65IK1z9JnG/jZxcbgNoqHo4IUFsT9VAo/ILn4iYd1apHrKJoDhL2', // vendors~main~other.js's hash is not tested since its // content seems to change based on the build environment. @@ -2535,7 +2556,7 @@ module.exports = { 'http://localhost:8090/assets/main.js': 'sha256-RtW3TYA1SBHUGuBnIBBJZ7etIGyYisjargouvET4sFE=', 'http://localhost:8090/assets/main~other.js': 'sha256-q9xPQWa0UBbMPUNmhDaDuBFjV2gZU6ICiKzLN7jPccc=', 'http://localhost:8090/assets/main~other.css': 'sha256-KVo9sI0v6MnbxPg/xZMSn2XE7qIChWiDh1uED1tP5Fo=', - 'http://localhost:8090/assets/other.js': 'sha256-rxT6mp9VrLO1++6G3g/VSLGisznX838ALokQhD3Jmyc=', + 'http://localhost:8090/assets/other.js': 'sha256-LlL6lle4zx9AYq+Pwx5fCLPx25wRBFLYXkOkv5FC4jw=', // vendors~main~other.js's hash is not tested since its // content seems to change based on the build environment. @@ -2567,7 +2588,7 @@ module.exports = { '/build/main.js': 'sha256-RtW3TYA1SBHUGuBnIBBJZ7etIGyYisjargouvET4sFE= sha512-/wl1U/L6meBga5eeRTxPz5BxFiLmwL/kjy1NTcK0DNdxV3oUI/zZ9DEDU43Cl7XqGMnUH8pJhhFJR+1k9vZrYQ==', '/build/main~other.js': 'sha256-q9xPQWa0UBbMPUNmhDaDuBFjV2gZU6ICiKzLN7jPccc= sha512-1xuC/Y+goI01JUPVYBQOpPY36ttTXnZFOBsTgNPCJu53b2/ccFqzeW3abV3KG5mFzo4cfSUOS7AXjj8ajp/MjA==', '/build/main~other.css': 'sha256-6AltZJTjdVuLywCBE8qQevkscxazmWyh/19OL6cxkwY= sha512-zE1kAcqJ/jNnycEwELK7BfauEgRlK6cGrN+9urz4JI1K+s5BpglYFF9G0VOiSA7Kj3w46XX1WcjZ5w5QohBFEw==', - '/build/other.js': 'sha256-rxT6mp9VrLO1++6G3g/VSLGisznX838ALokQhD3Jmyc= sha512-XZjuolIG3/QW1PwAIaPCtQZvKvkPNsAsoUjQdDqlW/uStd9lBrT3w16WrBdc3qe4X11bGkyA7IQpQwN3FGkPMA==', + '/build/other.js': 'sha256-LlL6lle4zx9AYq+Pwx5fCLPx25wRBFLYXkOkv5FC4jw= sha512-Blitdhb6rxaT7DpRNBmNjv1gH/St0kO9OURNytKJxF2sO8cv3uWTf4TLCjBEvQkWC0F1YxpVoJVyOQW9mvRgaA==', // vendors~main~other.js's hash is not tested since its // content seems to change based on the build environment. diff --git a/test/index.js b/test/index.js index cf2f72fd..871a663f 100644 --- a/test/index.js +++ b/test/index.js @@ -414,6 +414,16 @@ describe('Public API', () => { }); + describe('configureMiniCssExtractPlugin', () => { + + it('should return the API object', () => { + const returnedValue = api.configureMiniCssExtractPlugin(() => {}); + expect(returnedValue).to.equal(api); + }); + + }); + + describe('enableIntegrityHashes', () => { it('should return the API object', () => { diff --git a/test/loaders/css-extract.js b/test/loaders/css-extract.js new file mode 100644 index 00000000..323ba9af --- /dev/null +++ b/test/loaders/css-extract.js @@ -0,0 +1,61 @@ +/* + * This file is part of the Symfony Webpack Encore 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 cssExtractLoader = require('../../lib/loaders/css-extract'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +function createConfig() { + const runtimeConfig = new RuntimeConfig(); + runtimeConfig.context = __dirname; + runtimeConfig.babelRcFileExists = false; + + return new WebpackConfig(runtimeConfig); +} + +describe('loaders/css-extract', () => { + it('prependLoaders() basic usage', () => { + const config = createConfig(); + + const loaders = cssExtractLoader.prependLoaders(config, ['foo']); + + expect(loaders).to.have.lengthOf(2); + expect(loaders[0].loader).to.equal(MiniCssExtractPlugin.loader); + expect(loaders[0].options.hmr).to.be.false; + }); + + it('prependLoaders() with CSS extraction disabled', () => { + const config = createConfig(); + config.disableCssExtraction(); + config.enableSourceMaps(true); + + const loaders = cssExtractLoader.prependLoaders(config, ['foo']); + + expect(loaders).to.have.lengthOf(2); + expect(loaders[0].loader).to.equal('style-loader'); + expect(loaders[0].options.sourceMap).to.be.true; + }); + + it('prependLoaders() options callback', () => { + const config = createConfig(); + config.configureMiniCssExtractPlugin(options => { + options.hmr = true; + }); + + const loaders = cssExtractLoader.prependLoaders(config, ['foo']); + + expect(loaders).to.have.lengthOf(2); + expect(loaders[0].loader).to.equal(MiniCssExtractPlugin.loader); + expect(loaders[0].options.hmr).to.be.true; + }); +}); diff --git a/test/plugins/manifest.js b/test/plugins/manifest.js index 50005198..3278d29c 100644 --- a/test/plugins/manifest.js +++ b/test/plugins/manifest.js @@ -10,7 +10,7 @@ 'use strict'; const expect = require('chai').expect; -const ManifestPlugin = require('webpack-manifest-plugin'); +const ManifestPlugin = require('../../lib/webpack/webpack-manifest-plugin'); const WebpackConfig = require('../../lib/WebpackConfig'); const RuntimeConfig = require('../../lib/config/RuntimeConfig'); const manifestPluginUtil = require('../../lib/plugins/manifest'); diff --git a/test/plugins/mini-css-extract.js b/test/plugins/mini-css-extract.js index a7441a43..a500bea1 100644 --- a/test/plugins/mini-css-extract.js +++ b/test/plugins/mini-css-extract.js @@ -45,4 +45,31 @@ describe('plugins/mini-css-extract', () => { expect(plugins[0].plugin).to.be.instanceof(MiniCssExtractPlugin); expect(plugins[0].plugin.options.filename).to.equal('[name].[contenthash:8].css'); }); + + it('with CSS extraction disabled', () => { + const config = createConfig(); + const plugins = []; + + config.disableCssExtraction(); + + miniCssExtractPluginUtil(plugins, config); + expect(plugins.length).to.equal(0); + }); + + it('with options callback', () => { + const config = createConfig(); + const plugins = []; + + config.configureMiniCssExtractPlugin( + () => {}, + options => { + options.filename = '[name].css'; + } + ); + + miniCssExtractPluginUtil(plugins, config); + expect(plugins.length).to.equal(1); + expect(plugins[0].plugin).to.be.instanceof(MiniCssExtractPlugin); + expect(plugins[0].plugin.options.filename).to.equal('[name].css'); + }); }); diff --git a/yarn.lock b/yarn.lock index d0799cbd..890a55a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3548,15 +3548,6 @@ fs-extra@^2.0.0: graceful-fs "^4.1.2" jsonfile "^2.1.0" -fs-extra@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" - integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-minipass@^1.2.5: version "1.2.6" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" @@ -3784,10 +3775,15 @@ globule@^1.0.0: lodash "~4.17.10" minimatch "~3.0.2" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6: - version "4.2.0" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b" - integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg== +graceful-fs@^4.1.11, graceful-fs@^4.1.6: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= + +graceful-fs@^4.1.15, graceful-fs@^4.1.2: + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== growl@1.10.5: version "1.10.5" @@ -4506,6 +4502,11 @@ is-path-inside@^2.1.0: dependencies: path-is-inside "^1.0.2" +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= + is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -4732,13 +4733,6 @@ jsonfile@^2.1.0: optionalDependencies: graceful-fs "^4.1.6" -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= - optionalDependencies: - graceful-fs "^4.1.6" - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -5158,12 +5152,13 @@ mimic-fn@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -"mini-css-extract-plugin@>=0.4.0 <0.4.3": - version "0.4.2" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.2.tgz#b3ecc0d6b1bbe5ff14add42b946a7b200cf78651" - integrity sha512-ots7URQH4wccfJq9Ssrzu2+qupbncAce4TmTzunI9CIwlQMp2XI+WNUw6xWF6MMAGAm1cbUVINrSjATaVMyKXg== +mini-css-extract-plugin@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.7.0.tgz#5ba8290fbb4179a43dd27cca444ba150bee743a0" + integrity sha512-RQIw6+7utTYn8DBGsf/LpRgZCJMpZt+kuawJ/fju0KiOL6nAaTBNmCJwS7HtwSCXfS47gCkmtBFS7HdsquhdxQ== dependencies: loader-utils "^1.1.0" + normalize-url "1.9.1" schema-utils "^1.0.0" webpack-sources "^1.1.0" @@ -5547,6 +5542,16 @@ normalize-range@^0.1.2: resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= +normalize-url@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + normalize-url@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" @@ -6491,6 +6496,11 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= +prepend-http@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= + prettier@1.16.3: version "1.16.3" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.3.tgz#8c62168453badef702f34b45b6ee899574a6a65d" @@ -6650,6 +6660,14 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -7409,6 +7427,13 @@ sockjs@0.3.19: faye-websocket "^0.10.0" uuid "^3.0.1" +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= + dependencies: + is-plain-obj "^1.0.0" + source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" @@ -7617,6 +7642,11 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= + string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -8146,11 +8176,6 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -8449,15 +8474,6 @@ webpack-log@^2.0.0: ansi-colors "^3.0.0" uuid "^3.3.2" -webpack-manifest-plugin@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-2.0.4.tgz#e4ca2999b09557716b8ba4475fb79fab5986f0cd" - integrity sha512-nejhOHexXDBKQOj/5v5IZSfCeTO3x1Dt1RZEcGfBSul891X/eLIcIVH31gwxPDdsi2Z8LKKFGpM4w9+oTBOSCg== - dependencies: - fs-extra "^7.0.0" - lodash ">=3.5 <5" - tapable "^1.0.0" - webpack-notifier@^1.6.0: version "1.8.0" resolved "https://registry.yarnpkg.com/webpack-notifier/-/webpack-notifier-1.8.0.tgz#994bdde0fcefc5f1a92b6d91353c8152ddd9c583"