Skip to content

Commit 55981a8

Browse files
committed
Fix node-sass watcher
Currently node-sass watcher is used to compile sass files when some changes occurs. The problem is when file A is imported in file B. If some change occurs in file A, file A will be successfully compiled by `node-sass` but file B will not be updated. So when `livesync command` is executed, changes are not reflected in the app's UI. This PR removes `--watch` option from `node-sass` compiler and introduces chokidar watcher. Also fixes this #70
1 parent 54779a9 commit 55981a8

File tree

3 files changed

+133
-88
lines changed

3 files changed

+133
-88
lines changed

src/lib/after-watch.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
var converter = require('./converter');
22

33
module.exports = function ($logger) {
4-
var sass = converter.getSassProcess();
5-
if (sass) {
6-
$logger.info("Stopping sass watch");
7-
sass.kill("SIGINT")
4+
var watcher = converter.getWatcher();
5+
if (watcher) {
6+
$logger.info("Stopping nativescript-dev-sass watcher");
7+
watcher.close();
88
}
99
}

src/lib/converter.js

Lines changed: 128 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,138 @@
11
exports.convert = convert;
2-
exports.getSassProcess = getSassProcess;
2+
exports.getWatcher = getWatcher;
33

44
var spawn = require('child_process').spawn;
55
var fs = require('fs');
66
var path = require('path');
7-
var currentSassProcess = null;
7+
var choki = require('chokidar');
8+
var watcher = null;
89

910
function convert(logger, projectDir, appDir, options) {
10-
return new Promise(function (resolve, reject) {
11-
options = options || {};
12-
var sassPath = require.resolve('node-sass/bin/node-sass');
13-
var importerPath = path.join(__dirname, "importer.js");
14-
15-
if (fs.existsSync(sassPath)) {
16-
try {
17-
logger.info('Found peer node-sass');
18-
} catch (err) { }
19-
} else {
20-
throw Error('node-sass installation local to project was not found. Install by executing `npm install node-sass`.');
21-
}
22-
23-
// Node SASS Command Line Args (https://github.com/sass/node-sass#command-line-interface)
24-
// --ouput : Output directory
25-
// --output-style : CSS output style (nested | expanded | compact | compresed)
26-
// -q : Supress log output except on error
27-
// --follow : Follow symlinked directories
28-
// -r : Recursively watch directories or files
29-
// --watch : Watch a directory or file
30-
var nodeArgs = [sassPath, appDir, '--output', appDir, '--output-style', 'compressed', '-q', '--follow', '--importer', importerPath];
31-
if (options.watch) {
32-
nodeArgs.push('-r', '--watch');
33-
}
34-
35-
logger.trace(process.execPath, nodeArgs.join(' '));
36-
var env = Object.create( process.env );
37-
env.PROJECT_DIR = projectDir;
38-
env.APP_DIR = appDir;
39-
currentSassProcess = spawn(process.execPath, nodeArgs, { env: env });
40-
41-
var isResolved = false;
42-
var watchResolveTimeout;
43-
currentSassProcess.stdout.on('data', function (data) {
44-
var stringData = data.toString();
45-
logger.info(stringData);
46-
});
47-
48-
currentSassProcess.stderr.on('data', function (err) {
49-
var message = '';
50-
var stringData = err.toString();
51-
52-
try {
53-
var parsed = JSON.parse(stringData);
54-
message = parsed.formatted || parsed.message || stringData;
55-
} catch (e) {
56-
renderMsg = true;
57-
message = err.toString();
58-
}
59-
60-
logger.info(message);
61-
});
62-
63-
currentSassProcess.on('error', function (err) {
64-
logger.info(err.message);
65-
if (!isResolved) {
66-
isResolved = true;
67-
reject(err);
68-
}
69-
});
70-
71-
// TODO: Consider using close event instead of exit
72-
currentSassProcess.on('exit', function (code, signal) {
73-
currentSassProcess = null;
74-
if (!isResolved) {
75-
isResolved = true;
76-
if (code === 0) {
77-
resolve();
78-
} else {
79-
reject(Error('SASS compiler failed with exit code ' + code));
80-
}
81-
}
82-
});
83-
84-
// SASS does not recompile on watch, so directly resolve.
85-
if (options.watch && !isResolved) {
86-
isResolved = true;
87-
resolve();
88-
}
89-
});
11+
options = options || {};
12+
var sassPath = getSassPath();
13+
var data = {
14+
sassPath: sassPath,
15+
projectDir: projectDir,
16+
appDir: appDir,
17+
logger: logger,
18+
options: options
19+
};
20+
21+
if (options.watch) {
22+
createWatcher(data);
23+
}
24+
25+
return spawnNodeSass(data);
26+
}
27+
28+
function getWatcher() {
29+
return watcher;
30+
}
31+
32+
function createWatcher(data) {
33+
var appDir = data.appDir;
34+
var watcherOptions = {
35+
ignoreInitial: true,
36+
cwd: appDir,
37+
awaitWriteFinish: {
38+
pollInterval: 100,
39+
stabilityThreshold: 500
40+
},
41+
ignored: ['**/.*', '.*'] // hidden files
42+
};
43+
44+
watcher = choki.watch(['**/*.scss', '**/*.sass'], watcherOptions)
45+
.on('all', (event, filePath) => {
46+
spawnNodeSass(data);
47+
});
9048
}
9149

92-
function getSassProcess() {
93-
return currentSassProcess;
50+
function getSassPath() {
51+
var sassPath = require.resolve('node-sass/bin/node-sass');
52+
if (fs.existsSync(sassPath)) {
53+
try {
54+
logger.info('Found peer node-sass');
55+
} catch (err) { }
56+
} else {
57+
throw Error('node-sass installation local to project was not found. Install by executing `npm install node-sass`.');
58+
}
59+
60+
return sassPath;
61+
}
62+
63+
function spawnNodeSass(data) {
64+
return new Promise(function (resolve, reject) {
65+
var sassPath = data.sassPath,
66+
projectDir = data.projectDir,
67+
appDir = data.appDir,
68+
logger = data.logger,
69+
options = data.options;
70+
71+
var importerPath = path.join(__dirname, "importer.js");
72+
73+
// Node SASS Command Line Args (https://github.com/sass/node-sass#command-line-interface)
74+
// --ouput : Output directory
75+
// --output-style : CSS output style (nested | expanded | compact | compresed)
76+
// -q : Supress log output except on error
77+
// --follow : Follow symlinked directories
78+
// -r : Recursively watch directories or files
79+
// --watch : Watch a directory or file
80+
var nodeArgs = [sassPath, appDir, '--output', appDir, '--output-style', 'compressed', '-q', '--follow', '--importer', importerPath];
81+
logger.trace(process.execPath, nodeArgs.join(' '));
82+
83+
var env = Object.create( process.env );
84+
env.PROJECT_DIR = projectDir;
85+
env.APP_DIR = appDir;
86+
87+
var currentSassProcess = spawn(process.execPath, nodeArgs, { env: env });
88+
89+
var isResolved = false;
90+
var watchResolveTimeout;
91+
92+
currentSassProcess.stdout.on('data', function (data) {
93+
var stringData = data.toString();
94+
logger.info(stringData);
95+
});
96+
97+
currentSassProcess.stderr.on('data', function (err) {
98+
var message = '';
99+
var stringData = err.toString();
100+
101+
try {
102+
var parsed = JSON.parse(stringData);
103+
message = parsed.formatted || parsed.message || stringData;
104+
} catch (e) {
105+
message = err.toString();
106+
}
107+
108+
logger.info(message);
109+
});
110+
111+
currentSassProcess.on('error', function (err) {
112+
logger.info(err.message);
113+
if (!isResolved) {
114+
isResolved = true;
115+
reject(err);
116+
}
117+
});
118+
119+
// TODO: Consider using close event instead of exit
120+
currentSassProcess.on('exit', function (code, signal) {
121+
currentSassProcess = null;
122+
if (!isResolved) {
123+
isResolved = true;
124+
if (code === 0) {
125+
resolve();
126+
} else {
127+
reject(Error('SASS compiler failed with exit code ' + code));
128+
}
129+
}
130+
});
131+
132+
// SASS does not recompile on watch, so directly resolve.
133+
if (options.watch && !isResolved) {
134+
isResolved = true;
135+
resolve();
136+
}
137+
});
94138
}

src/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
},
6464
"dependencies": {
6565
"bluebird": "^3.4.6",
66+
"chokidar": "2.0.2",
6667
"node-sass": "^4.7.1",
6768
"glob": "^7.1.2",
6869
"nativescript-hook": "^0.2.0"

0 commit comments

Comments
 (0)