diff --git a/.eslintrc b/.eslintrc index 6bebcd2..9d817ff 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,9 +4,17 @@ env: mocha: true node: true +extends: "airbnb-base" + +parserOptions: + sourceType: "script" + rules: max-len: - 2 - 120 - -extends: "airbnb-base" + no-multi-assign: "off" + no-param-reassign: "off" + strict: + - "error" + - "safe" diff --git a/index.js b/index.js index 3d03cea..3d98e70 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,5 @@ +'use strict'; + const path = require('path'); const chalk = require('chalk'); const PluginError = require('plugin-error'); @@ -9,151 +11,161 @@ const applySourceMap = require('vinyl-sourcemaps-apply'); const PLUGIN_NAME = 'gulp-sass'; -/// /////////////////////////// -// Main Gulp Sass function -/// /////////////////////////// -const gulpSass = (options, sync) => transfob((file, enc, cb) => { // eslint-disable-line consistent-return - if (file.isNull()) { - return cb(null, file); - } +const MISSING_COMPILER_MESSAGE = ` +gulp-sass 5 does not have a default Sass compiler; please set one yourself. +Both the "sass" and "node-sass" packages are permitted. +For example, in your gulpfile: + + const sass = require('gulp-sass')(require('sass')); +`; + +/** + * Handles returning the file to the stream + */ +const filePush = (file, sassObject, callback) => { + // Build Source Maps! + if (sassObject.map) { + // Transform map into JSON + const sassMap = JSON.parse(sassObject.map.toString()); + // Grab the stdout and transform it into stdin + const sassMapFile = sassMap.file.replace(/^stdout$/, 'stdin'); + // Grab the base filename that's being worked on + const sassFileSrc = file.relative; + // Grab the path portion of the file that's being worked on + const sassFileSrcPath = path.dirname(sassFileSrc); + + if (sassFileSrcPath) { + const sourceFileIndex = sassMap.sources.indexOf(sassMapFile); + // Prepend the path to all files in the sources array except the file that's being worked on + sassMap.sources = sassMap.sources.map((source, index) => ( + index === sourceFileIndex + ? source + : path.join(sassFileSrcPath, source) + )); + } - if (file.isStream()) { - return cb(new PluginError(PLUGIN_NAME, 'Streaming not supported')); - } + // Remove 'stdin' from souces and replace with filenames! + sassMap.sources = sassMap.sources.filter((src) => src !== 'stdin' && src); - if (path.basename(file.path).startsWith('_')) { - return cb(); + // Replace the map file with the original filename (but new extension) + sassMap.file = replaceExtension(sassFileSrc, '.css'); + // Apply the map + applySourceMap(file, sassMap); } - if (!file.contents.length) { - file.path = replaceExtension(file.path, '.css'); // eslint-disable-line no-param-reassign - return cb(null, file); + file.contents = sassObject.css; + file.path = replaceExtension(file.path, '.css'); + + if (file.stat) { + file.stat.atime = file.stat.mtime = file.stat.ctime = new Date(); } - const opts = clonedeep(options || {}); - opts.data = file.contents.toString(); + callback(null, file); +}; + +/** + * Handles error message + */ +const handleError = (error, file, callback) => { + const filePath = (error.file === 'stdin' ? file.path : error.file) || file.path; + const relativePath = path.relative(process.cwd(), filePath); + const message = [chalk.underline(relativePath), error.formatted].join('\n'); - // we set the file path here so that libsass can correctly resolve import paths - opts.file = file.path; + error.messageFormatted = message; + error.messageOriginal = error.message; + error.message = stripAnsi(message); + error.relativePath = relativePath; - // Ensure `indentedSyntax` is true if a `.sass` file - if (path.extname(file.path) === '.sass') { - opts.indentedSyntax = true; - } + return callback(new PluginError(PLUGIN_NAME, error)); +}; - // Ensure file's parent directory in the include path - if (opts.includePaths) { - if (typeof opts.includePaths === 'string') { - opts.includePaths = [opts.includePaths]; +/** + * Main Gulp Sass function + */ + +// eslint-disable-next-line arrow-body-style +const gulpSass = (options, sync) => { + // eslint-disable-next-line consistent-return + return transfob((file, encoding, callback) => { + if (file.isNull()) { + return callback(null, file); + } + + if (file.isStream()) { + return callback(new PluginError(PLUGIN_NAME, 'Streaming not supported')); } - } else { - opts.includePaths = []; - } - opts.includePaths.unshift(path.dirname(file.path)); + if (path.basename(file.path).startsWith('_')) { + return callback(); + } - // Generate Source Maps if plugin source-map present - if (file.sourceMap) { - opts.sourceMap = file.path; - opts.omitSourceMapUrl = true; - opts.sourceMapContents = true; - } + if (!file.contents.length) { + file.path = replaceExtension(file.path, '.css'); + return callback(null, file); + } - /// /////////////////////////// - // Handles returning the file to the stream - /// /////////////////////////// - const filePush = (sassObj) => { - let sassMap; - let sassMapFile; - let sassFileSrc; - let sassFileSrcPath; - let sourceFileIndex; - - // Build Source Maps! - if (sassObj.map) { - // Transform map into JSON - sassMap = JSON.parse(sassObj.map.toString()); - // Grab the stdout and transform it into stdin - sassMapFile = sassMap.file.replace(/^stdout$/, 'stdin'); - // Grab the base file name that's being worked on - sassFileSrc = file.relative; - // Grab the path portion of the file that's being worked on - sassFileSrcPath = path.dirname(sassFileSrc); - if (sassFileSrcPath) { - // Prepend the path to all files in the sources array except the file that's being worked on - sourceFileIndex = sassMap.sources.indexOf(sassMapFile); - sassMap.sources = sassMap.sources.map((source, index) => { // eslint-disable-line arrow-body-style - return index === sourceFileIndex ? source : path.join(sassFileSrcPath, source); - }); - } + const opts = clonedeep(options || {}); + opts.data = file.contents.toString(); + + // We set the file path here so that libsass can correctly resolve import paths + opts.file = file.path; - // Remove 'stdin' from souces and replace with filenames! - sassMap.sources = sassMap.sources.filter((src) => src !== 'stdin' && src); + // Ensure `indentedSyntax` is true if a `.sass` file + if (path.extname(file.path) === '.sass') { + opts.indentedSyntax = true; + } - // Replace the map file with the original file name (but new extension) - sassMap.file = replaceExtension(sassFileSrc, '.css'); - // Apply the map - applySourceMap(file, sassMap); + // Ensure file's parent directory in the include path + if (opts.includePaths) { + if (typeof opts.includePaths === 'string') { + opts.includePaths = [opts.includePaths]; + } + } else { + opts.includePaths = []; } - file.contents = sassObj.css; // eslint-disable-line no-param-reassign - file.path = replaceExtension(file.path, '.css'); // eslint-disable-line no-param-reassign + opts.includePaths.unshift(path.dirname(file.path)); - if (file.stat) { - file.stat.atime = file.stat.mtime = file.stat.ctime = new Date(); // eslint-disable-line + // Generate Source Maps if the source-map plugin is present + if (file.sourceMap) { + opts.sourceMap = file.path; + opts.omitSourceMapUrl = true; + opts.sourceMapContents = true; } - cb(null, file); - }; - - /// /////////////////////////// - // Handles error message - /// /////////////////////////// - const errorM = (error) => { - const filePath = (error.file === 'stdin' ? file.path : error.file) || file.path; - const relativePath = path.relative(process.cwd(), filePath); - const message = [chalk.underline(relativePath), error.formatted].join('\n'); - - error.messageFormatted = message; // eslint-disable-line no-param-reassign - error.messageOriginal = error.message; // eslint-disable-line no-param-reassign - error.message = stripAnsi(message); // eslint-disable-line no-param-reassign - error.relativePath = relativePath; // eslint-disable-line no-param-reassign - - return cb(new PluginError(PLUGIN_NAME, error)); - }; - - if (sync !== true) { - /// /////////////////////////// - // Async Sass render - /// /////////////////////////// - const callback = (error, obj) => { // eslint-disable-line consistent-return - if (error) { - return errorM(error); + if (sync !== true) { + /** + * Async Sass render + */ + // eslint-disable-next-line consistent-return + gulpSass.compiler.render(opts, (error, obj) => { + if (error) { + return handleError(error, file, callback); + } + + filePush(file, obj, callback); + }); + } else { + /** + * Sync Sass render + */ + try { + filePush(file, gulpSass.compiler.renderSync(opts), callback); + } catch (error) { + return handleError(error, file, callback); } - filePush(obj); - }; - - gulpSass.compiler.render(opts, callback); - } else { - /// /////////////////////////// - // Sync Sass render - /// /////////////////////////// - try { - filePush(gulpSass.compiler.renderSync(opts)); - } catch (error) { - return errorM(error); } - } -}); + }); +}; -/// /////////////////////////// -// Sync Sass render -/// /////////////////////////// +/** + * Sync Sass render + */ gulpSass.sync = (options) => gulpSass(options, true); -/// /////////////////////////// -// Log errors nicely -/// /////////////////////////// +/** + * Log errors nicely + */ gulpSass.logError = function logError(error) { const message = new PluginError('sass', error.messageFormatted).toString(); process.stderr.write(`${message}\n`); @@ -164,17 +176,13 @@ module.exports = (compiler) => { if (!compiler || !compiler.render) { const message = new PluginError( PLUGIN_NAME, - '\n' - + 'gulp-sass 5 does not have a default Sass compiler; please set one yourself.\n' - + 'Both the `sass` and `node-sass` packages are permitted.\n' - - + 'For example, in your gulpfile:\n\n' - + ' var sass = require(\'gulp-sass\')(require(\'sass\'));\n', + MISSING_COMPILER_MESSAGE, { showProperties: false }, ).toString(); process.stderr.write(`${message}\n`); process.exit(1); } + gulpSass.compiler = compiler; return gulpSass; }; diff --git a/test/main.js b/test/main.js index 284f119..5cd8bb1 100644 --- a/test/main.js +++ b/test/main.js @@ -1,3 +1,5 @@ +'use strict'; + const fs = require('fs'); const path = require('path'); const should = require('should');