1
1
import RewritingStream from "parse5-html-rewriting-stream" ;
2
- import { SourceMapConsumer } from "source-map" ;
3
- import type { StartTagToken as StartTag } from "parse5-sax-parser" ;
2
+ import { SourceMapConsumer , SourceMapGenerator } from "source-map" ;
3
+ import type { StartTagToken as StartTag , TextToken } from "parse5-sax-parser" ;
4
4
import { EventEmitter } from "events" ;
5
5
import { parse , Url } from "url" ;
6
6
import { posix , isAbsolute , dirname , join } from "path" ;
7
7
import slash from "slash"
8
- import { Readable , Writable } from "stream"
8
+ import { Writable } from "stream"
9
+ import { Rebaser as CssRebaser } from "css-source-map-rebase" ;
9
10
10
11
export type Result = {
11
12
data : Buffer ,
@@ -84,10 +85,6 @@ export const createRebaser = (
84
85
return new Promise ( ( resolve , reject ) => {
85
86
let data : Buffer = Buffer . from ( '' ) ;
86
87
87
- const inputStream = new Readable ( {
88
- encoding : "utf-8"
89
- } ) ;
90
-
91
88
const outputStream = new Writable ( {
92
89
write ( chunk : any , _encoding : BufferEncoding , callback : ( error ?: ( Error | null ) ) => void ) {
93
90
data = Buffer . concat ( [ data , chunk ] ) ;
@@ -103,14 +100,22 @@ export const createRebaser = (
103
100
} ) ;
104
101
} ) ;
105
102
106
- inputStream
107
- . pipe ( rewritingStream )
108
- . pipe ( outputStream ) ;
103
+ rewritingStream . pipe ( outputStream ) ;
109
104
110
105
const isRebasable = ( url : Url ) : boolean => {
111
106
return ! isAbsolute ( url . href ) && ( url . host === null ) && ( ( url . hash === null ) || ( url . path !== null ) ) ;
112
107
} ;
113
108
109
+ let queue : Promise < void > = Promise . resolve ( ) ;
110
+
111
+ const defer = ( execution : ( ) => Promise < void > ) => {
112
+ queue = queue
113
+ . then ( execution )
114
+ . catch ( ( error ) => {
115
+ reject ( error ) ;
116
+ } ) ;
117
+ } ;
118
+
114
119
const getRegions = ( ) => {
115
120
if ( ! regions ) {
116
121
const foundRegions : Array < Region > = [ ] ;
@@ -151,6 +156,80 @@ export const createRebaser = (
151
156
return regions ;
152
157
}
153
158
159
+ const findRegion = (
160
+ startLine : number ,
161
+ startColumn : number
162
+ ) : Region | null => {
163
+ let i = 0 ;
164
+ let result : Region | null = null ;
165
+
166
+ const regions = getRegions ( ) ;
167
+ const tagStartLine = startLine ;
168
+ const tagStartColumn = startColumn - 1 ;
169
+
170
+ while ( ( i < regions . length ) && ( result === null ) ) {
171
+ let region = regions [ i ] ;
172
+
173
+ if (
174
+ ( ( region . startLine < tagStartLine ) || ( ( region . startLine === tagStartLine ) && ( region . startColumn <= tagStartColumn ) ) ) &&
175
+ (
176
+ ( region . endLine === null ) || ( region . endLine > tagStartLine ) ||
177
+ ( ( region . endLine === tagStartLine ) && ( region . endColumn === null || ( region . endColumn >= tagStartColumn ) ) )
178
+ )
179
+ ) {
180
+ result = region ;
181
+ }
182
+
183
+ i ++ ;
184
+ }
185
+
186
+ return result ;
187
+ }
188
+
189
+ const transformText = ( textToken : TextToken , rawHtml : string ) : Promise < void > => {
190
+ if ( currentStartTag ?. tagName !== "style" ) {
191
+ return Promise . resolve ( ) ;
192
+ }
193
+
194
+ const { startLine, startCol, endLine} = textToken . sourceCodeLocation ! ;
195
+ const numberOfLines = 1 + ( endLine - startLine ) ;
196
+ const region = findRegion ( startLine , startCol ) ! ;
197
+
198
+ const generator = new SourceMapGenerator ( ) ;
199
+
200
+ for ( let generatedLine = 1 ; generatedLine <= numberOfLines ; generatedLine ++ ) {
201
+ generator . addMapping ( {
202
+ source : region . source ,
203
+ generated : {
204
+ line : generatedLine ,
205
+ column : 0
206
+ } ,
207
+ original : {
208
+ line : 1 ,
209
+ column : 0
210
+ }
211
+ } ) ;
212
+ }
213
+
214
+ generator . setSourceContent ( region . source , rawHtml ) ;
215
+
216
+ const cssRebaser = new CssRebaser ( {
217
+ map : Buffer . from ( generator . toString ( ) ) ,
218
+ rebase
219
+ } ) ;
220
+
221
+ cssRebaser . on ( "rebase" , ( rebasedPath , resolvedPath ) => {
222
+ eventEmitter . emit ( 'rebase' , rebasedPath , resolvedPath ) ;
223
+ } ) ;
224
+
225
+ return cssRebaser . rebase ( Buffer . from ( rawHtml ) )
226
+ . then ( ( result ) => {
227
+ const { css} = result ;
228
+
229
+ textToken . text = css . toString ( ) ;
230
+ } ) ;
231
+ } ;
232
+
154
233
const transformStartTag = ( tag : StartTag ) => {
155
234
const processTag = ( tag : StartTag ) => {
156
235
const attributes = tag . attrs ;
@@ -162,31 +241,8 @@ export const createRebaser = (
162
241
const url = parse ( attribute . value ) ;
163
242
164
243
if ( isRebasable ( url ) ) {
165
- const location = tag . sourceCodeLocation ! ;
166
-
167
- let tagStartLine = location . startLine ;
168
- let tagStartColumn = location . startCol - 1 ;
169
-
170
- let i = 0 ;
171
- let tagRegion : Region | null = null ;
172
- let regions = getRegions ( ) ;
173
-
174
- while ( ( i < regions . length ) && ( tagRegion === null ) ) {
175
- let region = regions [ i ] ;
176
-
177
- if (
178
- ( ( region . startLine < tagStartLine ) || ( ( region . startLine === tagStartLine ) && ( region . startColumn <= tagStartColumn ) ) ) &&
179
- (
180
- ( region . endLine === null ) || ( region . endLine > tagStartLine ) ||
181
- ( ( region . endLine === tagStartLine ) && ( region . endColumn === null || ( region . endColumn >= tagStartColumn ) ) )
182
- )
183
- ) {
184
- tagRegion = region ;
185
- }
186
-
187
- i ++ ;
188
- }
189
-
244
+ const { startLine, startCol} = tag . sourceCodeLocation ! ;
245
+ const tagRegion = findRegion ( startLine , startCol ) ;
190
246
const { source} = tagRegion ! ;
191
247
192
248
const resolvedPath = posix . join ( dirname ( source ) , url . pathname ! ) ;
@@ -215,23 +271,58 @@ export const createRebaser = (
215
271
break ;
216
272
}
217
273
} ) ;
218
- } ;
274
+ }
219
275
220
276
processTag ( tag ) ;
221
277
}
222
278
279
+ let currentStartTag : StartTag | null = null ;
280
+
223
281
rewritingStream . on ( 'startTag' , ( startTag ) => {
224
- try {
225
- transformStartTag ( startTag ) ;
282
+ defer ( ( ) => {
283
+ currentStartTag = startTag ;
226
284
285
+ transformStartTag ( startTag ) ;
227
286
rewritingStream . emitStartTag ( startTag ) ;
228
- } catch ( error ) {
229
- reject ( error ) ;
230
- }
287
+
288
+ return Promise . resolve ( ) ;
289
+ } ) ;
290
+ } ) ;
291
+
292
+ rewritingStream . on ( 'text' , ( text , rawHtml ) => {
293
+ defer ( ( ) => {
294
+ return transformText ( text , rawHtml )
295
+ . then ( ( ) => {
296
+ rewritingStream . emitRaw ( text . text ) ;
297
+ } ) ;
298
+ } ) ;
231
299
} ) ;
232
300
233
- inputStream . push ( html ) ;
234
- inputStream . push ( null ) ;
301
+ rewritingStream . on ( "endTag" , ( endTag ) => {
302
+ defer ( ( ) => {
303
+ currentStartTag = null ;
304
+
305
+ rewritingStream . emitEndTag ( endTag ) ;
306
+
307
+ return Promise . resolve ( ) ;
308
+ } ) ;
309
+ } ) ;
310
+
311
+ for ( const eventName of [ 'doctype' , 'comment' ] ) {
312
+ rewritingStream . on ( eventName , ( _token , rawHtml ) => {
313
+ defer ( ( ) => {
314
+ rewritingStream . emitRaw ( rawHtml ) ;
315
+
316
+ return Promise . resolve ( ) ;
317
+ } ) ;
318
+ } ) ;
319
+ }
320
+
321
+ rewritingStream . write ( html . toString ( ) , ( ) => {
322
+ queue . then ( ( ) => {
323
+ rewritingStream . end ( )
324
+ } ) ;
325
+ } ) ;
235
326
} ) ;
236
327
} ;
237
328
0 commit comments