vue 组件封装并发布npm包详解

封装组件

我这里选择的是表情的一个组件例子,npm 包名称 aimee-plugins-emoji


image.png

首先创建组件项目

通过Vue 脚手架创建简易webpage项目

vue init webpack-simple aimee-plugins-emoji

生成后的目录如下


image.png

安装依赖 并 运行

cd webpack-simple aimee-plugins-emoji
npm install
npm run dev

添加自己的组件
创建组件文件

<!-- 留言评论模块 -->
<template>
    <div class="box">
    <div class="face-box">
            <textarea name="face" ref="textareaBox" cols="30" rows="10" v-model="message" placeholder="说点什么呢``" ></textarea>
            <div class="face-sina">
                <div class="face-logo" @click="pBody=!pBody">
                    <span>sina表情</span>
                </div>
                <div class="face-sina-body" v-show="pBody">
                    <ul class="face-sina-items face-sina-items-show">
                      <li v-for="(item, index) in faceList" :key="index" class="face-sina-item" @click="choseHandle(item.title)">
                        <img :src="require('../../assets/emot/image/'+item.url)" alt="">
                      </li>
                    </ul>
                </div>
            </div>
    </div>
    <div class="face-show-box" v-html="messageshow">
    </div>
</div>
</template>

<script>
export default {
  name: 'aimee-emoji',
  data() { //选项 / 数据
    return {
      faceList:[//表情包和表情路径
          {'title':'微笑','url':'weixiao.gif'},
          {'title':'嘻嘻','url':'xixi.gif'},
          {'title':'哈哈','url':'haha.gif'},
          {'title':'可爱','url':'keai.gif'},
          {'title':'可怜','url':'kelian.gif'},
          {'title':'挖鼻','url':'wabi.gif'},
          {'title':'吃惊','url':'chijing.gif'},
          {'title':'害羞','url':'haixiu.gif'},
          {'title':'挤眼','url':'jiyan.gif'},
          {'title':'闭嘴','url':'bizui.gif'},
          {'title':'鄙视','url':'bishi.gif'},
          {'title':'爱你','url':'aini.gif'},
          {'title':'泪','url':'lei.gif'},
          {'title':'偷笑','url':'touxiao.gif'},
          {'title':'亲亲','url':'qinqin.gif'},
          {'title':'生病','url':'shengbing.gif'},
          {'title':'太开心','url':'taikaixin.gif'},
          {'title':'白眼','url':'baiyan.gif'},
          {'title':'右哼哼','url':'youhengheng.gif'},
          {'title':'左哼哼','url':'zuohengheng.gif'},
          {'title':'嘘','url':'xu.gif'},
          {'title':'衰','url':'shuai.gif'},
          {'title':'吐','url':'tu.gif'},
          {'title':'哈欠','url':'haqian.gif'},
          {'title':'抱抱','url':'baobao.gif'},
          {'title':'怒','url':'nu.gif'},
          {'title':'疑问','url':'yiwen.gif'},
          {'title':'馋嘴','url':'chanzui.gif'},
          {'title':'拜拜','url':'baibai.gif'},
          {'title':'思考','url':'sikao.gif'},
          {'title':'汗','url':'han.gif'},
          {'title':'困','url':'kun.gif'},
          {'title':'睡','url':'shui.gif'},
          {'title':'钱','url':'qian.gif'},
          {'title':'失望','url':'shiwang.gif'},
          {'title':'酷','url':'ku.gif'},
          {'title':'色','url':'se.gif'},
          {'title':'哼','url':'heng.gif'},
          {'title':'鼓掌','url':'guzhang.gif'},
          {'title':'晕','url':'yun.gif'},
          {'title':'悲伤','url':'beishang.gif'},
          {'title':'抓狂','url':'zhuakuang.gif'},
          {'title':'黑线','url':'heixian.gif'},
          {'title':'阴险','url':'yinxian.gif'},
          {'title':'怒骂','url':'numa.gif'},
          {'title':'互粉','url':'hufen.gif'},
          {'title':'书呆子','url':'shudaizi.gif'},
          {'title':'愤怒','url':'fennu.gif'},
          {'title':'感冒','url':'ganmao.gif'},
          {'title':'心','url':'xin.gif'},
          {'title':'伤心','url':'shangxin.gif'},
          {'title':'猪','url':'zhu.gif'},
          {'title':'熊猫','url':'xiongmao.gif'},
          {'title':'兔子','url':'tuzi.gif'},
          {'title':'喔克','url':'ok.gif'},
          {'title':'耶','url':'ye.gif'},
          {'title':'棒棒','url':'good.gif'},
          {'title':'不','url':'no.gif'},
          {'title':'赞','url':'zan.gif'},
          {'title':'来','url':'lai.gif'},
          {'title':'弱','url':'ruo.gif'},
          {'title':'草泥马','url':'caonima.gif'},
          {'title':'神马','url':'shenma.gif'},
          {'title':'囧','url':'jiong.gif'},
          {'title':'浮云','url':'fuyun.gif'},
          {'title':'给力','url':'geili.gif'},
          {'title':'围观','url':'weiguan.gif'},
          {'title':'威武','url':'weiwu.gif'},
          {'title':'话筒','url':'huatong.gif'},
          {'title':'蜡烛','url':'lazhu.gif'},
          {'title':'蛋糕','url':'dangao.gif'},
          {'title':'发红包','url':'fahongbao.gif'}
      ],
      pBody:false,//是否展示表情框
      message: '',
      lastposition: 0
    }
  },
  computed: {
    messageshow(){
      var pat01 = /\[[\u4e00-\u9fa5]+\]/g;
      var pat02 = /\[[\u4e00-\u9fa5]+\]/;
      var content = this.message.match(pat01);
      var str = this.message;
      if(content){
        for(var i=0;i<content.length;i++){
          for(var j=0;j<this.faceList.length;j++){
            if("["+this.faceList[j].title +"]" == content[i]){
                var src = this.faceList[j].url;
                break;
            }
          }
          str = str.replace(pat02,'<img src="'+require('../../assets/emot/image/'+src)+'"/>');
        }
      }
      return str
    }
  },
  methods: {
    getCursortPosition (ctrl) {
      var CaretPos = 0 // IE Support
      if (document.selection) {
        ctrl.focus()
        var Sel = document.selection.createRange()
        Sel.moveStart('character', -ctrl.value.length)
        CaretPos = Sel.text.length
      } else if (ctrl.selectionStart || ctrl.selectionStart == '0') { // Firefox support
        CaretPos = ctrl.selectionStart
      }
      return (CaretPos)
    },
    choseHandle(title) {
      var position = this.getCursortPosition(this.$refs.textareaBox)    
      this.message = this.message.substring(0, position) + '[' + title + ']' + this.message.substring(position, this.message.length)  
      this.pBody = false
    }
  }
}
</script>
<style lang="less">
  .box{
  width:600px;
  margin: 0 auto;
}
.face-box textarea{
  background:#f4f6f7;
  width:100%;
  height:100px;
  margin-bottom: 10px;
  border-radius: 5px;
  box-sizing: border-box;
}
.face-sina{
  position: relative;
  z-index: 1;
}
.face-sina .face-logo ,.face-sina .face-showbtn{
  position: relative;
  border-radius: 4px;
  color:#444;
  display: inline-block;
  background: #fff;
  border:1px solid #ddd;
  font-size: 13px;
  padding:0 6px;
  cursor: pointer;
  height:30px;
  box-sizing: border-box;
  z-index: 2;
  line-height: 30px;
  margin-right:20px;
  transition:all 0.3s ease;
}
.face-sina .face-logo:hover,.face-sina .face-showbtn:hover{
  background-color: #f4f6f7;
}
.face-sina .face-sina-body{
  position: absolute;
  background: #fff;
  border:1px solid #ddd;
  z-index: 1;
  top:29px;
  border-radius: 0 4px 4px 4px;

}
.face-sina-open .face-sina-body{
  display: block;
}
.face-sina-open .face-logo{
  border-radius: 4px 4px 0 0;

}
.face-sina-open .face-logo:hover{
  animation:none;
  -webkit-animation:none;
}
.face-sina .face-sina-items {
  max-height: 197px;
  overflow: scroll;
  font-size: 0;
  padding:10px;
  z-index: 1
}
.face-sina .face-sina-items .face-sina-item{
  background: #f7f7f7;
  padding:5px 10px;
  border-radius: 5px;
  display: inline-block;
  margin: 0 10px 12px 0;
  transition: 0.3s;
  line-height: 19px;
  font-size: 20px;
  cursor: pointer;
}
.face-sina .face-sina-items .face-sina-item:hover{
  background: #eee;
  box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),
  0 3px 1px -2px rgba(0,0,0,.2),
  0 1px 5px 0 rgba(0,0,0,.12);
  animation:a 5s infinite ease-in-out;
  -webkit-animation:a 5s infinite ease-in-out;
}
.face-show-box{
  width:100%;
  margin: 300px 0;
  padding:20px;
  border: 1px solid #999;
  background: #f4f6f7;
  box-sizing: border-box;
  border-radius: 5px;
  vertical-align: middle;
  img{
    vertical-align: middle;
  }
}

</style>

在main.js 入口文件的同级目录下,添加index.js 打包的入口文件

import emoji from './plugins/emoji/index.vue'

const components = [
  emoji
]
const install = function (Vue, opts = {}) {
  components.map(component => {
    Vue.component(component.name, component)
  })
}

/* 支持使用标签的方式引入 */
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}

export default {install, emoji}

可以在入口文件main.js 中正常引用

import aimeePluginsEmoji from './index.js'
Vue.use(aimeePluginsEmoji)

这样在项目中任何地方可以直接使用组件,如下我在app.vue 文件中直接引入<aimee-emoji></aimee-emoji>使用

<template>
  <div id="app">
    <aimee-emoji></aimee-emoji>
  </div>
</template>

<script>
export default {
  name: 'app',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

到这个地方项目已经创建完了 ,接下来就是修改配置文件

修改配置文件

主要修改的配置文件为 webpack.config.js 和 package.json

webpack.config.js设置

首先项目中因为有图片的引用,需要设置图片为base64,可以通过设置limit 的值来实现。

{
    test: /\.(png|jpg|gif|svg)$/,
        loader: 'url-loader',
        options: {
          limit: 50000000,
          name: '[name].[ext]?[hash]'
        }
      }

设置入口和输出路径

  entry: process.env.NODE_ENV === 'production'? './src/index.js' : './src/main.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/dist/',
    filename: 'aimee-plugins-emoji.js',
    library: 'aimee-plugins-emoji', // 指定的就是你使用require时的模块名
    libraryTarget: 'umd', // libraryTarget会生成不同umd的代码,可以只是commonjs标准的,也可以是指amd标准的,也可以只是通过script标签引入的
    umdNamedDefine: true, // 会对 UMD 的构建过程中的 AMD 模块进行命名。否则就使用匿名的 define
  },
package.json设置

设置private属性为false,默认为true,npm 发布的时候如果为true会失败。
添加node_module 的入口文件设置,"main": "dist/aimee-plugins-emoji.js"

  "private": false,
  "main": "dist/aimee-plugins-emoji.js",

上传到git 仓库管理

注意 这里gitignore中需要取消对dist文件夹的忽略
仓库管理项目文件

发布到npm

前提:需要注册npm 账号并激活

登录 npm login
image.png

需要看清楚是否是注册的registry,如果是淘宝的会报错

发布 npm publish
![image.png](https://upload-images.jianshu.io/upload_images/13238271-7d21ddfd4202bc15.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

生成后的npm包https://www.npmjs.com/package/aimee-plugins-emoji
如果要修改组件内容,需要修改package.json中的版本号,然后重新发布

在项目中使用

通过npm install aimee-plugins-emoji --save-dev 安装到本地
在 项目的main.js 中引用

import aimeePluginsEmoji from 'aimee-plugins-emoji'
Vue.use(aimeePluginsEmoji)

组件中可以通过<aimee-emoji></aimee-emoji> 使用

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容