diff --git a/bin/src/run/batchWarnings.js b/bin/src/run/batchWarnings.js index 1fbb69d9d5a..032c225f1f8 100644 --- a/bin/src/run/batchWarnings.js +++ b/bin/src/run/batchWarnings.js @@ -68,6 +68,7 @@ export default function batchWarnings () { }); allWarnings = new Map(); + count = 0; } }; } diff --git a/bin/src/run/index.js b/bin/src/run/index.js index e66e4db8efa..d6fd20a9cc7 100644 --- a/bin/src/run/index.js +++ b/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'; @@ -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}` }); } @@ -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 ); diff --git a/bin/src/run/loadConfigFile.js b/bin/src/run/loadConfigFile.js new file mode 100644 index 00000000000..40c71c4c769 --- /dev/null +++ b/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]; + }); +} \ No newline at end of file diff --git a/bin/src/run/watch.js b/bin/src/run/watch.js index 751e98c83ab..d86a57c1d03 100644 --- a/bin/src/run/watch.js +++ b/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(); @@ -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(); + }); + } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f215871b9d3..9e6a7c6906f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3898,6 +3898,38 @@ "resolve": "1.4.0" } }, + "rollup-plugin-replace": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-replace/-/rollup-plugin-replace-1.1.1.tgz", + "integrity": "sha1-OWMV3tBQps5DuVGKiGo/YO+x6jM=", + "dev": true, + "requires": { + "magic-string": "0.15.2", + "minimatch": "3.0.4", + "rollup-pluginutils": "1.5.2" + }, + "dependencies": { + "magic-string": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.15.2.tgz", + "integrity": "sha1-BoHXOIdBu8Ot2qZQYJkmJMbAnpw=", + "dev": true, + "requires": { + "vlq": "0.2.2" + } + }, + "rollup-pluginutils": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-1.5.2.tgz", + "integrity": "sha1-HhVud4+UtyVb+hs9AXi+j1xVJAg=", + "dev": true, + "requires": { + "estree-walker": "0.2.1", + "minimatch": "3.0.4" + } + } + } + }, "rollup-plugin-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/rollup-plugin-string/-/rollup-plugin-string-2.0.2.tgz", diff --git a/package.json b/package.json index 221069aca51..4d97be9e8ea 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "rollup-plugin-commonjs": "^8.0.2", "rollup-plugin-json": "^2.3.0", "rollup-plugin-node-resolve": "^3.0.0", + "rollup-plugin-replace": "^1.1.1", "rollup-plugin-string": "^2.0.0", "rollup-pluginutils": "^2.0.1", "rollup-watch": "^4.3.1", diff --git a/src/watch/index.js b/src/watch/index.js index 31e80eaf2b6..f50433fd450 100644 --- a/src/watch/index.js +++ b/src/watch/index.js @@ -27,6 +27,8 @@ class Watcher extends EventEmitter { this.tasks.forEach(task => { task.close(); }); + + this.removeAllListeners(); } _makeDirty() {