Skip to content

Commit f840781

Browse files
committed
Refactored based on Pull Request #22
Now running node sass in separate process and using built-in watch capability to transpile SASS and SCSS files on change
1 parent aab1bfc commit f840781

File tree

4 files changed

+87
-92
lines changed

4 files changed

+87
-92
lines changed

lib/after-prepare.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,24 @@ var glob = require('glob');
44
var Promise = require('bluebird');
55

66
// Clean-up files from compiled app packages
7-
module.exports = function (logger, platformsData, projectData, hookArgs) {
7+
module.exports = function (logger, platformsData, projectData, hookArgs, $usbLiveSyncService) {
88
// delete all scss files from compiled sources
99

10-
// Don't include .SCSS files in LiveSync -- only sync CSS files
10+
var liveSync = $usbLiveSyncService.isInitialized;
11+
12+
// Don't include .SCSS files in LiveSync -- only sync CSS files (works in {N} 3.0+)
1113
if (hookArgs.filesToSync !== undefined) {
1214
hookArgs.filesToSync.forEach(function (file, index) {
13-
if (file.indexOf(".scss") !== -1) {
15+
if (file.indexOf(".scss") !== -1 || file.indexOf(".sass") !== -1) {
1416
// Remove the .SCSS file from LiveSync operation
1517
hookArgs.filesToSync.splice(index, 1);
1618
}
1719
});
1820
}
1921

22+
// Don't try to LiveSync .SCSS or .SASS files (they do not exist in app package)
23+
if (liveSync) return;
24+
2025
var platformData = platformsData.getPlatformData(hookArgs.platform.toLowerCase());
2126

2227
return new Promise(function(resolve, reject) {
@@ -25,7 +30,7 @@ module.exports = function (logger, platformsData, projectData, hookArgs) {
2530
deleteFolderRecursive(nodeSassPath);
2631

2732
// Find and remove unnecessary SCSS files from iOS and Android app packages
28-
var sassFilesPath = path.join(platformData.appDestinationDirectoryPath, 'app/**/*.scss');
33+
var sassFilesPath = path.join(platformData.appDestinationDirectoryPath, 'app/**/*.s[ca]ss');
2934
var sassFiles = glob.sync(sassFilesPath).filter(function (filePath) {
3035
var path = filePath;
3136
var parts = path.split('/');

lib/before-prepare.js

+7-17
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
11
var converter = require('./converter');
22

3-
module.exports = function (logger, platformsData, projectData, hookArgs) {
4-
// Do not run converter during LiveSync if there are no SCSS files being processed
5-
var runProcessor = false;
6-
if (hookArgs.filesToSync !== undefined) {
7-
hookArgs.filesToSync.forEach(function (file) {
8-
if (file.indexOf(".scss") !== -1) {
9-
runProcessor = true;
10-
}
11-
});
12-
} else {
13-
// Not a LiveSync operation; always run converter
14-
runProcessor = true;
15-
}
16-
17-
if (runProcessor) {
18-
console.log("Converting SCSS to CSS...");
19-
return converter.convert(logger, projectData.projectDir);
3+
module.exports = function ($logger, $projectData, $usbLiveSyncService) {
4+
var liveSync = $usbLiveSyncService.isInitialized;
5+
var bundle = $projectData.$options.bundle;
6+
if (liveSync || bundle) {
7+
return;
208
}
9+
10+
return converter.convert($logger, $projectData.projectDir);
2111
}

lib/converter.js

+66-71
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,85 @@
11
exports.convert = convert;
22

3+
var spawn = require('child_process').spawn;
34
var fs = require('fs');
45
var path = require('path');
5-
var sass = require('node-sass');
6-
var glob = require('glob');
76

87
function convert(logger, projectDir, options) {
9-
return new Promise(function (resolve, reject) {
10-
options = options || {};
8+
return new Promise(function (resolve, reject) {
9+
options = options || {};
1110

12-
var sassFilesPath = path.join(projectDir, 'app/**/*.scss');
13-
var sassImportPaths = [
14-
path.join(projectDir, 'app/'),
15-
path.join(projectDir, 'node_modules/')
16-
];
17-
//console.log("SASS Import Path", sassImportPaths);
11+
var peerSassPath = path.join(__dirname, '../../node-sass');
12+
var sassPath = path.join(peerSassPath, 'bin/node-sass');
13+
var appDir = path.join(projectDir, "app");
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+
}
1822

19-
var sassFiles = glob.sync(sassFilesPath, {follow: true}).filter(function (filePath) {
20-
if (!(sassImportPaths.find(function (element) {return path.dirname(filePath) === element}))) {
21-
sassImportPaths.push(path.dirname(filePath));
22-
}
23-
var parts = filePath.split('/');
24-
var filename = parts[parts.length - 1];
25-
return filePath.indexOf("App_Resources") === -1 && filename.indexOf("_") !== 0;
26-
});
27-
28-
if (sassFiles.length === 0) {
29-
//No sass files in project; skip parsing
30-
resolve();
31-
} else {
32-
var i = 0;
33-
var loopSassFilesAsync = function (sassFiles) {
34-
parseSass(sassFiles[i], sassImportPaths, function (e) {
35-
if (e !== undefined) {
36-
//Error in the LESS parser; Reject promise
37-
reject(Error(sassFiles[i] + ' SASS CSS pre-processing failed. Error: ' + e));
38-
}
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+
// -r : Recursively watch directories or files
28+
// --watch : Watch a directory or file
29+
var nodeArgs = [sassPath, appDir, '--output', appDir, '--output-style', 'compressed', '-q'];
30+
if (options.watch) {
31+
nodeArgs.push('-r', '--watch');
32+
}
3933

40-
i++; //Increment loop counter
34+
logger.trace(process.execPath, nodeArgs.join(' '));
35+
var sass = spawn(process.execPath, nodeArgs);
4136

42-
if (i < sassFiles.length) {
43-
loopSassFilesAsync(sassFiles);
44-
} else {
45-
//All files have been processed; Resolve promise
46-
resolve();
47-
}
37+
var isResolved = false;
38+
var watchResolveTimeout;
39+
sass.stdout.on('data', function (data) {
40+
var stringData = data.toString();
41+
logger.info(stringData);
4842
});
49-
}
5043

51-
loopSassFilesAsync(sassFiles);
52-
}
53-
});
54-
}
44+
sass.stderr.on('data', function (err) {
45+
var message = '';
46+
var stringData = err.toString();
5547

56-
function parseSass(filePath, importPaths, callback) {
57-
var sassFileContent = fs.readFileSync(filePath, {encoding: 'utf8'});
58-
var cssFilePath = filePath.replace('.scss', '.css');
48+
try {
49+
var parsed = JSON.parse(stringData);
50+
message = parsed.formatted || parsed.message || stringData;
51+
} catch (e) {
52+
renderMsg = true;
53+
message = err.toString();
54+
}
5955

60-
if (sassFileContent.trim().length === 0) {
61-
// No SASS content write an empty file
62-
fs.writeFile(cssFilePath, '', 'utf8', function () {
63-
callback();
64-
});
65-
return;
66-
}
56+
logger.info(message);
57+
});
6758

68-
sass.render({
69-
data: sassFileContent,
70-
includePaths: importPaths,
71-
outFile: cssFilePath,
72-
outputStyle: 'compressed'
73-
}, function (e, output) {
74-
if (e) {
75-
//Callback with error
76-
callback(e);
77-
}
59+
sass.on('error', function (err) {
60+
logger.info(err.message);
61+
if (!isResolved) {
62+
isResolved = true;
63+
reject(err);
64+
}
65+
});
7866

79-
if (output && output.css) {
80-
output = output.css;
81-
} else {
82-
output = '';
83-
}
67+
// TODO: Consider using close event instead of exit
68+
sass.on('exit', function (code, signal) {
69+
if (!isResolved) {
70+
isResolved = true;
71+
if (code === 0) {
72+
resolve();
73+
} else {
74+
reject(Error('SASS compiler failed with exit code ' + code));
75+
}
76+
}
77+
});
8478

85-
fs.writeFile(cssFilePath, output, 'utf8', function () {
86-
//File done writing
87-
callback();
79+
// SASS does not recompile on watch, so directly resolve.
80+
if (options.watch && !isResolved) {
81+
isResolved = true;
82+
resolve();
83+
}
8884
});
89-
});
9085
}

lib/watch.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
var converter = require('./converter');
2+
3+
module.exports = function (logger, projectData, usbLiveSyncService) {
4+
return converter.convert(logger, projectData.projectDir, { watch: true });
5+
}

0 commit comments

Comments
 (0)