Nuxt 挂载三方 js 脚本插件

Nuxt 挂载三方 js 脚本插件

原由

因公司的官网功能迁移,从原来的 SPA 应用迁移至 Nuxt 的 SSR 应用,牵扯到第三方客服脚本的加载,对前端不熟悉的美工特来求助,所以编写了一个 Vue 插件来救同事于水火之中。前端

TODO List

  • 挂载三方 js 脚本
  • 卸载三方 js 脚本
  • 提供全局方法
  • 初始化传入配置项
  • 使用 Vue.use 注册插件

挂载脚本

const PluginName = 'LoadScriptPlugin'
const prefix = `[${PluginName}] - `

/** @typedef {{name: string, src: string, selector?: string, defer?: boolean, async?: boolean}} ScriptOption */

/**
 * 建立并挂载脚本
 * @param {ScriptOption} option 挂载脚本选项
 * @returns {void}
 */
function createScript(option = {}) {
  if (typeof option !== 'object' || option === null) {
    option = {}
  }

  const scriptId = `${prefix}${name}`
  const script = document.createElement('script')
  const parentEl = document.querySelector(selector) || document.body

  script.id = scriptId
  script.src = option.src
  script.defer = option.defer
  script.async = option.async
  
  parentEl.append(script)
}
复制代码

上面代码经过 createElement 建立了 script 标签,并经过接收外部传递的参数来设置 script 的属性实现挂载脚本的过程。vue

为了方便后面卸载脚本,咱们还需记录每次挂载脚本的元素及自身元素。添加以下代码:markdown

/**
 * 全部脚本信息
 * @description
 * parent: 父级元素
 * self: 自身元素(脚本元素)
 *
 * @type {{parent: HTMLElement, self: HTMLElement}}
 */
const scriptMap = {}

function createScript(option = {}) {
  // 尾部追加
  + scriptMap[scriptId] = {
  +  parent: parentEl,
  +  self: script
  + }
}
复制代码

销毁脚本

咱们上面经过 scriptMap 记录了每次挂载脚本的父级元素和自身元素,下面咱们经过建立接收 name 标识的函数来销毁脚本。app

/**
 * 销毁脚本
 * @param {string} name 脚本名称
 * @returns {string}
 */
function destroyScript(name) {
  const scriptId = `${prefix}${name}`
  const scriptInfo = scriptMap[scriptId]

  scriptInfo.parent.removeChild(scriptInfo.self)
  delete scriptMap[scriptId]
}
复制代码

定义插件

上面咱们经将插件的基本功能实现完成,可是尚未看到咱们定义的插件在哪里,接下来须要让插件具象化,同时为 Vue.use 暴露安装接口。async

/**
 * Vue 自动挂载三方脚本插件
 * @example
 * Vue.use(Plugin, {})
 * // or
 * Vue.use(Plugin, [{}])
 */
const Plugin = {
  /**
   * 插件名
   */
  name: PluginName,
  /**
   * 安装记录
   */
  installed: false,
  /**
   * 安装行为
   * @param {Vue} Vue
   * @param {ScriptOption|ScriptOption[]|null} options
   * @returns {void}
   */
  install(Vue, options) {
    if (Plugin.installed || Vue.$isServerreturn

    if (options) {
      options = Array.isArray(options) ? options : [options]
      options.forEach((opt) => createScript(opt))
    }
    
    Plugin.installed = true

    Vue.prototype.$createScript = createScript
    Vue.prototype.$destroyScript = destroyScript
  }
}
复制代码

上面代码定义咱们插件的基本信息和安装接口,后面能够经过默认导出便可使用 Vue.use(LoadScriptPlugin) 进行注册,而且能够接收 options 选项来在初始化时进行挂载操做。ide

经过为 Vue 原型挂载咱们的脚本函数来方便在后续的业务中动态使用挂载和销毁脚本功能。函数

处理边界

到这里咱们的插件基本已经实现完成,可是仍然存在使用隐患,由于咱们开发出来是为其余人员提供便利操做的,在不能完整了解的状况下使用时可能会出现运行时报错,因此须要对接收参数作严格校验。ui

function log(...messages) {
  console.log(prefix, ...messages)
}

function warn(...messages) {
  console.warn(prefix, ...messages)
}

/**
 * 建立并挂载脚本
 * @param {ScriptOption} option 挂载脚本选项
 * @returns {void}
 */
function createScript(option = {}) {
  if (typeof option !== 'object' || option === null) {
    option = {}
  }

  if (['', null, void 0].includes(option.src)) {
    return warn('The src property of the option cannot be falsly value!')
  }

  if (['', null, void 0].includes(option.name)) {
    return warn(
      'The name property of the option cannot be falsly value! The name property will be used to identify the current script!'
    )
  }

  const scriptId = getScriptId(option.name)

  if (scriptId in scriptMap) {
    return warn('Duplicate name attribute, please re-enter!')
  }
  
  ...
  
  log(`The ${name} script been created!`)
}

/**
 * 销毁脚本
 * @param {string} name 脚本名称
 * @returns {string}
 */
function destroyScript(name) {
  ...
  
  if (!(scriptId in scriptMap) || scriptInfo === undefined) {
    return warn(`The script with name as ${name} does not exist!`)
  }

  ...
  
  log(`The ${name} script been destroyed!`)
}
复制代码

完整代码

import Vue from 'vue'

const PluginName = 'LoadScriptPlugin'
const prefix = `[${PluginName}] - `

/**
 * 全部脚本信息
 * @description
 * parent: 父级元素
 * self: 自身元素(脚本元素)
 *
 * @type {{parent: HTMLElement, self: HTMLElement}}
 */
const scriptMap = {}

function log(...messages) {
  console.log(prefix, ...messages)
}

function warn(...messages) {
  console.warn(prefix, ...messages)
}

/**
 * 获取须要挂载的父级元素
 * @param {string} selector 选择器
 * @returns {HTMLElement}
 */
function getParentEl(selector) {
  let el = null
  if (selector) el = document.querySelector(selector)
  return el || document.body
}

/**
 * 获取脚本惟一标识
 * @param {string} name 脚本名称
 * @returns {string}
 */
function getScriptId(name) {
  return `${prefix}${name}`
}

/** @typedef {{name: string, src: string, selector?: string, defer?: boolean, async?: boolean}} ScriptOption */

/**
 * 建立并挂载脚本
 * @param {ScriptOption} option 挂载脚本选项
 * @returns {void}
 */
function createScript(option = {}) {
  if (typeof option !== 'object' || option === null) {
    option = {}
  }

  if (['', null, void 0].includes(option.src)) {
    return warn('The src property of the option cannot be falsly value!')
  }

  if (['', null, void 0].includes(option.name)) {
    return warn(
      'The name property of the option cannot be falsly value! The name property will be used to identify the current script!'
    )
  }

  const scriptId = getScriptId(option.name)

  if (scriptId in scriptMap) {
    return warn('Duplicate name attribute, please re-enter!')
  }

  const script = document.createElement('script')
  const parentEl = getParentEl(option.selector)

  script.id = scriptId
  script.src = option.src
  script.defer = option.defer
  script.async = option.async

  parentEl.append(script)
  scriptMap[scriptId] = {
    parent: parentEl,
    self: script
  }

  log(`The ${name} script been created!`)
}

/**
 * 销毁脚本
 * @param {string} name 脚本名称
 * @returns {string}
 */
function destroyScript(name) {
  const scriptId = getScriptId(name)
  const scriptInfo = scriptMap[scriptId]

  if (!(scriptId in scriptMap) || scriptInfo === undefined) {
    return warn(`The script with name as ${name} does not exist!`)
  }

  scriptInfo.parent.removeChild(scriptInfo.self)
  delete scriptMap[scriptId]

  log(`The ${name} script been destroyed!`)
}

/**
 * Vue 自动挂载三方脚本插件
 * @example
 * Vue.use(Plugin, {})
 * // or
 * Vue.use(Plugin, [{}])
 */
const Plugin = {
  /**
   * 插件名
   */
  name: PluginName,
  /**
   * 安装记录
   */
  installed: false,
  /**
   * 安装插件
   * @param {Vue} Vue
   * @param {ScriptOption|ScriptOption[]|null} options
   * @returns {void}
   */
  install(Vue, options) {
    if (Plugin.installed || Vue.$isServerreturn

    if (options) {
      options = Array.isArray(options) ? options : [options]
      options.forEach((opt) => createScript(opt))
    }

    Plugin.installed = true

    Vue.prototype.$createScript = createScript
    Vue.prototype.$destroyScript = destroyScript
  }
}

// export default Plugin // 导出插件入口

// Nuxt plugin
Vue.use(Plugin, [
  /** 加载客服脚本 */
  {
    name: 'customService',
    src: 'xxx
  }
])
复制代码

最后

每个问题的解决,都为我实现财富自由前进了一步!lua

初次写文章,不喜勿喷,有问题欢迎评论区留言交流!url

相关文章
相关标签/搜索