diff --git a/src/Bundle.js b/src/Bundle.js index 31627f2..e356d88 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -1,4 +1,4 @@ -import { basename, dirname, extname, resolve } from 'path'; +import { basename, dirname, extname, relative, resolve } from 'path'; import { readFile, Promise } from 'sander'; import MagicString from 'magic-string'; import { keys, has } from './utils/object'; @@ -273,14 +273,20 @@ export default class Bundle { magicString = finalise( this, magicString.trim(), exportMode, options ); - return { - code: magicString.toString(), - map: magicString.generateMap({ - includeContent: true, - file: options.dest - // TODO - }) - }; + const code = magicString.toString(); + let map = magicString.generateMap({ + includeContent: true, + file: options.sourceMapFile || options.dest + // TODO + }); + + // make sources relative. TODO fix this upstream? + const dir = dirname( map.file ); + map.sources = map.sources.map( source => { + return source ? relative( dir, source ) : null + }); + + return { code, map }; } getExportMode ( exportMode ) { diff --git a/src/Module.js b/src/Module.js index 0691e60..aa3eb3a 100644 --- a/src/Module.js +++ b/src/Module.js @@ -3,6 +3,7 @@ import { Promise } from 'sander'; import { parse } from 'acorn'; import MagicString from 'magic-string'; import Statement from './Statement'; +import walk from './ast/walk'; import analyse from './ast/analyse'; import { blank, has, keys } from './utils/object'; import { sequence } from './utils/promise'; @@ -36,11 +37,18 @@ export default class Module { onComment: ( block, text, start, end ) => this.comments.push({ block, text, start, end }) }); + walk( ast, { + enter: node => { + this.magicString.addSourcemapLocation( node.start ); + } + }); + this.statements = ast.body.map( node => { const magicString = this.magicString.snip( node.start, node.end ); return new Statement( node, magicString, this ); }); } catch ( err ) { + err.code = 'PARSE_ERROR'; err.file = path; throw err; } diff --git a/src/rollup.js b/src/rollup.js index eb481d9..6006780 100644 --- a/src/rollup.js +++ b/src/rollup.js @@ -18,7 +18,12 @@ export function rollup ( entry, options = {} ) { let { code, map } = bundle.generate({ dest, format: options.format, - globalName: options.globalName + globalName: options.globalName, + + // sourcemap options + sourceMap: options.sourceMap, + sourceMapFile: options.sourceMapFile, + sourceMapRoot: options.sourceMapRoot }); code += `\n//# ${SOURCEMAPPING_URL}=${basename( dest )}.map`; diff --git a/test/sourcemaps/basic-support/_config.js b/test/sourcemaps/basic-support/_config.js new file mode 100644 index 0000000..b83cd6b --- /dev/null +++ b/test/sourcemaps/basic-support/_config.js @@ -0,0 +1,39 @@ +var path = require( 'path' ); +var assert = require( 'assert' ); +var getLocation = require( '../../utils/getLocation' ); +var SourceMapConsumer = require( 'source-map' ).SourceMapConsumer; + +module.exports = { + description: 'basic sourcemap support', + test: function ( code, map ) { + assert.equal( map.version, 3 ); + assert.equal( map.file, 'bundle.js' ); + + var smc = new SourceMapConsumer( map ); + var generatedLoc, originalLoc; + + // main.js + generatedLoc = getLocation( code, code.indexOf( "console.log( 'hello from main.js' )" ) ); + originalLoc = smc.originalPositionFor( generatedLoc ); + + assert.equal( originalLoc.line, 4 ); + assert.equal( originalLoc.column, 0 ); + assert.equal( path.resolve( originalLoc.source ), path.resolve( __dirname, 'main.js' ) ); + + // foo.js + generatedLoc = getLocation( code, code.indexOf( "console.log( 'hello from foo.js' )" ) ); + originalLoc = smc.originalPositionFor( generatedLoc ); + + assert.equal( originalLoc.line, 2 ); + assert.equal( originalLoc.column, 1 ); + assert.equal( path.resolve( originalLoc.source ), path.resolve( __dirname, 'foo.js' ) ); + + // bar.js + generatedLoc = getLocation( code, code.indexOf( "console.log( 'hello from bar.js' )" ) ); + originalLoc = smc.originalPositionFor( generatedLoc ); + + assert.equal( originalLoc.line, 2 ); + assert.equal( originalLoc.column, 1 ); + assert.equal( path.resolve( originalLoc.source ), path.resolve( __dirname, 'bar.js' ) ); + } +}; diff --git a/test/sourcemaps/basic-support/bar.js b/test/sourcemaps/basic-support/bar.js new file mode 100644 index 0000000..dcb017c --- /dev/null +++ b/test/sourcemaps/basic-support/bar.js @@ -0,0 +1,3 @@ +export default function bar () { + console.log( 'hello from bar.js' ); +} diff --git a/test/sourcemaps/basic-support/foo.js b/test/sourcemaps/basic-support/foo.js new file mode 100644 index 0000000..a80d173 --- /dev/null +++ b/test/sourcemaps/basic-support/foo.js @@ -0,0 +1,3 @@ +export default function foo () { + console.log( 'hello from foo.js' ); +} diff --git a/test/sourcemaps/basic-support/main.js b/test/sourcemaps/basic-support/main.js new file mode 100644 index 0000000..4cb88ba --- /dev/null +++ b/test/sourcemaps/basic-support/main.js @@ -0,0 +1,7 @@ +import foo from './foo'; +import bar from './bar'; + +console.log( 'hello from main.js' ); + +foo(); +bar(); diff --git a/test/test.js b/test/test.js index 8ca844b..8955f64 100644 --- a/test/test.js +++ b/test/test.js @@ -10,6 +10,15 @@ var rollup = require( '../dist/rollup' ); var FUNCTION = path.resolve( __dirname, 'function' ); var FORM = path.resolve( __dirname, 'form' ); +var SOURCEMAPS = path.resolve( __dirname, 'sourcemaps' ); + +var PROFILES = [ + { format: 'amd' }, + { format: 'cjs' }, + { format: 'es6' }, + { format: 'iife' }, + { format: 'umd' } +]; function extend ( target ) { [].slice.call( arguments, 1 ).forEach( function ( source ) { @@ -121,14 +130,6 @@ describe( 'rollup', function () { }); describe( 'form', function () { - var profiles = [ - { format: 'amd' }, - { format: 'cjs' }, - { format: 'es6' }, - { format: 'iife' }, - { format: 'umd' } - ]; - sander.readdirSync( FORM ).sort().forEach( function ( dir ) { if ( dir[0] === '.' ) return; // .DS_Store... @@ -143,7 +144,7 @@ describe( 'rollup', function () { var bundlePromise = rollup.rollup( FORM + '/' + dir + '/main.js', extend( {}, config.options ) ); - profiles.forEach( function ( profile ) { + PROFILES.forEach( function ( profile ) { ( config.skip ? it.skip : config.solo ? it.only : it )( 'generates ' + profile.format, function () { return bundlePromise.then( function ( bundle ) { var actual = bundle.generate({ @@ -163,4 +164,29 @@ describe( 'rollup', function () { }); }); }); + + describe( 'sourcemaps', function () { + sander.readdirSync( SOURCEMAPS ).sort().forEach( function ( dir ) { + if ( dir[0] === '.' ) return; // .DS_Store... + + describe( dir, function () { + var config = require( SOURCEMAPS + '/' + dir + '/_config' ); + + var bundlePromise = rollup.rollup( SOURCEMAPS + '/' + dir + '/main.js', extend( {}, config.options ) ); + + PROFILES.forEach( function ( profile ) { + ( config.skip ? it.skip : config.solo ? it.only : it )( 'generates ' + profile.format, function () { + return bundlePromise.then( function ( bundle ) { + var result = bundle.generate({ + format: profile.format, + sourceMapFile: 'bundle.js' + }); + + config.test( result.code, result.map ); + }); + }); + }); + }); + }); + }); }); diff --git a/test/utils/getLocation.js b/test/utils/getLocation.js new file mode 100644 index 0000000..4993997 --- /dev/null +++ b/test/utils/getLocation.js @@ -0,0 +1,20 @@ +module.exports = function getLocation ( source, charIndex ) { + var lines = source.split( '\n' ); + var len = lines.length; + + var lineStart = 0; + var i; + + for ( i = 0; i < len; i += 1 ) { + var line = lines[i]; + var lineEnd = lineStart + line.length + 1; // +1 for newline + + if ( lineEnd > charIndex ) { + return { line: i + 1, column: charIndex - lineStart }; + } + + lineStart = lineEnd; + } + + throw new Error( 'Could not determine location of character' ); +}