Skip to content

Commit

Permalink
chore: prepare 2.0.0 release
Browse files Browse the repository at this point in the history
  • Loading branch information
evilebottnawi committed Mar 5, 2018
1 parent 4fa3ae7 commit dd9a826
Show file tree
Hide file tree
Showing 13 changed files with 16,547 additions and 16,106 deletions.
5 changes: 0 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,6 @@ matrix:
- node_js: '6'
script: npm run test-only
env: CI=tests 6
# Curretly we support webpack@3, but webpack@4 doesn't support node v4
# - node_js: '4'
# script: npm run test-only
# env: CI=tests 4
# sudo: required

before_install:
- npm install -g npm@latest
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.

This project adheres to [Semantic Versioning](http://semver.org).

## 2.0.0 - 2018-02-28

* Changed: use `ModuleFilenameHelpers.matchObject` for `test` option (only `regex` now allowed).
* Changed: Drop support for `node` v4.
* Changed: emit original file in plugin when image corrupted.
* Feature: `include` and `exclude` option for plugin.
* Fixed: compatibility with `webpack > 4.0.0`.
* Fixed: don't override `bail` from `compiler`.
* Fixed: use `callback` loader for handle `No plugins` error.
* Fixed: don't convert `Buffer` to `utf8` when `asset` is not `Buffer`.

## 1.1.2 - 2017-06-20

* Chore: support `webpack` v3.
Expand Down
203 changes: 113 additions & 90 deletions ImageminWebpackPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,146 +5,169 @@ const RawSource = require("webpack-sources/lib/RawSource");
const imagemin = require("imagemin");
const createThrottle = require("async-throttle");
const nodeify = require("nodeify");
const compileTestOption = require("./utils/compile-test-option");
const interpolateName = require("./utils/interpolate-name");
const ModuleFilenameHelpers = require("webpack/lib/ModuleFilenameHelpers");

class ImageminWebpackPlugin {
constructor(options = {}) {
this.options = Object.assign(
{},
{
bail: false,
excludeChunksAssets: true,
imageminOptions: {
plugins: []
},
manifest: null,
maxConcurrency: os.cpus().length,
name: "[hash].[ext]",
test: /\.(jpe?g|png|gif|svg)$/i
const {
test = /\.(jpe?g|png|gif|svg)$/i,
include,
exclude,
bail = null,
excludeChunksAssets = true,
imageminOptions = {
plugins: []
},
options
);
manifest = null,
maxConcurrency = os.cpus().length,
name = "[hash].[ext]"
} = options;

this.options = {
bail,
exclude,
excludeChunksAssets,
imageminOptions,
include,
manifest,
maxConcurrency,
name,
test
};

if (
!this.options.imageminOptions ||
!this.options.imageminOptions.plugins ||
this.options.imageminOptions.plugins.length === 0
!imageminOptions ||
!imageminOptions.plugins ||
imageminOptions.plugins.length === 0
) {
throw new Error("No plugins found for imagemin");
}

this.testFile = (filename, regexes) => {
for (const regex of regexes) {
if (regex.test(filename)) {
return true;
}
}

return false;
};

this.optimizeImage = (asset, imageminOptions) => {
// Grab the orig source and size
const assetSource = asset.source();
const assetOrigSize = asset.size();

// Ensure that the contents i have are in the form of a buffer
const assetContents = Buffer.isBuffer(assetSource)
? assetSource
: Buffer.from(assetSource, "utf8");

// Await for imagemin to do the compression
return imagemin
.buffer(assetContents, imageminOptions)
.then(optimizedAssetContents => {
if (optimizedAssetContents.length < assetOrigSize) {
return new RawSource(optimizedAssetContents);
}

return asset;
});
};

this.testRegexes = compileTestOption(this.options.test);
}

apply(compiler) {
const excludeChunksAssets = [];
const plugin = { name: "ImageminPlugin" };

if (compiler.options.bail) {
if (typeof this.options.bail !== "boolean" && compiler.options.bail) {
this.options.bail = compiler.options.bail;
}

if (this.options.excludeChunksAssets) {
compiler.plugin("compilation", compilation => {
compilation.plugin("after-optimize-assets", assets => {
Object.keys(assets).forEach(name => {
if (
this.testFile(name, this.testRegexes) &&
excludeChunksAssets.indexOf(name) === -1
) {
excludeChunksAssets.push(name);
}
});
const afterOptimizeAssetsFn = assets => {
Object.keys(assets).forEach(file => {
if (
ModuleFilenameHelpers.matchObject(this.options, file) &&
excludeChunksAssets.indexOf(file) === -1
) {
excludeChunksAssets.push(file);
}
});
};

if (compiler.hooks) {
compiler.hooks.compilation.tap(plugin, compilation => {
compilation.hooks.afterOptimizeAssets.tap(
plugin,
afterOptimizeAssetsFn
);
});
} else {
compiler.plugin("compilation", compilation => {
compilation.plugin("after-optimize-assets", afterOptimizeAssetsFn);
});
});
}
}

compiler.plugin("emit", (compilation, callback) => {
const emitFn = (compilation, callback) => {
const { assets } = compilation;
const throttle = createThrottle(this.options.maxConcurrency);
const {
maxConcurrency,
imageminOptions,
bail,
name,
manifest
} = this.options;
const throttle = createThrottle(maxConcurrency);

return nodeify(
Promise.all(
Object.keys(assets).map(filename =>
Object.keys(assets).map(file =>
throttle(() => {
const asset = assets[filename];
const asset = assets[file];

if (excludeChunksAssets.indexOf(file) !== -1) {
return Promise.resolve(asset);
}

if (excludeChunksAssets.indexOf(filename) !== -1) {
if (!ModuleFilenameHelpers.matchObject(this.options, file)) {
return Promise.resolve(asset);
}

// Skip the image if it's not a match for the regex
if (this.testFile(filename, this.testRegexes)) {
return this.optimizeImage(asset, this.options.imageminOptions)
return Promise.resolve().then(() =>
this.optimizeImage(asset, imageminOptions)
.catch(error => {
if (this.options.bail) {
if (bail) {
throw error;
}

return new RawSource("");
return Promise.resolve(asset);
})
.then(compressedAsset => {
const interpolatedName = interpolateName(
filename,
this.options.name,
{
content: compressedAsset.source()
}
);
const interpolatedName = interpolateName(file, name, {
content: compressedAsset.source()
});

compilation.assets[interpolatedName] = compressedAsset;

if (interpolatedName !== filename) {
delete compilation.assets[filename];
if (interpolatedName !== file) {
delete compilation.assets[file];
}

if (this.options.manifest) {
this.options.manifest[filename] = interpolatedName;
if (manifest) {
manifest[file] = interpolatedName;
}

return Promise.resolve(compressedAsset);
});
}

return Promise.resolve(asset);
})
);
})
)
),
callback
);
});
};

if (compiler.hooks) {
compiler.hooks.emit.tapAsync(plugin, emitFn);
} else {
compiler.plugin("emit", emitFn);
}
}

optimizeImage(input) {
const { imageminOptions } = this.options;
// Grab the orig source and size
const assetSource = input.source();
const assetOrigSize = input.size();

// Ensure that the contents i have are in the form of a buffer
const assetContents = Buffer.isBuffer(assetSource)
? assetSource
: Buffer.from(assetSource);

// Await for imagemin to do the compression
return Promise.resolve().then(() =>
imagemin
.buffer(assetContents, imageminOptions)
.then(optimizedAssetContents => {
if (optimizedAssetContents.length < assetOrigSize) {
return new RawSource(optimizedAssetContents);
}

return input;
})
);
}
}

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ export default {
manifest: imageminManifest, // This object will contain source and interpolated filenames.
maxConcurrency: os.cpus().length,
name: "[hash].[ext]",
test: /\.(jpe?g|png|gif|svg)$/i
test: /\.(jpe?g|png|gif|svg)$/i,
include: undefined,
exclude: undefined
})
]
};
Expand Down
17 changes: 11 additions & 6 deletions __tests__/ImageminWebpackPlugin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ test("should execute successfully and optimize only emitted", t => {

return pify(webpack)(webpackConfig).then(stats => {
const promises = [];
const { warnings, errors, assets } = stats.compilation;

t.true(stats.compilation.errors.length === 0, "no compilation error");

const { assets } = stats.compilation;
t.true(warnings.length === 0, "no compilation warnings");
t.true(errors.length === 0, "no compilation error");

const testedNotOptimizedImages = [
"test.gif",
Expand Down Expand Up @@ -138,10 +138,11 @@ test("should execute successfully and optimize all images", t => {

return pify(webpack)(webpackConfig).then(stats => {
const promises = [];
const { warnings, errors, assets } = stats.compilation;

t.true(stats.compilation.errors.length === 0, "no compilation error");
t.true(warnings.length === 0, "no compilation warnings");
t.true(errors.length === 0, "no compilation error");

const { assets } = stats.compilation;
const testedImages = [
"test.gif",
"test.jpg",
Expand Down Expand Up @@ -271,7 +272,10 @@ test("should execute successfully and ignore corrupted images using `plugin.bail
];

return pify(webpack)(webpackConfig).then(stats => {
t.true(stats.compilation.errors.length === 0, "no compilation error");
const { warnings, errors } = stats.compilation;

t.true(warnings.length === 0, "no compilation warnings");
t.true(errors.length === 0, "no compilation error");

return stats;
});
Expand Down Expand Up @@ -302,6 +306,7 @@ test("should execute successfully and ignore corrupted images using `webpack.bai
];

return pify(webpack)(webpackConfig).then(stats => {
t.true(stats.compilation.warnings.length === 0, "no compilation warnings");
t.true(stats.compilation.errors.length === 0, "no compilation error");

return stats;
Expand Down
12 changes: 10 additions & 2 deletions __tests__/fixtures/EmitWepbackPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export default class EmitWepbackPlugin {
}

apply(compiler) {
compiler.plugin("emit", (compilation, callback) => {
const plugin = { name: "EmitPlugin" };

const emitFn = (compilation, callback) => {
const { filename } = this.options;
const filePath = path.join(__dirname, filename);

Expand All @@ -28,6 +30,12 @@ export default class EmitWepbackPlugin {
}),
callback
);
});
};

if (compiler.hooks) {
compiler.hooks.emit.tapAsync(plugin, emitFn);
} else {
compiler.plugin("emit", emitFn);
}
}
}
1 change: 1 addition & 0 deletions __tests__/fixtures/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export default {
context: __dirname,
entry: "./loader.js",
mode: "development",
module: {
rules: [
{
Expand Down
Loading

0 comments on commit dd9a826

Please sign in to comment.