发布自己的组件库到npm

1,263 阅读4分钟

最近学习了如何搭建自己的UI组件库并发布到npm,整理一下分享给大家。有写得不好的地方麻烦留言指点一下,谢谢~

一、创建组件库项目

我是通过vue-cli脚手架搭建的一个vue项目,搭建好之后在根目录下新建一个packages文件夹,用于存放自己封装的组件,在根目录下的src文件夹下新建一个docs文件夹,用于存放UI组件库的通过.md文件渲染的各个页面。在packages文件夹下新建一个index.js,作为抛出组件的出口;文件目录如下图所示:

image.png

image.png

image.png

这里以button为例封装个简单的组件,在packages文件夹下新建button文件夹,用于存放自己封装的button组件源代码,在button文件夹下新建一个src文件夹和index.js文件,index.js文件用于抛出button组件以便install下载安装使用,在packages/button/src路径下新建一个button.vue的文件用于编写xm-button组件,具体代码如下图所示:

image.png

image.png

注意:编写组件时,组件的name不可缺少,否则install组件失败!

编写完xm-button组件之后,继续编辑其他组件,和button一样创建单独的文件夹存放,统一在/packages/index.js文件抛出,使组件在UI库导入时能自动在Vue中注册我们的Component,将所写组件都统一导入进该文件里,代码如下图所示:

image.png

写好组件之后我们需要用markdown编写UI组件库的页面展示,在/packages/src/docs/路径下新建一个button.md文件,用于编写xm-button组件的展示页面,具体代码如下图所示:

image.png

页面效果如下图所示:

image.png

二、项目配置

UI组件库项目写好之后,就开始配置部署到npm上啦~
在根目录的/build文件夹下新建publish.js文件,用于编写实现一键部署发布到npm的功能代码,具体代码如下图所示,然后在根目录的package.json文件添加脚本命令publish,用于一键部署发布到npm上,如下图所示:

image.png

image.png

注意:版本号每次发布都需要更新

markdown的书写需要在webpack.base.conf.js配置,相关webpack的配置如下所示:

'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')

const MarkdownItContainer = require('markdown-it-container')
const striptags = require('./strip-tags')

const vueMarkdown = {
  preprocess: (MarkdownIt, source) => {
    // console.log("test-----",source,);
    //source代表文档所有内容
    MarkdownIt.renderer.rules.table_open = function () { //为说明文档表格添加样式
      return '<table class="table">'
    }
    //添加高亮显示
    MarkdownIt.renderer.rules.fence = utils.wrapCustomClass(MarkdownIt.renderer.rules.fence)

    // ```html `` 给这种样式加个class hljs
    //  但是markdown-it 有个bug fence整合attr的时候直接加载class数组上而不是class的值上
    //  markdown-it\lib\renderer.js 71行 这么修改可以修复bug
    //  tmpAttrs[i] += ' ' + options.langPrefix + langName; --> tmpAttrs[i][1] += ' ' + options.langPrefix + langName;
    // const fence = MarkdownIt.renderer.rules.fence
    // MarkdownIt.renderer.rules.fence = function(...args){
    //   args[0][args[1]].attrJoin('class', 'hljs')
    //   var a = fence(...args)
    //   return a
    // }

    // ```code`` 给这种样式加个class code_inline
    const code_inline = MarkdownIt.renderer.rules.code_inline
    MarkdownIt.renderer.rules.code_inline = function(...args){
      args[0][args[1]].attrJoin('class', 'code_inline')
      return code_inline(...args)
    }
    return source
  },
  use: [
    [MarkdownItContainer, 'demo', {
    //匹配```demo ``
      validate: params => params.trim().match(/^demo\s*(.*)$/),
      render: function(tokens, idx) {

        var m = tokens[idx].info.trim().match(/^demo\s*(.*)$/);
        //nesting 有 1 0 -1  如果小于0说明没有该属性
        if (tokens[idx].nesting === 1) { // 判断是否是开始标签

          var desc = tokens[idx + 2].content;
          // 获取代码块内的html和js代码
          const html = utils.convertHtml(striptags(tokens[idx + 1].content, 'script'))
          // 移除描述,防止被添加到代码块
          tokens[idx + 2].children = [];

          // console.log(desc,tokens,"test123",idx,html);
          // 使用自定义开发组件【DemoBlock】来包裹内容并且渲染成案例和代码示例
          return `<demo-block>
                        <div slot="desc">${html}</div>
                        <div slot="highlight">`;
        }
        return '</div></demo-block>\n';
      }
    }]
  ]
}

function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

const createLintingRule = () => ({
  test: /.(js|vue)$/,
  loader: 'eslint-loader',
  enforce: 'pre',
  include: [resolve('src'), resolve('test')],
  options: {
    formatter: require('eslint-friendly-formatter'),
    emitWarning: !config.dev.showEslintErrorsInOverlay
  }
})

module.exports = {
  context: path.resolve(__dirname, '../'),
  entry: {
    app: './src/main.js'
  },
  output: {
    path: config.build.assetsRoot,
    filename: '[name].js',
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
      "@packages":resolve('packages'),
      "@components":resolve('src/components'),
      "@utils":resolve('src/utils'),
    }
  },
  module: {
    rules: [
      ...(config.dev.useEslint ? [createLintingRule()] : []),
      {
        test: /.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      {
        test: /.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client'),resolve('packages')]
      },
      {
        test: /.(png|jpe?g|gif|svg)(?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /.(mp4|webm|ogg|mp3|wav|flac|aac)(?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
        test: /.(woff2?|eot|ttf|otf)(?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      },
      {
        test: /.md$/,
        loader: 'vue-markdown-loader',
        options: vueMarkdown
      }
    ]
  },
  node: {
    // prevent webpack from injecting useless setImmediate polyfill because Vue
    // source contains it (although only uses it if it's native).
    setImmediate: false,
    // prevent webpack from injecting mocks to Node native modules
    // that does not make sense for the client
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  }
}

三、发布npm前需要登录npm账号

配置完成之后,我们开始准备发布到npm上,先去NPM官网上注册一个npm号,需要设置账号、密码和邮箱验证,注册完成后,怎么通过命令实现发布到npm呢?
我们可以在项目中打开命令行工具输入npm login命令,然后回车输入账号、密码及邮箱验证码完成登录,登录成功就可以打包上传组件库啦,执行npm run publish即可打包并推送代码到npm上了。

注意:
1、确保项目名称没有被别人注册过,最好先在NPM官网上搜索确认是否有同名库,如有同名会发布失败;
2、每次推送发布,一定要改package.json里的版本号;

推送成功之后,就可以在NPM官网登录自己的账号查看刚刚发布的组件库了,如下图所示:

image.png

完成以上操作后,我们就可以直接在其他项目中下载使用我们发布的组件库啦,直接通过npm install 项目名称即可,同时在main.js引入该组件库,就可以在项目中全局使用了,同elementUi一致的使用方法。