1
1
import type * as d from '../../declarations' ;
2
- import { flatOne , unique } from '@utils' ;
2
+ import { catchError , flatOne , isString , unique } from '@utils' ;
3
3
import { getScopeId } from '../style/scope-css' ;
4
4
import { injectModulePreloads } from '../html/inject-module-preloads' ;
5
5
import { optimizeCss } from '../optimize/optimize-css' ;
6
6
import { optimizeJs } from '../optimize/optimize-js' ;
7
7
import { join } from 'path' ;
8
+ import { minifyCss } from '../optimize/minify-css' ;
8
9
9
- export const inlineExternalStyleSheets = async ( config : d . Config , appDir : string , doc : Document ) => {
10
+ export const inlineExternalStyleSheets = async ( sys : d . CompilerSystem , appDir : string , doc : Document ) => {
10
11
const documentLinks = Array . from ( doc . querySelectorAll ( 'link[rel=stylesheet]' ) ) as HTMLLinkElement [ ] ;
11
12
if ( documentLinks . length === 0 ) {
12
13
return ;
@@ -22,7 +23,7 @@ export const inlineExternalStyleSheets = async (config: d.Config, appDir: string
22
23
const fsPath = join ( appDir , href ) ;
23
24
24
25
try {
25
- let styles = await config . sys . readFile ( fsPath ) ;
26
+ let styles = await sys . readFile ( fsPath ) ;
26
27
27
28
const optimizeResults = await optimizeCss ( {
28
29
input : styles ,
@@ -94,25 +95,27 @@ export const minifyScriptElements = async (doc: Document, addMinifiedAttr: boole
94
95
) ;
95
96
} ;
96
97
97
- export const minifyStyleElements = async ( doc : Document , addMinifiedAttr : boolean ) => {
98
+ export const minifyStyleElements = async ( sys : d . CompilerSystem , appDir : string , doc : Document , currentUrl : URL , addMinifiedAttr : boolean ) => {
98
99
const styleElms = Array . from ( doc . querySelectorAll ( 'style' ) ) . filter ( styleElm => {
99
100
if ( styleElm . hasAttribute ( dataMinifiedAttr ) ) {
100
101
return false ;
101
102
}
102
103
return true ;
103
104
} ) ;
104
105
105
- if ( styleElms . length === 0 ) {
106
- return ;
107
- }
108
-
109
106
await Promise . all (
110
107
styleElms . map ( async styleElm => {
111
108
const content = styleElm . innerHTML . trim ( ) ;
112
109
if ( content . length > 0 ) {
113
110
const optimizeResults = await optimizeCss ( {
114
111
input : content ,
115
112
minify : true ,
113
+ async resolveUrl ( urlProp ) {
114
+ const assetUrl = new URL ( urlProp , currentUrl ) ;
115
+ const hash = await getAssetFileHash ( sys , appDir , assetUrl ) ;
116
+ assetUrl . searchParams . append ( 'v' , hash ) ;
117
+ return assetUrl . pathname + assetUrl . search ;
118
+ }
116
119
} ) ;
117
120
if ( optimizeResults . diagnostics . length === 0 ) {
118
121
styleElm . innerHTML = optimizeResults . output ;
@@ -191,4 +194,123 @@ export const hasStencilScript = (doc: Document) => {
191
194
return ! ! doc . querySelector ( 'script[data-stencil]' ) ;
192
195
} ;
193
196
197
+ export const hashAssets = async ( sys : d . CompilerSystem , diagnostics : d . Diagnostic [ ] , hydrateOpts : d . PrerenderHydrateOptions , appDir : string , doc : Document , currentUrl : URL ) => {
198
+ // do one at a time to prevent too many opened files and memory usage issues
199
+ // hash id is cached in each worker, so shouldn't have to do this for every page
200
+
201
+ // update the stylesheet content first so the hash url()s are apart of the file's hash too
202
+ const links = Array . from ( doc . querySelectorAll ( 'link[rel=stylesheet][href]' ) ) as HTMLLinkElement [ ] ;
203
+
204
+ for ( const link of links ) {
205
+ const href = link . getAttribute ( 'href' ) ;
206
+ if ( isString ( href ) && href . length > 0 ) {
207
+ const stylesheetUrl = new URL ( href , currentUrl ) ;
208
+ if ( currentUrl . host === stylesheetUrl . host ) {
209
+ try {
210
+ const filePath = join ( appDir , stylesheetUrl . pathname ) ;
211
+ let css = await sys . readFile ( filePath ) ;
212
+ if ( isString ( css ) ) {
213
+ css = await minifyCss ( {
214
+ css,
215
+ async resolveUrl ( urlProp ) {
216
+ const assetUrl = new URL ( urlProp , stylesheetUrl ) ;
217
+ const hash = await getAssetFileHash ( sys , appDir , assetUrl ) ;
218
+ assetUrl . searchParams . append ( 'v' , hash ) ;
219
+ return assetUrl . pathname + assetUrl . search ;
220
+ }
221
+ } ) ;
222
+ await sys . writeFile ( filePath , css ) ;
223
+ }
224
+ } catch ( e ) {
225
+ catchError ( diagnostics , e ) ;
226
+ }
227
+ }
228
+ }
229
+ }
230
+
231
+ await hashAsset ( sys , hydrateOpts , appDir , doc , currentUrl , 'link[rel="stylesheet"]' , [ 'href' ] ) ;
232
+ await hashAsset ( sys , hydrateOpts , appDir , doc , currentUrl , 'link[rel="prefetch"]' , [ 'href' ] ) ;
233
+ await hashAsset ( sys , hydrateOpts , appDir , doc , currentUrl , 'link[rel="preload"]' , [ 'href' ] ) ;
234
+ await hashAsset ( sys , hydrateOpts , appDir , doc , currentUrl , 'link[rel="modulepreload"]' , [ 'href' ] ) ;
235
+ await hashAsset ( sys , hydrateOpts , appDir , doc , currentUrl , 'link[rel="icon"]' , [ 'href' ] ) ;
236
+ await hashAsset ( sys , hydrateOpts , appDir , doc , currentUrl , 'link[rel="apple-touch-icon"]' , [ 'href' ] ) ;
237
+ await hashAsset ( sys , hydrateOpts , appDir , doc , currentUrl , 'link[rel="manifest"]' , [ 'href' ] ) ;
238
+ await hashAsset ( sys , hydrateOpts , appDir , doc , currentUrl , 'script' , [ 'src' ] ) ;
239
+ await hashAsset ( sys , hydrateOpts , appDir , doc , currentUrl , 'img' , [ 'src' , 'srcset' ] ) ;
240
+ await hashAsset ( sys , hydrateOpts , appDir , doc , currentUrl , 'picture > source' , [ 'srcset' ] ) ;
241
+ }
242
+
243
+ const hashAsset = async ( sys : d . CompilerSystem , hydrateOpts : d . PrerenderHydrateOptions , appDir : string , doc : Document , currentUrl : URL , selector : string , srcAttrs : string [ ] ) => {
244
+ const elms = Array . from ( doc . querySelectorAll ( selector ) ) ;
245
+
246
+ // do one at a time to prevent too many opened files and memory usage issues
247
+ for ( const elm of elms ) {
248
+ for ( const attrName of srcAttrs ) {
249
+ const srcValues = getAttrUrls ( attrName , elm . getAttribute ( attrName ) ) ;
250
+ for ( const srcValue of srcValues ) {
251
+ const assetUrl = new URL ( srcValue . src , currentUrl ) ;
252
+ if ( assetUrl . hostname === currentUrl . hostname ) {
253
+ if ( hydrateOpts . hashAssets === 'querystring' && ! assetUrl . searchParams . has ( 'v' ) ) {
254
+ const hash = await getAssetFileHash ( sys , appDir , assetUrl ) ;
255
+ if ( isString ( hash ) ) {
256
+ assetUrl . searchParams . append ( 'v' , hash ) ;
257
+ const attrValue = setAttrUrls ( assetUrl , srcValue . descriptor ) ;
258
+ elm . setAttribute ( attrName , attrValue ) ;
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+
267
+ export const getAttrUrls = ( attrName : string , attrValue : string ) => {
268
+ const srcValues : { src : string , descriptor ?: string } [ ] = [ ] ;
269
+ if ( isString ( attrValue ) ) {
270
+ if ( attrName . toLowerCase ( ) === 'srcset' ) {
271
+ attrValue . split ( ',' ) . map ( a => a . trim ( ) ) . filter ( a => a . length > 0 ) . forEach ( src => {
272
+ const spaceSplt = src . split ( ' ' ) ;
273
+ if ( spaceSplt [ 0 ] . length > 0 ) {
274
+ srcValues . push ( { src : spaceSplt [ 0 ] , descriptor : spaceSplt [ 1 ] } ) ;
275
+ }
276
+ } ) ;
277
+ } else {
278
+ srcValues . push ( { src : attrValue } ) ;
279
+ }
280
+ }
281
+ return srcValues ;
282
+ }
283
+
284
+ export const setAttrUrls = ( url : URL , descriptor : string ) => {
285
+ let src = url . pathname + url . search ;
286
+ if ( isString ( descriptor ) ) {
287
+ src += ' ' + descriptor ;
288
+ }
289
+ return src ;
290
+ } ;
291
+
292
+ const hashedAssets = new Map < string , Promise < string | null > > ( ) ;
293
+
294
+ const getAssetFileHash = async ( sys : d . CompilerSystem , appDir : string , assetUrl : URL ) => {
295
+ let p = hashedAssets . get ( assetUrl . pathname ) ;
296
+ if ( ! p ) {
297
+ p = new Promise < string | null > ( async resolve => {
298
+ const assetFilePath = join ( appDir , assetUrl . pathname ) ;
299
+ try {
300
+ const data = await sys . readFile ( assetFilePath , 'binary' ) ;
301
+ if ( data != null ) {
302
+ const hash = await sys . generateContentHash ( data , 10 ) ;
303
+ resolve ( hash ) ;
304
+ return ;
305
+ }
306
+ } catch ( e ) {
307
+ console . error ( e ) ;
308
+ }
309
+ resolve ( null ) ;
310
+ } ) ;
311
+ hashedAssets . set ( assetUrl . pathname , p ) ;
312
+ }
313
+ return p ;
314
+ }
315
+
194
316
const dataMinifiedAttr = 'data-m' ;
0 commit comments