@@ -211,11 +211,13 @@ export function getScriptInfo(node: DefaultTreeAdapterMap['element']): {
211211 sourceCodeLocation : Token . Location | undefined
212212 isModule : boolean
213213 isAsync : boolean
214+ isIgnored : boolean
214215} {
215216 let src : Token . Attribute | undefined
216217 let sourceCodeLocation : Token . Location | undefined
217218 let isModule = false
218219 let isAsync = false
220+ let isIgnored = false
219221 for ( const p of node . attrs ) {
220222 if ( p . prefix !== undefined ) continue
221223 if ( p . name === 'src' ) {
@@ -227,9 +229,11 @@ export function getScriptInfo(node: DefaultTreeAdapterMap['element']): {
227229 isModule = true
228230 } else if ( p . name === 'async' ) {
229231 isAsync = true
232+ } else if ( p . name === 'vite-ignore' ) {
233+ isIgnored = true
230234 }
231235 }
232- return { src, sourceCodeLocation, isModule, isAsync }
236+ return { src, sourceCodeLocation, isModule, isAsync, isIgnored }
233237}
234238
235239const attrValueStartRE = / = \s * ( .) /
@@ -260,6 +264,19 @@ export function overwriteAttrValue(
260264 return s
261265}
262266
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+
263280/**
264281 * Format parse5 @type {ParserError} to @type {RollupError}
265282 */
@@ -437,158 +454,165 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
437454
438455 // script tags
439456 if ( node . nodeName === 'script' ) {
440- const { src, sourceCodeLocation, isModule, isAsync } =
457+ const { src, sourceCodeLocation, isModule, isAsync, isIgnored } =
441458 getScriptInfo ( node )
442459
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 ) ) ,
469471 )
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+ }
474516 } else if ( node . childNodes . length ) {
475517 const scriptNode =
476518 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 ) ,
494521 )
495522 }
496- } else if ( node . childNodes . length ) {
497- const scriptNode =
498- node . childNodes . pop ( ) as DefaultTreeAdapterMap [ 'textNode' ]
499- scriptUrls . push (
500- ...extractImportExpressionFromClassicScript ( scriptNode ) ,
501- )
502523 }
503524 }
504525
505526 // For asset references in index.html, also generate an import
506527 // statement for each - this will be handled by the asset plugin
507528 const assetAttrs = assetAttrsConfig [ node . nodeName ]
508529 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+ } ,
533557 )
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+ } ) ( ) ,
544566 )
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 ) ) ,
554574 )
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 (
568577 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 ) ,
582598 )
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 ,
588605 )
589- }
590- } ) ( ) ,
591- )
606+ if ( processedUrl !== url ) {
607+ overwriteAttrValue (
608+ s ,
609+ getAttrSourceCodeLocation ( node , attrKey ) ,
610+ partialEncodeURIPath ( processedUrl ) ,
611+ )
612+ }
613+ } ) ( ) ,
614+ )
615+ }
592616 }
593617 }
594618 }
0 commit comments