From 4e29b022b89a6eeb7ba93ba92d0e955e83d2b997 Mon Sep 17 00:00:00 2001 From: Andrea Ascari Date: Wed, 12 Oct 2016 14:23:13 +0200 Subject: [PATCH] Made before and after "html-processing" events use callback value (#442) * Used returned value from applyPluginsAsyncWaterfall promise instead of referred object. * Removed the possibility to alter 'html' and 'assets' in 'html-webpack-plugin-alter-asset-tags'. * Added warning for non returned result from 'html-webpack-plugin-after-html-processing' and fixed tests. --- index.js | 44 +++++++++---- spec/BasicSpec.js | 157 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 184 insertions(+), 17 deletions(-) diff --git a/index.js b/index.js index d0fa09d..37cf63d 100644 --- a/index.js +++ b/index.js @@ -63,7 +63,7 @@ HtmlWebpackPlugin.prototype.apply = function (compiler) { }); compiler.plugin('emit', function (compilation, callback) { - var applyPluginsAsyncWaterfall = Promise.promisify(compilation.applyPluginsAsyncWaterfall, {context: compilation}); + var applyPluginsAsyncWaterfall = self.applyPluginsAsyncWaterfall(compilation); // Get all chunks var chunks = self.filterChunks(compilation.getStats().toJson(), self.options.chunks, self.options.excludeChunks); // Sort chunks @@ -137,28 +137,33 @@ HtmlWebpackPlugin.prototype.apply = function (compiler) { // Allow plugins to change the html before assets are injected .then(function (html) { var pluginArgs = {html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName}; - return applyPluginsAsyncWaterfall('html-webpack-plugin-before-html-processing', pluginArgs) - .then(function () { - return pluginArgs.html; - }); + return applyPluginsAsyncWaterfall('html-webpack-plugin-before-html-processing', pluginArgs); }) - .then(function (html) { + .then(function (result) { + var html = result.html; + var assets = result.assets; + var chunks = result.chunks; // Prepare script and link tags var assetTags = self.generateAssetTags(assets); var pluginArgs = {head: assetTags.head, body: assetTags.body, plugin: self, chunks: chunks, outputName: self.childCompilationOutputName}; // Allow plugins to change the assetTag definitions return applyPluginsAsyncWaterfall('html-webpack-plugin-alter-asset-tags', pluginArgs) - .then(function () { + .then(function (result) { // Add the stylesheets, scripts and so on to the resulting html - return self.postProcessHtml(html, assets, { body: pluginArgs.body, head: pluginArgs.head }); + return self.postProcessHtml(html, assets, { body: result.body, head: result.head }) + .then(function (html) { + return _.extend(result, {html: html, assets: assets}); + }); }); }) // Allow plugins to change the html after assets are injected - .then(function (html) { + .then(function (result) { + var html = result.html; + var assets = result.assets; var pluginArgs = {html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName}; return applyPluginsAsyncWaterfall('html-webpack-plugin-after-html-processing', pluginArgs) - .then(function () { - return pluginArgs.html; + .then(function (result) { + return result.html; }); }) .catch(function (err) { @@ -612,4 +617,21 @@ HtmlWebpackPlugin.prototype.getAssetFiles = function (assets) { return files; }; +/** + * Helper to promisify compilation.applyPluginsAsyncWaterfall that returns + * a function that helps to merge given plugin arguments with processed ones + */ +HtmlWebpackPlugin.prototype.applyPluginsAsyncWaterfall = function (compilation) { + var promisedApplyPluginsAsyncWaterfall = Promise.promisify(compilation.applyPluginsAsyncWaterfall, {context: compilation}); + return function (eventName, pluginArgs) { + return promisedApplyPluginsAsyncWaterfall(eventName, pluginArgs) + .then(function (result) { + if (!result) { + compilation.warnings.push(new Error('Using html-webpack-plugin-after-html-processing without returning a result is deprecated.')); + } + return _.extend(pluginArgs, result); + }); + }; +}; + module.exports = HtmlWebpackPlugin; diff --git a/spec/BasicSpec.js b/spec/BasicSpec.js index 1f6289f..a00df32 100644 --- a/spec/BasicSpec.js +++ b/spec/BasicSpec.js @@ -15,6 +15,7 @@ var path = require('path'); var fs = require('fs'); var webpack = require('webpack'); var rimraf = require('rimraf'); +var _ = require('lodash'); var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin'); var HtmlWebpackPlugin = require('../index.js'); @@ -746,7 +747,7 @@ describe('HtmlWebpackPlugin', function () { }, [], null, function () { expect(eventFired).toBe(true); done(); - }); + }, false, true); }); it('fires the html-webpack-plugin-before-html-processing event', function (done) { @@ -776,7 +777,7 @@ describe('HtmlWebpackPlugin', function () { }, [], null, function () { expect(eventFired).toBe(true); done(); - }); + }, false, true); }); it('fires the html-webpack-plugin-after-html-processing event', function (done) { @@ -806,7 +807,7 @@ describe('HtmlWebpackPlugin', function () { }, [], null, function () { expect(eventFired).toBe(true); done(); - }); + }, false, true); }); it('fires the html-webpack-plugin-after-emit event', function (done) { @@ -836,7 +837,7 @@ describe('HtmlWebpackPlugin', function () { }, [], null, function () { expect(eventFired).toBe(true); done(); - }); + }, false, true); }); it('allows to modify the html during html-webpack-plugin-after-html-processing event', function (done) { @@ -867,6 +868,150 @@ describe('HtmlWebpackPlugin', function () { }, ['Injected by plugin'], null, function () { expect(eventFired).toBe(true); done(); + }, false, true); + }); + + it('allows to modify sequentially the html during html-webpack-plugin-after-html-processing event by edit the given arguments object', function (done) { + var eventFiredForFirstPlugin = false; + var eventFiredForSecondPlugin = false; + var examplePlugin = { + apply: function (compiler) { + compiler.plugin('compilation', function (compilation) { + compilation.plugin('html-webpack-plugin-after-html-processing', function (object, callback) { + eventFiredForFirstPlugin = true; + object.html += 'Injected by first plugin'; + callback(null, object); + }); + }); + } + }; + var secondExamplePlugin = { + apply: function (compiler) { + compiler.plugin('compilation', function (compilation) { + compilation.plugin('html-webpack-plugin-after-html-processing', function (object, callback) { + eventFiredForSecondPlugin = true; + object.html += ' Injected by second plugin'; + callback(null); + }); + }); + } + }; + + testHtmlPlugin({ + entry: { + app: path.join(__dirname, 'fixtures/index.js') + }, + output: { + path: OUTPUT_DIR, + filename: '[name]_bundle.js' + }, + plugins: [ + new HtmlWebpackPlugin(), + examplePlugin, + secondExamplePlugin + ] + }, ['Injected by first plugin Injected by second plugin'], null, function () { + expect(eventFiredForFirstPlugin).toBe(true); + expect(eventFiredForSecondPlugin).toBe(true); + done(); + }, false, true); + }); + + it('allows to modify sequentially the html during html-webpack-plugin-after-html-processing event either by edit the given arguments object or by return a new object in the callback', function (done) { + var eventFiredForFirstPlugin = false; + var eventFiredForSecondPlugin = false; + var examplePlugin = { + apply: function (compiler) { + compiler.plugin('compilation', function (compilation) { + compilation.plugin('html-webpack-plugin-after-html-processing', function (object, callback) { + eventFiredForFirstPlugin = true; + var result = _.extend(object, { + html: object.html + 'Injected by first plugin' + }); + callback(null, result); + }); + }); + } + }; + var secondExamplePlugin = { + apply: function (compiler) { + compiler.plugin('compilation', function (compilation) { + compilation.plugin('html-webpack-plugin-after-html-processing', function (object, callback) { + eventFiredForSecondPlugin = true; + object.html += ' Injected by second plugin'; + callback(null); + }); + }); + } + }; + + testHtmlPlugin({ + entry: { + app: path.join(__dirname, 'fixtures/index.js') + }, + output: { + path: OUTPUT_DIR, + filename: '[name]_bundle.js' + }, + plugins: [ + new HtmlWebpackPlugin(), + examplePlugin, + secondExamplePlugin + ] + }, ['Injected by first plugin Injected by second plugin'], null, function () { + expect(eventFiredForFirstPlugin).toBe(true); + expect(eventFiredForSecondPlugin).toBe(true); + done(); + }, false, true); + }); + + it('allows to modify sequentially the html during html-webpack-plugin-after-html-processing event by return a new object in the callback', function (done) { + var eventFiredForFirstPlugin = false; + var eventFiredForSecondPlugin = false; + var examplePlugin = { + apply: function (compiler) { + compiler.plugin('compilation', function (compilation) { + compilation.plugin('html-webpack-plugin-after-html-processing', function (object, callback) { + eventFiredForFirstPlugin = true; + var result = _.extend(object, { + html: object.html + 'Injected by first plugin' + }); + callback(null, result); + }); + }); + } + }; + var secondExamplePlugin = { + apply: function (compiler) { + compiler.plugin('compilation', function (compilation) { + compilation.plugin('html-webpack-plugin-after-html-processing', function (object, callback) { + eventFiredForSecondPlugin = true; + var result = _.extend(object, { + html: object.html + ' Injected by second plugin' + }); + callback(null, result); + }); + }); + } + }; + + testHtmlPlugin({ + entry: { + app: path.join(__dirname, 'fixtures/index.js') + }, + output: { + path: OUTPUT_DIR, + filename: '[name]_bundle.js' + }, + plugins: [ + new HtmlWebpackPlugin(), + examplePlugin, + secondExamplePlugin + ] + }, ['Injected by first plugin Injected by second plugin'], null, function () { + expect(eventFiredForFirstPlugin).toBe(true); + expect(eventFiredForSecondPlugin).toBe(true); + done(); }); }); @@ -899,7 +1044,7 @@ describe('HtmlWebpackPlugin', function () { }, ['Injected by plugin', '