@@ -59,217 +59,175 @@ function encodeRelativeURL(startPath, relative, callerPath) {
5959 // may be undefined
6060 return result ;
6161 }
62- } else {
63- return ;
6462 }
6563}
6664
6765/**
68- * @param {number } [bannerWidth] The width of banner comment, zero or omitted for none
66+ * Use <code>node-sass</code> to compile the files of the input stream.
67+ * Outputs a stream of compiled files and their source-maps, alternately.
68+ * @see https://github.com/sass/node-sass#outputstyle
69+ * @param {Array.<string> } [libraryPaths] Any number of library path strings
70+ * @returns {stream.Through } A through stream that performs the operation of a gulp stream
6971 */
70- module . exports = function ( bannerWidth ) {
72+ module . exports = function ( bannerWidth , libraryPaths ) {
7173 'use strict' ;
72- var libList = [ ] ;
73-
74- /**
75- * Add string values to the <code>libList</code> uniquely.
76- * @param {string|Array } value An explicit library path string or array thereof
77- */
78- function addUnique ( value ) {
79- if ( ( typeof value === 'object' ) && ( value instanceof Array ) ) {
80- value . forEach ( addUnique ) ;
81- } else if ( ( typeof value === 'string' ) && ( libList . indexOf ( value ) < 0 ) ) {
82- libList . push ( value ) ;
83- }
84- }
85-
86- return {
74+ var output = [ ] ;
75+ var libList = ( libraryPaths || [ ] ) . filter ( function isString ( value ) {
76+ return ( typeof value === 'string' ) ;
77+ } ) ;
78+ return through . obj ( function ( file , encoding , done ) {
79+ var stream = this ;
80+
81+ // setup parameters
82+ var sourcePath = file . path . replace ( path . basename ( file . path ) , '' ) ;
83+ var sourceName = path . basename ( file . path , path . extname ( file . path ) ) ;
84+ var mapName = sourceName + '.css.map' ;
85+ var sourceMapConsumer ;
8786
8887 /**
89- * Infer library paths from the <code>base</code> paths in the input stream in preparation for <code>compile</code>.
90- * Any arguments given are treated as explicit paths and are added as-is to the library list.
91- * Outputs a stream of the same files.
92- * @param {...string|Array } Any number of explicit library path strings or arrays thereof
93- * @returns {stream.Through } A through stream that performs the operation of a gulp stream
88+ * Push file contents to the output stream.
89+ * @param {string } ext The extention for the file, including dot
90+ * @param {string|object? } contents The contents for the file or fields to assign to it
91+ * @return {vinyl.File } The file that has been pushed to the stream
9492 */
95- libraries : function ( ) {
96- addUnique ( Array . prototype . slice . call ( arguments ) ) ;
97- return through . obj ( function ( file , encoding , done ) {
98- addUnique ( file . base ) ;
99- this . push ( file ) ;
100- done ( ) ;
93+ function pushResult ( ext , contents ) {
94+ var pending = new gutil . File ( {
95+ cwd : file . cwd ,
96+ base : file . base ,
97+ path : sourcePath + sourceName + ext ,
98+ contents : ( typeof contents === 'string' ) ? new Buffer ( contents ) : null
10199 } ) ;
102- } ,
100+ if ( typeof contents === 'object' ) {
101+ for ( var key in contents ) {
102+ pending [ key ] = contents [ key ] ;
103+ }
104+ }
105+ stream . push ( pending ) ;
106+ return pending ;
107+ }
103108
104109 /**
105- * Use <code>node-sass</code> to compile the files of the input stream.
106- * Uses any library paths defined in <code>libraries</code>. Does not utilise the file content in the input stream.
107- * Outputs a stream of compiled files and their source-maps, alternately.
108- * @see https://github.com/sass/node-sass#outputstyle
109- * @param {string? } outputStyle One of the <code>libsass</code> supported output styles
110- * @returns {stream.Through } A through stream that performs the operation of a gulp stream
110+ * Plugin for css rework that follows SASS transpilation
111+ * @param {object } stylesheet AST for the CSS output from SASS
111112 */
112- compile : function ( outputStyle ) {
113- var output = [ ] ;
114- return through . obj ( function ( file , encoding , done ) {
115- var stream = this ;
116-
117- // setup parameters
118- var sourcePath = file . path . replace ( path . basename ( file . path ) , '' ) ;
119- var sourceName = path . basename ( file . path , path . extname ( file . path ) ) ;
120- var mapName = sourceName + '.css.map' ;
121- var stats = { } ;
122- var sourceMapConsumer ;
123-
124- /**
125- * Push file contents to the output stream.
126- * @param {string } ext The extention for the file, including dot
127- * @param {string|object? } contents The contents for the file or fields to assign to it
128- * @return {vinyl.File } The file that has been pushed to the stream
129- */
130- function pushResult ( ext , contents ) {
131- var pending = new gutil . File ( {
132- cwd : file . cwd ,
133- base : file . base ,
134- path : sourcePath + sourceName + ext ,
135- contents : ( typeof contents === 'string' ) ? new Buffer ( contents ) : null
136- } ) ;
137- if ( typeof contents === 'object' ) {
138- for ( var key in contents ) {
139- pending [ key ] = contents [ key ] ;
140- }
141- }
142- stream . push ( pending ) ;
143- return pending ;
144- }
145-
146- /**
147- * Plugin for css rework that follows SASS transpilation
148- * @param {object } stylesheet AST for the CSS output from SASS
149- */
150- function reworkPlugin ( stylesheet ) {
113+ function reworkPlugin ( stylesheet ) {
151114
152- // visit each node (selector) in the stylesheet recursively using the official utility method
153- visit ( stylesheet , function ( declarations , node ) {
115+ // visit each node (selector) in the stylesheet recursively using the official utility method
116+ visit ( stylesheet , function ( declarations , node ) {
154117
155- // each node may have multiple declarations
156- declarations . forEach ( function ( declaration ) {
118+ // each node may have multiple declarations
119+ declarations . forEach ( function ( declaration ) {
157120
158- // reverse the original source-map to find the original sass file
159- var cssStart = declaration . position . start ;
160- var sassStart = sourceMapConsumer . originalPositionFor ( {
161- line : cssStart . line ,
162- column : cssStart . column
163- } ) ;
164- var sassDir = path . dirname ( sassStart . source ) ;
165-
166- // allow multiple url() values in the declaration
167- // the url will be every second value (i % 2)
168- declaration . value = declaration . value
169- . split ( / u r l \s * \( \s * [ ' " ] ? ( [ ^ ' " ? # ] * ) [ ^ ) ] * \) / g) // split by url statements
170- . map ( function ( token , i ) {
171- return ( i % 2 ) ? ( encodeRelativeURL ( sassDir , token ) || ( 'url(' + token + ')' ) ) : token ;
172- } ) . join ( '' ) ;
173- } ) ;
174- } ) ;
175- }
176-
177- /**
178- * Handler for successful transpilation using node-sass.
179- * @param {string } css Compiled css
180- * @param {string } map The source-map for the compiled css
181- */
182- function successHandler ( css , map ) {
183-
184- // adjust sourcemap
185- var source = minimatch . makeRe ( file . cwd ) . source
186- . replace ( / ^ \^ | \$ $ / g, '' ) // match text anywhere on the line by removing line start/end
187- . replace ( / \\ \/ / g, '[\\\\\\/]' ) + // detect any platform path format
188- '|\\.\\.\\/' ; // relative paths are an artifact and must be removed
189- var parsable = slash ( map . replace ( new RegExp ( source , 'g' ) , '' ) ) ;
190- var sourceMap = JSON . parse ( parsable ) ;
191- sourceMap . sources . forEach ( function ( value , i , array ) {
192- array [ i ] = path . resolve ( value . replace ( / ^ \/ / , '' ) . replace ( / \b \/ + \b / g, '/' ) ) ; // ensure single slash absolute
193- } ) ;
194- sourceMapConsumer = new SourceMapConsumer ( sourceMap ) ;
195-
196- // embed sourcemap in css
197- var cssWithMap = css . replace (
198- / \/ \* # \s * s o u r c e M a p p i n g U R L = [ ^ * ] * \* \/ / m,
199- '/*# sourceMappingURL=data:application/json;base64,' + convert . fromObject ( sourceMap ) . toBase64 ( ) + ' */'
200- ) ;
201-
202- // rework css
203- var reworked = rework ( cssWithMap , '' )
204- . use ( reworkPlugin )
205- . toString ( {
206- sourcemap : true ,
207- sourcemapAsObject : true
208- } ) ;
209-
210- // adjust overall sourcemap
211- delete reworked . map . file ;
212- delete reworked . map . sourcesContent ;
213- reworked . map . sources . forEach ( function ( value , i , array ) {
214- array [ i ] = '/' + path . relative ( process . cwd ( ) , value ) ; // ensure root relative
121+ // reverse the original source-map to find the original sass file
122+ var cssStart = declaration . position . start ;
123+ var sassStart = sourceMapConsumer . originalPositionFor ( {
124+ line : cssStart . line ,
125+ column : cssStart . column
215126 } ) ;
127+ var sassDir = path . dirname ( sassStart . source ) ;
128+
129+ // allow multiple url() values in the declaration
130+ // the url will be every second value (i % 2)
131+ declaration . value = declaration . value
132+ . split ( / u r l \s * \( \s * [ ' " ] ? ( [ ^ ' " ? # ] * ) [ ^ ) ] * \) / g) // split by url statements
133+ . map ( function ( token , i ) {
134+ return ( i % 2 ) ? ( encodeRelativeURL ( sassDir , token ) || ( 'url(' + token + ')' ) ) : token ;
135+ } ) . join ( '' ) ;
136+ } ) ;
137+ } ) ;
138+ }
216139
217- // write stream output
218- pushResult ( '.css' , reworked . code + '\n/*# sourceMappingURL=' + mapName + ' */' ) ;
219- pushResult ( '.css.map' , JSON . stringify ( reworked . map , null , ' ' ) ) ;
220- done ( ) ;
221- }
140+ /**
141+ * Handler for successful transpilation using node-sass.
142+ * @param {string } css Compiled css
143+ * @param {string } map The source-map for the compiled css
144+ */
145+ function successHandler ( css , map ) {
146+
147+ // adjust sourcemap
148+ var source = minimatch . makeRe ( file . cwd ) . source
149+ . replace ( / ^ \^ | \$ $ / g, '' ) // match text anywhere on the line by removing line start/end
150+ . replace ( / \\ \/ / g, '[\\\\\\/]' ) + // detect any platform path format
151+ '|\\.\\.\\/' ; // relative paths are an artifact and must be removed
152+ var parsable = slash ( map . replace ( new RegExp ( source , 'g' ) , '' ) ) ;
153+ var sourceMap = JSON . parse ( parsable ) ;
154+ sourceMap . sources . forEach ( function ( value , i , array ) {
155+ array [ i ] = path . resolve ( value . replace ( / ^ \/ / , '' ) . replace ( / \b \/ + \b / g, '/' ) ) ; // ensure single slash absolute
156+ } ) ;
157+ sourceMapConsumer = new SourceMapConsumer ( sourceMap ) ;
158+
159+ // embed sourcemap in css
160+ var cssWithMap = css . replace (
161+ / \/ \* # \s * s o u r c e M a p p i n g U R L = [ ^ * ] * \* \/ / m,
162+ '/*# sourceMappingURL=data:application/json;base64,' + convert . fromObject ( sourceMap ) . toBase64 ( ) + ' */'
163+ ) ;
164+
165+ // rework css
166+ var reworked = rework ( cssWithMap , '' )
167+ . use ( reworkPlugin )
168+ . toString ( {
169+ sourcemap : true ,
170+ sourcemapAsObject : true
171+ } ) ;
222172
223- /**
224- * Handler for error in node-sass.
225- * @param {string } error The error text from node-sass
226- */
227- function errorHandler ( error ) {
228- var analysis = / ( .* ) \: ( \d + ) \: \s * e r r o r \: \s * ( .* ) / . exec ( error ) ;
229- var message = analysis ?
230- ( path . resolve ( analysis [ 1 ] ) + ':' + analysis [ 2 ] + ':0: ' + analysis [ 3 ] + '\n' ) :
231- ( 'TODO parse this error\n' + error + '\n' ) ;
232- if ( output . indexOf ( message ) < 0 ) {
233- output . push ( message ) ;
234- }
235- done ( ) ;
236- }
173+ // adjust overall sourcemap
174+ delete reworked . map . file ;
175+ delete reworked . map . sourcesContent ;
176+ reworked . map . sources . forEach ( function ( value , i , array ) {
177+ array [ i ] = '/' + slash ( path . relative ( process . cwd ( ) , value ) ) ; // ensure root relative
178+ } ) ;
237179
238- /**
239- * Perform the sass render with the given <code>sourceMap</code>, <code>error</code>, and <code>success</code>
240- * parameters.
241- * @param {string|boolean } map The source map filename or <code>false</code> for none
242- * @param {function ({string}) } error Handler for error
243- * @param {function ({string}, {string}) } success Handler for success
244- */
245- function render ( map , error , success ) {
246- sass . render ( {
247- file : file . path ,
248- success : success ,
249- error : error ,
250- includePaths : libList ,
251- outputStyle : outputStyle || 'compressed' ,
252- stats : stats ,
253- sourceMap : map
254- } ) ;
255- }
180+ // write stream output
181+ pushResult ( '.css' , reworked . code + '\n/*# sourceMappingURL=' + mapName + ' */' ) ;
182+ pushResult ( '.css.map' , JSON . stringify ( reworked . map , null , ' ' ) ) ;
183+ done ( ) ;
184+ }
256185
257- // run first without sourcemap as this can cause process exit where errors exist
258- render ( false , errorHandler , function ( ) {
259- render ( mapName , errorHandler , successHandler ) ;
260- } ) ;
186+ /**
187+ * Handler for error in node-sass.
188+ * @param {string } error The error text from node-sass
189+ */
190+ function errorHandler ( error ) {
191+ var analysis = / ( .* ) \: ( \d + ) \: \s * e r r o r \: \s * ( .* ) / . exec ( error ) ;
192+ var resolved = path . resolve ( analysis [ 1 ] ) ;
193+ var filename = [ '.scss' , '.css' ]
194+ . map ( function ( ext ) {
195+ return resolved + ext ;
196+ } )
197+ . filter ( function ( fullname ) {
198+ return fs . existsSync ( fullname ) ;
199+ } )
200+ . pop ( ) ;
201+ var message = analysis ?
202+ ( ( filename || resolved ) + ':' + analysis [ 2 ] + ':0: ' + analysis [ 3 ] + '\n' ) :
203+ ( 'TODO parse this error\n' + error + '\n' ) ;
204+ if ( output . indexOf ( message ) < 0 ) {
205+ output . push ( message ) ;
206+ }
207+ done ( ) ;
208+ }
261209
262- // display the output buffer with padding before and after and between each item
263- } , function ( done ) {
264- if ( output . length ) {
265- var width = Number ( bannerWidth ) || 0 ;
266- var hr = new Array ( width + 1 ) ; // this is a good trick to repeat a character N times
267- var start = ( width > 0 ) ? ( hr . join ( '\u25BC' ) + '\n' ) : '' ;
268- var stop = ( width > 0 ) ? ( hr . join ( '\u25B2' ) + '\n' ) : '' ;
269- process . stdout . write ( start + '\n' + output . join ( '\n' ) + '\n' + stop ) ;
270- }
271- done ( ) ;
272- } ) ;
210+ // perform the sass render
211+ sass . render ( {
212+ file : file . path ,
213+ data : file . contents . toString ( ) ,
214+ success : successHandler ,
215+ error : errorHandler ,
216+ includePaths : libList ,
217+ outputStyle : 'compressed' ,
218+ stats : { } ,
219+ sourceMap : mapName
220+ } ) ;
221+
222+ // display the output buffer with padding before and after and between each item
223+ } , function ( done ) {
224+ if ( output . length ) {
225+ var width = Number ( bannerWidth ) || 0 ;
226+ var hr = new Array ( width + 1 ) ; // this is a good trick to repeat a character N times
227+ var start = ( width > 0 ) ? ( hr . join ( '\u25BC' ) + '\n' ) : '' ;
228+ var stop = ( width > 0 ) ? ( hr . join ( '\u25B2' ) + '\n' ) : '' ;
229+ process . stdout . write ( start + '\n' + output . join ( '\n' ) + '\n' + stop ) ;
273230 }
274- } ;
275- } ;
231+ done ( ) ;
232+ } ) ;
233+ } ;
0 commit comments