diff --git a/lib/migrate/__snapshots__/index.test.js.snap b/lib/migrate/__snapshots__/index.test.js.snap index ab4675fb08a..7da8c9cc8a4 100644 --- a/lib/migrate/__snapshots__/index.test.js.snap +++ b/lib/migrate/__snapshots__/index.test.js.snap @@ -61,40 +61,32 @@ module.exports = { exports[`transform should respect recast options 1`] = ` " module.exports = { - devtool: 'eval', - - entry: [ + devtool: 'eval', + entry: [ './src/index' ], - - output: { + output: { path: path.join(__dirname, 'dist'), filename: 'index.js' }, - - module: { + module: { rules: [{ - test: /\.js$/, + test: /.js$/, use: [{ loader: \\"babel-loader\\", }], include: path.join(__dirname, 'src') }] }, - - resolve: { + resolve: { modules: ['node_modules', path.resolve('/src')], }, - - plugins: [ - new webpack.optimize.UglifyJsPlugin({ - sourceMap: true, - }), - new webpack.LoaderOptionsPlugin({ - debug: true, - minimize: true, - }) - ], + plugins: [new webpack.LoaderOptionsPlugin({ + debug: true, + })], + optimization: { + minimize: true, + } }; " `; @@ -135,40 +127,32 @@ module.exports = { exports[`transform should transform using all transformations 1`] = ` " module.exports = { - devtool: 'eval', - - entry: [ + devtool: 'eval', + entry: [ './src/index' ], - - output: { + output: { path: path.join(__dirname, 'dist'), filename: 'index.js' }, - - module: { + module: { rules: [{ - test: /\.js$/, + test: /.js$/, use: [{ loader: 'babel-loader' }], include: path.join(__dirname, 'src') }] }, - - resolve: { + resolve: { modules: ['node_modules', path.resolve('/src')] }, - - plugins: [ - new webpack.optimize.UglifyJsPlugin({ - sourceMap: true - }), - new webpack.LoaderOptionsPlugin({ - debug: true, - minimize: true - }) - ] + plugins: [new webpack.LoaderOptionsPlugin({ + debug: true + })], + optimization: { + minimize: true + } }; " `; diff --git a/lib/migrate/uglifyJsPlugin/__snapshots__/uglifyJsPlugin.test.js.snap b/lib/migrate/uglifyJsPlugin/__snapshots__/uglifyJsPlugin.test.js.snap index 9d5fb3b28bb..b53445ca5a1 100644 --- a/lib/migrate/uglifyJsPlugin/__snapshots__/uglifyJsPlugin.test.js.snap +++ b/lib/migrate/uglifyJsPlugin/__snapshots__/uglifyJsPlugin.test.js.snap @@ -2,36 +2,118 @@ exports[`uglifyJsPlugin transforms correctly using "uglifyJsPlugin-0" data 1`] = ` "module.exports = { - plugins: [ - new webpack.optimize.UglifyJsPlugin({ - sourceMap: true - }) - ] + optimization: { + minimize: true + } } " `; exports[`uglifyJsPlugin transforms correctly using "uglifyJsPlugin-1" data 1`] = ` -"module.exports = { +"const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); +module.exports = { devtool: \\"source-map\\", - plugins: [ - new webpack.optimize.UglifyJsPlugin({ - sourceMap: true - }) - ] + optimization: { + minimize: true, + + minimizer: [new UglifyJsPlugin({ + sourceMap: true, + compress: {} + })] + } } " `; exports[`uglifyJsPlugin transforms correctly using "uglifyJsPlugin-2" data 1`] = ` +"const Uglify = require('uglifyjs-webpack-plugin'); +module.exports = { + devtool: \\"source-map\\", + optimization: { + minimize: true, + + minimizer: [new Uglify({ + sourceMap: true, + compress: {} + })] + } +} +" +`; + +exports[`uglifyJsPlugin transforms correctly using "uglifyJsPlugin-3" data 1`] = ` "module.exports = { - devtool: \\"cheap-source-map\\", - plugins: [ - new webpack.optimize.UglifyJsPlugin({ - compress: {}, + devtool: 'eval', + + entry: [ + './src/index' + ], + + output: { + path: path.join(__dirname, 'dist'), + filename: 'index.js' + }, + + module: { + loaders: [{ + test: /.js$/, + loaders: ['babel'], + include: path.join(__dirname, 'src') + }] + }, + + resolve: { + root: path.resolve('/src'), + modules: ['node_modules'] + }, + + plugins: [new webpack.optimize.OccurrenceOrderPlugin()], + debug: true, + + optimization: { + minimize: true + } +}; +" +`; + +exports[`uglifyJsPlugin transforms correctly using "uglifyJsPlugin-4" data 1`] = ` +"const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); +module.exports = { + devtool: 'eval', + + entry: [ + './src/index' + ], + + output: { + path: path.join(__dirname, 'dist'), + filename: 'index.js' + }, + + module: { + loaders: [{ + test: /.js$/, + loaders: ['babel'], + include: path.join(__dirname, 'src') + }] + }, + + resolve: { + root: path.resolve('/src'), + modules: ['node_modules'] + }, + + plugins: [new webpack.optimize.OccurrenceOrderPlugin()], + debug: true, + + optimization: { + minimize: true, + + minimizer: [new UglifyJsPlugin({ sourceMap: true - }) - ] -} + })] + } +}; " `; diff --git a/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-0.input.js b/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-0.input.js index 900f7042075..a577b5143a6 100644 --- a/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-0.input.js +++ b/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-0.input.js @@ -1,5 +1,7 @@ +const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); + module.exports = { plugins: [ - new webpack.optimize.UglifyJsPlugin() + new UglifyJsPlugin() ] } diff --git a/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-1.input.js b/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-1.input.js index 57d7eb1c192..1b8d983f3bd 100644 --- a/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-1.input.js +++ b/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-1.input.js @@ -1,6 +1,10 @@ +const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); module.exports = { devtool: "source-map", plugins: [ - new webpack.optimize.UglifyJsPlugin({}) + new UglifyJsPlugin({ + sourceMap: true, + compress: {} + }) ] } diff --git a/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-2.input.js b/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-2.input.js index 3c13f02b203..db172f05e29 100644 --- a/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-2.input.js +++ b/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-2.input.js @@ -1,7 +1,9 @@ +const Uglify = require('uglifyjs-webpack-plugin'); module.exports = { - devtool: "cheap-source-map", + devtool: "source-map", plugins: [ - new webpack.optimize.UglifyJsPlugin({ + new Uglify({ + sourceMap: true, compress: {} }) ] diff --git a/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-3.input.js b/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-3.input.js new file mode 100644 index 00000000000..4f104918915 --- /dev/null +++ b/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-3.input.js @@ -0,0 +1,26 @@ +module.exports = { + devtool: 'eval', + entry: [ + './src/index' + ], + output: { + path: path.join(__dirname, 'dist'), + filename: 'index.js' + }, + module: { + loaders: [{ + test: /.js$/, + loaders: ['babel'], + include: path.join(__dirname, 'src') + }] + }, + resolve: { + root: path.resolve('/src'), + modules: ['node_modules'] + }, + plugins: [ + new webpack.optimize.UglifyJsPlugin(), + new webpack.optimize.OccurrenceOrderPlugin() + ], + debug: true +}; diff --git a/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-4.input.js b/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-4.input.js new file mode 100644 index 00000000000..707400c20bd --- /dev/null +++ b/lib/migrate/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-4.input.js @@ -0,0 +1,28 @@ +module.exports = { + devtool: 'eval', + entry: [ + './src/index' + ], + output: { + path: path.join(__dirname, 'dist'), + filename: 'index.js' + }, + module: { + loaders: [{ + test: /.js$/, + loaders: ['babel'], + include: path.join(__dirname, 'src') + }] + }, + resolve: { + root: path.resolve('/src'), + modules: ['node_modules'] + }, + plugins: [ + new webpack.optimize.UglifyJsPlugin({ + sourceMap: true + }), + new webpack.optimize.OccurrenceOrderPlugin() + ], + debug: true +}; diff --git a/lib/migrate/uglifyJsPlugin/uglifyJsPlugin.js b/lib/migrate/uglifyJsPlugin/uglifyJsPlugin.js index a5bae15d0a3..7d8a99ac31e 100644 --- a/lib/migrate/uglifyJsPlugin/uglifyJsPlugin.js +++ b/lib/migrate/uglifyJsPlugin/uglifyJsPlugin.js @@ -1,8 +1,20 @@ -const findPluginsByName = require("../../utils/ast-utils").findPluginsByName; +const { + findPluginsByName, + safeTraverse, + createProperty, + pushCreateProperty, + getRequire, + findPluginsArrayAndRemoveIfEmpty +} = require("../../utils/ast-utils"); /** * - * Transform which adds a `sourceMap: true` option to instantiations of the UglifyJsPlugin + * Transform which: + * - Removes UglifyWebpackPlugin from plugins array, if no options is passed to the plugin. + * and adds `optimization.minimize: true` to config + * + * - If any configuration is passed to UglifyWebpackPlugin + * plugin instantiation is moved to `optimization.minimizer`. * * @param {Object} j - jscodeshift top-level import * @param {Node} ast - jscodeshift ast to transform @@ -10,24 +22,71 @@ const findPluginsByName = require("../../utils/ast-utils").findPluginsByName; */ module.exports = function(j, ast) { - function createSourceMapsProperty() { - return j.property("init", j.identifier("sourceMap"), j.identifier("true")); - } - - return findPluginsByName(j, ast, ["webpack.optimize.UglifyJsPlugin"]).forEach( - path => { - const args = path.value.arguments; - - if (args.length) { - // Plugin is called with object as arguments - j(path) - .find(j.ObjectExpression) - .get("properties") - .value.push(createSourceMapsProperty()); + let pluginVariableAssignment; + + const searchForRequirePlugin = ast + .find(j.VariableDeclarator) + .filter(j.filters.VariableDeclarator.requiresModule("uglifyjs-webpack-plugin")); + + /** + * Look for a variable declaration which requires uglifyjs-webpack-plugin + * saves the name of this variable. + */ + searchForRequirePlugin.forEach(node => { + pluginVariableAssignment = node.value.id.name; + }); + + pluginVariableAssignment = !pluginVariableAssignment ? "webpack.optimize.UglifyJsPlugin" : pluginVariableAssignment; + + findPluginsByName(j, ast, [pluginVariableAssignment]).forEach(node => { + let expressionContent; + const configBody = safeTraverse(node, ["parent", "parent", "parent"]); + + // options passed to plugin + const pluginOptions = node.value.arguments; + + /** + * check if there are any options passed to UglifyWebpackPlugin + * If so, they are moved to optimization.minimizer. + * Otherwise, rely on default options + */ + if (pluginOptions.length) { + /* + * If user is using UglifyJsPlugin directly from webpack + * transformation must: + * - remove it + * - add require for uglify-webpack-plugin + * - add to minimizer + */ + if (pluginVariableAssignment.includes("webpack")) { + // create require for uglify-webpack-plugin + const pathRequire = getRequire(j, "UglifyJsPlugin", "uglifyjs-webpack-plugin"); + // append to source code. + ast.find(j.Program).replaceWith(p => j.program([].concat(pathRequire).concat(p.value.body))); + + expressionContent = j.property( + "init", + j.identifier("minimizer"), + j.arrayExpression([j.newExpression(j.identifier("UglifyJsPlugin"), [pluginOptions[0]])]) + ); } else { - // Plugin is called without arguments - args.push(j.objectExpression([createSourceMapsProperty()])); + expressionContent = j.property("init", j.identifier("minimizer"), j.arrayExpression([node.value])); } + } else { + searchForRequirePlugin.forEach(node => j(node).remove()); } - ); + + const minimizeProperty = createProperty(j, "minimize", "true"); + expressionContent = (expressionContent) ? [minimizeProperty, expressionContent] : [minimizeProperty]; + + // creates optimization property at the body of the config. + pushCreateProperty(j, configBody, "optimization", j.objectExpression(expressionContent)); + + // remove the old Uglify plugin from Plugins array. + j(node).remove(); + }); + + findPluginsArrayAndRemoveIfEmpty(j, ast); + + return ast; }; diff --git a/lib/migrate/uglifyJsPlugin/uglifyJsPlugin.test.js b/lib/migrate/uglifyJsPlugin/uglifyJsPlugin.test.js index f40d97f5792..eec2c07d1e9 100644 --- a/lib/migrate/uglifyJsPlugin/uglifyJsPlugin.test.js +++ b/lib/migrate/uglifyJsPlugin/uglifyJsPlugin.test.js @@ -1,7 +1,9 @@ "use strict"; -const defineTest = require("../../utils//defineTest"); +const defineTest = require("../../utils/defineTest"); defineTest(__dirname, "uglifyJsPlugin", "uglifyJsPlugin-0"); defineTest(__dirname, "uglifyJsPlugin", "uglifyJsPlugin-1"); defineTest(__dirname, "uglifyJsPlugin", "uglifyJsPlugin-2"); +defineTest(__dirname, "uglifyJsPlugin", "uglifyJsPlugin-3"); +defineTest(__dirname, "uglifyJsPlugin", "uglifyJsPlugin-4"); diff --git a/lib/utils/__snapshots__/ast-utils.test.js.snap b/lib/utils/__snapshots__/ast-utils.test.js.snap index 6065745e190..aeec09b728d 100644 --- a/lib/utils/__snapshots__/ast-utils.test.js.snap +++ b/lib/utils/__snapshots__/ast-utils.test.js.snap @@ -90,6 +90,22 @@ exports[`utils createProperty should create properties for non-literal keys 1`] }" `; +exports[`utils findPluginsArrayAndRemoveIfEmpty It should not remove plugins array, given an array with length greater than zero 1`] = ` +" + const a = { + plugins: [ + new MyCustomPlugin() + ] + } + " +`; + +exports[`utils findPluginsArrayAndRemoveIfEmpty should remove plugins property 1`] = ` +" + const a = {} + " +`; + exports[`utils getRequire should create a require statement 1`] = `"const filesys = require(\\"fs\\");"`; exports[`utils isAssignment should allow custom transform functions instead of singularProperty 1`] = ` diff --git a/lib/utils/ast-utils.js b/lib/utils/ast-utils.js index 8843e00cdf0..43657059acf 100644 --- a/lib/utils/ast-utils.js +++ b/lib/utils/ast-utils.js @@ -61,6 +61,23 @@ function findPluginsByName(j, node, pluginNamesArray) { }); } + +/** + * It lookouts for the plugins property and, if the array is empty, it removes it from the AST + * @param {any} j - jscodeshift API + * @param {Node} rootNode - node to start search from + * @returns {Node} rootNode modified AST. + */ + +function findPluginsArrayAndRemoveIfEmpty(j, rootNode) { + return rootNode.find(j.Identifier, { name: "plugins" }).forEach((node) => { + const elements = safeTraverse(node, ["parent", "value", "value", "elements"]); + if (!elements.length) { + j(node.parent).remove(); + } + }); +} + /** * * Finds the path to the `name: []` node @@ -728,6 +745,7 @@ module.exports = { findAndRemovePluginByName, createOrUpdatePluginByName, findVariableToPlugin, + findPluginsArrayAndRemoveIfEmpty, isType, createLiteral, createIdentifierOrLiteral, diff --git a/lib/utils/ast-utils.test.js b/lib/utils/ast-utils.test.js index 7dfdf7eeeb9..917f1bebbc0 100644 --- a/lib/utils/ast-utils.test.js +++ b/lib/utils/ast-utils.test.js @@ -67,6 +67,30 @@ describe("utils", () => { }); }); + describe("findPluginsArrayAndRemoveIfEmpty", () => { + it("should remove plugins property", () => { + const ast = j(` + const a = { + plugins: [] + } + `); + utils.findPluginsArrayAndRemoveIfEmpty(j, ast); + expect(ast.toSource()).toMatchSnapshot(); + }); + + it("It should not remove plugins array, given an array with length greater than zero", () => { + const ast = j(` + const a = { + plugins: [ + new MyCustomPlugin() + ] + } + `); + utils.findPluginsArrayAndRemoveIfEmpty(j, ast); + expect(ast.toSource()).toMatchSnapshot(); + }); + }); + describe("findRootNodesByName", () => { it("should find plugins: [] nodes", () => { const ast = j(` diff --git a/package.json b/package.json index 47dead298b3..9eba422c4a1 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ }, { "path": "./lib/utils/**.js", - "maxSize": "5.25 kB" + "maxSize": "5.32 kB" } ], "config": {