UNPKG

5.33 kBJavaScriptView Raw
1'use strict';
2
3const path = require('path');
4const { Transform } = require('stream');
5const picocolors = require('picocolors');
6const PluginError = require('plugin-error');
7const replaceExtension = require('replace-ext');
8const stripAnsi = require('strip-ansi');
9const clonedeep = require('lodash.clonedeep');
10const applySourceMap = require('vinyl-sourcemaps-apply');
11
12const PLUGIN_NAME = 'gulp-sass';
13
14const MISSING_COMPILER_MESSAGE = `
15gulp-sass no longer has a default Sass compiler; please set one yourself.
16Both the "sass" and "node-sass" packages are permitted.
17For example, in your gulpfile:
18
19 const sass = require('gulp-sass')(require('sass'));
20`;
21
22const transfob = (transform) => new Transform({ transform, objectMode: true });
23
24/**
25 * Handles returning the file to the stream
26 */
27const filePush = (file, sassObject, callback) => {
28 // Build Source Maps!
29 if (sassObject.map) {
30 // Transform map into JSON
31 const sassMap = JSON.parse(sassObject.map.toString());
32 // Grab the stdout and transform it into stdin
33 const sassMapFile = sassMap.file.replace(/^stdout$/, 'stdin');
34 // Grab the base filename that's being worked on
35 const sassFileSrc = file.relative;
36 // Grab the path portion of the file that's being worked on
37 const sassFileSrcPath = path.dirname(sassFileSrc);
38
39 if (sassFileSrcPath) {
40 const sourceFileIndex = sassMap.sources.indexOf(sassMapFile);
41 // Prepend the path to all files in the sources array except the file that's being worked on
42 sassMap.sources = sassMap.sources.map((source, index) => (
43 index === sourceFileIndex
44 ? source
45 : path.join(sassFileSrcPath, source)
46 ));
47 }
48
49 // Remove 'stdin' from souces and replace with filenames!
50 sassMap.sources = sassMap.sources.filter((src) => src !== 'stdin' && src);
51
52 // Replace the map file with the original filename (but new extension)
53 sassMap.file = replaceExtension(sassFileSrc, '.css');
54 // Apply the map
55 applySourceMap(file, sassMap);
56 }
57
58 file.contents = sassObject.css;
59 file.path = replaceExtension(file.path, '.css');
60
61 if (file.stat) {
62 file.stat.atime = file.stat.mtime = file.stat.ctime = new Date();
63 }
64
65 callback(null, file);
66};
67
68/**
69 * Handles error message
70 */
71const handleError = (error, file, callback) => {
72 const filePath = (error.file === 'stdin' ? file.path : error.file) || file.path;
73 const relativePath = path.relative(process.cwd(), filePath);
74 const message = `${picocolors.underline(relativePath)}\n${error.formatted}`;
75
76 error.messageFormatted = message;
77 error.messageOriginal = error.message;
78 error.message = stripAnsi(message);
79 error.relativePath = relativePath;
80
81 return callback(new PluginError(PLUGIN_NAME, error));
82};
83
84/**
85 * Main Gulp Sass function
86 */
87
88// eslint-disable-next-line arrow-body-style
89const gulpSass = (options, sync) => {
90 return transfob((file, encoding, callback) => {
91 if (file.isNull()) {
92 callback(null, file);
93 return;
94 }
95
96 if (file.isStream()) {
97 callback(new PluginError(PLUGIN_NAME, 'Streaming not supported'));
98 return;
99 }
100
101 if (path.basename(file.path).startsWith('_')) {
102 callback();
103 return;
104 }
105
106 if (!file.contents.length) {
107 file.path = replaceExtension(file.path, '.css');
108 callback(null, file);
109 return;
110 }
111
112 const opts = clonedeep(options || {});
113 opts.data = file.contents.toString();
114
115 // We set the file path here so that libsass can correctly resolve import paths
116 opts.file = file.path;
117
118 // Ensure `indentedSyntax` is true if a `.sass` file
119 if (path.extname(file.path) === '.sass') {
120 opts.indentedSyntax = true;
121 }
122
123 // Ensure file's parent directory in the include path
124 if (opts.includePaths) {
125 if (typeof opts.includePaths === 'string') {
126 opts.includePaths = [opts.includePaths];
127 }
128 } else {
129 opts.includePaths = [];
130 }
131
132 opts.includePaths.unshift(path.dirname(file.path));
133
134 // Generate Source Maps if the source-map plugin is present
135 if (file.sourceMap) {
136 opts.sourceMap = file.path;
137 opts.omitSourceMapUrl = true;
138 opts.sourceMapContents = true;
139 }
140
141 if (sync !== true) {
142 /**
143 * Async Sass render
144 */
145 gulpSass.compiler.render(opts, (error, obj) => {
146 if (error) {
147 handleError(error, file, callback);
148 return;
149 }
150
151 filePush(file, obj, callback);
152 });
153 } else {
154 /**
155 * Sync Sass render
156 */
157 try {
158 filePush(file, gulpSass.compiler.renderSync(opts), callback);
159 } catch (error) {
160 handleError(error, file, callback);
161 }
162 }
163 });
164};
165
166/**
167 * Sync Sass render
168 */
169gulpSass.sync = (options) => gulpSass(options, true);
170
171/**
172 * Log errors nicely
173 */
174gulpSass.logError = function logError(error) {
175 const message = new PluginError('sass', error.messageFormatted).toString();
176 process.stderr.write(`${message}\n`);
177 this.emit('end');
178};
179
180module.exports = (compiler) => {
181 if (!compiler || !compiler.render) {
182 const message = new PluginError(
183 PLUGIN_NAME,
184 MISSING_COMPILER_MESSAGE,
185 { showProperties: false },
186 ).toString();
187 process.stderr.write(`${message}\n`);
188 process.exit(1);
189 }
190
191 gulpSass.compiler = compiler;
192 return gulpSass;
193};