let File = require('../File'); let path = require('path'); let spawn = require('child_process').spawn; let notifier = require('node-notifier'); class StandaloneSass { /** * Create a new StandaloneSass instance. * * @param {string} src * @param {string} output * @param {object} pluginOptions */ constructor(src, output, pluginOptions) { src = new File(path.resolve(src)).parsePath(); output = new File(output).parsePath(); if (output.isDir) { output = new File( path.join(output.path, src.name + '.css') ).parsePath(); } this.src = src; this.output = output; this.pluginOptions = pluginOptions; this.shouldWatch = process.argv.includes('--watch'); } /** * Run the node-sass compiler. */ run() { this.compile(); if (this.shouldWatch) this.watch(); } /** * Compile Sass. * * @param {Boolean} watch */ compile(watch = false) { let output = this.output.path; if (! output.startsWith(options.publicPath)) { output = path.join(options.publicPath, output); } this.command = spawn( 'node-sass', [this.src.path, output].concat(this.options(watch)), { shell: true } ); this.whenOutputIsAvailable((output, event) => { if (event === 'error') this.onFail(output); if (event === 'success') this.onSuccess(output); }); return this; } /** * Fetch the node-sass options. * * @param {Boolean} watch */ options(watch) { let sassOptions = [ '--precision=8', '--output-style=' + (global.options.production ? 'compressed' : 'expanded'), ]; if (watch) sassOptions.push('--watch'); if (this.pluginOptions.includePaths) { this.pluginOptions.includePaths.forEach( path => sassOptions.push('--include-path=' + path) ); } if (global.options.sourcemaps && ! global.options.production) { sassOptions.push('--source-map-embed'); } return sassOptions; } /** * Compile Sass, while registering a watcher. */ watch() { return this.compile(true); } /** * Register a callback for when output is available. * * @param {Function} callback */ whenOutputIsAvailable(callback) { this.command.stderr.on('data', output => { output = output.toString(); let event = 'change'; if (/Error/.test(output)) event = 'error'; if (/Wrote CSS/.test(output)) event = 'success'; callback(output, event); }); } /** * Handle successful compilation. * * @param {string} output */ onSuccess(output) { console.log("\n"); console.log(output); if (global.options.notifications) { notifier.notify({ title: 'Laravel Mix', message: 'Sass Compilation Successful', contentImage: 'node_modules/laravel-mix/icons/laravel.png' }); } global.events.fire( 'standalone-sass-compiled', File.find(this.output.path) ); } /** * Handle failed compilation. * * @param {string} output */ onFail(output) { output = output.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, ''); console.log("\n"); console.log('Sass Compilation Failed!'); console.log(); console.log(output); if (global.options.notifications) { notifier.notify({ title: 'Laravel Mix', subtitle: 'Sass Compilation Failed', message: JSON.parse(output).message, contentImage: 'node_modules/laravel-mix/icons/laravel.png' }); } if (! this.shouldWatch) process.exit(); } } module.exports = StandaloneSass;