From 75dfe7ec6ccb7686e1f39efb48dd6c68870065db Mon Sep 17 00:00:00 2001 From: Gregory Waxman Date: Wed, 4 Jan 2017 10:42:04 -0500 Subject: [PATCH] Initial commit --- index.js | 146 +++++++++++++++++++++++++++++++ lib/ModuleFilenameHelpers.js | 162 +++++++++++++++++++++++++++++++++++ lib/RequestShortener.js | 56 ++++++++++++ package.json | 31 +++++++ 4 files changed, 395 insertions(+) create mode 100644 index.js create mode 100644 lib/ModuleFilenameHelpers.js create mode 100644 lib/RequestShortener.js create mode 100644 package.json diff --git a/index.js b/index.js new file mode 100644 index 0000000..718b25d --- /dev/null +++ b/index.js @@ -0,0 +1,146 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +"use strict"; + +const SourceMapConsumer = require("source-map").SourceMapConsumer; +const SourceMapSource = require("webpack-sources").SourceMapSource; +const RawSource = require("webpack-sources").RawSource; +const RequestShortener = require("./lib/RequestShortener"); +const ModuleFilenameHelpers = require("./lib/ModuleFilenameHelpers"); +const uglify = require("uglify-js"); + +class UglifyJsPlugin { + constructor(options) { + if(typeof options !== "object" || Array.isArray(options)) options = {}; + if(typeof options.compressor !== "undefined") options.compress = options.compressor; + this.options = options; + } + + apply(compiler) { + let options = this.options; + options.test = options.test || /\.js($|\?)/i; + + let requestShortener = new RequestShortener(compiler.context); + compiler.plugin("compilation", (compilation) => { + if(options.sourceMap) { + compilation.plugin("build-module", (module) => { + // to get detailed location info about errors + module.useSourceMap = true; + }); + } + compilation.plugin("optimize-chunk-assets", (chunks, callback) => { + let files = []; + chunks.forEach((chunk) => files.push.apply(files, chunk.files)); + files.push.apply(files, compilation.additionalChunkAssets); + files = files.filter(ModuleFilenameHelpers.matchObject.bind(undefined, options)); + files.forEach((file) => { + let oldWarnFunction = uglify.AST_Node.warn_function; + let warnings = []; + let sourceMap; + try { + let asset = compilation.assets[file]; + if(asset.__UglifyJsPlugin) { + compilation.assets[file] = asset.__UglifyJsPlugin; + return; + } + let input; + let inputSourceMap; + if(options.sourceMap) { + if(asset.sourceAndMap) { + let sourceAndMap = asset.sourceAndMap(); + inputSourceMap = sourceAndMap.map; + input = sourceAndMap.source; + } else { + inputSourceMap = asset.map(); + input = asset.source(); + } + sourceMap = new SourceMapConsumer(inputSourceMap); + uglify.AST_Node.warn_function = (warning) => { // eslint-disable-line camelcase + let match = /\[.+:([0-9]+),([0-9]+)\]/.exec(warning); + let line = +match[1]; + let column = +match[2]; + let original = sourceMap.originalPositionFor({ + line: line, + column: column + }); + if(!original || !original.source || original.source === file) return; + warnings.push(warning.replace(/\[.+:([0-9]+),([0-9]+)\]/, "") + + "[" + requestShortener.shorten(original.source) + ":" + original.line + "," + original.column + "]"); + }; + } else { + input = asset.source(); + uglify.AST_Node.warn_function = (warning) => { // eslint-disable-line camelcase + warnings.push(warning); + }; + } + uglify.base54.reset(); + let ast = uglify.parse(input, { + filename: file + }); + if(options.compress !== false) { + ast.figure_out_scope(); + let compress = uglify.Compressor(options.compress || { + warnings: false + }); // eslint-disable-line new-cap + ast = ast.transform(compress); + } + if(options.mangle !== false) { + ast.figure_out_scope(options.mangle || {}); + ast.compute_char_frequency(options.mangle || {}); + ast.mangle_names(options.mangle || {}); + if(options.mangle && options.mangle.props) { + uglify.mangle_properties(ast, options.mangle.props); + } + } + let output = {}; + output.comments = Object.prototype.hasOwnProperty.call(options, "comments") ? options.comments : /^\**!|@preserve|@license/; + output.beautify = options.beautify; + for(let k in options.output) { + output[k] = options.output[k]; + } + let map; + if(options.sourceMap) { + map = uglify.SourceMap({ // eslint-disable-line new-cap + file: file, + root: "" + }); + output.source_map = map; // eslint-disable-line camelcase + } + let stream = uglify.OutputStream(output); // eslint-disable-line new-cap + ast.print(stream); + if(map) map = map + ""; + stream = stream + ""; + asset.__UglifyJsPlugin = compilation.assets[file] = (map ? + new SourceMapSource(stream, file, JSON.parse(map), input, inputSourceMap) : + new RawSource(stream)); + if(warnings.length > 0) { + compilation.warnings.push(new Error(file + " from UglifyJs\n" + warnings.join("\n"))); + } + } catch(err) { + if(err.line) { + let original = sourceMap && sourceMap.originalPositionFor({ + line: err.line, + column: err.col + }); + if(original && original.source) { + compilation.errors.push(new Error(file + " from UglifyJs\n" + err.message + " [" + requestShortener.shorten(original.source) + ":" + original.line + "," + original.column + "][" + file + ":" + err.line + "," + err.col + "]")); + } else { + compilation.errors.push(new Error(file + " from UglifyJs\n" + err.message + " [" + file + ":" + err.line + "," + err.col + "]")); + } + } else if(err.msg) { + compilation.errors.push(new Error(file + " from UglifyJs\n" + err.msg)); + } else + compilation.errors.push(new Error(file + " from UglifyJs\n" + err.stack)); + } finally { + uglify.AST_Node.warn_function = oldWarnFunction; // eslint-disable-line camelcase + } + }); + callback(); + }); + }); + } +} + +module.exports = UglifyJsPlugin; diff --git a/lib/ModuleFilenameHelpers.js b/lib/ModuleFilenameHelpers.js new file mode 100644 index 0000000..6b4b391 --- /dev/null +++ b/lib/ModuleFilenameHelpers.js @@ -0,0 +1,162 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +var ModuleFilenameHelpers = exports; + +ModuleFilenameHelpers.ALL_LOADERS_RESOURCE = "[all-loaders][resource]"; +ModuleFilenameHelpers.REGEXP_ALL_LOADERS_RESOURCE = /\[all-?loaders\]\[resource\]/gi; +ModuleFilenameHelpers.LOADERS_RESOURCE = "[loaders][resource]"; +ModuleFilenameHelpers.REGEXP_LOADERS_RESOURCE = /\[loaders\]\[resource\]/gi; +ModuleFilenameHelpers.RESOURCE = "[resource]"; +ModuleFilenameHelpers.REGEXP_RESOURCE = /\[resource\]/gi; +ModuleFilenameHelpers.ABSOLUTE_RESOURCE_PATH = "[absolute-resource-path]"; +ModuleFilenameHelpers.REGEXP_ABSOLUTE_RESOURCE_PATH = /\[abs(olute)?-?resource-?path\]/gi; +ModuleFilenameHelpers.RESOURCE_PATH = "[resource-path]"; +ModuleFilenameHelpers.REGEXP_RESOURCE_PATH = /\[resource-?path\]/gi; +ModuleFilenameHelpers.ALL_LOADERS = "[all-loaders]"; +ModuleFilenameHelpers.REGEXP_ALL_LOADERS = /\[all-?loaders\]/gi; +ModuleFilenameHelpers.LOADERS = "[loaders]"; +ModuleFilenameHelpers.REGEXP_LOADERS = /\[loaders\]/gi; +ModuleFilenameHelpers.QUERY = "[query]"; +ModuleFilenameHelpers.REGEXP_QUERY = /\[query\]/gi; +ModuleFilenameHelpers.ID = "[id]"; +ModuleFilenameHelpers.REGEXP_ID = /\[id\]/gi; +ModuleFilenameHelpers.HASH = "[hash]"; +ModuleFilenameHelpers.REGEXP_HASH = /\[hash\]/gi; + +function getAfter(str, token) { + var idx = str.indexOf(token); + return idx < 0 ? "" : str.substr(idx); +} + +function getBefore(str, token) { + var idx = str.lastIndexOf(token); + return idx < 0 ? "" : str.substr(0, idx); +} + +function getHash(str) { + var hash = require("crypto").createHash("md5"); + hash.update(str); + return hash.digest("hex").substr(0, 4); +} + +function asRegExp(test) { + if(typeof test === "string") test = new RegExp("^" + test.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")); + return test; +} + +ModuleFilenameHelpers.createFilename = function createFilename(module, moduleFilenameTemplate, requestShortener) { + var absoluteResourcePath; + var hash; + var identifier; + var moduleId; + var shortIdentifier; + if(!module) module = ""; + if(typeof module === "string") { + shortIdentifier = requestShortener.shorten(module); + identifier = shortIdentifier; + moduleId = ""; + absoluteResourcePath = module.split("!").pop(); + hash = getHash(identifier); + } else { + shortIdentifier = module.readableIdentifier(requestShortener); + identifier = requestShortener.shorten(module.identifier()); + moduleId = module.id; + absoluteResourcePath = module.resourcePath || module.identifier().split("!").pop(); + hash = getHash(identifier); + } + var resource = shortIdentifier.split("!").pop(); + var loaders = getBefore(shortIdentifier, "!"); + var allLoaders = getBefore(identifier, "!"); + var query = getAfter(resource, "?"); + var resourcePath = resource.substr(0, resource.length - query.length); + if(typeof moduleFilenameTemplate === "function") { + return moduleFilenameTemplate({ + identifier: identifier, + shortIdentifier: shortIdentifier, + resource: resource, + resourcePath: resourcePath, + absoluteResourcePath: absoluteResourcePath, + allLoaders: allLoaders, + query: query, + moduleId: moduleId, + hash: hash + }); + } + return moduleFilenameTemplate + .replace(ModuleFilenameHelpers.REGEXP_ALL_LOADERS_RESOURCE, identifier) + .replace(ModuleFilenameHelpers.REGEXP_LOADERS_RESOURCE, shortIdentifier) + .replace(ModuleFilenameHelpers.REGEXP_RESOURCE, resource) + .replace(ModuleFilenameHelpers.REGEXP_RESOURCE_PATH, resourcePath) + .replace(ModuleFilenameHelpers.REGEXP_ABSOLUTE_RESOURCE_PATH, absoluteResourcePath) + .replace(ModuleFilenameHelpers.REGEXP_ALL_LOADERS, allLoaders) + .replace(ModuleFilenameHelpers.REGEXP_LOADERS, loaders) + .replace(ModuleFilenameHelpers.REGEXP_QUERY, query) + .replace(ModuleFilenameHelpers.REGEXP_ID, moduleId) + .replace(ModuleFilenameHelpers.REGEXP_HASH, hash); +}; + +ModuleFilenameHelpers.createFooter = function createFooter(module, requestShortener) { + if(!module) module = ""; + if(typeof module === "string") { + return [ + "// WEBPACK FOOTER //", + "// " + requestShortener.shorten(module) + ].join("\n"); + } else { + return [ + "//////////////////", + "// WEBPACK FOOTER", + "// " + module.readableIdentifier(requestShortener), + "// module id = " + module.id, + "// module chunks = " + module.chunks.map(function(c) { + return c.id; + }).join(" ") + ].join("\n"); + } +}; + +ModuleFilenameHelpers.replaceDuplicates = function replaceDuplicates(array, fn, comparator) { + var countMap = {}; + var posMap = {}; + array.forEach(function(item, idx) { + countMap[item] = (countMap[item] || []); + countMap[item].push(idx); + posMap[item] = 0; + }); + if(comparator) { + Object.keys(countMap).forEach(function(item) { + countMap[item].sort(comparator); + }); + } + return array.map(function(item, i) { + if(countMap[item].length > 1) { + if(comparator && countMap[item][0] === i) + return item; + return fn(item, i, posMap[item]++); + } else return item; + }); +}; + +ModuleFilenameHelpers.matchPart = function matchPart(str, test) { + if(!test) return true; + test = asRegExp(test); + if(Array.isArray(test)) { + return test.map(asRegExp).filter(function(regExp) { + return regExp.test(str); + }).length > 0; + } else { + return test.test(str); + } +}; + +ModuleFilenameHelpers.matchObject = function matchObject(obj, str) { + if(obj.test) + if(!ModuleFilenameHelpers.matchPart(str, obj.test)) return false; + if(obj.include) + if(!ModuleFilenameHelpers.matchPart(str, obj.include)) return false; + if(obj.exclude) + if(ModuleFilenameHelpers.matchPart(str, obj.exclude)) return false; + return true; +}; diff --git a/lib/RequestShortener.js b/lib/RequestShortener.js new file mode 100644 index 0000000..6d7a483 --- /dev/null +++ b/lib/RequestShortener.js @@ -0,0 +1,56 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +var path = require("path"); + +function RequestShortener(directory) { + directory = directory.replace(/\\/g, "/"); + var parentDirectory = path.dirname(directory); + if(/[\/\\]$/.test(directory)) directory = directory.substr(0, directory.length - 1); + if(directory) { + var currentDirectoryRegExp = directory.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + currentDirectoryRegExp = new RegExp("^" + currentDirectoryRegExp + "|(!)" + currentDirectoryRegExp, "g"); + + this.currentDirectoryRegExp = currentDirectoryRegExp; + } + + if(/[\/\\]$/.test(parentDirectory)) parentDirectory = parentDirectory.substr(0, parentDirectory.length - 1); + if(parentDirectory && parentDirectory !== directory) { + var parentDirectoryRegExp = parentDirectory.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + parentDirectoryRegExp = new RegExp("^" + parentDirectoryRegExp + "|(!)" + parentDirectoryRegExp, "g"); + + this.parentDirectoryRegExp = parentDirectoryRegExp; + } + + if(__dirname.length >= 2) { + var buildins = path.join(__dirname, "..").replace(/\\/g, "/"); + var buildinsAsModule = currentDirectoryRegExp && currentDirectoryRegExp.test(buildins); + var buildinsRegExp = buildins.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + buildinsRegExp = new RegExp("^" + buildinsRegExp + "|(!)" + buildinsRegExp, "g"); + + this.buildinsAsModule = buildinsAsModule; + this.buildinsRegExp = buildinsRegExp; + } + + this.nodeModulesRegExp = /\/node_modules\//g; + this.indexJsRegExp = /\/index.js(!|\?|\(query\))/g; +} +module.exports = RequestShortener; + +RequestShortener.prototype.shorten = function(request) { + if(!request) + return request; + request = request.replace(/\\/g, "/"); + if(this.buildinsAsModule && this.buildinsRegExp) + request = request.replace(this.buildinsRegExp, "!(webpack)"); + if(this.currentDirectoryRegExp) + request = request.replace(this.currentDirectoryRegExp, "!."); + if(this.parentDirectoryRegExp) + request = request.replace(this.parentDirectoryRegExp, "!.."); + if(!this.buildinsAsModule && this.buildinsRegExp) + request = request.replace(this.buildinsRegExp, "!(webpack)"); + request = request.replace(this.nodeModulesRegExp, "/~/"); + request = request.replace(this.indexJsRegExp, "$1"); + return request.replace(/^!|!$/, ""); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..1e4db46 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "webpack-uglify-harmony", + "version": "1.0.0", + "description": "A replacement for Webpack's UglifyJSPlugin to use UglifyJS Harmony", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Akkuma/webpack-uglify-harmony.git" + }, + "keywords": [ + "webpack", + "uglifyjs", + "harmony", + "uglify", + "plugin" + ], + "author": "Gregory Waxman", + "license": "BSD-3-Clause", + "bugs": { + "url": "https://github.com/Akkuma/webpack-uglify-harmony/issues" + }, + "homepage": "https://github.com/Akkuma/webpack-uglify-harmony#readme", + "dependencies": { + "source-map": "^0.5.6", + "uglify-js": "github:mishoo/UglifyJS2#harmony", + "webpack-sources": "^0.1.3" + } +}