├── README.md ├── index.js ├── package.json └── utils.js /README.md: -------------------------------------------------------------------------------- 1 | 2 | # vue-inset-loader 3 | #### 编译阶段在sfc模板指定位置插入自定义内容,适用于webpack构建的vue应用,常用于小程序需要全局引入组件的场景。(由于小程序没有开放根标签,没有办法在根标签下追加全局标签,所以要使用组件必须在当前页面引入组件标签) 4 | 5 | ### 第一步 安装 6 | 7 | npm install vue-inset-loader --save-dev 8 | 9 | ### 第二步 vue.config.js注入loader 10 | 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.vue$/, 15 | use:{ 16 | loader: "vue-inset-loader" 17 | // // 针对Hbuilder工具创建的uni-app项目 18 | // loader: path.resolve(__dirname,"./node_modules/vue-inset-loader") 19 | // // 支持自定义pages.json文件路径 20 | // options: { 21 | // pagesPath: path.resolve(__dirname,'./src/pages.json') 22 | // } 23 | } 24 | } 25 | ] 26 | }, 27 | 28 | ### 第三步 pages.json配置文件中添加insetLoader 29 | 30 | "insetLoader": { 31 | "config":{ 32 | "confirm": "", 33 | "abc": "" 34 | }, 35 | // 全局配置 36 | "label":["confirm"], 37 | "rootEle":"div" 38 | }, 39 | "pages": [ 40 | { 41 | "path": "pages/tabbar/index/index", 42 | "style": { 43 | "navigationBarTitleText": "测试页面", 44 | // 单独配置,用法跟全局配置一致,优先级高于全局 45 | "label": ["confirm","abc"], 46 | "rootEle":"div" 47 | } 48 | }, 49 | ] 50 | 51 | ### 配置说明 52 | 53 | - `config` (default: `{}`) 54 | 定义标签名称和内容的键值对 55 | - `label`(default: `[]`) 56 | 需要全局引入的标签,打包后会在所有页面引入此标签 57 | - `rootEle`(default: `"div"`) 58 | 根元素的标签类型,缺省值为div,支持正则,比如匹配任意标签 ".*" 59 | 60 | ✔ `label` 和 `rootEle` 支持在单独页面的style里配置,优先级高于全局配置 61 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { parseComponent } = require("../vue-template-compiler") 2 | const { 3 | generateHtmlCode, 4 | generateLabelCode, 5 | generateStyleCode, 6 | getPagesMap, 7 | initPages, 8 | getRoute 9 | } = require('./utils') 10 | 11 | // 是否初始化过 12 | let _init = false 13 | // 是否需要做处理 14 | let needHandle = false 15 | // 路由和配置的映射关系 16 | let pagesMap = {} 17 | 18 | module.exports = function(content) { 19 | if(!_init){ 20 | _init = true 21 | init(this) 22 | } 23 | 24 | // 配置无效不予处理 25 | if(!needHandle){ 26 | return content 27 | } 28 | 29 | // 获取当前文件的小程序路由 30 | const route = getRoute(this.resourcePath) 31 | // 根据路由并找到对应配置 32 | const curPage = pagesMap[route] 33 | if(curPage){ 34 | // 解析sfc 35 | const compiler = parseComponent(content) 36 | // 生成标签代码 37 | const labelCode = generateLabelCode(curPage.label) 38 | // 匹配标签位置 39 | const insertReg = new RegExp(`(<\/${curPage.ele}>$)`) 40 | // 在匹配的标签之前插入额外标签代码 41 | const templateCode = generateHtmlCode( 42 | compiler.template.content, 43 | labelCode, 44 | insertReg 45 | ) 46 | // 重组style标签及内容 47 | const style = generateStyleCode(compiler.styles || []) 48 | content = ` 49 | 52 | 55 | ${style} 56 | ` 57 | } 58 | return content 59 | } 60 | 61 | function init(that){ 62 | const isWx = process.env.VUE_APP_PLATFORM == 'mp-weixin' 63 | // 首次需要对pages配置文件做解析,并判断是否为有效配置 64 | // 非小程序环境或无效配置不予处理 65 | needHandle = isWx && initPages(that) 66 | // 转换为路由和配置的映射对象 67 | needHandle && (pagesMap = getPagesMap()) 68 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-inset-loader", 3 | "version": "1.2.6", 4 | "description": "在sfc模板指定位置插入自定义内容", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/1977474741/vue-inset-loader.git" 9 | }, 10 | "author": "鹅鹅鹅 <1977474741@qq.com>", 11 | "license": "ISC", 12 | "dependencies": { 13 | "vue-template-compiler": "^2.6.10", 14 | "strip-json-comments": "^2.0.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const stripJsonComments = require("../strip-json-comments") 4 | 5 | // 反序列化后的pages.json对象 6 | let pagesJson = {} 7 | // 此loader配置对象 8 | let insetLoader = {} 9 | // pages.json文件所在目录 10 | let rootPath = process.env.UNI_INPUT_DIR || (process.env.INIT_CWD + '\\src') 11 | 12 | 13 | // 在template中用正则匹配并替换一段代码 14 | const generateHtmlCode = (template,labelCode,regLabel)=>{ 15 | // 去除html所有注释和首尾空白 16 | const regNotes = //g 17 | const regBlank = /^\s+|\s+$/g 18 | return template 19 | .replace(regNotes,'') 20 | .replace(regBlank,'') 21 | .replace(regLabel,`${labelCode}$1`) 22 | } 23 | 24 | // 获取到需要插入的所有label标签 25 | const generateLabelCode = (labelArr) => labelArr.map(e => insetLoader.config[e] || '').join('') 26 | 27 | // 根据compiler组合成style标签字符串代码 28 | const generateStyleCode = (styles)=>styles.reduce((str,item,i)=>{ 29 | return str += `` 32 | },'') 33 | 34 | // 分析pages.json,生成路由和配置的映射对象 35 | const getPagesMap = ()=>{ 36 | // 获取主包路由配置 37 | const pages = pagesJson.pages || [] 38 | const subpackages = pagesJson.subpackages || pagesJson.subPackages || [] 39 | return pages.reduce((obj,item)=>{ 40 | const curPage = getLabelConfig(item) 41 | curPage.label && (obj['/'+item.path] = curPage) 42 | return obj 43 | },subpackages.reduce((obj,item)=>{ 44 | // 获取分包路由配置 45 | const root = item.root 46 | item.pages.forEach((item)=>{ 47 | const curPage = getLabelConfig(item) 48 | curPage.label && (obj['/'+root+'/'+item.path] = curPage) 49 | }) 50 | return obj 51 | },{})) 52 | } 53 | 54 | // 生成path对应的对象结构 55 | const getLabelConfig = (json)=>{ 56 | return { 57 | label: (json.style && json.style.label) || insetLoader.label, 58 | ele: (json.style && json.style.rootEle) || insetLoader.rootEle 59 | } 60 | } 61 | 62 | // 反序列化page.json并缓存, 63 | // 并根据page.json分析是否有效并且需要后续逻辑处理 64 | const initPages = (that)=>{ 65 | let pagesPath = (that.query || {}).pagesPath 66 | if(!pagesPath){ 67 | // 默认读取pages.json 68 | pagesPath = path.resolve(rootPath, 'pages.json') 69 | }else{ 70 | // 如有传自定义pagesPath,则截取出所在目录作为rootPath,用于后续匹配路由 71 | rootPath = path.resolve(pagesPath,'../') 72 | } 73 | pagesJson = JSON.parse(stripJsonComments(fs.readFileSync(pagesPath,'utf8'))) 74 | return initInsetLoader() 75 | } 76 | 77 | 78 | // 给非必填项设置缺省值,缺少主要对象返回false 79 | initInsetLoader = ()=>{ 80 | insetLoader = pagesJson.insetLoader || {} 81 | // label:全局标签配置 82 | // rootEle:根元素的类型,也支持正则,如匹配任意标签.* 83 | insetLoader.label = insetLoader.label || [] 84 | insetLoader.rootEle = insetLoader.rootEle || "div" 85 | 86 | // config对象为空视为无效配置 87 | const effective = typeof insetLoader.config == 'object' && Object.keys(insetLoader.config).length 88 | return effective 89 | } 90 | 91 | // 根据resourcePath获取路由 92 | const getRoute = (resourcePath) => resourcePath 93 | .replace(rootPath,'') 94 | .replace('.vue','') 95 | .replace(/\\/g,'/') 96 | 97 | module.exports = { 98 | generateHtmlCode, 99 | generateLabelCode, 100 | generateStyleCode, 101 | initInsetLoader, 102 | getPagesMap, 103 | initPages, 104 | getRoute 105 | } --------------------------------------------------------------------------------