Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,37 @@ const publicApi = {
return this;
},

/**
* Sorts plugins so they are added to Webpack in the given order.
*
* This method takes an array of the constructor functions used to
* initialize the plugins you want to sort.
*
* You only need to provide the plugins that need to be sorted:
* the other ones will keep their default position or respect
* the order in which the Encore.addPlugin() method has been called.
*
* For example:
*
* const webpack = require('webpack');
* const Plugin1 = require('plugin1');
* const Plugin2 = require('plugin2');
*
* Encore.sortPlugins([
* Plugin2,
* webpack.optimize.UglifyJsPlugin,
* Plugin1
* ])
*
* @param {Array} sortOrder
* @return {exports}
*/
sortPlugins(sortOrder) {
webpackConfig.sortPlugins(sortOrder);

return this;
},

/**
* Adds a custom loader config
*
Expand Down
9 changes: 9 additions & 0 deletions lib/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class WebpackConfig {
this.sharedCommonsEntryName = null;
this.providedVariables = {};
this.configuredFilenames = {};
this.sortPluginsOrder = [];

// Features/Loaders flags
this.useVersioning = false;
Expand Down Expand Up @@ -256,6 +257,14 @@ class WebpackConfig {
this.plugins.push(plugin);
}

sortPlugins(sortOrder = []) {
if (!Array.isArray(sortOrder)) {
throw new Error('Argument 1 to sortPlugins() must be an Array of constructor functions (e.g.: [ExtractTextPlugin, CleanWebpackPlugin])');
}

this.sortPluginsOrder = sortOrder;
}

addLoader(loader) {
this.loaders.push(loader);
}
Expand Down
34 changes: 30 additions & 4 deletions lib/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,6 @@ class ConfigGenerator {
// register the pure-style entries that should be deleted
deleteUnusedEntriesPluginUtil(plugins, this.webpackConfig);

// Dump the manifest.json file
manifestPluginUtil(plugins, this.webpackConfig);

loaderOptionsPluginUtil(plugins, this.webpackConfig);

versioningPluginUtil(plugins, this.webpackConfig);
Expand All @@ -236,11 +233,40 @@ class ConfigGenerator {

assetOutputDisplay(plugins, this.webpackConfig, friendlyErrorPlugin);

// Custom plugins
this.webpackConfig.plugins.forEach(function(plugin) {
plugins.push(plugin);
});

return plugins;
// Dump the manifest.json file last so it includes all
// emitted assets.
manifestPluginUtil(plugins, this.webpackConfig);

// Sort plugins based on the order given to Encore.sortPlugins()
const sortOrder = [...this.webpackConfig.sortPluginsOrder];
const sortedPlugins = [];

while (plugins.length) {
const currentPlugin = plugins.shift();
const sortedIndex = sortOrder.findIndex((type) => currentPlugin instanceof type);

// If the current plugin type has been found in the sortOrder array
// we move every instances of the types that are supposed to be placed
// before it.
for (let i = 0; i < sortedIndex; i++) {
const currentType = sortOrder.shift();

// Add plugins of the current type to the sorted array
sortedPlugins.push(...plugins.filter((plugin) => plugin instanceof currentType));

// Remove plugins of the current type from the unsorted array
plugins = plugins.filter((plugin) => !(plugin instanceof currentType));
}

sortedPlugins.push(currentPlugin);
}

return sortedPlugins;
}

buildStatsConfig() {
Expand Down
23 changes: 23 additions & 0 deletions test/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,29 @@ describe('WebpackConfig object', () => {
});
});

describe('sortPlugins', () => {
it('Calling method sets it', () => {
const config = createConfig();

expect(config.sortPluginsOrder.length).to.equal(0);

config.sortPlugins([
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new webpack.optimize.UglifyJsPlugin()
]);

expect(config.sortPluginsOrder.length).to.equal(2);
});

it('Calling with non-array throws an error', () => {
const config = createConfig();

expect(() => {
config.sortPlugins('foo');
}).to.throw('must be an Array');
});
});

describe('addLoader', () => {
it('Adds a new loader', () => {
const config = createConfig();
Expand Down
126 changes: 126 additions & 0 deletions test/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,35 @@ function findRule(regex, rules) {
throw new Error(`No rule found for regex ${regex}`);
}

/**
* Test if all the plugins respect the expected order.
*
* @param {Array} plugins
* @param {Array} expectedOrder
* @returns {void}
*/
function assertPluginsOrder(plugins, expectedOrder) {
let expectedIndex = 0;
let previousPlugin = null;

for (let plugin of plugins) {
if (previousPlugin && (plugin instanceof previousPlugin.constructor)) {
continue;
}

const actualIndex = expectedOrder.findIndex((type) => plugin instanceof type);
if (actualIndex !== -1) {
if (actualIndex !== expectedIndex) {
throw new Error(`Expected plugin ${plugin.constructor.name} to be at index ${expectedIndex}, got ${actualIndex}`);
} else {
expectedIndex++;
}
}

previousPlugin = plugin;
}
}

describe('The config-generator function', () => {

describe('Test basic output properties', () => {
Expand Down Expand Up @@ -557,4 +586,101 @@ describe('The config-generator function', () => {
});
});
});

describe('Test plugins sorting', () => {
function CustomPlugin1() {}
function CustomPlugin2() {}
function CustomPlugin3() {}

it('Without calling sortPlugins', () => {
const config = createConfig();
config.outputPath = '/tmp/public/build';
config.setPublicPath('/build/');
config.cleanupOutputBeforeBuild();
config.addPlugin(new CustomPlugin1());
config.addPlugin(new CustomPlugin2());

const actualConfig = configGenerator(config);
assertPluginsOrder(
actualConfig.plugins,
[
ExtractTextPlugin,
CleanWebpackPlugin,
CustomPlugin1,
CustomPlugin2,
ManifestPlugin
]
);
});

it('When calling sortPlugins without custom plugins', () => {
const config = createConfig();
config.outputPath = '/tmp/public/build';
config.setPublicPath('/build/');
config.cleanupOutputBeforeBuild();

config.sortPlugins([
CleanWebpackPlugin,
ManifestPlugin,
ExtractTextPlugin
]);

const actualConfig = configGenerator(config);
assertPluginsOrder(
actualConfig.plugins,
[
CleanWebpackPlugin,
ManifestPlugin,
ExtractTextPlugin
]
);
});

it('When calling sortPlugins with custom plugins', () => {
const config = createConfig();
config.outputPath = '/tmp/public/build';
config.setPublicPath('/build/');
config.cleanupOutputBeforeBuild();

config.addPlugin(new CustomPlugin1());
config.addPlugin(new CustomPlugin2());
config.addPlugin(new CustomPlugin3());

// It should work with multiple instances of the same plugin
config.addPlugin(new CustomPlugin3());
config.addPlugin(new CustomPlugin2());
config.addPlugin(new CustomPlugin1());
config.addPlugin(new CustomPlugin3());

config.sortPlugins([
CustomPlugin1,
CleanWebpackPlugin,
CustomPlugin2,
CustomPlugin3,
ManifestPlugin,
ExtractTextPlugin
]);

const actualConfig = configGenerator(config);
assertPluginsOrder(
actualConfig.plugins,
[
CustomPlugin1,
CleanWebpackPlugin,
CustomPlugin2,
CustomPlugin3,
ManifestPlugin,
ExtractTextPlugin
]
);

// It shouldn't remove any plugin
expect(actualConfig.plugins.filter((plugin) => plugin instanceof CleanWebpackPlugin).length).to.equal(1);
expect(actualConfig.plugins.filter((plugin) => plugin instanceof ManifestPlugin).length).to.equal(1);
expect(actualConfig.plugins.filter((plugin) => plugin instanceof ExtractTextPlugin).length).to.equal(1);
expect(actualConfig.plugins.filter((plugin) => plugin instanceof CustomPlugin1).length).to.equal(2);
expect(actualConfig.plugins.filter((plugin) => plugin instanceof CustomPlugin2).length).to.equal(2);
expect(actualConfig.plugins.filter((plugin) => plugin instanceof CustomPlugin3).length).to.equal(3);
});
});
});
9 changes: 9 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ describe('Public API', () => {

});

describe('sortPlugins', () => {

it('must return the API object', () => {
const returnedValue = api.sortPlugins([]);
expect(returnedValue).to.equal(api);
});

});

describe('addLoader', () => {

it('must return the API object', () => {
Expand Down