diff --git a/packages/core/parcel/src/assets/JSAsset.js b/packages/core/parcel/src/assets/JSAsset.js index 57301b8956d..3e089da0924 100644 --- a/packages/core/parcel/src/assets/JSAsset.js +++ b/packages/core/parcel/src/assets/JSAsset.js @@ -13,6 +13,9 @@ const generate = require('babel-generator').default; const terser = require('../transforms/terser'); const SourceMap = require('../SourceMap'); const hoist = require('../scope-hoisting/hoist'); +const path = require('path'); +const fs = require('../utils/fs'); +const logger = require('../Logger'); const IMPORT_RE = /\b(?:import\b|export\b|require\s*\()/; const ENV_RE = /\b(?:process\.env)\b/; @@ -20,6 +23,8 @@ const GLOBAL_RE = /\b(?:process|__dirname|__filename|global|Buffer|define)\b/; const FS_RE = /\breadFileSync\b/; const SW_RE = /\bnavigator\s*\.\s*serviceWorker\s*\.\s*register\s*\(/; const WORKER_RE = /\bnew\s*Worker\s*\(/; +const SOURCEMAP_RE = /\/\/\s*[@#]\s*sourceMappingURL\s*=\s*([^\s]+)/; +const DATA_URL_RE = /^data:[^;]+(?:;charset=[^;]+)?;base64,(.*)/; class JSAsset extends Asset { constructor(name, options) { @@ -104,7 +109,74 @@ class JSAsset extends Asset { walk.ancestor(this.ast, collectDependencies, this); } + async loadSourceMap() { + // Get original sourcemap if there is any + let match = this.contents.match(SOURCEMAP_RE); + if (match) { + this.contents = this.contents.replace(SOURCEMAP_RE, ''); + + let url = match[1]; + let dataURLMatch = url.match(DATA_URL_RE); + + try { + let json, filename; + if (dataURLMatch) { + filename = this.name; + json = new Buffer(dataURLMatch[1], 'base64').toString(); + } else { + filename = path.join(path.dirname(this.name), url); + json = await fs.readFile(filename, 'utf8'); + + // Add as a dep so we watch the source map for changes. + this.addDependency(filename, {includedInParent: true}); + } + + this.sourceMap = JSON.parse(json); + + // Attempt to read missing source contents + if (!this.sourceMap.sourcesContent) { + this.sourceMap.sourcesContent = []; + } + + let missingSources = this.sourceMap.sources.slice( + this.sourceMap.sourcesContent.length + ); + if (missingSources.length) { + let contents = await Promise.all( + missingSources.map(async source => { + try { + let sourceFile = path.join( + path.dirname(filename), + this.sourceMap.sourceRoot || '', + source + ); + let result = await fs.readFile(sourceFile, 'utf8'); + this.addDependency(sourceFile, {includedInParent: true}); + return result; + } catch (err) { + logger.warn( + `Could not load source file "${source}" in source map of "${ + this.relativeName + }".` + ); + } + }) + ); + + this.sourceMap.sourcesContent = this.sourceMap.sourcesContent.concat( + contents + ); + } + } catch (e) { + logger.warn( + `Could not load existing sourcemap of "${this.relativeName}".` + ); + } + } + } + async pretransform() { + await this.loadSourceMap(); await babel(this); // Inline environment variables diff --git a/packages/core/parcel/test/graphql.js b/packages/core/parcel/test/graphql.js index b532006994a..ed6eacc93af 100644 --- a/packages/core/parcel/test/graphql.js +++ b/packages/core/parcel/test/graphql.js @@ -31,6 +31,7 @@ describe('graphql', function() { firstName lastName } + `.definitions ); }); diff --git a/packages/core/parcel/test/integration/sourcemap-existing/index.js b/packages/core/parcel/test/integration/sourcemap-existing/index.js new file mode 100644 index 00000000000..66b67d1dbc5 --- /dev/null +++ b/packages/core/parcel/test/integration/sourcemap-existing/index.js @@ -0,0 +1,5 @@ +const sum = require('./sum'); + +module.exports = function() { + return sum(1, 2); +} \ No newline at end of file diff --git a/packages/core/parcel/test/integration/sourcemap-existing/sum.js b/packages/core/parcel/test/integration/sourcemap-existing/sum.js new file mode 100644 index 00000000000..00d08409ec2 --- /dev/null +++ b/packages/core/parcel/test/integration/sourcemap-existing/sum.js @@ -0,0 +1,11 @@ +// Generated by CoffeeScript 1.12.6 +(function() { + module.exports = (function(_this) { + return function(a, b) { + return a + b; + }; + })(this); + +}).call(this); + +//# sourceMappingURL=sum.map diff --git a/packages/core/parcel/test/integration/sourcemap-existing/sum.map b/packages/core/parcel/test/integration/sourcemap-existing/sum.map new file mode 100644 index 00000000000..a445462081d --- /dev/null +++ b/packages/core/parcel/test/integration/sourcemap-existing/sum.map @@ -0,0 +1,11 @@ +{ + "version": 3, + "file": "sum.js", + "sourceRoot": "", + "sources": [ + "sum.coffee" + ], + "names": [], + "mappings": ";AAAA;EAAA,MAAM,CAAC,OAAP,GAAiB,CAAA,SAAA,KAAA;WAAA,SAAC,CAAD,EAAI,CAAJ;aAAU,CAAA,GAAI;IAAd;EAAA,CAAA,CAAA,CAAA,IAAA;AAAjB", + "sourcesContent": ["module.exports = (a, b) => a + b"] +} \ No newline at end of file diff --git a/packages/core/parcel/test/integration/sourcemap-external-contents/index.js b/packages/core/parcel/test/integration/sourcemap-external-contents/index.js new file mode 100644 index 00000000000..66b67d1dbc5 --- /dev/null +++ b/packages/core/parcel/test/integration/sourcemap-external-contents/index.js @@ -0,0 +1,5 @@ +const sum = require('./sum'); + +module.exports = function() { + return sum(1, 2); +} \ No newline at end of file diff --git a/packages/core/parcel/test/integration/sourcemap-external-contents/sum.coffee b/packages/core/parcel/test/integration/sourcemap-external-contents/sum.coffee new file mode 100644 index 00000000000..e84213f385f --- /dev/null +++ b/packages/core/parcel/test/integration/sourcemap-external-contents/sum.coffee @@ -0,0 +1 @@ +module.exports = (a, b) => a + b diff --git a/packages/core/parcel/test/integration/sourcemap-external-contents/sum.js b/packages/core/parcel/test/integration/sourcemap-external-contents/sum.js new file mode 100644 index 00000000000..00d08409ec2 --- /dev/null +++ b/packages/core/parcel/test/integration/sourcemap-external-contents/sum.js @@ -0,0 +1,11 @@ +// Generated by CoffeeScript 1.12.6 +(function() { + module.exports = (function(_this) { + return function(a, b) { + return a + b; + }; + })(this); + +}).call(this); + +//# sourceMappingURL=sum.map diff --git a/packages/core/parcel/test/integration/sourcemap-external-contents/sum.map b/packages/core/parcel/test/integration/sourcemap-external-contents/sum.map new file mode 100644 index 00000000000..87b0481c875 --- /dev/null +++ b/packages/core/parcel/test/integration/sourcemap-external-contents/sum.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "file": "sum.js", + "sourceRoot": "", + "sources": [ + "sum.coffee" + ], + "names": [], + "mappings": ";AAAA;EAAA,MAAM,CAAC,OAAP,GAAiB,CAAA,SAAA,KAAA;WAAA,SAAC,CAAD,EAAI,CAAJ;aAAU,CAAA,GAAI;IAAd;EAAA,CAAA,CAAA,CAAA,IAAA;AAAjB" +} diff --git a/packages/core/parcel/test/integration/sourcemap-inline/index.js b/packages/core/parcel/test/integration/sourcemap-inline/index.js new file mode 100644 index 00000000000..66b67d1dbc5 --- /dev/null +++ b/packages/core/parcel/test/integration/sourcemap-inline/index.js @@ -0,0 +1,5 @@ +const sum = require('./sum'); + +module.exports = function() { + return sum(1, 2); +} \ No newline at end of file diff --git a/packages/core/parcel/test/integration/sourcemap-inline/sum.js b/packages/core/parcel/test/integration/sourcemap-inline/sum.js new file mode 100644 index 00000000000..11a42a89d75 --- /dev/null +++ b/packages/core/parcel/test/integration/sourcemap-inline/sum.js @@ -0,0 +1,9 @@ +// Generated by CoffeeScript 2.3.1 +(function() { + module.exports = (a, b) => { + return a + b; + }; + +}).call(this); + +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3VtLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsic3VtLmNvZmZlZSJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7RUFBQSxNQUFNLENBQUMsT0FBUCxHQUFpQixDQUFDLENBQUQsRUFBSSxDQUFKLENBQUEsR0FBQTtXQUFVLENBQUEsR0FBSTtFQUFkO0FBQWpCIiwic291cmNlc0NvbnRlbnQiOlsibW9kdWxlLmV4cG9ydHMgPSAoYSwgYikgPT4gYSArIGJcbiJdfQ== diff --git a/packages/core/parcel/test/sourcemaps.js b/packages/core/parcel/test/sourcemaps.js index 7544dbfdfdd..be8b1223ad0 100644 --- a/packages/core/parcel/test/sourcemaps.js +++ b/packages/core/parcel/test/sourcemaps.js @@ -204,4 +204,110 @@ describe('sourcemaps', function() { let map = (await fs.readFile(path.join(sourcemapReference))).toString(); mapValidator(jsOutput, map); }); + + it('should load existing sourcemaps of libraries', async function() { + let b = await bundle( + __dirname + '/integration/sourcemap-existing/index.js' + ); + + assertBundleTree(b, { + name: 'index.js', + assets: ['index.js', 'sum.js'], + childBundles: [ + { + type: 'map' + } + ] + }); + + let jsOutput = await fs.readFile(b.name, 'utf8'); + + let sourcemapReference = path.join( + __dirname, + '/dist/', + jsOutput.substring(jsOutput.lastIndexOf('//# sourceMappingURL') + 22) + ); + + assert( + await fs.exists(path.join(sourcemapReference)), + 'referenced sourcemap should exist' + ); + + let map = await fs.readFile(path.join(sourcemapReference), 'utf8'); + assert( + map.indexOf('module.exports = (a, b) => a + b') > -1, + 'Sourcemap should contain the existing sourcemap' + ); + mapValidator(jsOutput, map); + }); + + it('should load inline sourcemaps of libraries', async function() { + let b = await bundle(__dirname + '/integration/sourcemap-inline/index.js'); + + assertBundleTree(b, { + name: 'index.js', + assets: ['index.js', 'sum.js'], + childBundles: [ + { + type: 'map' + } + ] + }); + + let jsOutput = await fs.readFile(b.name, 'utf8'); + + let sourcemapReference = path.join( + __dirname, + '/dist/', + jsOutput.substring(jsOutput.lastIndexOf('//# sourceMappingURL') + 22) + ); + + assert( + await fs.exists(path.join(sourcemapReference)), + 'referenced sourcemap should exist' + ); + + let map = await fs.readFile(path.join(sourcemapReference), 'utf8'); + assert( + map.indexOf('module.exports = (a, b) => a + b') > -1, + 'Sourcemap should contain the existing sourcemap' + ); + mapValidator(jsOutput, map); + }); + + it('should load referenced contents of sourcemaps', async function() { + let b = await bundle( + __dirname + '/integration/sourcemap-external-contents/index.js' + ); + + assertBundleTree(b, { + name: 'index.js', + assets: ['index.js', 'sum.js'], + childBundles: [ + { + type: 'map' + } + ] + }); + + let jsOutput = await fs.readFile(b.name, 'utf8'); + + let sourcemapReference = path.join( + __dirname, + '/dist/', + jsOutput.substring(jsOutput.lastIndexOf('//# sourceMappingURL') + 22) + ); + + assert( + await fs.exists(path.join(sourcemapReference)), + 'referenced sourcemap should exist' + ); + + let map = await fs.readFile(path.join(sourcemapReference), 'utf8'); + assert( + map.indexOf('module.exports = (a, b) => a + b') > -1, + 'Sourcemap should contain the existing sourcemap' + ); + mapValidator(jsOutput, map); + }); });