diff --git a/History.md b/History.md index 0f6819a013..85dc0920ed 100644 --- a/History.md +++ b/History.md @@ -5,6 +5,11 @@ `nodeType` property of an object couldn't be checked, fixing [#7354](https://github.com/meteor/meteor/issues/7354). +* The `standard-minifier-js` and `minifier-js` packages now have improved + error capturing to provide more information on otherwise unhelpful errors + thrown when UglifyJS encounters ECMAScript grammar it is not familiar with. + [#8414](https://github.com/meteor/meteor/pull/8414) + ## v1.4.3.1, 2017-02-14 * The `meteor-babel` npm package has been upgraded to version 0.14.4, diff --git a/packages/minifier-js/package.js b/packages/minifier-js/package.js index be89e758e0..b741592858 100644 --- a/packages/minifier-js/package.js +++ b/packages/minifier-js/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "JavaScript minifier", - version: "1.2.17" + version: "1.2.18" }); Npm.depends({ @@ -12,7 +12,6 @@ Npm.strip({ }); Package.onUse(function (api) { - api.use('underscore', 'server'); api.export(['UglifyJSMinify', 'UglifyJS']); api.addFiles(['minifier.js'], 'server'); }); diff --git a/packages/standard-minifier-js/package.js b/packages/standard-minifier-js/package.js index 94ec659c3a..736b1d27d1 100644 --- a/packages/standard-minifier-js/package.js +++ b/packages/standard-minifier-js/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'standard-minifier-js', - version: '1.2.2', + version: '1.2.3', summary: 'Standard javascript minifiers used with Meteor apps by default.', documentation: 'README.md' }); @@ -8,7 +8,7 @@ Package.describe({ Package.registerBuildPlugin({ name: "minifyStdJS", use: [ - 'minifier-js' + 'minifier-js@1.2.18' ], sources: [ 'plugin/minify-js.js' diff --git a/packages/standard-minifier-js/plugin/minify-js.js b/packages/standard-minifier-js/plugin/minify-js.js index b9a5248826..c1ff32c96b 100644 --- a/packages/standard-minifier-js/plugin/minify-js.js +++ b/packages/standard-minifier-js/plugin/minify-js.js @@ -32,13 +32,109 @@ UglifyJSMinifier.prototype.processFilesForBundle = function (files, options) { } }; + function maybeThrowMinifyErrorBySourceFile(error, file) { + var minifierErrorRegex = /\(line: (\d+), col: (\d+), pos: (\d+)\)/; + var parseError = minifierErrorRegex.exec(error.toString()); + + if (parseError) { + var lineErrorMessage = parseError[0]; + var lineErrorLineNumber = parseError[1]; + + var parseErrorContentIndex = lineErrorLineNumber - 1; + + // Unlikely, since we have a multi-line fixed header in this file. + if (parseErrorContentIndex < 0) { + return; + } + + /* + + What we're parsing looks like this: + + ///////////////////////////////////////// + // // + // path/to/file.js // + // // + ///////////////////////////////////////// + // 1 + var illegalECMAScript = true; // 2 + // 3 + ///////////////////////////////////////// + + Btw, the above code is intentionally not newer ECMAScript so + we don't break ourselves. + + */ + + var contents = file.getContentsAsString().split(/\n/); + var lineContent = contents[parseErrorContentIndex]; + + // Try to grab the line number, which sometimes doesn't exist on + // line, abnormally-long lines in a larger block. + var lineSrcLineParts = /^(.*?)(?:\s*\/\/ (\d+))?$/.exec(lineContent); + + // The line didn't match at all? Let's just not try. + if (!lineSrcLineParts) { + return; + } + + var lineSrcLineContent = lineSrcLineParts[1]; + var lineSrcLineNumber = lineSrcLineParts[2]; + + // Count backward from the failed line to find the filename. + for (var c = parseErrorContentIndex - 1; c >= 0; c--) { + var sourceLine = contents[c]; + + // If the line is a boatload of slashes, we're in the right place. + if (/^\/\/\/{6,}$/.test(sourceLine)) { + + // If 4 lines back is the same exact line, we've found the framing. + if (contents[c - 4] === sourceLine) { + + // So in that case, 2 lines back is the file path. + var parseErrorPath = contents[c - 2] + .substring(3) + .replace(/\s+\/\//, ""); + + var minError = new Error( + "UglifyJS minification error: \n\n" + + error.message + " at " + parseErrorPath + + (lineSrcLineNumber ? " line " + lineSrcLineNumber + "\n\n" : "") + + " within " + file.getPathInBundle() + " " + + lineErrorMessage + ":\n\n" + + lineSrcLineContent + "\n" + ); + + throw minError; + } + } + } + } + } + var allJs = ''; files.forEach(function (file) { // Don't reminify *.min.js. if (/\.min\.js$/.test(file.getPathInBundle())) { allJs += file.getContentsAsString(); } else { - allJs += UglifyJSMinify(file.getContentsAsString(), minifyOptions).code; + var minified; + try { + minified = UglifyJSMinify(file.getContentsAsString(), minifyOptions); + if (!(minified && typeof minified.code === "string")) { + throw new Error(); + } + } catch (err) { + var filePath = file.getPathInBundle(); + + // Try to catch the ugly Uglify error. + maybeThrowMinifyErrorBySourceFile(err, file); + + err.message += " while minifying " + filePath; + throw err; + } + + allJs += minified.code; } allJs += '\n\n'; @@ -49,5 +145,3 @@ UglifyJSMinifier.prototype.processFilesForBundle = function (files, options) { files[0].addJavaScript({ data: allJs }); } }; - -