Skip to content

Commit d9f3c25

Browse files
Jasper De Moordevongovett
authored andcommitted
Uglify sourcemaps support (#617)
1 parent 0d9d14c commit d9f3c25

10 files changed

Lines changed: 176 additions & 10 deletions

File tree

src/Bundler.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,7 @@ class Bundler extends EventEmitter {
9292
hmrPort: options.hmrPort || 0,
9393
rootDir: Path.dirname(this.mainFile),
9494
sourceMaps:
95-
typeof options.sourceMaps === 'boolean'
96-
? options.sourceMaps
97-
: !isProduction,
95+
typeof options.sourceMaps === 'boolean' ? options.sourceMaps : true,
9896
hmrHostname: options.hmrHostname || '',
9997
detailedReport: options.detailedReport || false
10098
};

src/SourceMap.js

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,12 @@ class SourceMap {
137137

138138
async extendSourceMap(original, extension) {
139139
if (!(extension instanceof SourceMap)) {
140-
throw new Error(
141-
'[SOURCEMAP] Type of extension should be a SourceMap instance!'
142-
);
140+
extension = await new SourceMap().addMap(extension);
141+
}
142+
if (!(original instanceof SourceMap)) {
143+
original = await this.getConsumer(original);
143144
}
144145

145-
original = await this.getConsumer(original);
146146
extension.eachMapping(mapping => {
147147
let originalMapping = original.originalPositionFor({
148148
line: mapping.original.line,
@@ -182,6 +182,90 @@ class SourceMap {
182182
return this;
183183
}
184184

185+
findClosest(line, column, key = 'original') {
186+
if (line < 1) {
187+
throw new Error('Line numbers must be >= 1');
188+
}
189+
190+
if (column < 0) {
191+
throw new Error('Column numbers must be >= 0');
192+
}
193+
194+
if (this.mappings.length < 1) {
195+
return undefined;
196+
}
197+
198+
let startIndex = 0;
199+
let stopIndex = this.mappings.length - 1;
200+
let middleIndex = Math.floor((stopIndex + startIndex) / 2);
201+
202+
while (
203+
startIndex < stopIndex &&
204+
this.mappings[middleIndex][key].line !== line
205+
) {
206+
if (line < this.mappings[middleIndex][key].line) {
207+
stopIndex = middleIndex - 1;
208+
} else if (line > this.mappings[middleIndex][key].line) {
209+
startIndex = middleIndex + 1;
210+
}
211+
middleIndex = Math.floor((stopIndex + startIndex) / 2);
212+
}
213+
214+
let mapping = this.mappings[middleIndex];
215+
if (!mapping || mapping[key].line !== line) {
216+
return this.mappings.length - 1;
217+
}
218+
219+
while (
220+
middleIndex >= 1 &&
221+
this.mappings[middleIndex - 1][key].line === line
222+
) {
223+
middleIndex--;
224+
}
225+
226+
while (
227+
middleIndex < this.mappings.length - 1 &&
228+
this.mappings[middleIndex + 1][key].line === line &&
229+
column > this.mappings[middleIndex][key].column
230+
) {
231+
middleIndex++;
232+
}
233+
234+
return middleIndex;
235+
}
236+
237+
originalPositionFor(generatedPosition) {
238+
let index = this.findClosest(
239+
generatedPosition.line,
240+
generatedPosition.column,
241+
'generated'
242+
);
243+
return {
244+
source: this.mappings[index].source,
245+
name: this.mappings[index].name,
246+
line: this.mappings[index].original.line,
247+
column: this.mappings[index].original.column
248+
};
249+
}
250+
251+
generatedPositionFor(originalPosition) {
252+
let index = this.findClosest(
253+
originalPosition.line,
254+
originalPosition.column,
255+
'original'
256+
);
257+
return {
258+
source: this.mappings[index].source,
259+
name: this.mappings[index].name,
260+
line: this.mappings[index].generated.line,
261+
column: this.mappings[index].generated.column
262+
};
263+
}
264+
265+
sourceContentFor(fileName) {
266+
return this.sources[fileName];
267+
}
268+
185269
offset(lineOffset = 0, columnOffset = 0) {
186270
this.mappings.map(mapping => {
187271
mapping.generated.line = mapping.generated.line + lineOffset;

src/cli.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ program
9898
)
9999
.option('--no-minify', 'disable minification')
100100
.option('--no-cache', 'disable the filesystem cache')
101+
.option('--no-source-maps', 'disable sourcemaps')
101102
.option(
102103
'-t, --target <target>',
103104
'set the runtime environment, either "node", "browser" or "electron". defaults to "browser"',

src/transforms/uglify.js

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
const {minify} = require('uglify-es');
2+
const SourceMap = require('../SourceMap');
23

34
module.exports = async function(asset) {
45
await asset.parseIfNeeded();
56

67
// Convert AST into JS
7-
let code = (await asset.generate()).js;
8+
let source = (await asset.generate()).js;
89

910
let customConfig = await asset.getConfig(['.uglifyrc']);
1011
let options = {
@@ -14,15 +15,50 @@ module.exports = async function(asset) {
1415
}
1516
};
1617

18+
let sourceMap;
19+
if (asset.options.sourceMap) {
20+
sourceMap = new SourceMap();
21+
options.output = {
22+
source_map: {
23+
add(source, gen_line, gen_col, orig_line, orig_col, name) {
24+
sourceMap.addMapping({
25+
source,
26+
name,
27+
original: {
28+
line: orig_line,
29+
column: orig_col
30+
},
31+
generated: {
32+
line: gen_line,
33+
column: gen_col
34+
}
35+
});
36+
}
37+
}
38+
};
39+
}
40+
1741
if (customConfig) {
1842
options = Object.assign(options, customConfig);
1943
}
2044

21-
let result = minify(code, options);
45+
let result = minify(source, options);
46+
2247
if (result.error) {
2348
throw result.error;
2449
}
2550

51+
if (sourceMap) {
52+
if (asset.sourceMap) {
53+
asset.sourceMap = await new SourceMap().extendSourceMap(
54+
asset.sourceMap,
55+
sourceMap
56+
);
57+
} else {
58+
asset.sourceMap = sourceMap;
59+
}
60+
}
61+
2662
// babel-generator did our code generation for us, so remove the old AST
2763
asset.ast = null;
2864
asset.outputCode = result.code;

test/css.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ describe('css', function() {
160160
assets: ['index.css'],
161161
childBundles: []
162162
},
163+
{
164+
type: 'map'
165+
},
163166
{
164167
type: 'woff2',
165168
assets: ['test.woff2'],
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const local = require('./local');
2+
3+
module.exports = function() {
4+
return local.a + local.b;
5+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const util = require('./utils/util');
2+
3+
exports.a = 5;
4+
exports.b = util.count(4, 5);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
exports.count = function(a, b) {
2+
return a + b;
3+
}

test/sourcemaps.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,36 @@ describe('sourcemaps', function() {
116116
assert.equal(typeof output, 'function');
117117
assert.equal(output(), 14);
118118
});
119+
120+
it('should create a valid sourcemap for a minified js bundle with requires', async function() {
121+
let b = await bundle(
122+
__dirname + '/integration/sourcemap-nested-minified/index.js',
123+
{
124+
minify: true
125+
}
126+
);
127+
128+
assertBundleTree(b, {
129+
name: 'index.js',
130+
assets: ['index.js', 'local.js', 'util.js'],
131+
childBundles: [
132+
{
133+
name: 'index.map',
134+
type: 'map'
135+
}
136+
]
137+
});
138+
139+
let raw = fs
140+
.readFileSync(path.join(__dirname, '/dist/index.js'))
141+
.toString();
142+
let map = fs
143+
.readFileSync(path.join(__dirname, '/dist/index.map'))
144+
.toString();
145+
mapValidator(raw, map);
146+
147+
let output = run(b);
148+
assert.equal(typeof output, 'function');
149+
assert.equal(output(), 14);
150+
});
119151
});

test/typescript.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ describe('typescript', function() {
8080
);
8181

8282
assert.equal(b.assets.size, 2);
83-
assert.equal(b.childBundles.size, 0);
83+
assert.equal(b.childBundles.size, 1);
8484

8585
let output = run(b);
8686
assert.equal(typeof output.count, 'function');

0 commit comments

Comments
 (0)