Vue中如何将组件字符串转换为真实组件以适配核心插件商店场景
Vue2 插件商店场景下字符串转动态组件实现方案
适配插件商店的前置优化建议
- 所有从核心端下发的组件资源需提前做签名校验,避免加载被篡改的恶意代码
- 对加载的组件做沙箱隔离,限制其可访问的全局API范围,规避安全风险
- 核心端可统一内置插件常用的公共组件、工具方法,避免组件重复打包依赖,减小下发资源体积
- 增加组件缓存机制,已解析过的组件可直接复用,无需重复编译
具体实现代码
核心解析逻辑
// 解析单文件组件字符串,拆分template、script、style部分 function parseSfc(sfcStr) { // 提取模板 const templateMatch = sfcStr.match(/<template>([\s\S]*?)<\/template>/); const template = templateMatch ? templateMatch[1].trim() : ''; // 提取脚本 const scriptMatch = sfcStr.match(/<script>([\s\S]*?)<\/script>/); const scriptContent = scriptMatch ? scriptMatch[1].trim() : ''; // 提取样式 const styleMatch = sfcStr.match(/<style[^>]*>([\s\S]*?)<\/style>/); const styleContent = styleMatch ? styleMatch[1].trim() : ''; // 检测是否是scoped样式 const isScoped = /<style[^>]*scoped[^>]*>/.test(sfcStr); return { template, scriptContent, styleContent, isScoped }; } // 字符串转Vue组件 function stringToComponent(sfcStr) { const { template, scriptContent, styleContent, isScoped } = parseSfc(sfcStr); // 解析脚本配置,沙箱环境下可替换window为代理对象做权限限制 const scriptFn = new Function('exports', 'require', 'module', scriptContent); const module = { exports: {} }; // 可自定义require方法,注入核心侧提供的公共依赖/组件 scriptFn(module.exports, () => {}, module); const componentOptions = module.exports.default || module.exports; // 编译模板 componentOptions.template = template; // 处理scoped样式 if (isScoped && styleContent) { // 生成唯一scoped标识 const scopeId = `data-v-${Math.random().toString(36).slice(2, 10)}`; componentOptions._scopeId = scopeId; // 给样式选择器加上scopeId属性 const scopedStyle = styleContent.replace(/([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)/g, (match, selector, suffix) => { return `${selector}[${scopeId}] ${suffix}`; }); // 动态插入样式 const styleTag = document.createElement('style'); styleTag.textContent = scopedStyle; document.head.appendChild(styleTag); } else if (styleContent) { // 非scoped样式直接插入 const styleTag = document.createElement('style'); styleTag.textContent = styleContent; document.head.appendChild(styleTag); } // 返回Vue组件 return Vue.extend(componentOptions); }
调用示例
// 组件字符串示例 let componentAsString = `<template> <div> <h1 class="red">{{title}}</h1> </div> </template> <script> export default { name: 'App', data(){ return { title:'Hello World' } } } </script> <style scoped> .red{color:red;} </style>`; // 转换为组件并挂载 const DynamicComponent = stringToComponent(componentAsString); new DynamicComponent().$mount('#app');
<!-- 页面DOM示例 --> <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> <div id="app"></div>
注意事项
- 如果使用scss等预处理器语法,建议提前在核心侧把样式编译为原生css后再下发,避免插件侧增加编译负担
- 若要支持组件内引用其他子组件,可在核心侧提供公共组件注册能力,解析脚本时将公共组件注入到组件配置的components字段中
- 生产环境建议提前在核心侧把单文件组件编译为render函数后再下发,省去插件侧编译模板的性能开销,同时可以减小下发资源体积
内容的提问来源于stack exchange,提问作者EaBengaluru
相关产品推荐
相关产品推荐

