1
- import type { OptionalPreprocessor , Preprocessor , ProcessedFile , ResolvedConfiguration } from "@css-blocks/core" ;
1
+ import type { OptionalPreprocessor , OptionalPreprocessorSync , Preprocessor , PreprocessorSync , ProcessedFile , ResolvedConfiguration } from "@css-blocks/core" ;
2
2
import type { EyeglassOptions , default as Eyeglass } from "eyeglass" ; // works, even tho a cjs export. huh.
3
3
import type { Result , SassError } from "node-sass" ;
4
4
import type SassImplementation from "node-sass" ;
5
5
import { sep as PATH_SEPARATOR } from "path" ;
6
6
7
7
export type Adaptor = ( sass : typeof SassImplementation , eyeglass : typeof Eyeglass , options : EyeglassOptions ) => Preprocessor ;
8
+ export type AdaptorSync = ( sass : typeof SassImplementation , eyeglass : typeof Eyeglass , options : EyeglassOptions ) => PreprocessorSync ;
8
9
export type OptionalAdaptor = ( sass : typeof SassImplementation , eyeglass : typeof Eyeglass , options : EyeglassOptions ) => OptionalPreprocessor ;
10
+ export type OptionalAdaptorSync = ( sass : typeof SassImplementation , eyeglass : typeof Eyeglass , options : EyeglassOptions ) => OptionalPreprocessorSync ;
9
11
10
12
/**
11
13
* Given a Sass compiler (either dart-sass or node-sass), an Eyeglass
@@ -40,13 +42,40 @@ export const adaptor: Adaptor = (sass: typeof SassImplementation, eyeglass: type
40
42
} ;
41
43
} ;
42
44
45
+ /**
46
+ * Given a Sass compiler (either dart-sass or node-sass), an Eyeglass
47
+ * constructor, and common eyeglass/sass options. This function returns a
48
+ * sync preprocessor, which is a function that can be used preprocess a single file.
49
+ *
50
+ * This function ensures that Sass is properly configured using the common
51
+ * options for each file and that source map information is passed along to CSS
52
+ * Blocks for correct error reporting.
53
+ */
54
+ export const adaptorSync : AdaptorSync = ( sass : typeof SassImplementation , eyeglass : typeof Eyeglass , options : EyeglassOptions = { } ) => {
55
+ return ( file : string , data : string ) => {
56
+ const sassOptions = Object . assign ( { } , options , {
57
+ file,
58
+ data,
59
+ sourceMap : true ,
60
+ outFile : file . replace ( / s c s s $ / , "css" ) ,
61
+ } ) ;
62
+ let res = sass . renderSync ( eyeglass ( sassOptions ) ) ;
63
+ return {
64
+ content : res . css . toString ( ) ,
65
+ sourceMap : res . map . toString ( ) ,
66
+ dependencies : res . stats . includedFiles ,
67
+ } ;
68
+ } ;
69
+ } ;
70
+
43
71
/**
44
72
* This is the core interface that adaptAll depends on to use an object (as
45
73
* opposed to an OptionalAdaptor function) to create a preprocessor.
46
74
*/
47
75
export interface PreprocessorProvider {
48
76
init ( sass : typeof SassImplementation , eyeglass : typeof Eyeglass , options : EyeglassOptions ) : void ;
49
77
preprocessor ( ) : Preprocessor | OptionalPreprocessor ;
78
+ preprocessorSync ( ) : PreprocessorSync | OptionalPreprocessorSync ;
50
79
}
51
80
52
81
/**
@@ -56,7 +85,8 @@ export interface PreprocessorProvider {
56
85
function isPreprocessorProvider ( obj : unknown ) : obj is PreprocessorProvider {
57
86
if ( typeof obj !== "object" || obj === null ) return false ;
58
87
let provider = < PreprocessorProvider > obj ;
59
- return typeof provider . init === "function" && typeof provider . preprocessor === "function" ;
88
+ return typeof provider . init === "function" && typeof provider . preprocessor === "function"
89
+ && typeof provider . preprocessorSync === "function" ;
60
90
}
61
91
62
92
/**
@@ -66,6 +96,7 @@ function isPreprocessorProvider(obj: unknown): obj is PreprocessorProvider {
66
96
export class DirectoryScopedPreprocessor implements PreprocessorProvider {
67
97
protected filePrefix : string ;
68
98
protected scssProcessor : Preprocessor | undefined ;
99
+ protected scssProcessorSync : PreprocessorSync | undefined ;
69
100
70
101
/**
71
102
* Instantiates the preprocessor provider.
@@ -91,7 +122,10 @@ export class DirectoryScopedPreprocessor implements PreprocessorProvider {
91
122
* eyeglass.VERSION.
92
123
*/
93
124
init ( sass : typeof SassImplementation , eyeglass : typeof Eyeglass , options : EyeglassOptions = { } ) {
94
- this . scssProcessor = adaptor ( sass , eyeglass , this . setupOptions ( options ) ) ;
125
+ let sassOptions = this . setupOptions ( options ) ;
126
+ let sassOptionsSync = this . setupOptionsSync ? this . setupOptionsSync ( sassOptions ) : sassOptions ;
127
+ this . scssProcessor = adaptor ( sass , eyeglass , sassOptions ) ;
128
+ this . scssProcessorSync = adaptorSync ( sass , eyeglass , sassOptionsSync ) ;
95
129
}
96
130
97
131
/**
@@ -107,6 +141,20 @@ export class DirectoryScopedPreprocessor implements PreprocessorProvider {
107
141
return options ;
108
142
}
109
143
144
+ /**
145
+ * Subclasses can override this to manipulate/override the eyeglass options
146
+ * provided from the application that will be used for compiling this
147
+ * package's block files synchronously.
148
+ *
149
+ * The options passed into this function are those returned by
150
+ * setupOptions(), so this method only needs to update those options as
151
+ * appropriate to support synchronous compilation.
152
+ *
153
+ * If not provided, the options returned from setupOptions() are used for
154
+ * synchronous compilation.
155
+ */
156
+ setupOptionsSync ?( options : EyeglassOptions ) : EyeglassOptions ;
157
+
110
158
/**
111
159
* Subclasses can override this to decide whether a file should be processed.
112
160
* By default it just checks that the file is within the directory for this
@@ -130,6 +178,23 @@ export class DirectoryScopedPreprocessor implements PreprocessorProvider {
130
178
}
131
179
} ;
132
180
}
181
+
182
+ /**
183
+ * Subclasses shouldn't need to override this.
184
+ * @returns the preprocessor expected by adaptAll.
185
+ */
186
+ preprocessorSync ( ) : OptionalPreprocessorSync {
187
+ return ( file : string , data : string , config : ResolvedConfiguration ) => {
188
+ if ( ! this . scssProcessorSync ) {
189
+ throw new Error ( "Adaptor was not initialized!" ) ;
190
+ }
191
+ if ( this . shouldProcessFile ( file ) ) {
192
+ return this . scssProcessorSync ( file , data , config ) ;
193
+ } else {
194
+ return null ;
195
+ }
196
+ } ;
197
+ }
133
198
}
134
199
135
200
/**
@@ -160,3 +225,32 @@ export function adaptAll(adaptors: Array<OptionalAdaptor | PreprocessorProvider>
160
225
return lastResortProcessor ( file , data , config ) ;
161
226
} ;
162
227
}
228
+
229
+ /**
230
+ * Creates a unified preprocessor for an application to use when consuming
231
+ * css blocks that have Sass preprocessed.
232
+ *
233
+ * The application provides a list of preprocessor adaptors, as well as the
234
+ * desired versions of sass, eyeglass and common Sass/Eyeglass options for
235
+ * compiling the sass files with eyeglass support.
236
+ */
237
+ export function adaptAllSync ( adaptors : Array < OptionalAdaptorSync | PreprocessorProvider > , sass : typeof SassImplementation , eyeglass : typeof Eyeglass , options : EyeglassOptions ) : PreprocessorSync {
238
+ let processors = adaptors . map ( adaptor => {
239
+ if ( isPreprocessorProvider ( adaptor ) ) {
240
+ adaptor . init ( sass , eyeglass , options ) ;
241
+ return adaptor . preprocessorSync ( ) ;
242
+ } else {
243
+ return adaptorSync ( sass , eyeglass , options ) ;
244
+ }
245
+ } ) ;
246
+ let lastResortProcessor = adaptorSync ( sass , eyeglass , options ) ;
247
+ return ( file : string , data : string , config : ResolvedConfiguration ) => {
248
+ for ( let processor of processors ) {
249
+ let result = processor ( file , data , config ) ;
250
+ if ( result ) {
251
+ return result ;
252
+ }
253
+ }
254
+ return lastResortProcessor ( file , data , config ) ;
255
+ } ;
256
+ }
0 commit comments