Skip to content

Added support for image optimiziation with image-webpack-loader #675

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
50 changes: 50 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,56 @@ class Encore {
return this;
}

/**
* If enabled, images will be optimized with
* imagemin.
*
* ```
* Encore.enableImagemin()
* ```
*
* or configure the imagemin options
* https://github.com/tcoopman/image-webpack-loader#options
*
* ```
* Encore.enableImagemin({
* mozjpeg: {
* progressive: true,
* quality: 65
* },
* // optipng.enabled: false will disable optipng
* optipng: {
* enabled: false,
* },
* pngquant: {
* quality: [0.65, 0.90],
* speed: 4
* },
* gifsicle: {
* interlaced: false,
* },
* // the webp option will enable WEBP
* webp: {
* quality: 75
* }
* });
*
* // For a more advanced usage you can pass in a callback
* Encore.enableImagemin((options) => {
* options.mozjpeg = { progressive: true };
* options.optipng = { enabled: false };
* });
* ```
*
* @param {object|function} imageminLoaderOptionsOrCallback
* @return {Encore}
*/
enableImagemin(imageminLoaderOptionsOrCallback = () => {}) {
webpackConfig.enableImageminLoader(imageminLoaderOptionsOrCallback);

return this;
}

/**
* Call this if you wish to disable the default
* fonts loader.
Expand Down
20 changes: 20 additions & 0 deletions lib/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class WebpackConfig {
this.cleanupOutput = false;
this.extractCss = true;
this.useImagesLoader = true;
this.useImagemin = false;
this.useFontsLoader = true;
this.usePostCssLoader = false;
this.useLessLoader = false;
Expand Down Expand Up @@ -157,6 +158,7 @@ class WebpackConfig {
this.devServerOptionsConfigurationCallback = () => {};
this.vueLoaderOptionsCallback = () => {};
this.eslintLoaderOptionsCallback = () => {};
this.imageminLoaderOptionsCallback = () => {};
this.tsConfigurationCallback = () => {};
this.handlebarsConfigurationCallback = () => {};
this.loaderConfigurationCallbacks = {
Expand Down Expand Up @@ -820,6 +822,24 @@ class WebpackConfig {
this.useImagesLoader = false;
}

enableImagemin(imageminLoaderOptionsOrCallback = () => {}) {
this.useImagemin = true;

if (typeof imageminLoaderOptionsOrCallback === 'function') {
this.imageminLoaderOptionsCallback = imageminLoaderOptionsOrCallback;
return;
}

if (typeof imageminLoaderOptionsOrCallback === 'object') {
this.imageminLoaderOptionsCallback = (options) => {
Object.assign(options, imageminLoaderOptionsOrCallback);
};
return;
}

throw new Error('Argument 1 to enableImagemin() must be either an object or callback function.');
}

disableFontsLoader() {
this.useFontsLoader = false;
}
Expand Down
24 changes: 2 additions & 22 deletions lib/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const tsLoaderUtil = require('./loaders/typescript');
const vueLoaderUtil = require('./loaders/vue');
const handlebarsLoaderUtil = require('./loaders/handlebars');
const eslintLoaderUtil = require('./loaders/eslint');
const imagesLoaderUtil = require('./loaders/images');
// plugins utils
const miniCssExtractPluginUtil = require('./plugins/mini-css-extract');
const deleteUnusedEntriesPluginUtil = require('./plugins/delete-unused-entries');
Expand Down Expand Up @@ -295,30 +296,9 @@ class ConfigGenerator {
];

if (this.webpackConfig.useImagesLoader) {
// Default filename can be overridden using Encore.configureFilenames({ images: '...' })
let filename = 'images/[name].[hash:8].[ext]';
if (this.webpackConfig.configuredFilenames.images) {
filename = this.webpackConfig.configuredFilenames.images;
}

// The url-loader can be used instead of the default file-loader by
// calling Encore.configureUrlLoader({ images: {/* ... */}})
let loaderName = 'file-loader';
const loaderOptions = {
name: filename,
publicPath: this.webpackConfig.getRealPublicPath()
};

if (this.webpackConfig.urlLoaderOptions.images) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('urlloader');
loaderName = 'url-loader';
Object.assign(loaderOptions, this.webpackConfig.urlLoaderOptions.images);
}

rules.push(applyRuleConfigurationCallback('images', {
test: /\.(png|jpg|jpeg|gif|ico|svg|webp)$/,
loader: require.resolve(loaderName),
options: loaderOptions
use: imagesLoaderUtil.getLoaders(this.webpackConfig)
}));
}

Expand Down
7 changes: 7 additions & 0 deletions lib/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ const features = {
{ name: 'handlebars-loader', enforce_version: true }
],
description: 'load Handlebars files'
},
imagemin: {
method: 'enableImagemin',
packages: [
{ name: 'image-webpack-loader', enforce_version: true }
],
description: 'minify PNG, JPEG, GIF, SVG and WEBP images with imagemin'
}
};

Expand Down
59 changes: 59 additions & 0 deletions lib/loaders/images.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

'use strict';

const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const loaderFeatures = require('../features');
const applyOptionsCallback = require('../utils/apply-options-callback');

module.exports = {
/**
* @param {WebpackConfig} webpackConfig
* @return {Array} of loaders to use for images
*/
getLoaders(webpackConfig) {

// Default filename can be overridden using Encore.configureFilenames({ images: '...' })
let filename = 'images/[name].[hash:8].[ext]';
if (webpackConfig.configuredFilenames.images) {
filename = webpackConfig.configuredFilenames.images;
}

// The url-loader can be used instead of the default file-loader by
// calling Encore.configureUrlLoader({ images: {/* ... */}})
let loaderName = 'file-loader';
const loaderOptions = {
name: filename,
publicPath: webpackConfig.getRealPublicPath()
};

if (webpackConfig.urlLoaderOptions.images) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('urlloader');
loaderName = 'url-loader';
Object.assign(loaderOptions, webpackConfig.urlLoaderOptions.images);
}

const imagesLoaders = [{
loader: require.resolve(loaderName),
options: loaderOptions
}];

if (webpackConfig.useImagemin) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('imagemin');

imagesLoaders.push({
loader: require.resolve('image-webpack-loader'),
options: applyOptionsCallback(webpackConfig.imageminLoaderOptionsCallback, {})
});
}

return imagesLoaders;
}
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"handlebars": "^4.0.11",
"handlebars-loader": "^1.7.0",
"http-server": "^0.12.3",
"image-webpack-loader": "^6.0.0",
"less": "^3.11.3",
"less-loader": "^6.2.0",
"mocha": "^7.1.2",
Expand Down
21 changes: 21 additions & 0 deletions test/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,27 @@ describe('WebpackConfig object', () => {
});
});

describe('enableImagemin', () => {
it('Without options', () => {
const config = createConfig();
config.enableImagemin();

expect(config.useImagemin).to.be.true;
});

it('With options', () => {
const config = createConfig();
const callback = (options) => {
options.optipng = { enable: false };
};

config.enableImagemin(callback);

expect(config.useImagemin).to.be.true;
expect(config.imageminLoaderOptionsCallback).to.equal(callback);
});
});

describe('disableFontsLoader', () => {
it('Disable default fonts loader', () => {
const config = createConfig();
Expand Down
59 changes: 50 additions & 9 deletions test/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,47 @@ describe('The config-generator function', () => {
});
});


describe('enableImagemin() adds the image-webpack-loader', () => {
it('without config', () => {
const config = createConfig();
config.outputPath = '/tmp/public/build';
config.setPublicPath('/build/');
config.enableImagemin();

const actualConfig = configGenerator(config);
const imagesRule = findRule(/\.(png|jpg|jpeg|gif|ico|svg|webp)$/, actualConfig.module.rules);
expect(imagesRule.use[1].loader).to.contain('image-webpack-loader');
});

it('with config', () => {
const config = createConfig();
config.outputPath = '/tmp/public/build';
config.setPublicPath('/build/');
config.enableImagemin({ optipng: { enabled: false } });

const actualConfig = configGenerator(config);
const imagesRule = findRule(/\.(png|jpg|jpeg|gif|ico|svg|webp)$/, actualConfig.module.rules);
expect(imagesRule.use[1].loader).to.contain('image-webpack-loader');
expect(imagesRule.use[1].options.optipng.enabled).to.false;
});

it('is ignored with disableImagesLoader()', () => {
const config = createConfig();
config.outputPath = '/tmp/output/public-path';
config.publicPath = '/public-path';
config.addEntry('main', './main');
config.disableImagesLoader();
config.enableImagemin();

const actualConfig = configGenerator(config);

expect(function() {
findRule(/\.(png|jpg|jpeg|gif|ico|svg|webp)$/, actualConfig.module.rules);
}).to.throw();
});
});

describe('disableFontsLoader() removes the default fonts loader', () => {
it('without disableFontsLoader()', () => {
const config = createConfig();
Expand Down Expand Up @@ -846,7 +887,7 @@ describe('The config-generator function', () => {
expect(miniCssExtractPlugin.options.filename).to.equal('[name].foo.css');

const imagesRule = findRule(/\.(png|jpg|jpeg|gif|ico|svg|webp)$/, actualConfig.module.rules);
expect(imagesRule.options.name).to.equal('[name].foo.[ext]');
expect(imagesRule.use[0].options.name).to.equal('[name].foo.[ext]');

const fontsRule = findRule(/\.(woff|woff2|ttf|eot|otf)$/, actualConfig.module.rules);
expect(fontsRule.options.name).to.equal('[name].bar.[ext]');
Expand All @@ -872,7 +913,7 @@ describe('The config-generator function', () => {
expect(miniCssExtractPlugin.options.filename).to.equal('[name].foo.css');

const imagesRule = findRule(/\.(png|jpg|jpeg|gif|ico|svg|webp)$/, actualConfig.module.rules);
expect(imagesRule.options.name).to.equal('[name].foo.[ext]');
expect(imagesRule.use[0].options.name).to.equal('[name].foo.[ext]');

const fontsRule = findRule(/\.(woff|woff2|ttf|eot|otf)$/, actualConfig.module.rules);
expect(fontsRule.options.name).to.equal('[name].bar.[ext]');
Expand All @@ -889,7 +930,7 @@ describe('The config-generator function', () => {
const actualConfig = configGenerator(config);

const imagesRule = findRule(/\.(png|jpg|jpeg|gif|ico|svg|webp)$/, actualConfig.module.rules);
expect(imagesRule.loader).to.contain('file-loader');
expect(imagesRule.use[0].loader).to.contain('file-loader');

const fontsRule = findRule(/\.(woff|woff2|ttf|eot|otf)$/, actualConfig.module.rules);
expect(fontsRule.loader).to.contain('file-loader');
Expand All @@ -905,7 +946,7 @@ describe('The config-generator function', () => {
const actualConfig = configGenerator(config);

const imagesRule = findRule(/\.(png|jpg|jpeg|gif|ico|svg|webp)$/, actualConfig.module.rules);
expect(imagesRule.loader).to.contain('file-loader');
expect(imagesRule.use[0].loader).to.contain('file-loader');

const fontsRule = findRule(/\.(woff|woff2|ttf|eot|otf)$/, actualConfig.module.rules);
expect(fontsRule.loader).to.contain('file-loader');
Expand All @@ -928,9 +969,9 @@ describe('The config-generator function', () => {
const actualConfig = configGenerator(config);

const imagesRule = findRule(/\.(png|jpg|jpeg|gif|ico|svg|webp)$/, actualConfig.module.rules);
expect(imagesRule.loader).to.contain('url-loader');
expect(imagesRule.options.name).to.equal('[name].foo.[ext]');
expect(imagesRule.options.limit).to.equal(8192);
expect(imagesRule.use[0].loader).to.contain('url-loader');
expect(imagesRule.use[0].options.name).to.equal('[name].foo.[ext]');
expect(imagesRule.use[0].options.limit).to.equal(8192);

const fontsRule = findRule(/\.(woff|woff2|ttf|eot|otf)$/, actualConfig.module.rules);
expect(fontsRule.loader).to.contain('url-loader');
Expand Down Expand Up @@ -1217,13 +1258,13 @@ describe('The config-generator function', () => {

it('configure rule for "images"', () => {
config.configureLoaderRule('images', (loaderRule) => {
loaderRule.options.name = 'dirname-images/[hash:42].[ext]';
loaderRule.use[0].options.name = 'dirname-images/[hash:42].[ext]';
});

const webpackConfig = configGenerator(config);
const rule = findRule(/\.(png|jpg|jpeg|gif|ico|svg|webp)$/, webpackConfig.module.rules);

expect(rule.options.name).to.equal('dirname-images/[hash:42].[ext]');
expect(rule.use[0].options.name).to.equal('dirname-images/[hash:42].[ext]');
});

it('configure rule for "fonts"', () => {
Expand Down
Loading