Skip to content

Commit

Permalink
Merge pull request #1545 from rollup/gh-1535
Browse files Browse the repository at this point in the history
watch config file, restart on changes
  • Loading branch information
Rich-Harris committed Aug 12, 2017
2 parents 7414cdd + 0c03d28 commit abfb862
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 89 deletions.
1 change: 1 addition & 0 deletions bin/src/run/batchWarnings.js
Expand Up @@ -68,6 +68,7 @@ export default function batchWarnings () {
});

allWarnings = new Map();
count = 0;
}
};
}
Expand Down
77 changes: 16 additions & 61 deletions bin/src/run/index.js
@@ -1,12 +1,9 @@
import path from 'path';
import chalk from 'chalk';
import { realpathSync } from 'fs';
import * as rollup from 'rollup';
import relative from 'require-relative';
import { handleError, stderr } from '../logging.js';
import { handleError } from '../logging.js';
import mergeOptions from './mergeOptions.js';
import batchWarnings from './batchWarnings.js';
import relativeId from '../../../src/utils/relativeId.js';
import loadConfigFile from './loadConfigFile.js';
import sequence from '../utils/sequence.js';
import build from './build.js';
import watch from './watch.js';
Expand Down Expand Up @@ -41,21 +38,21 @@ export default function runRollup ( command ) {
});
}

let config = command.config === true ? 'rollup.config.js' : command.config;
let configFile = command.config === true ? 'rollup.config.js' : command.config;

if ( config ) {
if ( config.slice( 0, 5 ) === 'node:' ) {
const pkgName = config.slice( 5 );
if ( configFile ) {
if ( configFile.slice( 0, 5 ) === 'node:' ) {
const pkgName = configFile.slice( 5 );
try {
config = relative.resolve( `rollup-config-${pkgName}`, process.cwd() );
configFile = relative.resolve( `rollup-config-${pkgName}`, process.cwd() );
} catch ( err ) {
try {
config = relative.resolve( pkgName, process.cwd() );
configFile = relative.resolve( pkgName, process.cwd() );
} catch ( err ) {
if ( err.code === 'MODULE_NOT_FOUND' ) {
handleError({
code: 'MISSING_EXTERNAL_CONFIG',
message: `Could not resolve config file ${config}`
message: `Could not resolve config file ${configFile}`
});
}

Expand All @@ -64,63 +61,21 @@ export default function runRollup ( command ) {
}
} else {
// find real path of config so it matches what Node provides to callbacks in require.extensions
config = realpathSync( config );
configFile = realpathSync( configFile );
}

const warnings = batchWarnings();

rollup.rollup({
entry: config,
external: id => {
return (id[0] !== '.' && !path.isAbsolute(id)) || id.slice(-5,id.length) === '.json';
},
onwarn: warnings.add
})
.then( bundle => {
if ( !command.silent && warnings.count > 0 ) {
stderr( chalk.bold( `loaded ${relativeId( config )} with warnings` ) );
warnings.flush();
}

return bundle.generate({
format: 'cjs'
});
})
.then( ({ code }) => {
// temporarily override require
const defaultLoader = require.extensions[ '.js' ];
require.extensions[ '.js' ] = ( m, filename ) => {
if ( filename === config ) {
m._compile( code, filename );
} else {
defaultLoader( m, filename );
}
};

const configs = require( config );
if ( Object.keys( configs ).length === 0 ) {
handleError({
code: 'MISSING_CONFIG',
message: 'Config file must export an options object, or an array of options objects',
url: 'https://github.com/rollup/rollup/wiki/Command-Line-Interface#using-a-config-file'
});
}

require.extensions[ '.js' ] = defaultLoader;

const normalized = Array.isArray( configs ) ? configs : [configs];
return execute( normalized, command );
})
.catch( handleError );
loadConfigFile(configFile, command.silent)
.then(normalized => execute( configFile, normalized, command ))
.catch(handleError);
} else {
return execute( [{}], command );
return execute( configFile, [{}], command );
}
}

function execute ( configs, command ) {
function execute ( configFile, configs, command ) {
if ( command.watch ) {
process.env.ROLLUP_WATCH = 'true';
watch( configs, command, command.silent );
watch( configFile, configs, command, command.silent );
} else {
return sequence( configs, config => {
const options = mergeOptions( config, command );
Expand Down
52 changes: 52 additions & 0 deletions bin/src/run/loadConfigFile.js
@@ -0,0 +1,52 @@
import path from 'path';
import chalk from 'chalk';
import * as rollup from 'rollup';
import batchWarnings from './batchWarnings.js';
import relativeId from '../../../src/utils/relativeId.js';
import { handleError, stderr } from '../logging.js';

export default function loadConfigFile (configFile, silent) {
const warnings = batchWarnings();

return rollup.rollup({
entry: configFile,
external: id => {
return (id[0] !== '.' && !path.isAbsolute(id)) || id.slice(-5,id.length) === '.json';
},
onwarn: warnings.add
})
.then( bundle => {
if ( !silent && warnings.count > 0 ) {
stderr( chalk.bold( `loaded ${relativeId( configFile )} with warnings` ) );
warnings.flush();
}

return bundle.generate({
format: 'cjs'
});
})
.then( ({ code }) => {
// temporarily override require
const defaultLoader = require.extensions[ '.js' ];
require.extensions[ '.js' ] = ( m, filename ) => {
if ( filename === configFile ) {
m._compile( code, filename );
} else {
defaultLoader( m, filename );
}
};

const configs = require( configFile );
if ( Object.keys( configs ).length === 0 ) {
handleError({
code: 'MISSING_CONFIG',
message: 'Config file must export an options object, or an array of options objects',
url: 'https://github.com/rollup/rollup/wiki/Command-Line-Interface#using-a-config-file'
});
}

require.extensions[ '.js' ] = defaultLoader;

return Array.isArray( configs ) ? configs : [configs];
});
}
108 changes: 80 additions & 28 deletions bin/src/run/watch.js
@@ -1,12 +1,14 @@
import fs from 'fs';
import * as rollup from 'rollup';
import chalk from 'chalk';
import ms from 'pretty-ms';
import mergeOptions from './mergeOptions.js';
import batchWarnings from './batchWarnings.js';
import loadConfigFile from './loadConfigFile.js';
import relativeId from '../../../src/utils/relativeId.js';
import { handleError, stderr } from '../logging.js';

export default function watch(configs, command, silent) {
export default function watch(configFile, configs, command, silent) {
process.stderr.write('\x1b[?1049h'); // alternate screen buffer

const warnings = batchWarnings();
Expand All @@ -26,49 +28,99 @@ export default function watch(configs, command, silent) {
return merged;
});

const watcher = rollup.watch(configs);
let watcher;
let configWatcher;
let closed = false;

watcher.on('event', event => {
switch (event.code) {
case 'FATAL':
process.stderr.write('\x1b[?1049l'); // reset screen buffer
handleError(event.error, true);
process.exit(1);
break;
function start(configs) {
stderr(`\x1B[2J\x1B[0f${chalk.underline( `rollup v${rollup.VERSION}` )}`); // clear, move to top-left

case 'ERROR':
warnings.flush();
handleError(event.error, true);
break;
watcher = rollup.watch(configs);

case 'START':
stderr(`\x1B[2J\x1B[0f${chalk.underline( 'rollup.watch' )}`); // clear, move to top-left
break;
watcher.on('event', event => {
switch (event.code) {
case 'FATAL':
process.stderr.write('\x1b[?1049l'); // reset screen buffer
handleError(event.error, true);
process.exit(1);
break;

case 'BUNDLE_START':
if ( !silent ) stderr( chalk.cyan( `\n${chalk.bold( event.input )}${chalk.bold( event.output.map( relativeId ).join( ', ' ) )}...` ) );
break;
case 'ERROR':
warnings.flush();
handleError(event.error, true);
break;

case 'BUNDLE_END':
warnings.flush();
if ( !silent ) stderr( chalk.green( `created ${chalk.bold( event.output.map( relativeId ).join( ', ' ) )} in ${chalk.bold(ms(event.duration))}` ) );
break;
case 'START':
stderr(`\x1B[2J\x1B[0f${chalk.underline( `rollup v${rollup.VERSION}` )}`); // clear, move to top-left
break;

case 'END':
if ( !silent ) stderr( `\nwaiting for changes...` );
}
});
case 'BUNDLE_START':
if ( !silent ) stderr( chalk.cyan( `\n${chalk.bold( event.input )}${chalk.bold( event.output.map( relativeId ).join( ', ' ) )}...` ) );
break;

case 'BUNDLE_END':
warnings.flush();
if ( !silent ) stderr( chalk.green( `created ${chalk.bold( event.output.map( relativeId ).join( ', ' ) )} in ${chalk.bold(ms(event.duration))}` ) );
break;

case 'END':
if ( !silent ) stderr( `\nwaiting for changes...` );
}
});
}

let closed = false;
const close = () => {
if (!closed) {
process.stderr.write('\x1b[?1049l'); // reset screen buffer
closed = true;
watcher.close();

if (configWatcher) configWatcher.close();
}
};
process.on('SIGINT', close); // ctrl-c
process.on('SIGTERM', close); // killall node
process.on('uncaughtException', close); // on error
process.stdin.on('end', close); // in case we ever support stdin!

start(configs);

if (configFile && !configFile.startsWith('node:')) {
let restarting = false;
let aborted = false;
let configFileData = fs.readFileSync(configFile, 'utf-8');

const restart = () => {
const newConfigFileData = fs.readFileSync(configFile, 'utf-8');
if (newConfigFileData === configFileData) return;
configFileData = newConfigFileData;

if (restarting) {
aborted = true;
return;
}

restarting = true;

loadConfigFile(configFile, silent)
.then(configs => {
restarting = false;

if (aborted) {
aborted = false;
restart();
} else {
watcher.close();
start(configs);
}
})
.catch(err => {
handleError(err, true);
});
};

configWatcher = fs.watch(configFile, event => {
if (event === 'change') restart();
});
}
}
2 changes: 2 additions & 0 deletions src/watch/index.js
Expand Up @@ -27,6 +27,8 @@ class Watcher extends EventEmitter {
this.tasks.forEach(task => {
task.close();
});

this.removeAllListeners();
}

_makeDirty() {
Expand Down

0 comments on commit abfb862

Please sign in to comment.