从写库到发布npm看这一篇就够了


theme: devui-blue

image.png

背景

        我们在日常的开发工作中,经常会有一些封装的比较好用的库或者自定义组件,用的顺手又封装的比较完善,但是呢,公司又没有搭建私有npm仓库,这时候我们就可以将封装好的库发布至公有云npm上,以后就可以直接安装使用,提升开发效率。同时开放给大家使用,也会严格要求自己的代码质量不断维护优化,提升自己的技术水平。
        接下来按照下面的流程看完,基本上能够让你具备发布一个简单库的技能,也可以在开发vue项目的同时,将封装好的vue自定义组件同步发布供大家一起使用。

打包工具选择

不同的需要使用不同的打包工具,比如我们在发布js库的时候,优先选择使用Rollup进行打包,它要比webpack更加简单并且打包体积要更小一些,只是将我们的js库代码转换为目标js,并且如果有需要可以生成支持umd、commonjs、es的js代码,早期在看vue2+源码的时候就是使用Rollup进行打包的。

那我们在项目中使用的自定义组件用什么进行打包封装呢? 这取决于我们在项目中使用到了什么工具,vue2.0一般都是内置了webpack进行打包,我们可以使用vue-cli-service build –target lib打包命令进行打包(详情可以网上查询),下面的案例中我是在vue3.0项目中进行打包封装,使用的是vite打包工具。

图片懒加载库从编写到打包发布(rollup.js)

准备rollup打包配置

初始化package.json

npm init

安装rollup

npm install rollup --save-dev

创建rollup.config.js文件,配置基础打包配置

import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
const pkg = require('./package.json')

export default {
  input:'./src/index.js',// 打包入口文件
  // 输出文件配置
  output:[
    {
      file: pkg.main,// 打包完成后文件位置
      format:'umd', // 输出格式 amd es6 iife umd cjs均可配置
      sourcemap: true, // 输出.map文件,方便调试
      name:'simLazy'// 如果iife,umd需要指定一个全局变量,window.simLazy
    },
    {
      file: pkg.module,
      format: 'es',
      sourcemap: true
    },
  ],
  plugins:[commonjs(), resolve()]
}

由于我编写的库比较简单,配置文件中都是最基本的打包一个库的配置,你也可以根据你的需要配置一些其他插件类似解析ts或者增加sourceMap等功能,这些rollup都是支持的。

resolve :插件是在我们库中使用依赖插件代码合并打包在一起。

commonjs :在rollup中默认只支持es6+import/export方式,但是在我们在库中使用commonjs的库就会无法正常导入使用,该插件帮我们将commonjs模块转换为es6

还有很多插件可自行上官网查询…

配置文件配置完成,我们在package.json中增加script,方便我们进行打包:

"scripts": { 
    "build": "rollup --config" 
}

IntersectionObserver实现图片懒加载

该插件是应用于vue3框架下,主要基于IntersectionObserver对象的实例方法对页面元素进行监听,在图片出现的某个阶段出发图片进行加载展示,达到懒加载的效果。

// src/index.js
import SimLazy from './modules/simLazy'

const simLazyPlugin = {
  install(app, options) {
    const simlazy = new SimLazy(options)

    app.directive('simlazy', {
      mounted: simlazy.add.bind(simlazy),
    })
  }
}

export default simLazyPlugin

我们编写的是在vue3下使用的懒加载插件,首先编写插件的install方法,该插件在项目中被use()调用时会执行该install方法,所以在方法中我们new SimLazy(options)初始化插件。并调用vue实例全局注册指令,在mounted阶段执行插件add方法。

我们看下add函数中做了什么事情:

/**
 * 
 * @param {DOM} el 元素
 * @param {Directive} binding 指令传递的属性,instance、value...
 */
SimLazy.prototype.add = function (el, binding) {
  let imgSrc = binding.value
  let imgInstance = new ImgManager({
    el,
    src: imgSrc, // 传入图片的地址
    loadingSrc: this.loadingSrc, // 配置的加载图片地址
    errorSrc: this.errorSrc // 配置的错误展示的图片地址
  })
  this.manageCollection.push(imgInstance)
  // intersectionObserver 实例对指定元素进行观察
  if (isSupport()) this.observer.observe(el)
}

add函数接收需要懒加载的图片dom元素el,将元素交给图片管理对象ImgManager进行实例化,最后由this.observer.observer(el)对元素进行监听。

我们看下this.observer是什么:

SimLazy.prototype.initIntersectionObserver = function() {
  this.observer = new IntersectionObserver((entries) => {
    console.log(entries)
    // isIntersecting 直接对观察元素判断是否可见
    // entries 为一个数组,每个元素都时实例监听的元素对象
    entries.forEach((observerItem) => {
      if (observerItem.isIntersecting) {
        const currentImgManage = findTargetImg(observerItem.target, this.manageCollection)
        if (currentImgManage) {
          if (currentImgManage.status === LOADED_STATUS) {
            this.remove(currentImgManage)
            return
          }
          currentImgManage.load() // 找到对应的图片对象加载图片
        }
      }
    })
  },{
    threshold: [0]
  })
}

初始化IntersectionObserver(callback, options)对象,该对象接收一个callback函数,这个函数会在监听的元素展示在可见容器内时进行触发。每次触发都会向回调函数传递一个数组entries,该数组包含了当前所有监听元素最新的状态,我们可根据每个元素的isIntersecting来判断元素是否展示在可见范围内,从而触发currentImgManage.load()加载图片进行展示。

rootMargin: 是一个数组,可以在监听元素展示比例在0%、25%、50%、75%、100%时触发。

我们可以在本地新建一个项目来引用我们的库进行使用,同时方便调试,保证没问题后我们就可以将插件发布至npm,具体发布流程看后面的 npm 发布准备。

自定义组件打包发布(vite)

此处以vue3.0+vite项目为例,我们在开发项目过程中,可以将自定义组件进行封装发布,这样做的好处是在项目中可以检验组件封装的是否完善,也不需要再配置一堆环境来弄一个demo项目,验证我们的自定义组件是否有效。项目结束之后,我们的自定义组件也水到渠成直接做到可发布给其他同事使用的程度。

准备vite打包配置

我的项目环境是vue3+vite,所以这里我们直接使用vite对自定义组件进行打包,以下是vite.config.js配置:

export default defineConfig({
  // VitePluginStyleInject 将样式打包注入到js中
  plugins: [vue(), VitePluginStyleInject()], 
  build: {
    // 库模式
    lib: {
      entry: path.resolve(__dirname, 'src/packages/index.js'), // 入口
      name: 'SimUpload', //暴露的全局变量
      // formats: ['es', 'umd'], // 默认打包格式
      fileName: (format) => `my-lib.${format}.js`, //输出的包文件名
    },
    // outDir: 'dist', //默认输出目录
    rollupOptions: {
      external: ['vue', 'element-plus', 'swiper', 'lodash', 'swiper'], // 确保外部化处理那些不想打包进库的依赖
      output: {
        globals: { // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
          vue: 'Vue'
        }
      }
    }
  }
 }

这里我们在vite.config.js中配置lib库模式进行打包,当配置中存在lib属性时会优先对该模式进行打包。

如果你配置正确,打包成功后会在根目录下出现dist目录:

image.png

图片上传组件

这里以我在vue3项目中开发的集成了图片上传、删除、查看等基本功能的组件为例,该组件基于element-plusupload组件完成上传功能,配合dialog modalswiper完成查看效果。因为在后台管理项目中经常有这种需求,直接封装成自定义组件方便我们进行使用,提升我们的开发效率与用户体验。

上传效果

image.png

查看以上传图片

image.png

我们在项目src下创建packages的目录,用于存放我们封装的自定义组件。

新建packages/index.js文件作为自定义组件的总入口:

import upload from "./upload/index.js";
import elementPlus from 'element-plus' // 全局导入 视自己项目需要自行导入
// 存储组件列表
const components = [upload];
// 定义 install 方法,接收 Vue 作为参数。如果使用 use 注册插件,则所有的组件都将被注册
const install = function(Vue) {
  // 判断是否安装
  if (install.installed) return;
  Vue.use(elementPlus)
  // 遍历注册全局组件
  components.forEach(component => {
    Vue.component(component.name, component)
  });
  install.installed = true
};
// 判断是否是直接引入文件
if (typeof window !== "undefined" && window.Vue) {
  install(window.Vue);
  install.installed = true
}
export default {
  // 导出的对象必须具有 install,才能被 Vue.use() 方法安装
  install,
  // 以下是具体的组件列表
  ...components
};

总入口中编写install注册函数,该函数会在项目中使用use()时执行,对所有组件进行全局注册。

在我们每一个自定义组件目录下,我们可以创建单独入口src/packages/upload/index.js

import upload from './index.vue';
upload.install = Vue => Vue.component(upload.name,upload);
export default upload;

单独编写组件的install注册函数,这样我们可以单独对需要的组件按需加载,import {upload} from 'xxx',配合vitetree shaking减小我们项目最终打包的体积。

在我们一切准备就绪打包准备发布之前,我们先解决几个问题:

打包问题

样式与代码一起打包

我在打包过程中发现自定义组件的样式代码会单独被打包成一个文件,这倒也不影响我们使用,就是每次使用组件都需要像import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css',引入组件还不够,还需要引入样式文件才能正常使用。

考虑到封装的自定义组件样式都是比较少的,我们这里想要将样式文件一并打入到js中方便使用。接下来一顿面向百度编程发现vite并没有提供我们想要的解决方案,并且发现已经有人提了issues,我们发现尤大也说了,样式不多的话可以用行内样式来解决。

image.png

接着往下找找就发现有位大兄弟写了个vite插件vite-plugin-style-inject,在vite打包过程中将样式文件内容提取最后插入到页面中,有需要的可以直接连接跳转安装使用。

样式丢失

在自定义组件打包后使用时发现样式有丢失情况,经过多次打包后总结:

  • 自定义组件中只留一个style标签存放样式
  • 不要使用scoped
  • 组件中使用的样式比如插件样式都需要在组件中进行引入

第三方依赖库不进行打包

我在自定义组件中用到了不少第三方库,大概有'element-plus', 'swiper', 'lodash', 'swiper'这些,整个都给我打包到库里面体积就相当大了。看下上面的vite.config.js配置,我们配置external将额外的第三方库排除在外,配置的同时,我们也要保证使用我们库的时候也会下载到这些依赖项,要不然我们库也没办法运行。。

我们在package.jsondependencies中添加我们自定义组件依赖的第三方库,这样我们在项目中使用install时就会下载这些依赖包了。

发布到npm

上面主要说了如何打包一个纯粹的脚本库和在项目中封装一个自定义组件,经过我们的配置打包,没有问题的话我们已经生成了dist目录,接下来按照步骤我们将dist目录发布到云端即可供其他人下载使用。

package.json配置
{
  "name": "XXX",
  "version": "0.0.1",
  "main": "dist/XXX.es.js",
  "module": "dist/XXX.umd.js",
  "files": [
    "dist"
  ]
}

以上是我截取了package.json一部分代码,我们看下每一项的意义:

name:这里的名称就是我们上传后库的名称,下载也是这个名称。

version:上传库的版本号,这里我们需要注意,每一次发布至npm库都需要设置不同的版本号,否则无法发布成功。

mainmodule:在项目中引入我们库的代码时,此处配置指向我们的代码位置。

files: 表示我们发布时会上传的文件,数组类型,这里我们配置了dist目录,发布后只会上传我们dist目录。

以上就是我们发布npm需要知道的最基本的配置,我的项目没有远程链接我的git,所以少了一些关于git的配置,看你需要,我这里直接本地发布就好。

npm发布执行

发布到npm上至少要需要有一个npm账号,没有先去注册一个吧。

注册完毕,先看下我们本地的npm源是不是默认的,执行npm get registry:

image.png

如果是设置了镜像的话执行npm config set registry https://registry.npmjs.org/ 恢复默认。

接下来在命令行执行npm login进行登录账号,输入账号和密码后,会给绑定邮箱发送一个one-time password,输入后即可登录成功。

来到我们的项目下,执行npm publish,只要不重名或者不是网络问题,基本上就可以发布成功啦,为了其他小伙伴更加方便的使用,记得在目录下增加README.md文件,对自己的库添加使用说明。

感谢大家的耐心阅读,如果有帮助,点赞鼓励鼓励吧。

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容