-
Notifications
You must be signed in to change notification settings - Fork 170
/
Copy pathsourcemap.js
262 lines (239 loc) · 8.59 KB
/
sourcemap.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
/*
* 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;
};