Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Gregory Waxman committed Jan 4, 2017
1 parent 6406da7 commit 75dfe7e
Show file tree
Hide file tree
Showing 4 changed files with 395 additions and 0 deletions.
146 changes: 146 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -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;
162 changes: 162 additions & 0 deletions lib/ModuleFilenameHelpers.js
Original file line number Diff line number Diff line change
@@ -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;
};
56 changes: 56 additions & 0 deletions lib/RequestShortener.js
Original file line number Diff line number Diff line change
@@ -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(/^!|!$/, "");
};
31 changes: 31 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}

0 comments on commit 75dfe7e

Please sign in to comment.