import parse5 from 'parse5' ; import traverse from 'parse5-traverse' ; import merge from 'lodash.merge' ; import shouldTriggerAttribute , { Attr } from '../helpers/shouldTriggerAttribute' ; import selectorsLibrary from '../selectorsLibrary' ; import replaceJs from './js' ; import replaceCss from './css' ; import htmlToAst from '../helpers/htmlToAst' ; import replaceString from './string' ; export type EspreeOptions = any ; export interface ReplaceHtmlOptions { sourceFile ? : string ; espreeOptions ? : EspreeOptions ; triggerClassAttributes ? : string [ ] ; triggerIdAttributes ? : string [ ] ; } const replaceHtml = ( code , opts = { } ) : string => { const defaultOptions = { espreeOptions : { } , triggerClassAttributes : [ ] , triggerIdAttributes : [ ] , } ; const options = merge ( { } , opts , defaultOptions ) ; const ast = htmlToAst ( code ) ; const srcOpt = { sourceFile : opts . sourceFile } ; traverse ( ast , { pre : ( node ) => { if ( node . parentNode && node . parentNode . tagName === 'script' ) { const hasAnyAttrs = node . parentNode . attrs . length === 0 ; const hasType = node . parentNode . attrs . some ( ( attr ) => attr . name === 'type' ) ; const hasTypeAndJavaScript = ( node . parentNode . attrs . some ( ( attr ) => ( attr . name === 'type' && ( attr . value === 'application/javascript' || attr . value === 'module' ) ) ) ) ; if ( hasAnyAttrs || ! hasType || hasTypeAndJavaScript ) { node . value = replaceJs ( node . value , merge ( { } , options . espreeOptions , srcOpt ) ) ; } } if ( node . parentNode && node . parentNode . tagName === 'noscript' ) { node . value = replaceHtml ( node . value , opts ) ; } if ( node . parentNode && node . parentNode . tagName === 'style' ) { node . value = replaceCss ( node . value , srcOpt ) ; } if ( Array . isArray ( node . attrs ) && node . attrs . length >= 0 ) { node . attrs . forEach ( ( attr ) => { let selectorType ; const isVueClass = attr . name === 'v-bind:class' || attr . name === ':class' ; const isVueId = attr . name === 'v-bind:id' || attr . name === ':id' ; if ( attr . name === 'class' || isVueClass || options . triggerClassAttributes . some ( ( item ) => ( shouldTriggerAttribute ( attr , item ) ) ) ) { selectorType = '.' ; } if ( attr . name === 'id' || isVueId || options . triggerIdAttributes . some ( ( item ) => ( shouldTriggerAttribute ( attr , item ) ) ) ) { selectorType = '#' ; } if ( node . tagName === 'label' && attr . name === 'for' ) { selectorType = '#' ; } if ( ! selectorType ) { return ; } if ( isVueClass || isVueId ) { attr . value = attr . value . replace ( /'[\s\S]*?'|"[\s\S]*?"/g , ( match ) => { const replacedString = replaceString ( match , undefined , { forceReplace : true , source : { file : opts . sourceFile || '' , line : node . sourceCodeLocation . startLine , text : '' , } , } ) ; return replacedString . replace ( /"/g , "'" ) ; } ) ; } else { attr . value = attr . value . split ( ' ' ) . map ( ( value ) => ( selectorsLibrary . get ( `${ selectorType }${ value }` , { source : { file : opts . sourceFile || '' , line : node . sourceCodeLocation . startLine , text : '' , } , } ) . replace ( new RegExp ( `^\\${ selectorType }` ) , '' ) ) ) . join ( ' ' ) ; } } ) ; } } , } ) ; return parse5 . serialize ( ast ) ; } ; export default replaceHtml ;