import { existsSync , promises } from "fs" ; import { Plugin } from "esbuild" ; import { createRequire } from "module" ; import { compile } from "svelte/compiler" ; import { IconifyJSON } from "@iconify/types" ; import { iconToSVG } from "@iconify/utils/lib/svg/build" ; import { getIconData } from "@iconify/utils/lib/icon-set/get-icon" ; import { defaults as DefaultIconCustomizations } from "@iconify/utils/lib/customisations" ; import { warnOnce } from "./utils" ; const read = promises . readFile ; const SvelteCompiler = ( svg ) => { const openTagEnd = svg . indexOf ( ">" , svg . indexOf ( "<svg " ) ) ; const closeTagStart = svg . lastIndexOf ( "</svg" ) ; const openTag = `${ svg . slice ( 0 , openTagEnd ) } {...$$props}>` ; const content = escapeSvelte ( svg . slice ( openTagEnd + 1 , closeTagStart ) ) ; const closeTag = svg . slice ( closeTagStart ) ; return `${ openTag }${ content }${ closeTag }` ; } ; function escapeSvelte ( str ) { return str . replace ( /{/g , "&#123;" ) . replace ( /}/g , "&#125;" ) . replace ( /`/g , "&#96;" ) . replace ( /\\([trn])/g , " " ) ; } const require = createRequire ( import . meta . url ) ; const collections = { } ; async function loadCollection ( name ) { return ( collections [ name ] ||= task ( ) ) ; async function task ( ) { let jsonPath = require . resolve ( `@iconify-json/${ name }/icons.json` ) ; if ( jsonPath ) return JSON . parse ( await read ( jsonPath , "utf8" ) ) ; } } function mergeIconProps ( svg , props ) { return svg . replace ( "<svg" , `<svg ${ Object . keys ( props ) . map ( ( p ) => `${ p }="${ props [ p ] }"` ) . join ( " " ) }` ) ; } function searchForIcon ( iconSet , ids ) { for ( const id of ids ) { const iconData = getIconData ( iconSet , id , true ) ; if ( iconData ) { const { attributes , body } = iconToSVG ( iconData , { ... DefaultIconCustomizations , width : "1em" , height : "1em" , } ) ; return mergeIconProps ( `<svg>${ body }</svg>` , attributes ) ; } } } const validateFilterRE = /(?!\d|-{2}|-\d)[a-zA-Z0-9\u00A0-\uFFFF-_:%-?]/ ; function isValidSelector ( selector = "" ) { return validateFilterRE . test ( selector ) ; } function splitCode ( code ) { return code . split ( /[\s'"`;>=]+/g ) . filter ( isValidSelector ) ; } function isEqualSet < T > ( a , b ) { if ( a === b ) return true ; if ( a . size !== b . size ) return false ; for ( const e of a ) if ( ! b . has ( e ) ) return false ; return true ; } async function searchForIconSvg ( collection , icon ) { const ids = [ icon , icon . replace ( /([a-z])([A-Z])/g , "$1-$2" ) . toLowerCase ( ) , icon . replace ( /([a-z])(\d+)/g , "$1-$2" ) , ] ; const iconSet = await loadCollection ( collection ) ; return iconSet && searchForIcon ( iconSet , ids ) ; } function encodeSvg ( svg ) { return svg . replace ( "<svg" , '<svg xmlns="http://www.w3.org/2000/svg"' ) . replace ( /"/g , "'" ) . replace ( /%/g , "%25" ) . replace ( /#/g , "%23" ) . replace ( /{/g , "%7B" ) . replace ( /}/g , "%7D" ) . replace ( /</g , "%3C" ) . replace ( />/g , "%3E" ) ; } const classRE = /^i-(\w+)-([-\w]+)$/ ; async function generate ( cls , warn ) { const match = cls . match ( classRE ) ; if ( match ) { const [ , collection , icon ] = match ; const svg = await searchForIconSvg ( collection , icon ) ; if ( ! svg ) { warn && warnOnce ( `failed to load icon "${ cls }"` ) ; return ; } const mode = svg . includes ( "currentColor" ) ? "mask" : "background-img" ; const url = `url("data:image/svg+xml;utf8,${ encodeSvg ( svg ) }")` ; let css = [ [ "width" , "1em" ] , [ "height" , "1em" ] , [ "display" , "inline-block" ] , [ "vertical-align" , "middle" ] , ] ; if ( mode === "mask" ) { css = css . concat ( [ [ "--icon" , url ] , [ "mask" , "var(--icon) no-repeat" ] , [ "mask-size" , "100% 100%" ] , [ "-webkit-mask" , "var(--icon) no-repeat" ] , [ "-webkit-mask-size" , "100% 100%" ] , [ "background-color" , "currentColor" ] , [ "color" , "var(--fg)" ] , ] ) ; } else { css = css . concat ( [ [ "background" , url ] , [ "background-size" , "100% 100%" ] , [ "background-color" , "transparent" ] , ] ) ; } return ( `.${ cls . replace ( ":" , "\\:" ) } {\n` + `${ css . map ( ( [ k , v ] ) => `  ${ k }: ${ v };\n` ) . join ( "" ) }}` ) ; } } interface Options { ssr ? : boolean ; } /**
 * ```svelte
 * import Plus from "~icons/mdi/plus.svelte"
 * <div class="i-mdi-plus"><Plus /></div>
 * ```
 */ export function icons ( { ssr } = { } ) { let collected = new Set < string > ( ) ; let applied = new Set < string > ( ) ; const scanClass = async ( path ) => { if ( existsSync ( path ) ) { const text = await read ( path , "utf8" ) ; splitCode ( text ) . map ( ( e ) => ( e . startsWith ( "class:" ) ? e . slice ( 6 ) : e ) ) . filter ( ( e ) => e . startsWith ( "i-" ) ) . forEach ( ( e ) => collected . add ( e ) ) ; } } ; return { name : "icons" , setup ( { onResolve , onLoad , onStart , onEnd , esbuild , initialOptions } ) { onResolve ( { filter : /^~icons\/.+\.svelte$/ } , ( args ) => { return { path : args . path . slice ( 7 ) , namespace : "icons" , pluginData : { resolveDir : args . resolveDir } , } ; } ) ; onLoad ( { filter : /.+\.svelte$/ , namespace : "icons" } , async ( args ) => { const [ collection , icon ] = args . path . slice ( 0 , - 7 ) . split ( "/" ) ; const svg = await searchForIconSvg ( collection , icon ) ; if ( svg ) { const svelte = SvelteCompiler ( svg ) ; const filename = "/icons/" + args . path ; const { js } = compile ( svelte , { filename , generate : ssr ? "ssr" : "dom" , } ) ; js . map . sources = [ filename ] ; js . map . sourcesContent = [ svelte ] ; const contents = js . code + `\n//# sourceMappingURL=` + js . map . toUrl ( ) ; return { contents , resolveDir : args . pluginData . resolveDir } ; } } ) ; onStart ( ( ) => { collected . clear ( ) ; } ) ; onEnd ( async ( result ) => { if ( ! isEqualSet ( collected , applied ) ) { applied = new Set ( collected ) ; const buildOptions = { ... initialOptions } ; delete buildOptions . watch ; Object . assign ( result , await esbuild . build ( buildOptions ) ) ; } } ) ; onLoad ( { filter : /\.svelte$/ } , ( args ) => { scanClass ( args . path ) ; return null ; } ) ; onResolve ( { filter : /^icons\.css$/ } , ( args ) => { return { path : args . path , namespace : "icons" } ; } ) ; const warn = initialOptions . logLevel !== "silent" ; onLoad ( { filter : /^icons\.css$/ , namespace : "icons" } , async ( ) => { const tasks = [ ... applied ] . map ( ( e ) => generate ( e , warn ) ) ; const generated = ( await Promise . all ( tasks ) ) . filter ( Boolean ) . join ( "\n" ) ; const { code , warnings } = await esbuild . transform ( generated , { loader : "css" , sourcefile : "../src/icons.css" , sourcemap : "inline" , } ) ; return { contents : code , warnings , loader : "css" } ; } ) ; } , } ; }