how to define and register a custom lable in Vue in google chrome extension

I am using vue "vue": "^1.0.24" to develop a google chrome extension right now. This is my widget template.html file:

<st-div id="__selection-translator__">
  <st-div class="__st-box__" v-el:st-box :style="boxStyle">
    <st-header v-if="!inline" v-el:st-drag>
      <st-span class="st-icon-pin" title="固定" @click="pinned=!pinned" :class="{'__pinned__':pinned}"></st-span>
      <st-span class="st-icon-down-open" :class="{'__open__':showForm}" :title="showForm?'收起':'展开'" @click="showForm=!showForm"></st-span>
      <st-span class="st-icon-cog" title="设置" @click="openOptions"></st-span>
    </st-header>
    <st-div class="__query-form__" v-show="showForm">
      <st-div>
        <textarea placeholder="输入要翻译的句子或单词" v-model="query.text" v-el:textarea @keyup.enter="ctrlEnter" @keyup="safeTranslate | debounce 600"></textarea>
      </st-div>
      <st-div>
        <select v-model="query.from" @change="safeTranslate">
          <option value="">自动判断</option>
          <option v-for="locale in locales" track-by="localeId" :value="locale.localeId" v-text="locale['zh-CN']"></option>
        </select>
        <st-div class="__exchange__">
          <st-span class="st-icon-exchange" @click="exchangeLocale"></st-span>
        </st-div>
        <select v-model="query.to" @change="safeTranslate">
          <option value="">自动选择</option>
          <option v-for="locale in locales" track-by="localeId" :value="locale.localeId" v-text="locale['zh-CN']"></option>
        </select>
      </st-div>
      <st-div>
        <select v-model="query.api" @change="safeTranslate">
          <option value="YouDao">有道翻译</option>
          <option value="BaiDu">百度翻译</option>
          <!-- <option value="Bing">必应翻译</option> -->
          <option value="Google">谷歌翻译</option>
          <option value="GoogleCN">谷歌翻译(国内)</option>
          <option value="Reddwarf">红矮星翻译</option>
        </select>
        <st-div class="__action-list__">
          <st-div class="__button__ __btn-translate__" @click="safeTranslate">翻译
            <st-span class="st-icon-down-dir"></st-span>
          </st-div>
          <st-div class="__expand__">
            <st-div class="__button__" @click="play(query.text,query.from)">朗读</st-div>
            <st-div class="__button__" @click="copy(query.text,$event)">复制</st-div>
          </st-div>
        </st-div>
      </st-div>
    </st-div>
    <st-div class="__translate-result__" v-show="loading">正在查询,请稍候……</st-div>
    <st-div class="__translate-result__" v-show="showResult && !loading">
      <st-div v-show="result.error">
        <st-span v-text="result.error"></st-span>
        <st-span class="__retry__" @click="safeTranslate">重试</st-span>
      </st-div>
      <st-div v-else>
        <st-div class="__phonetic__">
          <st-span v-show="result.phonetic" v-text="result.phonetic"></st-span>
          <st-span class="__copy-and-read__">
            <st-span @click="play(query.text,query.from)">朗读</st-span>
            <st-span v-show="result.phonetic" @click="copy(result.phonetic,$event)">复制</st-span>
            <st-span v-show="result.phonetic" @click="addGlossary(query.text,query.from)">添加到单词本</st-span>
          </st-span>
        </st-div>
        <st-div v-show="result.dict && result.dict.length">
          <st-ul>
            <st-li v-for="d in result.dict" v-text="d"></st-li>
          </st-ul>
          <st-div class="__copy-and-read__">
            <st-span class="__copy-and-read__" @click="copy(result.dict,$event)">复制</st-span>
          </st-div>
        </st-div>
        <st-div v-show="result.result&&result.result.length">
          <st-div v-for="text in result.result" v-text="text"></st-div>
          <st-div class="__copy-and-read__">
            <st-span class="__copy-and-read__" @click="play(result.result,result.to)">朗读</st-span>
            <st-span class="__copy-and-read__" @click="copy(result.result,$event)">复制</st-span>
          </st-div>
        </st-div>
      </st-div>
    </st-div>
    <st-footer>
      <st-span v-show="!loading && apiName">via
        <a :href="result.link" target="_blank" v-text="apiName"></a></st-span>
    </st-footer>
  </st-div>
  <st-div class="__st-btn__" v-el:st-btn :style="btnStyle">译</st-div>
</st-div>

when I run this extension, the console show warning like this:

vue.common.js:1137 [Vue warn]: Unknown custom element: <st-span> - did you register the component correctly? For recursive components, make sure to provide the "name" option.

and this is index.js file:

/**
 * @files 基础 ST 组件,内容脚本和弹出页都会用到
 */

import '../fontello/css/selection-translator.css';
import './style.scss';
import Vue from 'vue';
import widgetMixin from './vue-st';
import chromeCall from 'chrome-call';

import locales from '../locales';
import template from './template.html';
// const request = require('superagent');



// 去掉 locales 里的 *-* 类语种,除了 zh-CN、zh-TW 和 zh-HK(百度翻译里的粤语)
const translateLocales = [];

locales.forEach( locale => {
  const {localeId} = locale;

  if ( !localeId.includes( '-' ) || ( localeId === 'zh-CN' || localeId == 'zh-TW' || localeId == 'zh-HK' ) ) {
    translateLocales.push( locale );
  }
} );

const resolvedEmptyPromise = Promise.resolve() ,
  noop = ()=> {};

/**
 * 翻译窗口的基础 Vue 构造函数。
 * 注意:这个构造函数需要一个额外的 options:client
 */
export default Vue.extend( {
  template ,
  data : ()=>({
    access_token: '', // 扇贝单词授权 token
    locales : translateLocales ,
    showForm : false ,
    query : {
      text : '' ,
      from : '' ,
      to : '' ,
      api : ''
    } ,
    result : {
      error : '' ,
      phonetic : '' ,
      dict : [] ,
      result : [] ,
      link : '',
    }
  }) ,
  created() {
    this.$options.client.on( 'disconnect' , ()=> {
      this.result = {
        error : 'index连接到翻译引擎时发生了错误,请刷新网页或重启浏览器后再试。'
      }
    } );
  } ,
  computed : {
    apiName() {
      return {
        YouDao: '有道翻译',
        Google: '谷歌翻译',
        GoogleCN: '谷歌翻译(国内)',
        BaiDu: '百度翻译',
        Reddwarf: '红矮星翻译'
      }[this.query.api] || ''
    }
  },
  methods : {

    /**
     * 翻译快捷键:Ctrl + Enter
     * @param event
     */
    ctrlEnter( event ) {
      if ( event.ctrlKey ) {
        this.safeTranslate();
      }
    } ,

    /**
     * 仅当有文本时才翻译
     */
    safeTranslate() {
      if ( this.query.text.trim() ) {
        this.translate();
      }
    } ,

    /**
     * 从后台网页获取查询结果
     * @returns {Promise}
     */
    getResult() {
      if ( this.$options.client.disconnected ) {
        return resolvedEmptyPromise;
      }
      return this.$options.client
        .send( 'get translate result' , this.query , true )
        .then( resultObj => {
          debugger;
          if ("200" !==resultObj.response.statusCode||"200" !== resultObj.response.resultCode) {
            let errMsg = {
              NETWORK_ERROR: '网络错误,请检查你的网络设置。',
              API_SERVER_ERROR: '接口返回了错误的数据,请稍候重试。',
              UNSUPPORTED_LANG: '不支持的语种,请使用谷歌翻译重试。',
              NETWORK_TIMEOUT: '查询超时:5 秒内没有查询到翻译结果,已中断查询。'
            }[resultObj.code]
            if (resultObj.error) {
              errMsg += resultObj.error
            }
            this.result = {error: errMsg}
          } else {
            const {phonetic} = resultObj;
            this.result = resultObj;
            this.result.error = '';
            this.result.phonetic = resultObj.response.result.translation;
          }
        } , noop );
      // 只有在一种特殊情况下才会走进 catch 分支:
      // 消息发送出去后但还没得到响应时就被后台断开了连接.
      // 不过出现这种情况的可能性极低.
    } ,

    /**
     * 交换源语种与目标语种
     */
    exchangeLocale() {
      const {to,from} = this.query;
      this.query.to = from;
      this.query.from = to;
    } ,

    /**
     * 打开设置页
     */
    openOptions() {
      this.$options.client.send( 'open options' );
    } ,

    /**
     * 复制文本
     * @param {String|String[]} textOrTextArray
     * @param {MouseEvent} event
     */
    copy( textOrTextArray , event ) {
      if ( Array.isArray( textOrTextArray ) ) {
        textOrTextArray = textOrTextArray.join( 'n' );
      }
      this.$options.client.send( 'copy' , textOrTextArray );

      const {target} = event ,
        original = target.textContent;
      target.textContent = '已复制';
      setTimeout( ()=> target.textContent = original , 2000 );
    } ,
    /**
     * 添加单词
     * @param {String|String[]} textOrTextArray
     * @param {MouseEvent} event
     */
    addWord(text, event) {
      chromeCall('storage.local.get', ['access_token'])
        .then((res) => {
          if (res.access_token) {
            this.access_token = res.access_token;
            this.queryWord(text, event);
          } else {
            alert('未绑定扇贝账号,请授权绑定')
            this.gotoAccessToken();
          }
        });
    },
    /**
     * 添加单词
     * @param {String|String[]} textOrTextArray
     * @param {MouseEvent} event
     */
     addGlossary(text, event) {
      chromeCall('storage.local.get', ['reddwarf_access_token'])
        .then((res) => {
          if (res.access_token) {
            this.access_token = res.access_token;
            this.queryWord(text, event);
          } else {
            alert('未绑定红矮星账号,请授权绑定')
            this.gotoReddwarfAccessToken();
          }
        });
    },
    gotoAccessToken() {
      chrome.runtime.sendMessage({ action: 'shanbay_authorize' })
    },
    gotoReddwarfAccessToken() {
      chrome.runtime.sendMessage({ action: 'reddwarf_authorize' })
    },
    queryWord(text, event) {
      let params = { word: text, access_token: this.access_token }
      request.get('https://api.shanbay.com/bdc/search/')
        .query(params)
        .end((err, res) => {
          switch (res.status) {
            case 200:
              let info = res.body
              if (info.status_code == 0) {
                this.realAddWord(info.data.id, event);
              } else {
                alert(`查词错误, ${info.msg}`)
              }
              break;
            case 401:
              alert('token 失效,请重新授权')
              this.gotoAccessToken()
              break;
            case 429:
              alert('今日请求次数过多')
              break;
            default:
              alert(`未知错误, ${err}`)
              break;
          }
        })
    },

    realAddWord(id, event) {
      let params = { id: id, access_token: this.access_token }
      request.post('https://api.shanbay.com/bdc/learning/')
        .type('form')
        .send(params)
        .end((err, res) => {
          switch (res.status) {
            case 200:
              let info = res.body
              if (info.status_code == 0) {
                const { target } = event;
                let original = target.textContent;
                target.textContent = '已添加';
                setTimeout(() => target.textContent = original, 2000);
              } else {
                alert(`添加单词发生错误, ${info.msg}`)
              }
              break;
            default:
              alert(`添加单词发生错误, ${err}`)
              break;
          }
        })
    },

    /**
     * 播放语音
     * @param {String|String[]} textOrTextArray
     * @param {String} [lang] - 文本的语种
     */
    play( textOrTextArray , lang ) {
      if ( Array.isArray( textOrTextArray ) ) {
        textOrTextArray = textOrTextArray.join( 'n' );
      }
      this.$options.client.send( 'play' , {
        text : textOrTextArray ,
        api : this.query.api ,
        from : lang
      } );
    }
  } ,
  mixins : [ widgetMixin ]
} );

I read the docs that tell me should register the custom lable like this:

components : {
    'st-span' : <component>
  },

but I did not know how to implement the custom <component>, how to define the <component> of st-span/st-div/st-footer?