@@ -3,7 +3,7 @@ import * as path from 'path';
3
3
import hash from 'string-hash' ;
4
4
import * as codec from 'sourcemap-codec' ;
5
5
import { PageComponent , Dirs } from '../../interfaces' ;
6
- import { CompileResult } from './interfaces' ;
6
+ import { CompileResult , Chunk } from './interfaces' ;
7
7
import { posixify } from '../../utils'
8
8
9
9
const inline_sourcemap_header = 'data:application/json;charset=utf-8;base64,' ;
@@ -46,6 +46,65 @@ type SourceMap = {
46
46
mappings : string ;
47
47
} ;
48
48
49
+ function get_css_from_modules ( modules : string [ ] , css_map : Map < string , string > , dirs : Dirs ) {
50
+ const parts : string [ ] = [ ] ;
51
+ const mappings : number [ ] [ ] [ ] = [ ] ;
52
+
53
+ const combined_map : SourceMap = {
54
+ version : 3 ,
55
+ file : null ,
56
+ sources : [ ] ,
57
+ sourcesContent : [ ] ,
58
+ names : [ ] ,
59
+ mappings : null
60
+ } ;
61
+
62
+ modules . forEach ( module => {
63
+ if ( ! / \. c s s $ / . test ( module ) ) return ;
64
+
65
+ const css = css_map . get ( module ) ;
66
+
67
+ const { code, map } = extract_sourcemap ( css , module ) ;
68
+
69
+ parts . push ( code ) ;
70
+
71
+ if ( map ) {
72
+ const lines = codec . decode ( map . mappings ) ;
73
+
74
+ if ( combined_map . sources . length > 0 || combined_map . names . length > 0 ) {
75
+ lines . forEach ( line => {
76
+ line . forEach ( segment => {
77
+ // adjust source index
78
+ segment [ 1 ] += combined_map . sources . length ;
79
+
80
+ // adjust name index
81
+ if ( segment [ 4 ] ) segment [ 4 ] += combined_map . names . length ;
82
+ } ) ;
83
+ } ) ;
84
+ }
85
+
86
+ combined_map . sources . push ( ...map . sources ) ;
87
+ combined_map . sourcesContent . push ( ...map . sourcesContent ) ;
88
+ combined_map . names . push ( ...map . names ) ;
89
+
90
+ mappings . push ( ...lines ) ;
91
+ }
92
+ } ) ;
93
+
94
+ if ( parts . length > 0 ) {
95
+ combined_map . mappings = codec . encode ( mappings ) ;
96
+
97
+ combined_map . sources = combined_map . sources . map ( source => path . relative ( `${ dirs . dest } /client` , source ) ) ;
98
+
99
+ return {
100
+ code : parts . join ( '\n' ) ,
101
+ map : combined_map
102
+ } ;
103
+ }
104
+
105
+ return null ;
106
+ }
107
+
49
108
export default function extract_css ( client_result : CompileResult , components : PageComponent [ ] , dirs : Dirs ) {
50
109
const result : {
51
110
main : string | null ;
@@ -57,151 +116,94 @@ export default function extract_css(client_result: CompileResult, components: Pa
57
116
58
117
if ( ! client_result . css_files ) return ; // Rollup-only for now
59
118
60
- const unaccounted_for = new Set ( ) ;
119
+ let asset_dir = `${ dirs . dest } /client` ;
120
+ if ( process . env . SAPPER_LEGACY_BUILD ) asset_dir += '/legacy' ;
61
121
62
- const css_map = new Map ( ) ;
63
- client_result . css_files . forEach ( css => {
64
- unaccounted_for . add ( css . id ) ;
65
- css_map . set ( css . id , css . code ) ;
66
- } ) ;
122
+ const unclaimed = new Set ( client_result . css_files . map ( x => x . id ) ) ;
67
123
68
- const chunk_map = new Map ( ) ;
124
+ const lookup = new Map ( ) ;
69
125
client_result . chunks . forEach ( chunk => {
70
- chunk_map . set ( chunk . file , chunk ) ;
126
+ lookup . set ( chunk . file , chunk ) ;
71
127
} ) ;
72
128
73
- const chunks_with_css = new Set ( ) ;
74
-
75
- // figure out which chunks belong to which components...
76
- const component_owners = new Map ( ) ;
77
- client_result . chunks . forEach ( chunk => {
78
- chunk . modules . forEach ( module => {
79
- const component = posixify ( path . relative ( dirs . routes , module ) ) ;
80
- component_owners . set ( component , chunk ) ;
81
- } ) ;
129
+ const css_map = new Map ( ) ;
130
+ client_result . css_files . forEach ( css_module => {
131
+ css_map . set ( css_module . id , css_module . code ) ;
82
132
} ) ;
83
133
84
- const chunks_depended_upon_by_component = new Map ( ) ;
85
-
86
- // ...so we can figure out which chunks don't belong
87
- components . forEach ( component => {
88
- const chunk = component_owners . get ( component . file ) ;
89
- if ( ! chunk ) {
90
- // this should never happen!
91
- throw new Error ( `Could not find chunk that owns ${ component . file } ` ) ;
92
- }
93
-
94
- const chunks = new Set ( [ chunk ] ) ;
95
- chunks . forEach ( chunk => {
96
- chunk . imports . forEach ( ( file : string ) => {
97
- const chunk = chunk_map . get ( file ) ;
98
- if ( chunk ) chunks . add ( chunk ) ;
99
- } ) ;
100
- } ) ;
101
-
102
- chunks . forEach ( chunk => {
103
- chunk . modules . forEach ( ( module : string ) => {
104
- unaccounted_for . delete ( module ) ;
105
- } ) ;
106
- } ) ;
107
-
108
- chunks_depended_upon_by_component . set (
109
- component ,
110
- chunks
111
- ) ;
112
- } ) ;
134
+ const chunks_with_css = new Set ( ) ;
113
135
114
- function get_css_from_modules ( modules : string [ ] ) {
115
- const parts : string [ ] = [ ] ;
116
- const mappings : number [ ] [ ] [ ] = [ ] ;
117
-
118
- const combined_map : SourceMap = {
119
- version : 3 ,
120
- file : null ,
121
- sources : [ ] ,
122
- sourcesContent : [ ] ,
123
- names : [ ] ,
124
- mappings : null
125
- } ;
136
+ // concatenate and emit CSS
137
+ client_result . chunks . forEach ( chunk => {
138
+ const css_modules = chunk . modules . filter ( m => css_map . has ( m ) ) ;
139
+ if ( ! css_modules . length ) return ;
126
140
127
- modules . forEach ( module => {
128
- if ( ! / \. c s s $ / . test ( module ) ) return ;
141
+ const css = get_css_from_modules ( css_modules , css_map , dirs ) ;
129
142
130
- const css = css_map . get ( module ) ;
143
+ const { code , map } = css ;
131
144
132
- const { code , map } = extract_sourcemap ( css , module ) ;
145
+ const output_file_name = chunk . file . replace ( / \. j s $ / , '.css' ) ;
133
146
134
- parts . push ( code ) ;
147
+ map . file = output_file_name ;
148
+ map . sources = map . sources . map ( source => path . relative ( `${ asset_dir } ` , source ) ) ;
135
149
136
- if ( map ) {
137
- const lines = codec . decode ( map . mappings ) ;
150
+ fs . writeFileSync ( ` ${ asset_dir } / ${ output_file_name } ` , ` ${ code } \n/* sourceMappingURL=./ ${ output_file_name } .map */` ) ;
151
+ fs . writeFileSync ( ` ${ asset_dir } / ${ output_file_name } . map` , JSON . stringify ( map , null , ' ' ) ) ;
138
152
139
- if ( combined_map . sources . length > 0 || combined_map . names . length > 0 ) {
140
- lines . forEach ( line => {
141
- line . forEach ( segment => {
142
- // adjust source index
143
- segment [ 1 ] += combined_map . sources . length ;
153
+ chunks_with_css . add ( chunk ) ;
154
+ } ) ;
144
155
145
- // adjust name index
146
- if ( segment [ 4 ] ) segment [ 4 ] += combined_map . names . length ;
147
- } ) ;
148
- } ) ;
149
- }
156
+ const entry = path . resolve ( dirs . src , 'client.js' ) ;
157
+ const entry_chunk = client_result . chunks . find ( chunk => chunk . modules . indexOf ( entry ) !== - 1 ) ;
150
158
151
- combined_map . sources . push ( ...map . sources ) ;
152
- combined_map . sourcesContent . push ( ...map . sourcesContent ) ;
153
- combined_map . names . push ( ...map . names ) ;
159
+ const entry_chunk_dependencies : Set < Chunk > = new Set ( [ entry_chunk ] ) ;
160
+ const entry_css_modules : string [ ] = [ ] ;
154
161
155
- mappings . push ( ...lines ) ;
156
- }
162
+ // recursively find the chunks this component depends on
163
+ entry_chunk_dependencies . forEach ( chunk => {
164
+ chunk . imports . forEach ( file => {
165
+ entry_chunk_dependencies . add ( lookup . get ( file ) ) ;
157
166
} ) ;
158
167
159
- if ( parts . length > 0 ) {
160
- combined_map . mappings = codec . encode ( mappings ) ;
161
-
162
- combined_map . sources = combined_map . sources . map ( source => path . relative ( `${ dirs . dest } /client` , source ) ) ;
163
-
164
- return {
165
- code : parts . join ( '\n' ) ,
166
- map : combined_map
167
- } ;
168
+ if ( chunks_with_css . has ( chunk ) ) {
169
+ chunk . modules . forEach ( file => {
170
+ unclaimed . delete ( file ) ;
171
+ if ( css_map . has ( file ) ) {
172
+ entry_css_modules . push ( file ) ;
173
+ }
174
+ } ) ;
168
175
}
176
+ } ) ;
169
177
170
- return null ;
171
- }
172
-
173
- let asset_dir = `${ dirs . dest } /client` ;
174
- if ( process . env . SAPPER_LEGACY_BUILD ) asset_dir += '/legacy' ;
175
-
176
- const replacements = new Map ( ) ;
177
-
178
- chunks_depended_upon_by_component . forEach ( ( chunks , component ) => {
179
- const chunks_with_css = Array . from ( chunks ) . filter ( chunk => {
180
- const css = get_css_from_modules ( chunk . modules ) ;
178
+ // figure out which (css-having) chunks each component depends on
179
+ components . forEach ( component => {
180
+ const resolved = path . resolve ( dirs . routes , component . file ) ;
181
+ const chunk : Chunk = client_result . chunks . find ( chunk => chunk . modules . indexOf ( resolved ) !== - 1 ) ;
181
182
182
- if ( css ) {
183
- const { code, map } = css ;
183
+ if ( ! chunk ) {
184
+ // this should never happen!
185
+ throw new Error ( `Could not find chunk that owns ${ component . file } ` ) ;
186
+ }
184
187
185
- const output_file_name = chunk . file . replace ( / \. j s $ / , '.css' ) ;
188
+ const chunk_dependencies : Set < Chunk > = new Set ( [ chunk ] ) ;
189
+ const css_dependencies : string [ ] = [ ] ;
186
190
187
- map . file = output_file_name ;
188
- map . sources = map . sources . map ( source => path . relative ( `${ asset_dir } ` , source ) ) ;
191
+ // recursively find the chunks this component depends on
192
+ chunk_dependencies . forEach ( chunk => {
193
+ chunk . imports . forEach ( file => {
194
+ chunk_dependencies . add ( lookup . get ( file ) ) ;
195
+ } ) ;
189
196
190
- fs . writeFileSync ( ` ${ asset_dir } / ${ output_file_name } ` , ` ${ code } \n/* sourceMappingURL=./ ${ output_file_name } .map */` ) ;
191
- fs . writeFileSync ( ` ${ asset_dir } / ${ output_file_name } .map` , JSON . stringify ( map , null , ' ' ) ) ;
197
+ if ( chunks_with_css . has ( chunk ) ) {
198
+ css_dependencies . push ( chunk . file ) ;
192
199
193
- return true ;
200
+ chunk . modules . forEach ( file => {
201
+ unclaimed . delete ( file ) ;
202
+ } ) ;
194
203
}
195
204
} ) ;
196
205
197
- const files = chunks_with_css . map ( chunk => chunk . file . replace ( / \. j s $ / , '.css' ) ) ;
198
-
199
- replacements . set (
200
- component . file ,
201
- files
202
- ) ;
203
-
204
- result . chunks [ component . file ] = files ;
206
+ result . chunks [ component . file ] = css_dependencies ;
205
207
} ) ;
206
208
207
209
fs . readdirSync ( asset_dir ) . forEach ( file => {
@@ -210,13 +212,17 @@ export default function extract_css(client_result: CompileResult, components: Pa
210
212
const source = fs . readFileSync ( `${ asset_dir } /${ file } ` , 'utf-8' ) ;
211
213
212
214
const replaced = source . replace ( / [ " ' ] _ _ S A P P E R _ C S S _ P L A C E H O L D E R : ( .+ ?) _ _ [ " ' ] / g, ( m , route ) => {
213
- return JSON . stringify ( replacements . get ( route ) ) ;
215
+ return JSON . stringify ( result . chunks [ route ] ) ;
214
216
} ) ;
215
217
216
218
fs . writeFileSync ( `${ asset_dir } /${ file } ` , replaced ) ;
217
219
} ) ;
218
220
219
- const leftover = get_css_from_modules ( Array . from ( unaccounted_for ) ) ;
221
+ unclaimed . forEach ( file => {
222
+ entry_css_modules . push ( css_map . get ( file ) ) ;
223
+ } ) ;
224
+
225
+ const leftover = get_css_from_modules ( entry_css_modules , css_map , dirs ) ;
220
226
if ( leftover ) {
221
227
const { code, map } = leftover ;
222
228
0 commit comments