diff --git a/src/Bundle.js b/src/Bundle.js index d224bef..4a8da0e 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -12,7 +12,8 @@ import getIndentString from './utils/getIndentString.js'; import { unixizePath } from './utils/normalizePlatform.js'; import transform from './utils/transform.js'; import transformBundle from './utils/transformBundle.js'; -import collapseSourcemaps from './utils/collapseSourcemaps.js'; +import collapseSourcemaps from './utils/sourcemap/collapseSourcemaps.js'; +import SOURCEMAPPING_URL from './utils/sourcemap/sourceMappingURL.js'; import callIfFunction from './utils/callIfFunction.js'; import { isRelative } from './utils/path.js'; @@ -247,22 +248,25 @@ export default class Bundle { if ( banner ) magicString.prepend( banner + '\n' ); if ( footer ) magicString.append( '\n' + footer ); - const code = magicString.toString(); + let code = magicString.toString(); let map = null; + let bundleSourcemapChain = []; + + code = transformBundle( code, this.bundleTransformers, bundleSourcemapChain ) + .replace( new RegExp( `\\/\\/#\\s+${SOURCEMAPPING_URL}=.+\\n?`, 'g' ), '' ); if ( options.sourceMap ) { const file = options.sourceMapFile || options.dest; - map = magicString.generateMap({ - includeContent: true, - file - // TODO - }); + map = magicString.generateMap({ file, includeContent: true }); + + if ( this.transformers.length || this.bundleTransformers.length ) { + map = collapseSourcemaps( map, usedModules, bundleSourcemapChain ); + } - if ( this.transformers.length ) map = collapseSourcemaps( map, usedModules ); map.sources = map.sources.map( unixizePath ); } - return transformBundle( { code, map }, this.bundleTransformers ); + return { code, map }; } sort () { diff --git a/src/Module.js b/src/Module.js index e1965ab..32471a1 100644 --- a/src/Module.js +++ b/src/Module.js @@ -6,7 +6,7 @@ import { blank, keys } from './utils/object.js'; import { basename, extname } from './utils/path.js'; import getLocation from './utils/getLocation.js'; import makeLegalIdentifier from './utils/makeLegalIdentifier.js'; -import SOURCEMAPPING_URL from './utils/sourceMappingURL.js'; +import SOURCEMAPPING_URL from './utils/sourcemap/sourceMappingURL.js'; import { SyntheticDefaultDeclaration, SyntheticNamespaceDeclaration } from './Declaration.js'; import { isFalsy, isTruthy } from './ast/conditions.js'; import { emptyBlockStatement } from './ast/create.js'; diff --git a/src/rollup.js b/src/rollup.js index d509bd2..1be02a5 100644 --- a/src/rollup.js +++ b/src/rollup.js @@ -3,7 +3,7 @@ import { basename } from './utils/path.js'; import { writeFile } from './utils/fs.js'; import { keys } from './utils/object.js'; import validateKeys from './utils/validateKeys.js'; -import SOURCEMAPPING_URL from './utils/sourceMappingURL.js'; +import SOURCEMAPPING_URL from './utils/sourcemap/sourceMappingURL.js'; import Bundle from './Bundle.js'; export const VERSION = '<@VERSION@>'; diff --git a/src/utils/collapseSourcemaps.js b/src/utils/collapseSourcemaps.js deleted file mode 100644 index 8e583cf..0000000 --- a/src/utils/collapseSourcemaps.js +++ /dev/null @@ -1,72 +0,0 @@ -import { encode, decode } from 'sourcemap-codec'; - -function traceSegment ( loc, mappings ) { - const line = loc[0]; - const column = loc[1]; - - const segments = mappings[ line ]; - - if ( !segments ) return null; - - for ( let i = 0; i < segments.length; i += 1 ) { - const segment = segments[i]; - - if ( segment[0] > column ) return null; - - if ( segment[0] === column ) { - if ( segment[1] !== 0 ) { - throw new Error( 'Bad sourcemap' ); - } - - return [ segment[2], segment[3] ]; - } - } - - return null; -} - -export default function collapseSourcemaps ( map, modules ) { - const chains = modules.map( module => { - return module.sourceMapChain.map( map => { - if ( !map ) throw new Error( 'Cannot generate a sourcemap if non-sourcemap-generating transformers are used' ); - return decode( map.mappings ); - }); - }); - - const decodedMappings = decode( map.mappings ); - - const tracedMappings = decodedMappings.map( line => { - let tracedLine = []; - - line.forEach( segment => { - const sourceIndex = segment[1]; - const sourceCodeLine = segment[2]; - const sourceCodeColumn = segment[3]; - - const chain = chains[ sourceIndex ]; - - let i = chain.length; - let traced = [ sourceCodeLine, sourceCodeColumn ]; - - while ( i-- && traced ) { - traced = traceSegment( traced, chain[i] ); - } - - if ( traced ) { - tracedLine.push([ - segment[0], - segment[1], - traced[0], - traced[1] - // TODO name? - ]); - } - }); - - return tracedLine; - }); - - map.sourcesContent = modules.map( module => module.originalCode ); - map.mappings = encode( tracedMappings ); - return map; -} diff --git a/src/utils/sourcemap/collapseSourcemaps.js b/src/utils/sourcemap/collapseSourcemaps.js new file mode 100644 index 0000000..f6d7acf --- /dev/null +++ b/src/utils/sourcemap/collapseSourcemaps.js @@ -0,0 +1,87 @@ +import { encode, decode } from 'sourcemap-codec'; + +function Source ( map, sources ) { + if ( !map ) throw new Error( 'Cannot generate a sourcemap if non-sourcemap-generating transformers are used' ); + + this.sources = sources; + this.names = map.names; + this.mappings = decode( map.mappings ); +} + +Source.prototype = { // TODO bring into line with others post-https://github.com/rollup/rollup/pull/386 + traceMappings () { + return this.mappings.map( line => { + let tracedLine = []; + + line.forEach( segment => { + const source = this.sources[ segment[1] ]; + + const sourceCodeLine = segment[2]; + const sourceCodeColumn = segment[3]; + + const traced = source.traceSegment( sourceCodeLine, sourceCodeColumn ); + + if ( traced ) { + tracedLine.push([ + segment[0], + traced.index, + traced.line, + traced.column + // TODO name? + ]); + } + }); + + return tracedLine; + }); + }, + + traceSegment ( line, column ) { + const segments = this.mappings[ line ]; + + if ( !segments ) return null; + + for ( let i = 0; i < segments.length; i += 1 ) { + const segment = segments[i]; + + if ( segment[0] > column ) return null; + + if ( segment[0] === column ) { + const source = this.sources[ segment[1] ]; + + if ( !source ) throw new Error( 'Bad sourcemap' ); + + if ( source.isOriginal ) { + return { index: source.index, line: segment[2], column: segment[3] }; + } + + return source.traceSegment( segment[2], segment[3] ); + } + } + + return null; + } +}; + +export default function collapseSourcemaps ( map, modules, bundleSourcemapChain ) { + const sources = modules.map( ( module, i ) => { + let source = { isOriginal: true, index: i }; + + module.sourceMapChain.forEach( map => { + source = new Source( map, [ source ]); + }); + + return source; + }); + + let source = new Source( map, sources ); + + bundleSourcemapChain.forEach( map => { + source = new Source( map, [ source ] ); + }); + + // we re-use the `map` object because it has convenient toString/toURL methods + map.sourcesContent = modules.map( module => module.originalCode ); + map.mappings = encode( source.traceMappings() ); + return map; +} diff --git a/src/utils/sourcemap/removeSourceMappingURL.js b/src/utils/sourcemap/removeSourceMappingURL.js new file mode 100644 index 0000000..1a555fe --- /dev/null +++ b/src/utils/sourcemap/removeSourceMappingURL.js @@ -0,0 +1,3 @@ +export default function removeSourceMappingURL ( code ) { + +} diff --git a/src/utils/sourceMappingURL.js b/src/utils/sourcemap/sourceMappingURL.js similarity index 100% rename from src/utils/sourceMappingURL.js rename to src/utils/sourcemap/sourceMappingURL.js diff --git a/src/utils/transformBundle.js b/src/utils/transformBundle.js index efbe3a1..f82bfb6 100644 --- a/src/utils/transformBundle.js +++ b/src/utils/transformBundle.js @@ -1,17 +1,8 @@ -import MagicString from 'magic-string'; +export default function transformBundle ( code, transformers, sourceMapChain ) { + return transformers.reduce( ( code, transformer ) => { + let result = transformer( code ); -export default function transformBundle ( source, transformers ) { - if ( typeof source === 'string' ) { - source = { - code: source, - map: null - }; - } - - return transformers.reduce( ( previous, transformer ) => { - let result = transformer( previous.code, previous.map ); - - if ( result == null ) return previous; + if ( result == null ) return code; if ( typeof result === 'string' ) { result = { @@ -19,21 +10,10 @@ export default function transformBundle ( source, transformers ) { map: null }; } - // `result.map` can only be a string if `result` isn't - else if ( typeof result.map === 'string' ) { - result.map = JSON.parse( result.map ); - } - if (result.map != null) { - let map = new MagicString.Bundle().generateMap({}); - map.file = result.map.file; - map.sources = result.map.sources; - map.sourcesContent = result.map.sourcesContent; - map.names = result.map.names; - map.mappings = result.map.mappings; - result.map = map; - } + const map = typeof result.map === 'string' ? JSON.parse( result.map ) : map; + sourceMapChain.push( map ); - return result; - }, source ); + return result.code; + }, code ); } diff --git a/test/sourcemaps/transform-bundle/_config.js b/test/sourcemaps/transform-bundle/_config.js index abb986d..f1e16ef 100644 --- a/test/sourcemaps/transform-bundle/_config.js +++ b/test/sourcemaps/transform-bundle/_config.js @@ -9,21 +9,13 @@ module.exports = { options: { plugins: [ { - transformBundle: function ( code, map ) { - var options = { fromString: true }; + transformBundle: function ( code ) { + var options = { + fromString: true, + outSourceMap: 'x' // trigger sourcemap generation + }; - if ( map != null ) { - options.inSourceMap = map; - options.outSourceMap = "out"; - } - - var result = uglify.minify( code, options ); - - if ( map != null ) { - result.code = result.code.slice( 0, -25 ); - } - - return result; + return uglify.minify( code, options ); } } ]