backoffice/node_modules/grunt-contrib-concat/tasks/lib/sourcemap.js

263 lines
8.6 KiB
JavaScript

/*
* grunt-contrib-concat
* http://gruntjs.com/
*
* Copyright (c) 2016 "Cowboy" Ben Alman, contributors
* Licensed under the MIT license.
*/
'use strict';
exports.init = function(grunt) {
var exports = {};
// Node first party libs
var path = require('path');
// Third party libs
var chalk = require('chalk');
var SourceMap = require('source-map');
var SourceMapConsumer = SourceMap.SourceMapConsumer;
var SourceMapGenerator = SourceMap.SourceMapGenerator;
var NO_OP = function(){};
function SourceMapConcatHelper(options) {
this.files = options.files;
this.dest = options.dest;
this.options = options.options;
this.line = 1;
this.column = 0;
// ensure we're using forward slashes, because these are URLs
var file = path.relative(path.dirname(this.dest), this.files.dest).replace(/\\/g, '/');
var generator = new SourceMapGenerator({
file: file
});
this.file = file;
this.generator = generator;
this.addMapping = function(genLine, genCol, orgLine, orgCol, source, name) {
if (!source) {
generator.addMapping({
generated: {line: genLine, column: genCol}
});
} else {
if (!name) {
generator.addMapping({
generated: {line: genLine, column: genCol},
original: {line: orgLine, column: orgCol},
source: source
});
} else {
generator.addMapping({
generated: {line: genLine, column: genCol},
original: {line: orgLine, column: orgCol},
source: source,
name: name
});
}
}
};
}
// Return an object that is used to track sourcemap data between calls.
exports.helper = function(files, options) {
// Figure out the source map destination.
var dest = files.dest;
if (options.sourceMapStyle === 'inline') {
// Leave dest as is. It will be used to compute relative sources.
} else if (typeof options.sourceMapName === 'string') {
dest = options.sourceMapName;
} else if (typeof options.sourceMapName === 'function') {
dest = options.sourceMapName(dest);
} else {
dest += '.map';
}
// Inline style and sourceMapName together doesn't work
if (options.sourceMapStyle === 'inline' && options.sourceMapName) {
grunt.log.warn(
'Source map will be inlined, sourceMapName option ignored.'
);
}
return new SourceMapConcatHelper({
files: files,
dest: dest,
options: options
});
};
// Parse only to increment the generated file's column and line count
SourceMapConcatHelper.prototype.add = function(src) {
this._forEachTokenPosition(src);
};
/**
* Parse the source file into tokens and apply the provided callback
* with the position of the token boundaries in the original file, and
* in the generated file.
*
* @param src The sources to tokenize. Required
* @param filename The name of the source file. Optional
* @param callback What to do with the token position indices. Optional
*/
SourceMapConcatHelper.prototype._forEachTokenPosition = function(src, filename, callback) {
var genLine = this.line;
var genCol = this.column;
var orgLine = 1;
var orgCol = 0;
// Tokenize on words, new lines, and white space.
var tokens = src.split(/(\n|[^\S\n]+|\b)/g);
if (!callback) {
callback = NO_OP;
}
for (var i = 0, len = tokens.length; i < len; i++) {
var token = tokens[i];
if (token) {
// The if statement filters out empty strings.
callback(genLine, genCol, orgLine, orgCol, filename);
if (token === '\n') {
++orgLine;
++genLine;
orgCol = 0;
genCol = 0;
} else {
orgCol += token.length;
genCol += token.length;
}
}
}
this.line = genLine;
this.column = genCol;
};
// Add the lines of a given file to the sourcemap. If in the file, store a
// prior sourcemap and return src with sourceMappingURL removed.
SourceMapConcatHelper.prototype.addlines = function(src, filename) {
var sourceMapRegEx = /\n\/[*/][@#]\s+sourceMappingURL=((?:(?!\s+\*\/).)*).*/;
var relativeFilename = path.relative(path.dirname(this.dest), filename);
// sourceMap path references are URLs, so ensure forward slashes are used for paths passed to sourcemap library
relativeFilename = relativeFilename.replace(/\\/g, '/');
if (sourceMapRegEx.test(src)) {
var sourceMapFile = RegExp.$1;
var sourceMapPath;
var sourceContent;
// Browserify, as an example, stores a datauri at sourceMappingURL.
if (/data:application\/json;(charset:utf-8;)?base64,([^\s]+)/.test(sourceMapFile)) {
// Set sourceMapPath to the file that the map is inlined.
sourceMapPath = filename;
sourceContent = new Buffer(RegExp.$2, 'base64').toString();
} else {
// If sourceMapPath is relative, expand relative to the file
// referring to it.
sourceMapPath = path.resolve(path.dirname(filename), sourceMapFile);
sourceContent = grunt.file.read(sourceMapPath);
}
var sourceMapDir = path.dirname(sourceMapPath);
var sourceMap = JSON.parse(sourceContent);
var sourceMapConsumer = new SourceMapConsumer(sourceMap);
// Consider the relative path from source files to new sourcemap.
var sourcePathToSourceMapPath = path.relative(path.dirname(this.dest), sourceMapDir);
// Transfer the existing mappings into this mapping
var initLine = this.line;
var initCol = this.column;
sourceMapConsumer.eachMapping(function(args){
var source;
if (args.source) {
source = path.join(sourcePathToSourceMapPath, args.source).replace(/\\/g, '/');
} else {
source = null;
}
this.line = initLine + args.generatedLine - 1;
if (this.line === initLine) {
this.column = initCol + args.generatedColumn;
} else {
this.column = args.generatedColumn;
}
this.addMapping(
this.line,
this.column,
args.originalLine,
args.originalColumn,
source,
args.name
);
}, this);
if (sourceMap.sources && sourceMap.sources.length && sourceMap.sourcesContent) {
for (var i = 0; i < sourceMap.sources.length; ++i) {
this.generator.setSourceContent(
path.join(sourcePathToSourceMapPath, sourceMap.sources[i]).replace(/\\/g, '/'),
sourceMap.sourcesContent[i]
);
}
}
// Remove the old sourceMappingURL.
src = src.replace(sourceMapRegEx, '');
} else {
// Otherwise perform a rudimentary tokenization of the source.
this._forEachTokenPosition(src, relativeFilename, this.addMapping);
}
if (this.options.sourceMapStyle !== 'link') {
this.generator.setSourceContent(relativeFilename, src);
}
return src;
};
// Return the comment sourceMappingURL that must be appended to the
// concatenated file.
SourceMapConcatHelper.prototype.url = function() {
// Create the map filepath. Either datauri or destination path.
var mapfilepath;
if (this.options.sourceMapStyle === 'inline') {
var inlineMap = new Buffer(this._write()).toString('base64');
mapfilepath = 'data:application/json;base64,' + inlineMap;
} else {
// Compute relative path to source map destination.
mapfilepath = path.relative(path.dirname(this.files.dest), this.dest);
}
// Create the sourceMappingURL.
var url;
if (/\.css$/.test(this.files.dest)) {
url = '\n/*# sourceMappingURL=' + mapfilepath + ' */';
} else {
url = '\n//# sourceMappingURL=' + mapfilepath;
}
return url;
};
// Return a string for inline use or write the source map to disk.
SourceMapConcatHelper.prototype._write = function() {
// New sourcemap.
var newSourceMap = this.generator.toJSON();
// Return a string for inline use or write the map.
if (this.options.sourceMapStyle === 'inline') {
grunt.verbose.writeln(
'Source map for ' + chalk.cyan(this.files.dest) + ' inlined.'
);
return JSON.stringify(newSourceMap, null, '');
}
grunt.file.write(
this.dest,
JSON.stringify(newSourceMap, null, '')
);
grunt.verbose.writeln('Source map ' + chalk.cyan(this.dest) + ' created.');
};
// Non-private function to write the sourcemap. Shortcuts if writing a inline
// style map.
SourceMapConcatHelper.prototype.write = function() {
if (this.options.sourceMapStyle !== 'inline') {
this._write();
}
};
return exports;
};