Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lazy sourcemaps #71

Merged
merged 4 commits into from
May 9, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 3 additions & 25 deletions src/builtins/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,7 @@ import config from '../config';
import extractLocationInfo from '../utils/extractLocationInfo';
import { isRegExp } from '../utils/is';
import { ABORTED } from '../utils/signals';

let SOURCEMAPPING_URL = 'sourceMa';
SOURCEMAPPING_URL += 'ppingURL';

const SOURCEMAP_COMMENT = new RegExp( `\n*(?:` +
`\\/\\/[@#]\\s*${SOURCEMAPPING_URL}=([^'"]+)|` + // js
`\\/\\*#?\\s*${SOURCEMAPPING_URL}=([^'"]+)\\s\\+\\/)` + // css
`\\s*$`, 'g' );
import { getSourcemapComment, SOURCEMAP_COMMENT } from '../utils/sourcemap';

export default function map ( inputdir, outputdir, options ) {
let changed = {};
Expand Down Expand Up @@ -109,23 +102,12 @@ export default function map ( inputdir, outputdir, options ) {
});
}

function sourceMappingURLComment ( codepath ) {
const ext = extname( codepath );
const url = encodeURI( codepath ) + '.map';

if ( ext === '.css' ) {
return `\n/*# ${SOURCEMAPPING_URL}=${url} */\n`;
}

return `\n//# ${SOURCEMAPPING_URL}=${url}\n`;
}

function processResult ( result, original, src, dest, codepath ) {
if ( typeof result === 'object' && 'code' in result ) {
// if a sourcemap was returned, use it
if ( result.map ) {
return {
code: result.code.replace( SOURCEMAP_COMMENT, '' ) + sourceMappingURLComment( codepath ),
code: result.code.replace( SOURCEMAP_COMMENT, '' ) + getSourcemapComment( encodeURI( codepath + '.map' ), extname( codepath ) ),
map: processSourcemap( result.map, src, dest, original )
};
}
Expand Down Expand Up @@ -158,7 +140,7 @@ function processInlineSourceMap ( code, src, dest, original, codepath ) {
let json = atob( match[1] );

map = processSourcemap( json, src, dest, original );
code = code.replace( SOURCEMAP_COMMENT, '' ) + sourceMappingURLComment( codepath );
code = code.replace( SOURCEMAP_COMMENT, '' ) + getSourcemapComment( encodeURI( codepath + '.map' ), extname( codepath ) );
}

return { code, map };
Expand Down Expand Up @@ -234,7 +216,3 @@ function shouldSkip ( options, ext, filename ) {
function atob ( base64 ) {
return new Buffer( base64, 'base64' ).toString( 'utf8' );
}

function btoa ( str ) {
return new Buffer( str ).toString( 'base64' );
}
44 changes: 8 additions & 36 deletions src/nodes/Node.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { EventEmitter2 } from 'eventemitter2';
import * as crc32 from 'buffer-crc32';
import { copydir, lsrSync, readFileSync, rimraf } from 'sander';
import { join, resolve } from 'path';
import { lsrSync, readFileSync, rimraf } from 'sander';
import { resolve } from 'path';
import * as requireRelative from 'require-relative';
import { grab, include, map as mapTransform, move } from '../builtins';
import { Observer, Transformer } from './index';
import config from '../config';
import GobbleError from '../utils/GobbleError';
import flattenSourcemaps from '../utils/flattenSourcemaps';
import assign from '../utils/assign';
import warnOnce from '../utils/warnOnce';
import compareBuffers from '../utils/compareBuffers';
Expand All @@ -16,7 +15,6 @@ import build from './build';
import watch from './watch';
import { isRegExp } from '../utils/is';
import { ABORTED } from '../utils/signals';
import session from '../session';

// TODO remove this in a future version
function enforceCorrectArguments ( options ) {
Expand Down Expand Up @@ -48,10 +46,9 @@ export default class Node extends EventEmitter2 {
return build( this, options );
}

createWatchTask ( dest ) {
createWatchTask () {
const node = this;
const watchTask = new EventEmitter2({ wildcard: true });
let uid = 1;

// TODO is this the best place to handle this stuff? or is it better
// to pass off the info to e.g. gobble-cli?
Expand Down Expand Up @@ -82,38 +79,13 @@ export default class Node extends EventEmitter2 {
node.on( 'error', handleError );

function build () {
const buildStart = Date.now();

buildScheduled = false;

watchTask.emit( 'build:start' );

node.ready()
.then( inputdir => {
const sourcemapProcessStart = Date.now();

watchTask.emit( 'info', {
code: 'SOURCEMAP_PROCESS_START',
progressIndicator: true
});

// create new directory for sourcemaps...
const outputdir = join( session.config.gobbledir, '.final', '' + uid++ );

return copydir( inputdir ).to( outputdir )
.then( () => flattenSourcemaps( inputdir, outputdir, dest, watchTask ) )
.then( () => {
watchTask.emit( 'info', {
code: 'SOURCEMAP_PROCESS_COMPLETE',
duration: Date.now() - sourcemapProcessStart
});

watchTask.emit( 'info', {
code: 'BUILD_COMPLETE',
duration: Date.now() - buildStart,
watch: true
});

watchTask.emit( 'built', outputdir );
});
.then( outputdir => {
watchTask.emit( 'build:end', outputdir );
})
.catch( handleError );
}
Expand All @@ -131,7 +103,7 @@ export default class Node extends EventEmitter2 {
watchTask.close = () => node.stop();

this.start();
build();
process.nextTick( build );

return watchTask;
}
Expand Down
2 changes: 0 additions & 2 deletions src/nodes/Transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ export default class Transformer extends Node {
transformation.aborted = true;
};

this.sourcemaps = {};

outputdir = resolve( session.config.gobbledir, this.id, '' + this.counter++ );

this._ready = this.input.ready().then( inputdir => {
Expand Down
10 changes: 8 additions & 2 deletions src/nodes/serve/handleRequest.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { join } from 'path';
import { extname, join } from 'path';
import { parse } from 'url';
import { stat, Promise } from 'sander';
import serveFile from './serveFile';
import serveDir from './serveDir';
import serveSourcemap from './serveSourcemap';
import serveError from './serveError';

export default function handleRequest ( srcDir, error, request, response ) {
export default function handleRequest ( srcDir, error, sourcemapPromises, request, response ) {
const parsedUrl = parse( request.url );
const pathname = parsedUrl.pathname;

Expand All @@ -29,6 +30,11 @@ export default function handleRequest ( srcDir, error, request, response ) {

filepath = join( srcDir, pathname );

if ( extname( filepath ) === '.map' ) {
return serveSourcemap( filepath, sourcemapPromises, request, response )
.catch( err => serveError( err, request, response ) );
}

return stat( filepath ).then( stats => {
if ( stats.isDirectory() ) {
// might need to redirect from `foo` to `foo/`
Expand Down
19 changes: 14 additions & 5 deletions src/nodes/serve/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Promise } from 'sander';
import * as tinyLr from 'tiny-lr';
import cleanup from '../../utils/cleanup';
import session from '../../session';
import config from '../../config';
import GobbleError from '../../utils/GobbleError';
import handleRequest from './handleRequest';

Expand All @@ -16,6 +15,7 @@ export default function serve ( node, options = {} ) {
let buildStarted = Date.now();
let watchTask;
let srcDir;
let sourcemapPromises;
let server;
let serverReady;
let lrServer;
Expand All @@ -26,7 +26,7 @@ export default function serve ( node, options = {} ) {

task.resume = n => {
node = n;
watchTask = node.createWatchTask( config.cwd );
watchTask = node.createWatchTask();

watchTask.on( 'info', details => task.emit( 'info', details ) );

Expand All @@ -35,14 +35,23 @@ export default function serve ( node, options = {} ) {
task.emit( 'error', err );
});

watchTask.on( 'built', d => {
let buildStart;
watchTask.on( 'build:start', () => buildStart = Date.now() );

watchTask.on( 'build:end', dir => {
error = null;
srcDir = d;
sourcemapPromises = {};
srcDir = dir;

built = true;

task.emit( 'built' );

task.emit( 'info', {
code: 'BUILD_COMPLETE',
duration: Date.now() - buildStart
});

if ( !firedReadyEvent && serverReady ) {
task.emit( 'ready' );
firedReadyEvent = true;
Expand Down Expand Up @@ -114,7 +123,7 @@ export default function serve ( node, options = {} ) {
});

server.on( 'request', ( request, response ) => {
handleRequest( srcDir, error, request, response )
handleRequest( srcDir, error, sourcemapPromises, request, response )
.catch( err => task.emit( 'error', err ) );
});

Expand Down
29 changes: 24 additions & 5 deletions src/nodes/serve/serveFile.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
import { basename, extname } from 'path';
import { lookup } from 'mime';
import { readFile } from 'sander';
import { readFile, stat, createReadStream } from 'sander';
import { getSourcemapComment, SOURCEMAP_COMMENT } from '../../utils/sourcemap';

export default function serveFile ( filepath, request, response ) {
return readFile( filepath ).then( data => {
const ext = extname( filepath );

// this might be turn out to be a really bad idea. But let's try it and see
if ( ext === '.js' || ext === '.css' ) {
return readFile( filepath ).then( data => {
// this takes the auto-generated absolute sourcemap path, and turns
// it into what you'd get with `gobble build` or `gobble watch`
const sourcemapComment = getSourcemapComment( basename( filepath ) + '.map', ext );
data = data.toString().replace( SOURCEMAP_COMMENT, sourcemapComment );

response.statusCode = 200;
response.setHeader( 'Content-Type', lookup( filepath ) );

response.write( data );
response.end();
});
}

return stat( filepath ).then( stats => {
response.statusCode = 200;
response.setHeader( 'Content-Type', lookup( filepath ) );
response.setHeader( 'Content-Length', data.length );
response.setHeader( 'Content-Length', stats.size );

response.write( data );
response.end();
createReadStream( filepath ).pipe( response );
});
}
24 changes: 24 additions & 0 deletions src/nodes/serve/serveSourcemap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { load } from 'sorcery';

export default function serveSourcemap ( filepath, sourcemapPromises, request, response ) {
const owner = filepath.slice( 0, -4 );

if ( !sourcemapPromises[ filepath ] ) {
sourcemapPromises[ filepath ] = load( owner )
.then( chain => {
if ( !chain ) {
throw new Error( 'Could not resolve sourcemap for ' + owner );
}

return chain.apply().toString();
});
}

return sourcemapPromises[ filepath ].then( map => {
response.statusCode = 200;
response.setHeader( 'Content-Type', 'application/json' );

response.write( map );
response.end();
});
}
31 changes: 28 additions & 3 deletions src/nodes/watch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { copydir, rimraf, Promise } from 'sander';
import cleanup from '../../utils/cleanup';
import session from '../../session';
import GobbleError from '../../utils/GobbleError';
import flattenSourcemaps from '../../utils/flattenSourcemaps';

export default function watch ( node, options ) {
if ( !options || !options.dest ) {
Expand All @@ -18,16 +19,40 @@ export default function watch ( node, options ) {

task.resume = n => {
node = n;
watchTask = node.createWatchTask( options.dest );
watchTask = node.createWatchTask();

watchTask.on( 'info', details => task.emit( 'info', details ) );
watchTask.on( 'error', err => task.emit( 'error', err ) );

watchTask.on( 'built', outputdir => {
let buildStart;
watchTask.on( 'build:start', () => buildStart = Date.now() );

watchTask.on( 'build:end', dir => {
const dest = options.dest;

rimraf( dest )
.then( () => copydir( outputdir ).to( dest ) )
.then( () => copydir( dir ).to( dest ) )
.then( () => {
const sourcemapProcessStart = Date.now();

task.emit( 'info', {
code: 'SOURCEMAP_PROCESS_START',
progressIndicator: true
});

return flattenSourcemaps( dir, dest, dest, task ).then( () => {
task.emit( 'info', {
code: 'SOURCEMAP_PROCESS_COMPLETE',
duration: Date.now() - sourcemapProcessStart
});

task.emit( 'info', {
code: 'BUILD_COMPLETE',
duration: Date.now() - buildStart,
watch: true
});
});
})
.then( () => task.emit( 'built', dest ) )
.catch( err => task.emit( 'error', err ) );
});
Expand Down
17 changes: 17 additions & 0 deletions src/utils/sourcemap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
let SOURCEMAPPING_URL = 'sourceMa';
SOURCEMAPPING_URL += 'ppingURL';

const SOURCEMAP_COMMENT = new RegExp( `\n*(?:` +
`\\/\\/[@#]\\s*${SOURCEMAPPING_URL}=([^'"]+)|` + // js
`\\/\\*#?\\s*${SOURCEMAPPING_URL}=([^'"]+)\\s\\+\\/)` + // css
`\\s*$`, 'g' );

function getSourcemapComment ( url, ext ) {
if ( ext === '.css' ) {
return `\n/*# ${SOURCEMAPPING_URL}=${url} */\n`;
}

return `\n//# ${SOURCEMAPPING_URL}=${url}\n`;
}

export { getSourcemapComment, SOURCEMAP_COMMENT, SOURCEMAPPING_URL };
Loading