@@ -211,11 +211,13 @@ export function getScriptInfo(node: DefaultTreeAdapterMap['element']): {
211
211
sourceCodeLocation : Token . Location | undefined
212
212
isModule : boolean
213
213
isAsync : boolean
214
+ isIgnored : boolean
214
215
} {
215
216
let src : Token . Attribute | undefined
216
217
let sourceCodeLocation : Token . Location | undefined
217
218
let isModule = false
218
219
let isAsync = false
220
+ let isIgnored = false
219
221
for ( const p of node . attrs ) {
220
222
if ( p . prefix !== undefined ) continue
221
223
if ( p . name === 'src' ) {
@@ -227,9 +229,11 @@ export function getScriptInfo(node: DefaultTreeAdapterMap['element']): {
227
229
isModule = true
228
230
} else if ( p . name === 'async' ) {
229
231
isAsync = true
232
+ } else if ( p . name === 'vite-ignore' ) {
233
+ isIgnored = true
230
234
}
231
235
}
232
- return { src, sourceCodeLocation, isModule, isAsync }
236
+ return { src, sourceCodeLocation, isModule, isAsync, isIgnored }
233
237
}
234
238
235
239
const attrValueStartRE = / = \s * ( .) /
@@ -260,6 +264,19 @@ export function overwriteAttrValue(
260
264
return s
261
265
}
262
266
267
+ export function removeViteIgnoreAttr (
268
+ s : MagicString ,
269
+ sourceCodeLocation : Token . Location ,
270
+ ) : MagicString {
271
+ const loc = ( sourceCodeLocation as Token . LocationWithAttributes ) . attrs ?. [
272
+ 'vite-ignore'
273
+ ]
274
+ if ( loc ) {
275
+ s . remove ( loc . startOffset , loc . endOffset )
276
+ }
277
+ return s
278
+ }
279
+
263
280
/**
264
281
* Format parse5 @type {ParserError} to @type {RollupError}
265
282
*/
@@ -437,158 +454,165 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
437
454
438
455
// script tags
439
456
if ( node . nodeName === 'script' ) {
440
- const { src, sourceCodeLocation, isModule, isAsync } =
457
+ const { src, sourceCodeLocation, isModule, isAsync, isIgnored } =
441
458
getScriptInfo ( node )
442
459
443
- const url = src && src . value
444
- const isPublicFile = ! ! ( url && checkPublicFile ( url , config ) )
445
- if ( isPublicFile ) {
446
- // referencing public dir url, prefix with base
447
- overwriteAttrValue (
448
- s ,
449
- sourceCodeLocation ! ,
450
- partialEncodeURIPath ( toOutputPublicFilePath ( url ) ) ,
451
- )
452
- }
453
-
454
- if ( isModule ) {
455
- inlineModuleIndex ++
456
- if ( url && ! isExcludedUrl ( url ) && ! isPublicFile ) {
457
- setModuleSideEffectPromises . push (
458
- this . resolve ( url , id )
459
- . then ( ( resolved ) => {
460
- if ( ! resolved ) {
461
- return Promise . reject ( )
462
- }
463
- return this . load ( resolved )
464
- } )
465
- . then ( ( mod ) => {
466
- // set this to keep the module even if `treeshake.moduleSideEffects=false` is set
467
- mod . moduleSideEffects = true
468
- } ) ,
460
+ if ( isIgnored ) {
461
+ removeViteIgnoreAttr ( s , node . sourceCodeLocation ! )
462
+ } else {
463
+ const url = src && src . value
464
+ const isPublicFile = ! ! ( url && checkPublicFile ( url , config ) )
465
+ if ( isPublicFile ) {
466
+ // referencing public dir url, prefix with base
467
+ overwriteAttrValue (
468
+ s ,
469
+ sourceCodeLocation ! ,
470
+ partialEncodeURIPath ( toOutputPublicFilePath ( url ) ) ,
469
471
)
470
- // <script type="module" src="..."/>
471
- // add it as an import
472
- js += `\nimport ${ JSON . stringify ( url ) } `
473
- shouldRemove = true
472
+ }
473
+
474
+ if ( isModule ) {
475
+ inlineModuleIndex ++
476
+ if ( url && ! isExcludedUrl ( url ) && ! isPublicFile ) {
477
+ setModuleSideEffectPromises . push (
478
+ this . resolve ( url , id )
479
+ . then ( ( resolved ) => {
480
+ if ( ! resolved ) {
481
+ return Promise . reject ( )
482
+ }
483
+ return this . load ( resolved )
484
+ } )
485
+ . then ( ( mod ) => {
486
+ // set this to keep the module even if `treeshake.moduleSideEffects=false` is set
487
+ mod . moduleSideEffects = true
488
+ } ) ,
489
+ )
490
+ // <script type="module" src="..."/>
491
+ // add it as an import
492
+ js += `\nimport ${ JSON . stringify ( url ) } `
493
+ shouldRemove = true
494
+ } else if ( node . childNodes . length ) {
495
+ const scriptNode =
496
+ node . childNodes . pop ( ) as DefaultTreeAdapterMap [ 'textNode' ]
497
+ const contents = scriptNode . value
498
+ // <script type="module">...</script>
499
+ const filePath = id . replace ( normalizePath ( config . root ) , '' )
500
+ addToHTMLProxyCache ( config , filePath , inlineModuleIndex , {
501
+ code : contents ,
502
+ } )
503
+ js += `\nimport "${ id } ?html-proxy&index=${ inlineModuleIndex } .js"`
504
+ shouldRemove = true
505
+ }
506
+
507
+ everyScriptIsAsync &&= isAsync
508
+ someScriptsAreAsync ||= isAsync
509
+ someScriptsAreDefer ||= ! isAsync
510
+ } else if ( url && ! isPublicFile ) {
511
+ if ( ! isExcludedUrl ( url ) ) {
512
+ config . logger . warn (
513
+ `<script src="${ url } "> in "${ publicPath } " can't be bundled without type="module" attribute` ,
514
+ )
515
+ }
474
516
} else if ( node . childNodes . length ) {
475
517
const scriptNode =
476
518
node . childNodes . pop ( ) as DefaultTreeAdapterMap [ 'textNode' ]
477
- const contents = scriptNode . value
478
- // <script type="module">...</script>
479
- const filePath = id . replace ( normalizePath ( config . root ) , '' )
480
- addToHTMLProxyCache ( config , filePath , inlineModuleIndex , {
481
- code : contents ,
482
- } )
483
- js += `\nimport "${ id } ?html-proxy&index=${ inlineModuleIndex } .js"`
484
- shouldRemove = true
485
- }
486
-
487
- everyScriptIsAsync &&= isAsync
488
- someScriptsAreAsync ||= isAsync
489
- someScriptsAreDefer ||= ! isAsync
490
- } else if ( url && ! isPublicFile ) {
491
- if ( ! isExcludedUrl ( url ) ) {
492
- config . logger . warn (
493
- `<script src="${ url } "> in "${ publicPath } " can't be bundled without type="module" attribute` ,
519
+ scriptUrls . push (
520
+ ...extractImportExpressionFromClassicScript ( scriptNode ) ,
494
521
)
495
522
}
496
- } else if ( node . childNodes . length ) {
497
- const scriptNode =
498
- node . childNodes . pop ( ) as DefaultTreeAdapterMap [ 'textNode' ]
499
- scriptUrls . push (
500
- ...extractImportExpressionFromClassicScript ( scriptNode ) ,
501
- )
502
523
}
503
524
}
504
525
505
526
// For asset references in index.html, also generate an import
506
527
// statement for each - this will be handled by the asset plugin
507
528
const assetAttrs = assetAttrsConfig [ node . nodeName ]
508
529
if ( assetAttrs ) {
509
- for ( const p of node . attrs ) {
510
- const attrKey = getAttrKey ( p )
511
- if ( p . value && assetAttrs . includes ( attrKey ) ) {
512
- if ( attrKey === 'srcset' ) {
513
- assetUrlsPromises . push (
514
- ( async ( ) => {
515
- const processedEncodedUrl = await processSrcSet (
516
- p . value ,
517
- async ( { url } ) => {
518
- const decodedUrl = decodeURI ( url )
519
- if ( ! isExcludedUrl ( decodedUrl ) ) {
520
- const result = await processAssetUrl ( url )
521
- return result !== decodedUrl
522
- ? encodeURIPath ( result )
523
- : url
524
- }
525
- return url
526
- } ,
527
- )
528
- if ( processedEncodedUrl !== p . value ) {
529
- overwriteAttrValue (
530
- s ,
531
- getAttrSourceCodeLocation ( node , attrKey ) ,
532
- processedEncodedUrl ,
530
+ const nodeAttrs : Record < string , string > = { }
531
+ for ( const attr of node . attrs ) {
532
+ nodeAttrs [ getAttrKey ( attr ) ] = attr . value
533
+ }
534
+ const shouldIgnore =
535
+ node . nodeName === 'link' && 'vite-ignore' in nodeAttrs
536
+ if ( shouldIgnore ) {
537
+ removeViteIgnoreAttr ( s , node . sourceCodeLocation ! )
538
+ } else {
539
+ for ( const attrKey in nodeAttrs ) {
540
+ const attrValue = nodeAttrs [ attrKey ]
541
+ if ( attrValue && assetAttrs . includes ( attrKey ) ) {
542
+ if ( attrKey === 'srcset' ) {
543
+ assetUrlsPromises . push (
544
+ ( async ( ) => {
545
+ const processedEncodedUrl = await processSrcSet (
546
+ attrValue ,
547
+ async ( { url } ) => {
548
+ const decodedUrl = decodeURI ( url )
549
+ if ( ! isExcludedUrl ( decodedUrl ) ) {
550
+ const result = await processAssetUrl ( url )
551
+ return result !== decodedUrl
552
+ ? encodeURIPath ( result )
553
+ : url
554
+ }
555
+ return url
556
+ } ,
533
557
)
534
- }
535
- } ) ( ) ,
536
- )
537
- } else {
538
- const url = decodeURI ( p . value )
539
- if ( checkPublicFile ( url , config ) ) {
540
- overwriteAttrValue (
541
- s ,
542
- getAttrSourceCodeLocation ( node , attrKey ) ,
543
- partialEncodeURIPath ( toOutputPublicFilePath ( url ) ) ,
558
+ if ( processedEncodedUrl !== attrValue ) {
559
+ overwriteAttrValue (
560
+ s ,
561
+ getAttrSourceCodeLocation ( node , attrKey ) ,
562
+ processedEncodedUrl ,
563
+ )
564
+ }
565
+ } ) ( ) ,
544
566
)
545
- } else if ( ! isExcludedUrl ( url ) ) {
546
- if (
547
- node . nodeName === 'link' &&
548
- isCSSRequest ( url ) &&
549
- // should not be converted if following attributes are present (#6748)
550
- ! node . attrs . some (
551
- ( p ) =>
552
- p . prefix === undefined &&
553
- ( p . name === 'media' || p . name === 'disabled' ) ,
567
+ } else {
568
+ const url = decodeURI ( attrValue )
569
+ if ( checkPublicFile ( url , config ) ) {
570
+ overwriteAttrValue (
571
+ s ,
572
+ getAttrSourceCodeLocation ( node , attrKey ) ,
573
+ partialEncodeURIPath ( toOutputPublicFilePath ( url ) ) ,
554
574
)
555
- ) {
556
- // CSS references, convert to import
557
- const importExpression = `\nimport ${ JSON . stringify ( url ) } `
558
- styleUrls . push ( {
559
- url,
560
- start : nodeStartWithLeadingWhitespace ( node ) ,
561
- end : node . sourceCodeLocation ! . endOffset ,
562
- } )
563
- js += importExpression
564
- } else {
565
- // If the node is a link, check if it can be inlined. If not, set `shouldInline`
566
- // to `false` to force no inline. If `undefined`, it leaves to the default heuristics.
567
- const isNoInlineLink =
575
+ } else if ( ! isExcludedUrl ( url ) ) {
576
+ if (
568
577
node . nodeName === 'link' &&
569
- node . attrs . some (
570
- ( p ) =>
571
- p . name === 'rel' &&
572
- parseRelAttr ( p . value ) . some ( ( v ) =>
573
- noInlineLinkRels . has ( v ) ,
574
- ) ,
575
- )
576
- const shouldInline = isNoInlineLink ? false : undefined
577
- assetUrlsPromises . push (
578
- ( async ( ) => {
579
- const processedUrl = await processAssetUrl (
580
- url ,
581
- shouldInline ,
578
+ isCSSRequest ( url ) &&
579
+ // should not be converted if following attributes are present (#6748)
580
+ ! ( 'media' in nodeAttrs || 'disabled' in nodeAttrs )
581
+ ) {
582
+ // CSS references, convert to import
583
+ const importExpression = `\nimport ${ JSON . stringify ( url ) } `
584
+ styleUrls . push ( {
585
+ url,
586
+ start : nodeStartWithLeadingWhitespace ( node ) ,
587
+ end : node . sourceCodeLocation ! . endOffset ,
588
+ } )
589
+ js += importExpression
590
+ } else {
591
+ // If the node is a link, check if it can be inlined. If not, set `shouldInline`
592
+ // to `false` to force no inline. If `undefined`, it leaves to the default heuristics.
593
+ const isNoInlineLink =
594
+ node . nodeName === 'link' &&
595
+ nodeAttrs . rel &&
596
+ parseRelAttr ( nodeAttrs . rel ) . some ( ( v ) =>
597
+ noInlineLinkRels . has ( v ) ,
582
598
)
583
- if ( processedUrl !== url ) {
584
- overwriteAttrValue (
585
- s ,
586
- getAttrSourceCodeLocation ( node , attrKey ) ,
587
- partialEncodeURIPath ( processedUrl ) ,
599
+ const shouldInline = isNoInlineLink ? false : undefined
600
+ assetUrlsPromises . push (
601
+ ( async ( ) => {
602
+ const processedUrl = await processAssetUrl (
603
+ url ,
604
+ shouldInline ,
588
605
)
589
- }
590
- } ) ( ) ,
591
- )
606
+ if ( processedUrl !== url ) {
607
+ overwriteAttrValue (
608
+ s ,
609
+ getAttrSourceCodeLocation ( node , attrKey ) ,
610
+ partialEncodeURIPath ( processedUrl ) ,
611
+ )
612
+ }
613
+ } ) ( ) ,
614
+ )
615
+ }
592
616
}
593
617
}
594
618
}
0 commit comments