1
+ import path from 'path' ;
1
2
import OriginalSource from 'webpack-sources/lib/OriginalSource' ;
2
3
import RawSource from 'webpack-sources/lib/RawSource' ;
3
4
import ReplaceSource from 'webpack-sources/lib/ReplaceSource' ;
4
5
import SourceMapSource from 'webpack-sources/lib/SourceMapSource' ;
5
6
import loaderUtils from 'loader-utils' ;
6
7
import parse from './parse' ;
7
8
9
+ function toRelativeUrl ( p , context ) {
10
+ const url = path . relative ( context , p ) . replace ( / \\ / g, '/' ) ;
11
+ if ( / ^ \. \. ? \/ / . test ( url ) ) {
12
+ return url ;
13
+ }
14
+ return `./${ url } ` ;
15
+ }
16
+
8
17
export default function loader ( source , map ) {
9
18
const options = loaderUtils . getOptions ( this ) || { } ;
10
19
const remainingRequest = loaderUtils . getRemainingRequest ( this ) ;
@@ -19,9 +28,11 @@ export default function loader(source, map) {
19
28
replacer = new ReplaceSource ( new RawSource ( source ) , remainingRequest ) ;
20
29
}
21
30
31
+ // List of @imports with media queries
22
32
const includedStylesheets = new Set ( ) ;
23
33
const includedStylesheetsMediaQuery = new Map ( ) ;
24
34
35
+ // Interate parsed @import
25
36
parseResult . atImports . forEach ( ( imp ) => {
26
37
if ( loaderUtils . isUrlRequest ( imp . url , options . root ) ) {
27
38
const request = loaderUtils . urlToRequest ( imp . url , options . root ) ;
@@ -31,17 +42,29 @@ export default function loader(source, map) {
31
42
}
32
43
} ) ;
33
44
45
+ // Flag if column mappings make sense for this SourceMap
46
+ // It only makes sense if we don't replace values from imported values
34
47
let columns = true ;
48
+
49
+ // Mapping from css-identifier to import
35
50
const importedNames = new Map ( ) ;
36
51
52
+ // Interate parsed :import
37
53
parseResult . imports . forEach ( ( imp ) => {
38
54
importedNames . set ( imp . alias , imp ) ;
39
55
} ) ;
40
56
57
+ // List of all declarates that should be emitted to the output
41
58
const declarations = [ ] ;
59
+
60
+ // Mapping from css-identifier to imported identifier
42
61
const importReplacements = new Map ( ) ;
43
62
63
+ // Counter to generate unique ids for identifiers
44
64
let id = 0 ;
65
+
66
+ // Iterate all imported names and internal identifiers
67
+ // Also make sure that :imports are imported like @imports
45
68
for ( const pair of importedNames ) {
46
69
const internalName = `cssLoaderImport${ id } _${ pair [ 1 ] . importName } ` ;
47
70
id += 1 ;
@@ -50,6 +73,7 @@ export default function loader(source, map) {
50
73
includedStylesheets . add ( pair [ 1 ] . from ) ;
51
74
}
52
75
76
+ // Iterate all replacements and replace them with a maker token
53
77
for ( const pair of importReplacements ) {
54
78
const identifier = parseResult . identifiers . get ( pair [ 0 ] ) ;
55
79
if ( identifier ) {
@@ -61,10 +85,12 @@ export default function loader(source, map) {
61
85
}
62
86
}
63
87
88
+ // Delete all metablocks, they only contain meta information and are not valid css
64
89
parseResult . metablocks . forEach ( ( block ) => {
65
90
replacer . replace ( block . start , block . end , '' ) ;
66
91
} ) ;
67
92
93
+ // Generate declarations for all imports
68
94
const includedStylesheetsArray = [ ] ;
69
95
for ( const include of includedStylesheets ) {
70
96
const internalName = `cssLoaderImport${ id } ` ;
@@ -76,14 +102,62 @@ export default function loader(source, map) {
76
102
} ) ;
77
103
}
78
104
105
+ // Mapping from exported name to exported value as array (will be joined by spaces)
106
+ const exportedNames = new Map ( ) ;
107
+
108
+ // Iterate parsed exports
109
+ parseResult . exports . forEach ( ( exp ) => {
110
+ // Note this elimiate duplicates, only last exported value is valid
111
+ exportedNames . set ( exp . name , exp . value ) ;
112
+ } ) ;
113
+
114
+ for ( const pair of exportedNames ) {
115
+ const [ name , value ] = pair ;
116
+ const processedValues = value . map ( ( item ) => {
117
+ const replacement = importReplacements . get ( item ) ;
118
+ if ( replacement ) {
119
+ return {
120
+ name : replacement ,
121
+ } ;
122
+ }
123
+ return item ;
124
+ } ) . reduce ( ( arr , item ) => {
125
+ if ( typeof item === 'string' && arr . length > 0 && typeof arr [ arr . length - 1 ] === 'string' ) {
126
+ arr [ arr . length - 1 ] += item ; // eslint-disable-line no-param-reassign
127
+ return arr ;
128
+ }
129
+ arr . push ( item ) ;
130
+ return arr ;
131
+ } , [ ] ) ;
132
+ if ( processedValues . length === 1 && typeof processedValues [ 0 ] !== 'string' ) {
133
+ declarations . push ( `export { ${ processedValues [ 0 ] . name } as ${ name } };` ) ;
134
+ } else {
135
+ const valuesJs = processedValues . map ( ( item ) => {
136
+ if ( typeof item === 'string' ) {
137
+ return JSON . stringify ( item ) ;
138
+ }
139
+ return item . name ;
140
+ } ) . join ( ' + ' ) ;
141
+ declarations . push ( `export var ${ name } = ${ valuesJs } ;` ) ;
142
+ }
143
+ }
144
+
79
145
let css ;
80
146
let sourceMap ;
81
147
if ( options . sourceMap ) {
82
148
const sourceAndMap = replacer . sourceAndMap ( typeof options . sourceMap === 'object' ? options . sourceMap : {
83
149
columns,
84
150
} ) ;
85
- css = sourceAndMap . code ;
151
+ css = sourceAndMap . source ;
86
152
sourceMap = sourceAndMap . map ;
153
+ if ( options . sourceMapContext ) {
154
+ sourceMap . sources = sourceMap . sources . map ( absPath => toRelativeUrl ( absPath , options . sourceMapContext ) ) ;
155
+ }
156
+ if ( options . sourceMapPrefix ) {
157
+ sourceMap . sources = sourceMap . sources . map ( sourcePath => options . sourceMapPrefix + sourcePath ) ;
158
+ } else {
159
+ sourceMap . sources = sourceMap . sources . map ( sourcePath => `webpack-css:///${ sourcePath } ` ) ;
160
+ }
87
161
} else {
88
162
css = replacer . source ( ) ;
89
163
sourceMap = null ;
@@ -94,19 +168,17 @@ export default function loader(source, map) {
94
168
return [
95
169
'// css runtime' ,
96
170
`import * as runtime from ${ loaderUtils . stringifyRequest ( this , require . resolve ( '../runtime' ) ) } ;` ,
97
- '' ,
98
- '// declarations' ,
99
171
declarations . join ( '\n' ) ,
100
172
'' ,
101
173
'// CSS' ,
102
174
'export default /*#__PURE__*/runtime.create([' ,
103
175
] . concat (
104
176
includedStylesheetsArray . map ( ( include ) => {
105
177
if ( ! include . mediaQuery ) return ` ${ include . name } ,` ;
106
- return ` runtime.importStylesheet(${ include . name } , ${ JSON . stringify ( include . mediaQuery ) } ,` ;
178
+ return ` runtime.importStylesheet(${ include . name } , ${ JSON . stringify ( include . mediaQuery ) } ) ,` ;
107
179
} ) ,
108
180
) . concat ( [
109
- ` runtime.${ sourceMap ? 'moduleWithSourceMap' : 'moduleWithoutSourceMap' } (module.id, ${ cssJs } ${ sourceMap ? `, ${ sourceMap } ` : '' } )` ,
181
+ ` runtime.${ sourceMap ? 'moduleWithSourceMap' : 'moduleWithoutSourceMap' } (module.id, ${ cssJs } ${ sourceMap ? `, ${ JSON . stringify ( sourceMap ) } ` : '' } )` ,
110
182
']);' ,
111
183
] ) . join ( '\n' ) ;
112
184
}
0 commit comments