@@ -17,8 +17,7 @@ import * as nodeSass from 'node-sass';
1717import * as postcss from 'postcss' ;
1818import * as CleanCss from 'clean-css' ;
1919import * as autoprefixer from 'autoprefixer' ;
20- import * as cssModules from 'postcss-modules' ;
21- import * as crypto from 'crypto' ;
20+ import CSSModules , { ICSSModules } from './CSSModules' ;
2221
2322export interface ISassTaskConfig {
2423 /**
@@ -27,7 +26,8 @@ export interface ISassTaskConfig {
2726 preamble ?: string ;
2827
2928 /**
30- * An optional parameter for text to include at the end of the generated TypeScript file.
29+ * An optional parameter for text to include at the end of the generated
30+ * TypeScript file.
3131 */
3232 postamble ?: string ;
3333
@@ -37,47 +37,48 @@ export interface ISassTaskConfig {
3737 sassMatch ?: string [ ] ;
3838
3939 /**
40- * If this option is specified, ALL files will be treated as module.sass or module.scss and will
41- * automatically generate a corresponding TypeScript file. All classes will be
42- * appended with a hash to help ensure uniqueness on a page. This file can be
43- * imported directly, and will contain an object describing the mangled class names.
40+ * If this option is specified, ALL files will be treated as module.sass or
41+ * module.scss and will automatically generate a corresponding TypeScript
42+ * file. All classes will be appended with a hash to help ensure uniqueness
43+ * on a page. This file can be imported directly, and will contain an object
44+ * describing the mangled class names.
4445 */
4546 useCSSModules ?: boolean ;
4647
4748 /**
48- * If false, we will set the CSS property naming warning to verbose message while the module generates
49- * to prevent task exit with exitcode: 1.
50- * Default value is true
49+ * If false, we will set the CSS property naming warning to verbose message
50+ * while the module generates to prevent task exit with exitcode: 1.
51+ * Default value is true.
5152 */
5253 warnOnCssInvalidPropertyName ?: boolean ;
5354
5455 /**
55- * If true, we will generate a CSS in the lib folder. If false, the CSS is directly embedded
56- * into the TypeScript file
56+ * If true, we will generate CSS in the lib folder. If false, the CSS is
57+ * directly embedded into the TypeScript file.
5758 */
5859 dropCssFiles ?: boolean ;
5960
6061 /**
61- * If files are matched by sassMatch which do not end in .module.sass or .module.scss, log a warning.
62+ * If files are matched by sassMatch which do not end in .module.sass or
63+ * .module.scss, log a warning.
6264 */
6365 warnOnNonCSSModules ?: boolean ;
6466
6567 /**
66- * If this option is specified, module CSS will be exported using the name provided. If an
67- * empty value is specified, the styles will be exported using 'export =', rather than a
68- * named export. By default we use the 'default' export name.
68+ * If this option is specified, module CSS will be exported using the name
69+ * provided. If an empty value is specified, the styles will be exported
70+ * using 'export =', rather than a named export. By default, we use the
71+ * 'default' export name.
6972 */
7073 moduleExportName ?: string ;
7174
7275 /**
73- * Allows the override of the options passed to clean-css. Options such a returnPromise and
74- * sourceMap will be ignored.
76+ * Allows the override of the options passed to clean-css. Options such a
77+ * returnPromise and sourceMap will be ignored.
7578 */
7679 cleanCssOptions ?: CleanCss . Options ;
7780}
7881
79- const _classMaps : { [ file : string ] : Object } = { } ;
80-
8182export class SassTask extends GulpTask < ISassTaskConfig > {
8283 public cleanMatch : string [ ] = [
8384 'src/**/*.sass.ts' ,
@@ -88,13 +89,6 @@ export class SassTask extends GulpTask<ISassTaskConfig> {
8889 autoprefixer ( { browsers : [ '> 1%' , 'last 2 versions' , 'ie >= 10' ] } )
8990 ] ;
9091
91- private _modulePostCssAdditionalPlugins : postcss . AcceptedPlugin [ ] = [
92- cssModules ( {
93- getJSON : this . _generateModuleStub . bind ( this ) ,
94- generateScopedName : this . generateScopedName . bind ( this )
95- } )
96- ] ;
97-
9892 constructor ( ) {
9993 super (
10094 'sass' ,
@@ -127,19 +121,6 @@ export class SassTask extends GulpTask<ISassTaskConfig> {
127121 } ) . then ( ( ) => { /* collapse void[] to void */ } ) ;
128122 }
129123
130- public generateScopedName ( name : string , fileName : string , css : string ) : string {
131- const fileBaseName : string = path . basename ( fileName ) ;
132- const hash : string = crypto . createHmac ( 'sha1' , fileBaseName )
133- . update ( css )
134- . digest ( 'hex' )
135- . substring ( 0 , 8 ) ;
136- return `${ name } _${ hash } ` ;
137- }
138-
139- private _generateModuleStub ( cssFileName : string , json : Object ) : void {
140- _classMaps [ cssFileName ] = json ;
141- }
142-
143124 private _processFile ( filePath : string ) : Promise < void > {
144125 // Ignore files that start with underscores
145126 if ( path . basename ( filePath ) . match ( / ^ \_ / ) ) {
@@ -148,11 +129,16 @@ export class SassTask extends GulpTask<ISassTaskConfig> {
148129
149130 const isFileModuleCss : boolean = ! ! filePath . match ( / \. m o d u l e \. s ( a | c ) s s / ) ;
150131 const processAsModuleCss : boolean = isFileModuleCss || ! ! this . taskConfig . useCSSModules ;
132+ const cssModules : ICSSModules = new CSSModules ( ) ;
151133
152- if ( ! isFileModuleCss && ! this . taskConfig . useCSSModules && this . taskConfig . warnOnNonCSSModules ) {
153- // If the file doesn't end with .module.scss and we don't treat all files as module-scss, warn
154- const relativeFilePath : string = path . relative ( this . buildConfig . rootPath , filePath ) ;
155- this . logWarning ( `${ relativeFilePath } : filename should end with module.sass or module.scss` ) ;
134+ if ( ! processAsModuleCss && this . taskConfig . warnOnNonCSSModules ) {
135+ const relativeFilePath : string = path . relative (
136+ this . buildConfig . rootPath , filePath
137+ ) ;
138+ this . logWarning (
139+ `${ relativeFilePath } : filename should end with either .module.sass ` +
140+ `or .module.scss`
141+ ) ;
156142 }
157143
158144 let cssOutputPath : string | undefined = undefined ;
@@ -190,10 +176,10 @@ export class SassTask extends GulpTask<ISassTaskConfig> {
190176 } ;
191177 }
192178
193- const plugins : postcss . AcceptedPlugin [ ] = [
194- ... this . _postCSSPlugins ,
195- ... ( processAsModuleCss ? this . _modulePostCssAdditionalPlugins : [ ] )
196- ] ;
179+ const plugins : postcss . AcceptedPlugin [ ] = [ ... this . _postCSSPlugins ] ;
180+ if ( processAsModuleCss ) {
181+ plugins . push ( cssModules . getPlugin ( ) ) ;
182+ }
197183 return postcss ( plugins ) . process ( result . css . toString ( ) , options ) as PromiseLike < postcss . Result > ;
198184 } ) . then ( ( result : postcss . Result ) => {
199185 let cleanCssOptions : CleanCss . Options = { level : 1 , returnPromise : true } ;
@@ -210,44 +196,40 @@ export class SassTask extends GulpTask<ISassTaskConfig> {
210196 result . styles . toString ( )
211197 ] ;
212198 if ( result . sourceMap && ! this . buildConfig . production ) {
213- const encodedSourceMap : string = Buffer . from ( result . sourceMap . toString ( ) ) . toString ( 'base64' ) ;
214- generatedFileLines . push ( ...[
199+ const encodedSourceMap : string = Buffer . from ( result . sourceMap . toString ( ) )
200+ . toString ( 'base64' ) ;
201+ generatedFileLines . push (
215202 `/*# sourceMappingURL=data:application/json;base64,${ encodedSourceMap } */`
216- ] ) ;
203+ ) ;
217204 }
218205
219- FileSystem . writeFile ( cssOutputPathAbsolute , generatedFileLines . join ( EOL ) , { ensureFolderExists : true } ) ;
206+ FileSystem . writeFile (
207+ cssOutputPathAbsolute ,
208+ generatedFileLines . join ( EOL ) ,
209+ { ensureFolderExists : true }
210+ ) ;
220211 }
221212
222213 const scssTsOutputPath : string = `${ filePath } .ts` ;
223- const classNames : Object = _classMaps [ filePath ] ;
214+ const classMap : Object = cssModules . getCssJSON ( ) ;
224215 let exportClassNames : string = '' ;
225216 const content : string | undefined = result . styles ;
226217
227- if ( classNames ) {
228- const classNamesLines : string [ ] = [
229- 'const styles = {'
230- ] ;
231-
232- const classKeys : string [ ] = Object . keys ( classNames ) ;
233- classKeys . forEach ( ( key : string , index : number ) => {
234- const value : string = classNames [ key ] ;
235- let line : string = '' ;
218+ if ( classMap ) {
219+ const classKeys : string [ ] = Object . keys ( classMap ) ;
220+ const styleLines : string [ ] = [ ] ;
221+ classKeys . forEach ( ( key : string ) => {
222+ const value : string = classMap [ key ] ;
236223 if ( key . indexOf ( '-' ) !== - 1 ) {
237- const message : string = `The local CSS class '${ key } ' is not camelCase and will not be type-safe.` ;
238- this . taskConfig . warnOnCssInvalidPropertyName ?
239- this . logWarning ( message ) :
224+ const message : string = `The local CSS class '${ key } ' is not ` +
225+ `camelCase and will not be type-safe.` ;
226+ if ( this . taskConfig . warnOnCssInvalidPropertyName ) {
227+ this . logWarning ( message ) ;
228+ } else {
240229 this . logVerbose ( message ) ;
241- line = ` '${ key } ': '${ value } '` ;
242- } else {
243- line = ` ${ key } : '${ value } '` ;
244- }
245-
246- if ( ( index + 1 ) <= classKeys . length ) {
247- line += ',' ;
230+ }
248231 }
249-
250- classNamesLines . push ( line ) ;
232+ styleLines . push ( ` ${ key } : '${ value } '` ) ;
251233 } ) ;
252234
253235 let exportString : string = 'export default styles;' ;
@@ -258,17 +240,16 @@ export class SassTask extends GulpTask<ISassTaskConfig> {
258240 // exportString = `export const ${this.taskConfig.moduleExportName} = styles;`;
259241 }
260242
261- classNamesLines . push (
243+ exportClassNames = [
244+ 'const styles = {' ,
245+ styleLines . join ( `,${ EOL } ` ) ,
262246 '};' ,
263247 '' ,
264248 exportString
265- ) ;
266-
267- exportClassNames = classNamesLines . join ( EOL ) ;
249+ ] . join ( EOL ) ;
268250 }
269251
270252 let lines : string [ ] = [ ] ;
271-
272253 lines . push ( this . taskConfig . preamble || '' ) ;
273254
274255 if ( cssOutputPathAbsolute ) {
0 commit comments