diff --git a/src/lib/after-watch.js b/src/lib/after-watch.js index 7a3674d..e61f970 100644 --- a/src/lib/after-watch.js +++ b/src/lib/after-watch.js @@ -1,9 +1,9 @@ var converter = require('./converter'); module.exports = function ($logger) { - var sass = converter.getSassProcess(); - if (sass) { - $logger.info("Stopping sass watch"); - sass.kill("SIGINT") + var watcher = converter.getWatcher(); + if (watcher) { + $logger.info("Stopping nativescript-dev-sass watcher"); + watcher.close(); } } diff --git a/src/lib/converter.js b/src/lib/converter.js index de56f7e..94bc290 100644 --- a/src/lib/converter.js +++ b/src/lib/converter.js @@ -1,94 +1,130 @@ exports.convert = convert; -exports.getSassProcess = getSassProcess; +exports.getWatcher = getWatcher; var spawn = require('child_process').spawn; var fs = require('fs'); var path = require('path'); -var currentSassProcess = null; +var choki = require('chokidar'); +var watcher = null; +var watchPromisesChain = Promise.resolve(); function convert(logger, projectDir, appDir, options) { - return new Promise(function (resolve, reject) { - options = options || {}; - var sassPath = require.resolve('node-sass/bin/node-sass'); - var importerPath = path.join(__dirname, "importer.js"); - - if (fs.existsSync(sassPath)) { - try { - logger.info('Found peer node-sass'); - } catch (err) { } - } else { - throw Error('node-sass installation local to project was not found. Install by executing `npm install node-sass`.'); - } - - // Node SASS Command Line Args (https://github.com/sass/node-sass#command-line-interface) - // --ouput : Output directory - // --output-style : CSS output style (nested | expanded | compact | compresed) - // -q : Supress log output except on error - // --follow : Follow symlinked directories - // -r : Recursively watch directories or files - // --watch : Watch a directory or file - var nodeArgs = [sassPath, appDir, '--output', appDir, '--output-style', 'compressed', '-q', '--follow', '--importer', importerPath]; - if (options.watch) { - nodeArgs.push('-r', '--watch'); - } - - logger.trace(process.execPath, nodeArgs.join(' ')); - var env = Object.create( process.env ); - env.PROJECT_DIR = projectDir; - env.APP_DIR = appDir; - currentSassProcess = spawn(process.execPath, nodeArgs, { env: env }); - - var isResolved = false; - var watchResolveTimeout; - currentSassProcess.stdout.on('data', function (data) { - var stringData = data.toString(); - logger.info(stringData); - }); - - currentSassProcess.stderr.on('data', function (err) { - var message = ''; - var stringData = err.toString(); - - try { - var parsed = JSON.parse(stringData); - message = parsed.formatted || parsed.message || stringData; - } catch (e) { - renderMsg = true; - message = err.toString(); - } - - logger.info(message); - }); - - currentSassProcess.on('error', function (err) { - logger.info(err.message); - if (!isResolved) { - isResolved = true; - reject(err); - } - }); - - // TODO: Consider using close event instead of exit - currentSassProcess.on('exit', function (code, signal) { - currentSassProcess = null; - if (!isResolved) { - isResolved = true; - if (code === 0) { - resolve(); - } else { - reject(Error('SASS compiler failed with exit code ' + code)); - } - } - }); - - // SASS does not recompile on watch, so directly resolve. - if (options.watch && !isResolved) { - isResolved = true; - resolve(); - } - }); + options = options || {}; + var sassPath = getSassPath(logger); + var data = { + sassPath, + projectDir, + appDir, + logger, + options + }; + + if (options.watch) { + createWatcher(data); + } + + return spawnNodeSass(data); +} + +function getWatcher() { + return watcher; +} + +function createWatcher(data) { + var appDir = data.appDir; + var watcherOptions = { + ignoreInitial: true, + cwd: appDir, + awaitWriteFinish: { + pollInterval: 100, + stabilityThreshold: 300 + }, + ignored: ['**/.*', '.*'] // hidden files + }; + + watcher = choki.watch(['**/*.scss', '**/*.sass'], watcherOptions) + .on('all', (event, filePath) => { + watchPromisesChain = watchPromisesChain.then(() => spawnNodeSass(data)); + }); } -function getSassProcess() { - return currentSassProcess; +function getSassPath(logger) { + var sassPath = require.resolve('node-sass/bin/node-sass'); + if (fs.existsSync(sassPath)) { + logger.info('Found peer node-sass'); + } else { + throw new Error('node-sass installation local to project was not found. Install by executing `npm install node-sass`.'); + } + + return sassPath; +} + +function spawnNodeSass(data) { + return new Promise(function (resolve, reject) { + var sassPath = data.sassPath, + projectDir = data.projectDir, + appDir = data.appDir, + logger = data.logger, + options = data.options; + + var importerPath = path.join(__dirname, "importer.js"); + + // Node SASS Command Line Args (https://github.com/sass/node-sass#command-line-interface) + // --ouput : Output directory + // --output-style : CSS output style (nested | expanded | compact | compresed) + // -q : Supress log output except on error + // --follow : Follow symlinked directories + // -r : Recursively watch directories or files + // --watch : Watch a directory or file + var nodeArgs = [sassPath, appDir, '--output', appDir, '--output-style', 'compressed', '-q', '--follow', '--importer', importerPath]; + logger.trace(process.execPath, nodeArgs.join(' ')); + + var env = Object.create( process.env ); + env.PROJECT_DIR = projectDir; + env.APP_DIR = appDir; + + var currentSassProcess = spawn(process.execPath, nodeArgs, { env: env }); + + var isResolved = false; + + currentSassProcess.stdout.on('data', function (data) { + var stringData = data.toString(); + logger.info(stringData); + }); + + currentSassProcess.stderr.on('data', function (err) { + var message = ''; + var stringData = err.toString(); + + try { + var parsed = JSON.parse(stringData); + message = parsed.formatted || parsed.message || stringData; + } catch (e) { + message = err.toString(); + } + + logger.info(message); + }); + + currentSassProcess.on('error', function (err) { + logger.info(err.message); + if (!isResolved) { + isResolved = true; + reject(err); + } + }); + + // TODO: Consider using close event instead of exit + currentSassProcess.on('exit', function (code, signal) { + currentSassProcess = null; + if (!isResolved) { + isResolved = true; + if (code === 0) { + resolve(); + } else { + reject(new Error('SASS compiler failed with exit code ' + code)); + } + } + }); + }); } diff --git a/src/package.json b/src/package.json index e6a8d2a..1a81f4b 100644 --- a/src/package.json +++ b/src/package.json @@ -63,6 +63,7 @@ }, "dependencies": { "bluebird": "^3.4.6", + "chokidar": "2.0.2", "node-sass": "^4.7.1", "glob": "^7.1.2", "nativescript-hook": "^0.2.0"