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

相關文章
相關標籤/搜索