实现小程序全局组件(基于uni-app,loader解决全局DOM的痛点)

740 阅读2分钟

背景

显示一个全局组件,在一般的终端是个小事情,奈何小程序不允许操作DOM。

uni也是一个笔者不怎么like的框架,奈何历史原因就偏偏选中了他。

为啥要突然注册一个全局组件,奈何** 的产品需求。

一两百个的页面引入如何处理,网上搜了一遍也,奈何还是无解。

于是,有了利用webpack的loader机制,在编译前完成一次转义的想法。

思考过程

1)webpack的配置

vue.config.js的配置,相信vue的小伙伴都认识他。

需求明显,明显是需要用loader进行转换。主要利用什么机制进行转换。首先要考虑,使用configureWebpack还是chainWebpack的适合。

configureWebpack更倾向简单文件转换:

image.png

手写chainWebpack而是更适合""

image.png

这里直接赋源码(全局引入global-test为例):

module.exports = {
  chainWebpack: config => {
    config.module
      .rule('vue')
      .use('./loaders/global-loader')
      .loader('./loaders/global-loader')
      .options({
        globalComponents: '<global-test></global-test>',
      })
      .end()
  },
}

2)全局引入组件

这个看uni-app官方文档就可以知道结果,只需改pages.json的去全局组件即可。

这里:

 "globalStyle": {
    "usingComponents": {
      "van-button": "/components/global/test",
     }
  }

3)代码如何转换

按照代码理解,我们应该去找template的第一个元素。因为vue2,必须dom层是个树状结构。所以, 我们应该找出第一个元素,在它的第一个子集,插入对应的全局组件。这里假设第一个元素为view,(如果该页面没有用view包裹的话,需要修改一下源码,或者要手动补充,这里满足98%以上的场景)

   source = source.replace(/<view(.*?)>/, s => s + config.globalComponents)

4)如何遍历所有路由

这里需要用到fs跟path,思路就是根据pages.json配置的路由,读取到对应的代码,再执行步骤3的转换。

const getFileMatchReg = function(publicPath, path) {
  const fullPath = Path__default['default'].join(publicPath, `/${path}`)
  const regStr = JSON.stringify(`^${fullPath}.?vue$`)
  const reg = new RegExp(regStr.substring(1, regStr.length - 1))
  return reg
}

/**
 * 获取 所有已注册的路由文件的正则规则(vue)
 * @returns
 */
const getRouteFileMatchRegAll = function(config) {
  try {
    const jsonStr = Fs__default['default'].readFileSync(Path__default['default'].join(process.env.UNI_INPUT_DIR, './pages.json'), 'utf8')
    const { pages, subPackages = [] } = JSON.parse(jsonStr)
    const list = []

    pages.forEach(({ path }) => {
      list.push(getFileMatchReg(process.env.UNI_INPUT_DIR, path))
    })

    subPackages.forEach(({ pages, root }) => {
      pages.forEach(({ path }) => {
        list.push(getFileMatchReg(process.env.UNI_INPUT_DIR, root + '/' + path))
      })
    })

    return list
  } catch (e) {
    console.log(e, '获取路由失败')
  }
}

5)完整loader代码

var Path = require('path')
var Fs = require('fs')

function _interopDefaultLegacy(e) {
  return e && typeof e === 'object' && 'default' in e ? e : { default: e }
}

var Path__default = /*#__PURE__*/ _interopDefaultLegacy(Path)
var Fs__default = /*#__PURE__*/ _interopDefaultLegacy(Fs)

/**
 * pages.json 在项目中的相对路径
 */

/**
 * 获取 文件匹配正则(vue)
 * @param {string} publicPath
 * @param {string} path
 * @returns
 */
const getFileMatchReg = function(publicPath, path) {
  const fullPath = Path__default['default'].join(publicPath, `/${path}`)
  const regStr = JSON.stringify(`^${fullPath}.?vue$`)
  const reg = new RegExp(regStr.substring(1, regStr.length - 1))
  return reg
}

/**
 * 获取 所有已注册的路由文件的正则规则(vue)
 * @returns
 */
const getRouteFileMatchRegAll = function(config) {
  try {
    const jsonStr = Fs__default['default'].readFileSync(Path__default['default'].join(process.env.UNI_INPUT_DIR, './pages.json'), 'utf8')
    const { pages, subPackages = [] } = JSON.parse(jsonStr)
    const list = []

    pages.forEach(({ path }) => {
      list.push(getFileMatchReg(process.env.UNI_INPUT_DIR, path))
    })

    subPackages.forEach(({ pages, root }) => {
      pages.forEach(({ path }) => {
        list.push(getFileMatchReg(process.env.UNI_INPUT_DIR, root + '/' + path))
      })
    })

    return list
  } catch (e) {
    console.log(e, '获取路由失败')
  }
}

function default(source) {
  const config = Object.assign({}, this.query)

  const routeFilePathRegList = getRouteFileMatchRegAll()

  // 匹配 路由文件
  if (routeFilePathRegList.some(reg => reg.test(this.resourcePath))) {
    source = source.replace(/<view(.*?)>/, s => s + config.ylGlobalComponents)
  }

  return source
}

exports.default = default

至此,一个无需手动引入DOM的全局组件,已经实现。