From 00c87e1f9f2e465dca4d189bc6ce38c4e5a9f184 Mon Sep 17 00:00:00 2001 From: Jasper De Moor Date: Sat, 12 May 2018 11:48:40 +0200 Subject: [PATCH 1/3] extend existing sourcemaps --- src/assets/JSAsset.js | 22 ++++++++++++ test/integration/sourcemap-existing/index.js | 5 +++ test/integration/sourcemap-existing/sum.js | 7 ++++ test/integration/sourcemap-existing/sum.map | 19 +++++++++++ test/sourcemaps.js | 36 ++++++++++++++++++++ 5 files changed, 89 insertions(+) create mode 100644 test/integration/sourcemap-existing/index.js create mode 100644 test/integration/sourcemap-existing/sum.js create mode 100644 test/integration/sourcemap-existing/sum.map diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index 57301b8956d..3acf6ee4f43 100644 --- a/src/assets/JSAsset.js +++ b/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/; @@ -105,6 +108,25 @@ class JSAsset extends Asset { } async pretransform() { + // Get original sourcemap if there is any + let sourceMapLine = this.contents.lastIndexOf('//# sourceMappingURL='); + if (sourceMapLine > -1) { + let sourceMapReference = this.contents.substring(sourceMapLine + 21); + this.contents = this.contents.substring(0, sourceMapLine); + + try { + this.sourceMap = JSON.parse( + await fs.readFile( + path.join(path.dirname(this.name), sourceMapReference) + ) + ); + } catch (e) { + logger.warn( + `Could not load existing sourcemap of ${this.relativeName}.` + ); + } + } + await babel(this); // Inline environment variables diff --git a/test/integration/sourcemap-existing/index.js b/test/integration/sourcemap-existing/index.js new file mode 100644 index 00000000000..66b67d1dbc5 --- /dev/null +++ b/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/test/integration/sourcemap-existing/sum.js b/test/integration/sourcemap-existing/sum.js new file mode 100644 index 00000000000..634f219de04 --- /dev/null +++ b/test/integration/sourcemap-existing/sum.js @@ -0,0 +1,7 @@ +function sum(a, b) { + return a + b; +} + +module.exports = sum; + +//# sourceMappingURL=/sum.map \ No newline at end of file diff --git a/test/integration/sourcemap-existing/sum.map b/test/integration/sourcemap-existing/sum.map new file mode 100644 index 00000000000..6334648a7cf --- /dev/null +++ b/test/integration/sourcemap-existing/sum.map @@ -0,0 +1,19 @@ +{ + "version": 3, + "sources": [ + "sum.js" + ], + "names": [ + "sum", + "a", + "b", + "module", + "exports" + ], + "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAASA,GAAT,CAAaC,CAAb,EAAgBC,CAAhB,EAAmB;AACjB,SAAOD,IAAIC,CAAX;AACD;;AAEDC,OAAOC,OAAP,GAAiBJ,GAAjB", + "file": "sum.map", + "sourceRoot": '', + "sourcesContent": [ + "function sum(a, b) {\n return a + b;\n}\n\nmodule.exports = sum;\n\n" + ] +} \ No newline at end of file diff --git a/test/sourcemaps.js b/test/sourcemaps.js index 7544dbfdfdd..adb932f9362 100644 --- a/test/sourcemaps.js +++ b/test/sourcemaps.js @@ -204,4 +204,40 @@ 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 = fs.readFileSync(b.name).toString(); + + let sourcemapReference = path.join( + __dirname, + '/dist/', + jsOutput.substring(jsOutput.lastIndexOf('//# sourceMappingURL') + 22) + ); + + assert( + fs.existsSync(path.join(sourcemapReference)), + 'referenced sourcemap should exist' + ); + + let map = fs.readFileSync(path.join(sourcemapReference)).toString(); + assert( + map.indexOf('return a + b;') > -1, + 'Sourcemap should contain the existing sourcemap' + ); + mapValidator(jsOutput, map); + }); }); From 2fd004f144e31f55bac7f38696e0e8cb52522307 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sat, 21 Jul 2018 18:37:30 -0700 Subject: [PATCH 2/3] Support inline base64 source maps --- src/assets/JSAsset.js | 36 +++++++++++++----- test/graphql.js | 1 + test/integration/sourcemap-existing/sum.js | 14 ++++--- test/integration/sourcemap-existing/sum.map | 20 +++------- test/integration/sourcemap-inline/index.js | 5 +++ test/integration/sourcemap-inline/sum.js | 9 +++++ test/sourcemaps.js | 42 +++++++++++++++++++-- 7 files changed, 94 insertions(+), 33 deletions(-) create mode 100644 test/integration/sourcemap-inline/index.js create mode 100644 test/integration/sourcemap-inline/sum.js diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index 3acf6ee4f43..c9e6c1422b3 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -23,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) { @@ -107,26 +109,40 @@ class JSAsset extends Asset { walk.ancestor(this.ast, collectDependencies, this); } - async pretransform() { + async loadSourceMap() { // Get original sourcemap if there is any - let sourceMapLine = this.contents.lastIndexOf('//# sourceMappingURL='); - if (sourceMapLine > -1) { - let sourceMapReference = this.contents.substring(sourceMapLine + 21); - this.contents = this.contents.substring(0, sourceMapLine); + 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 { - this.sourceMap = JSON.parse( - await fs.readFile( - path.join(path.dirname(this.name), sourceMapReference) - ) - ); + let json; + if (dataURLMatch) { + json = new Buffer(dataURLMatch[1], 'base64').toString(); + } else { + json = await fs.readFile( + path.join(path.dirname(this.name), url), + 'utf8' + ); + } + + this.sourceMap = JSON.parse(json); + + // Add as a dep so we watch the source map for changes. + this.addDependency(match[1], {includedInParent: true}); } 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/test/graphql.js b/test/graphql.js index b532006994a..ed6eacc93af 100644 --- a/test/graphql.js +++ b/test/graphql.js @@ -31,6 +31,7 @@ describe('graphql', function() { firstName lastName } + `.definitions ); }); diff --git a/test/integration/sourcemap-existing/sum.js b/test/integration/sourcemap-existing/sum.js index 634f219de04..00d08409ec2 100644 --- a/test/integration/sourcemap-existing/sum.js +++ b/test/integration/sourcemap-existing/sum.js @@ -1,7 +1,11 @@ -function sum(a, b) { - return a + b; -} +// Generated by CoffeeScript 1.12.6 +(function() { + module.exports = (function(_this) { + return function(a, b) { + return a + b; + }; + })(this); -module.exports = sum; +}).call(this); -//# sourceMappingURL=/sum.map \ No newline at end of file +//# sourceMappingURL=sum.map diff --git a/test/integration/sourcemap-existing/sum.map b/test/integration/sourcemap-existing/sum.map index 6334648a7cf..a445462081d 100644 --- a/test/integration/sourcemap-existing/sum.map +++ b/test/integration/sourcemap-existing/sum.map @@ -1,19 +1,11 @@ { "version": 3, + "file": "sum.js", + "sourceRoot": "", "sources": [ - "sum.js" + "sum.coffee" ], - "names": [ - "sum", - "a", - "b", - "module", - "exports" - ], - "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAASA,GAAT,CAAaC,CAAb,EAAgBC,CAAhB,EAAmB;AACjB,SAAOD,IAAIC,CAAX;AACD;;AAEDC,OAAOC,OAAP,GAAiBJ,GAAjB", - "file": "sum.map", - "sourceRoot": '', - "sourcesContent": [ - "function sum(a, b) {\n return a + b;\n}\n\nmodule.exports = sum;\n\n" - ] + "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/test/integration/sourcemap-inline/index.js b/test/integration/sourcemap-inline/index.js new file mode 100644 index 00000000000..66b67d1dbc5 --- /dev/null +++ b/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/test/integration/sourcemap-inline/sum.js b/test/integration/sourcemap-inline/sum.js new file mode 100644 index 00000000000..11a42a89d75 --- /dev/null +++ b/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/test/sourcemaps.js b/test/sourcemaps.js index adb932f9362..c8bb5f51a88 100644 --- a/test/sourcemaps.js +++ b/test/sourcemaps.js @@ -220,7 +220,7 @@ describe('sourcemaps', function() { ] }); - let jsOutput = fs.readFileSync(b.name).toString(); + let jsOutput = await fs.readFile(b.name, 'utf8'); let sourcemapReference = path.join( __dirname, @@ -229,13 +229,47 @@ describe('sourcemaps', function() { ); assert( - fs.existsSync(path.join(sourcemapReference)), + 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 = fs.readFileSync(path.join(sourcemapReference)).toString(); + let map = await fs.readFile(path.join(sourcemapReference), 'utf8'); assert( - map.indexOf('return a + b;') > -1, + map.indexOf('module.exports = (a, b) => a + b') > -1, 'Sourcemap should contain the existing sourcemap' ); mapValidator(jsOutput, map); From 66e2e4a6532b75ff5981627bbb7c96411304b040 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sat, 21 Jul 2018 19:04:36 -0700 Subject: [PATCH 3/3] Load missing source contents if needed --- src/assets/JSAsset.js | 50 ++++++++++++++++--- .../sourcemap-external-contents/index.js | 5 ++ .../sourcemap-external-contents/sum.coffee | 1 + .../sourcemap-external-contents/sum.js | 11 ++++ .../sourcemap-external-contents/sum.map | 10 ++++ test/sourcemaps.js | 36 +++++++++++++ 6 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 test/integration/sourcemap-external-contents/index.js create mode 100644 test/integration/sourcemap-external-contents/sum.coffee create mode 100644 test/integration/sourcemap-external-contents/sum.js create mode 100644 test/integration/sourcemap-external-contents/sum.map diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index c9e6c1422b3..3e089da0924 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -119,23 +119,57 @@ class JSAsset extends Asset { let dataURLMatch = url.match(DATA_URL_RE); try { - let json; + let json, filename; if (dataURLMatch) { + filename = this.name; json = new Buffer(dataURLMatch[1], 'base64').toString(); } else { - json = await fs.readFile( - path.join(path.dirname(this.name), url), - 'utf8' - ); + 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); - // Add as a dep so we watch the source map for changes. - this.addDependency(match[1], {includedInParent: true}); + // 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}.` + `Could not load existing sourcemap of "${this.relativeName}".` ); } } diff --git a/test/integration/sourcemap-external-contents/index.js b/test/integration/sourcemap-external-contents/index.js new file mode 100644 index 00000000000..66b67d1dbc5 --- /dev/null +++ b/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/test/integration/sourcemap-external-contents/sum.coffee b/test/integration/sourcemap-external-contents/sum.coffee new file mode 100644 index 00000000000..e84213f385f --- /dev/null +++ b/test/integration/sourcemap-external-contents/sum.coffee @@ -0,0 +1 @@ +module.exports = (a, b) => a + b diff --git a/test/integration/sourcemap-external-contents/sum.js b/test/integration/sourcemap-external-contents/sum.js new file mode 100644 index 00000000000..00d08409ec2 --- /dev/null +++ b/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/test/integration/sourcemap-external-contents/sum.map b/test/integration/sourcemap-external-contents/sum.map new file mode 100644 index 00000000000..87b0481c875 --- /dev/null +++ b/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/test/sourcemaps.js b/test/sourcemaps.js index c8bb5f51a88..be8b1223ad0 100644 --- a/test/sourcemaps.js +++ b/test/sourcemaps.js @@ -274,4 +274,40 @@ describe('sourcemaps', function() { ); 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); + }); });