UNPKG

vite-plugin-vue-style-bundler

Version:

`Automatically extract the `css` in `Vue` components and bundle them into the `js` source code, and then automatically insert the style into the head at runtime.

1 lines 11.9 kB
{"version":3,"sources":["../src/parse.ts","../src/utils.ts","../src/less.ts","../src/sass.ts","../src/index.ts"],"names":["styleRegex","stylePropsRegex","parseStyleProps","str","matched","props","match","groupIndex","groups","name","value","parseStyles","code","clean","styles","css","setupScriptRegex","injectCodeToSetup","styleId","injectCode","shortHash","hash","i","char","less","less_default","options","compileStringAsync","sass_default","path","insertScopeId","scopeId","regex","rules","r","injectScopeId","openTag","template","closeTag","tag","src_default","opts","id","fileId","newCode","compiledCss","e"],"mappings":"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","sourcesContent":["//import MagicString from \"magic-string\";\r\n// 匹配样式样式块, 必须有bundle\r\nconst styleRegex = /\\<style(.*?)\\>([\\s\\S]*?)\\<\\/style\\>/gm\r\nconst stylePropsRegex = /\\b(\\w+)(\\s*(\\=)\\s*(([\\\"\\'])(.*?)\\5)?)?/gm\r\n\r\n\r\n/**\r\n * 解析样式标签中的属性\r\n * \r\n * props=\"bundle lang='ts' scoped\"\r\n * \r\n * =={bundle:true,lang:\"ts\",scoped:true}\r\n * \r\n */\r\nexport function parseStyleProps(str:string){\r\n \r\n let matched:RegExpExecArray | null;\r\n const props:Record<string,boolean | string > = {}\r\n \r\n while ((matched = stylePropsRegex.exec(str)) !== null) {\r\n // 这对于避免零宽度匹配的无限循环是必要的\r\n if (matched.index === stylePropsRegex.lastIndex) {\r\n stylePropsRegex.lastIndex++;\r\n }\r\n \r\n // 结果可以通过`m变量`访问。\r\n // @ts-ignore\r\n matched.forEach((match, groupIndex,groups) => {\r\n const name = groups[1] \r\n const value = groups[6]\r\n props[name] = value==undefined ? true : value=='true' ? true : value=='false' ? false : value\r\n });\r\n }\r\n // 默认scoped为true\r\n if(props.scoped===undefined) props.scoped = true\r\n return props\r\n}\r\nexport type StyleProps =Record<string,string | boolean>\r\nexport type StyleParseResult = [string, Array<[StyleProps, string]>]\r\n/**\r\n * \r\n * 使用styleRegex解析代码中的样式声明:\r\n * - 返回[[样式标签属性,样式内容],[样式标签属性,样式内容],...]\r\n * - 当clead=true时会清空code中的样式\r\n * \r\n * @param code \r\n * @param clean 解析来清空样式,因为将被注入到head \r\n */\r\nexport function parseStyles(code:string,clean:boolean=true):StyleParseResult{\r\n\r\n let styles: Array<[Record<string,string | boolean>, string]> = []; \r\n let match; \r\n \r\n // 使用正则表达式匹配所有的<style>标签 \r\n while ((match = styleRegex.exec(code)) !== null) { \r\n const props = parseStyleProps(match[1].trim())\r\n const css = match[2].trim()\r\n if(props.bundle) {\r\n styles.push([props, css]); \r\n if (clean) { \r\n code = code.replace(match[0], ''); \r\n // if(props.scoped){\r\n\r\n // code = code.replace(match[0], '<style scoped>:v{}</style>'); \r\n // }else{\r\n // code = code.replace(match[0], ''); \r\n // } \r\n } \r\n } \r\n } \r\n return [code,styles] \r\n\r\n}\r\n\r\nconst setupScriptRegex = /(\\<script\\s*.*?\\bsetup\\s*.*?\\>)([\\s\\S]*?)(\\<\\/script\\s*>)/gm\r\n\r\n/**\r\n * \r\n * @param id \r\n * @param props 样式style里面的属性,形式如{scoped:true}等,用来传递参数\r\n * @param css \r\n */\r\nexport function injectCodeToSetup(code:string,{styleId,css}:{styleId:string,props:StyleProps,css:string}){\r\n \r\n const matched = setupScriptRegex.exec(code)\r\n const injectCode = `\r\n const $insertStylesheet = (id,css)=>{\r\n let style = document.getElementById('${styleId}')\r\n if(!style){\r\n style = document.createElement(\"style\")\r\n style.id = '${styleId}'\r\n document.head.appendChild(style) \r\n style.innerHTML = css\r\n }\r\n }\r\n $insertStylesheet('${styleId}',\\`${css}\\`)\r\n `\r\n if(matched===null){\r\n return `<script setup>${injectCode}</script>\\n${code}`\r\n }else{\r\n return code.replace(setupScriptRegex,`$1${injectCode}\\n$2\\n$3`)\r\n } \r\n}","export function shortHash(str:string) {\r\n let hash = 0;\r\n if (str.length === 0) {\r\n return hash.toString();\r\n }\r\n for (let i = 0; i < str.length; i++) {\r\n const char = str.charCodeAt(i);\r\n hash = (hash << 5) - hash + char;\r\n hash = hash & hash; // 32-bit integer\r\n }\r\n return \"h\"+Math.abs(hash).toString(36).substring(0, 6);\r\n }","import less from \"less\"\r\n\r\nexport default async function (code:string,options:Less.Options){\r\n return (await less.render(code,options)).css\r\n}","import { StringOptions, compileStringAsync } from \"sass\"\r\n\r\nexport default async function (code:string,options:StringOptions<'async'>){ \r\n return (await compileStringAsync(code,options)).css\r\n}","import { injectCodeToSetup, parseStyles } from \"./parse\";\r\nimport { shortHash } from \"./utils\";\r\nimport less from \"./less\";\r\nimport sass from \"./sass\"; \r\nimport type { Plugin } from \"vite\"\r\nimport path from \"path\"\r\nexport interface StyleBundlerOptions {\r\n\tlessOptions?: any;\r\n\tsassOptions?: any;\r\n} \r\n\r\n/**\r\n * 在一段CSS代码中为所有规则注入scope属性\r\n * @param css \r\n * @param scopeId \r\n */\r\nfunction insertScopeId(css: string, scopeId: string) {\r\n const regex = /(?<=\\}|^)([^\\{\\}]+)(?=\\{)/gm; \r\n\t// @ts-ignore\r\n return css.replace(regex, (match:string, rules:string) => { \r\n return rules.split(\",\").map(r=>{\r\n\t\t\tr=r.trim()\r\n if(r.startsWith(\"@\")) return r\r\n const i = r.indexOf(\":\")\r\n if(i==-1){\r\n return \"\\n\"+r + `[data-l-${scopeId}]`\r\n }else{\r\n return \"\\n\"+r.slice(0,i) + `[data-l-${scopeId}]` + r.slice(i)\r\n }\r\n }).join(\",\") \r\n }); \r\n}\r\n \r\nfunction injectScopeId(code: string, scopeId: string) {\r\n\t// @ts-ignore\r\n\treturn code.replace(/(<template>)([\\s\\S]+)(<\\/template>)/gm, (matched: string,openTag:string, template: string,closeTag:string) => {\r\n\t\treturn `${openTag}${template.replace(/(?<!=[\\'\\\"\\\\])\\<\\w+\\s*/gm, (tag: string) => {\t\t\t\t\t \r\n\t\t\treturn `${tag} data-l-${scopeId} `\r\n\t\t})}${closeTag}`\r\n\t})\r\n}\r\n\r\nexport default (options?: StyleBundlerOptions):Plugin => {\r\n\tconst opts = Object.assign(\r\n\t\t{\r\n\t\t\tlessOptions: {},\r\n\t\t\tsassOptions: {},\r\n\t\t},\r\n\t\toptions\r\n\t);\r\n\r\n\treturn {\r\n\t\tname: \"vue-plugin-vue-style-bundler\",\r\n\t\tenforce: \"pre\",\r\n\t\tasync transform(code: string, id: string) {\r\n\t\t\t// 1. 只处理.vue文件\r\n\t\t\tif (!/.vue$/.test(id)) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\tconst fileId = shortHash(id);\r\n\t\t\t// 2. 检查是否有style\r\n\t\t\tlet [newCode, styles] = parseStyles(code);\r\n\t\t\t// 3. 获取需要捆绑的样式\r\n\t\t\tif (styles.length > 0) {\r\n\t\t\t\t// 在<script setup>中插入代码\r\n\t\t\t\tfor (const [props, css] of styles) {\r\n\t\t\t\t\tconst styleId = typeof props.bundle == \"string\" ? props.bundle : fileId; \r\n\t\t\t\t\tlet compiledCss = css; \r\n\t\t\t\t\tif (props.lang == \"less\") {\r\n\t\t\t\t\t\ttry{\r\n\t\t\t\t\t\t\tcompiledCss = await less(css, Object.assign({\r\n\t\t\t\t\t\t\t\tsyncImport:true,\r\n\t\t\t\t\t\t\t\trootPath:path.dirname(id),\r\n\t\t\t\t\t\t\t\tpaths:path.dirname(id)\r\n\t\t\t\t\t\t\t},opts.lessOptions));\r\n\t\t\t\t\t\t}catch(e){\r\n\t\t\t\t\t\t\tconsole.error(\"Error while compile less:\",e)\r\n\t\t\t\t\t\t\tthrow e\r\n\t\t\t\t\t\t}\t\t\t\t\t\t\r\n\t\t\t\t\t} else if ([\"sass\", \"scss\"].includes(props.lang as string)) {\r\n\t\t\t\t\t\ttry{\r\n\t\t\t\t\t\t\tcompiledCss = await sass(css, opts.sassOptions);\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t}catch(e){\r\n\t\t\t\t\t\t\tconsole.error(\"Error while compile sass:\",e)\r\n\t\t\t\t\t\t\tthrow e\r\n\t\t\t\t\t\t}\t\t\t\t\t\t\r\n\t\t\t\t\t} \r\n\t\t\t\t\tif(props.scoped){\r\n\t\t\t\t\t\t// 此时template还没有被编译,无法得到scopeId,先用styleId代替\r\n\t\t\t\t\t\tcompiledCss = insertScopeId(compiledCss, styleId); \r\n\t\t\t\t\t\t// 提取vue中的template内容\r\n\t\t\t\t\t\tnewCode = injectScopeId(newCode,styleId)\r\n\t\t\t\t\t}\r\n\t\t\t\t\tnewCode = injectCodeToSetup(newCode, { styleId, props, css:compiledCss });\r\n\t\t\t\t} \r\n\t\t\t\treturn newCode;\r\n\t\t\t} else {\r\n\t\t\t\treturn code;\r\n\t\t\t}\r\n\t\t},\r\n\t} \r\n}; \r\n\r\n "]}