Skip to content

Commit

Permalink
JS Source Maps support (#506)
Browse files Browse the repository at this point in the history
* run prettier
* initial sourcemap
* improve sourcemap constructor
* also support SourceMapConsumer as addMap input
* some cleanup and fixes
* add lineOffset and 1:1 sourcemaps
* add lineOffset and 1:1 sourcemaps
* slight sourcemaputil improvement
* use babel-generator instead of double generating
* add 1:1 sourcemap for untranspiled assets
* update offset
* fix source paths
* Better offset solution
* fix sourceContent not being set by babel-generator
* fix small offset bug
* add sourcemap option and offset when globals are added
* add cli option to disable sourcemaps
* ts & coffeescript support
* fix most tests
* All tests (except hmr) fixed + bugfix for ts
* fix hmr tests
* comment out error throwing in tests
* fix windows tests
* add sourcemap tests
* add comment to why throwing errors is commented out in tests
* update with circular fix, now throws on test failures
* fix generator for invalid maps
* fix tests
* fix tiny issues
* small performance improvement
* rewrite sourcemap handling, less generator constructing overhead and some more improvements
* extendSourceMap bugfix
* Use babel rawMappings to remove encoding step
* small performance fixes
* improve linecounter
* small performance improvement and bugfix
* small improvement
* change for source-map 0.7 compatibility, improves performance a lil
* switch to official npm release
* tiny improvement
* only install source-map 0.7 if possible
* remove optional 0.7 dep
* Minor refactorings
* Store precomputed line count as part of source map
* Clean up
* Last cleanup
  • Loading branch information
Jasper De Moor authored and devongovett committed Jan 22, 2018
1 parent b5773c6 commit 5bafd4a
Show file tree
Hide file tree
Showing 37 changed files with 1,005 additions and 83 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ lib
!test/**/node_modules
.vscode/
.idea/
*.min.js
*.min.js
7 changes: 4 additions & 3 deletions packages/core/parcel-bundler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"babylon-walk": "^1.0.2",
"browser-resolve": "^1.11.2",
"chalk": "^2.1.0",
"child-process-promise": "^2.2.1",
"chokidar": "^1.7.0",
"commander": "^2.11.0",
"cross-spawn": "^5.1.0",
Expand All @@ -40,6 +39,7 @@
"resolve": "^1.4.0",
"sanitize-filename": "^1.6.1",
"serve-static": "^1.12.4",
"source-map": "0.6.1",
"uglify-es": "^3.2.1",
"v8-compile-cache": "^1.1.0",
"worker-farm": "^1.4.1",
Expand All @@ -49,6 +49,7 @@
"babel-cli": "^6.26.0",
"babel-preset-env": "^1.6.1",
"bsb-js": "^1.0.1",
"codecov": "^3.0.0",
"coffeescript": "^2.0.3",
"cross-env": "^5.1.1",
"eslint": "^4.13.0",
Expand All @@ -66,9 +67,9 @@
"posthtml-include": "^1.1.0",
"prettier": "^1.9.1",
"rimraf": "^2.6.1",
"sourcemap-validator": "^1.0.6",
"stylus": "^0.54.5",
"typescript": "^2.6.2",
"codecov": "^3.0.0"
"typescript": "^2.6.2"
},
"scripts": {
"test": "cross-env NODE_ENV=test mocha",
Expand Down
5 changes: 3 additions & 2 deletions packages/core/parcel-bundler/src/Asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Asset {
this.id = ASSET_ID++;
this.name = name;
this.basename = path.basename(this.name);
this.relativeName = path.relative(options.rootDir, this.name);
this.package = pkg || {};
this.options = options;
this.encoding = 'utf8';
Expand Down Expand Up @@ -125,7 +126,7 @@ class Asset {
// do nothing by default
}

generate() {
async generate() {
return {
[this.type]: this.contents
};
Expand All @@ -137,7 +138,7 @@ class Asset {
await this.pretransform();
await this.getDependencies();
await this.transform();
this.generated = this.generate();
this.generated = await this.generate();
this.hash = this.generateHash();
}

Expand Down
21 changes: 19 additions & 2 deletions packages/core/parcel-bundler/src/Bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ class Bundle {
this.entryAsset = null;
this.assets = new Set();
this.childBundles = new Set();
this.siblingBundles = new Set;
this.siblingBundles = new Set();
this.siblingBundlesMap = new Map();
this.offsets = new Map();
}

static createWithAsset(asset, parentBundle) {
Expand All @@ -41,6 +42,14 @@ class Bundle {
this.assets.delete(asset);
}

addOffset(asset, line) {
this.offsets.set(asset, line);
}

getOffset(asset) {
return this.offsets.get(asset) || 0;
}

getSiblingBundle(type) {
if (!type || type === this.type) {
return this;
Expand Down Expand Up @@ -89,15 +98,23 @@ class Bundle {
newHashes.set(this.name, hash);

let promises = [];
let mappings = [];
if (!oldHashes || oldHashes.get(this.name) !== hash) {
promises.push(this._package(bundler));
}

for (let bundle of this.childBundles.values()) {
promises.push(bundle.package(bundler, oldHashes, newHashes));
if (bundle.type === 'map') {
mappings.push(bundle);
} else {
promises.push(bundle.package(bundler, oldHashes, newHashes));
}
}

await Promise.all(promises);
for (let bundle of mappings) {
await bundle.package(bundler, oldHashes, newHashes);
}
return newHashes;
}

Expand Down
7 changes: 7 additions & 0 deletions packages/core/parcel-bundler/src/Bundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ class Bundler extends EventEmitter {
logLevel: typeof options.logLevel === 'number' ? options.logLevel : 3,
mainFile: this.mainFile,
hmrPort: options.hmrPort || 0,
rootDir: Path.dirname(this.mainFile),
sourceMaps:
typeof options.sourceMaps === 'boolean'
? options.sourceMaps
: !isProduction,
hmrHostname: options.hmrHostname || ''
};
}
Expand Down Expand Up @@ -204,6 +209,8 @@ class Bundler extends EventEmitter {

if (process.env.NODE_ENV === 'production') {
process.exitCode = 1;
} else if (process.env.NODE_ENV === 'test' && !this.hmr) {
throw err;
}
} finally {
this.pending = false;
Expand Down
190 changes: 190 additions & 0 deletions packages/core/parcel-bundler/src/SourceMap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
const {SourceMapConsumer, SourceMapGenerator} = require('source-map');
const lineCounter = require('./utils/lineCounter');

class SourceMap {
constructor(mappings, sources) {
this.mappings = mappings || [];
this.sources = sources || {};
this.lineCount = null;
}

async getConsumer(map) {
if (map instanceof SourceMapConsumer) {
return map;
}

return await new SourceMapConsumer(map);
}

async addMap(map, lineOffset = 0, columnOffset = 0) {
if (!(map instanceof SourceMap) && map.version) {
let consumer = await this.getConsumer(map);

consumer.eachMapping(mapping => {
this.addConsumerMapping(mapping, lineOffset, columnOffset);
if (!this.sources[mapping.source]) {
this.sources[mapping.source] = consumer.sourceContentFor(
mapping.source,
true
);
}
});

if (consumer.destroy) {
// Only needs to happen in source-map 0.7
consumer.destroy();
}
} else {
if (!map.eachMapping) {
map = new SourceMap(map.mappings, map.sources);
}

if (lineOffset === 0 && columnOffset === 0) {
this.mappings = this.mappings.concat(map.mappings);
} else {
map.eachMapping(mapping => {
this.addMapping(mapping, lineOffset, columnOffset);
});
}

Object.keys(map.sources).forEach(sourceName => {
if (!this.sources[sourceName]) {
this.sources[sourceName] = map.sources[sourceName];
}
});
}

return this;
}

addMapping(mapping, lineOffset = 0, columnOffset = 0) {
mapping.generated = {
line: mapping.generated.line + lineOffset,
column: mapping.generated.column + columnOffset
};

this.mappings.push(mapping);
}

addConsumerMapping(mapping, lineOffset = 0, columnOffset = 0) {
if (
!mapping.source ||
!mapping.originalLine ||
(!mapping.originalColumn && mapping.originalColumn !== 0)
) {
return;
}

this.mappings.push({
source: mapping.source,
original: {
line: mapping.originalLine,
column: mapping.originalColumn
},
generated: {
line: mapping.generatedLine + lineOffset,
column: mapping.generatedColumn + columnOffset
},
name: mapping.name
});
}

eachMapping(callback) {
this.mappings.forEach(callback);
}

generateEmptyMap(sourceName, sourceContent) {
this.sources[sourceName] = sourceContent;

this.lineCount = lineCounter(sourceContent);
for (let line = 1; line < this.lineCount + 1; line++) {
this.addMapping({
source: sourceName,
original: {
line: line,
column: 0
},
generated: {
line: line,
column: 0
}
});
}

return this;
}

async extendSourceMap(original, extension) {
if (!(extension instanceof SourceMap)) {
throw new Error(
'[SOURCEMAP] Type of extension should be a SourceMap instance!'
);
}

original = await this.getConsumer(original);
extension.eachMapping(mapping => {
let originalMapping = original.originalPositionFor({
line: mapping.original.line,
column: mapping.original.column
});

if (!originalMapping.line) {
return false;
}

this.addMapping({
source: originalMapping.source,
name: originalMapping.name,
original: {
line: originalMapping.line,
column: originalMapping.column
},
generated: {
line: mapping.generated.line,
column: mapping.generated.column
}
});

if (!this.sources[originalMapping.source]) {
this.sources[originalMapping.source] = original.sourceContentFor(
originalMapping.source,
true
);
}
});

if (original.destroy) {
// Only needs to happen in source-map 0.7
original.destroy();
}

return this;
}

offset(lineOffset = 0, columnOffset = 0) {
this.mappings.map(mapping => {
mapping.generated.line = mapping.generated.line + lineOffset;
mapping.generated.column = mapping.generated.column + columnOffset;
return mapping;
});

if (this.lineCount != null) {
this.lineCount += lineOffset;
}
}

stringify(file) {
let generator = new SourceMapGenerator({
file: file
});

this.eachMapping(mapping => generator.addMapping(mapping));
Object.keys(this.sources).forEach(sourceName =>
generator.setSourceContent(sourceName, this.sources[sourceName])
);

return generator.toString();
}
}

module.exports = SourceMap;
12 changes: 11 additions & 1 deletion packages/core/parcel-bundler/src/assets/CoffeeScriptAsset.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,17 @@ class CoffeeScriptAsset extends JSAsset {
let coffee = await localRequire('coffeescript', this.name);

// Transpile Module using CoffeeScript and parse result as ast format through babylon
this.contents = coffee.compile(code, {});
let transpiled = coffee.compile(code, {
sourceMap: this.options.sourceMaps
});

if (transpiled.sourceMap) {
this.sourceMap = transpiled.sourceMap.generate();
this.sourceMap.sources = [this.relativeName];
this.sourceMap.sourcesContent = [this.contents];
}

this.contents = this.options.sourceMaps ? transpiled.js : transpiled;
return await super.parse(this.contents);
}
}
Expand Down
Loading

0 comments on commit 5bafd4a

Please sign in to comment.