开发自己的第一个npm包

前言

生命不息,折腾不止。我很喜欢那些搞事情的人,比如说创业者,发明者。从大处说,他们不是解决了我们的就业,就是推动了社会的进步。从小处说,生活需要目标和想法,不然就会像一杯白开水一样,索然无味。最近萌生了想开发一个列表项固定高度虚拟滚动工具库的想法。对于项目中高频使用的库,若有时间和精力的话,最好自己实现一下。这样做至少有两个好处,第一,做定制化业务开发很容易,第二,工具出现问题很快能定位到症结之所在。沿着这个思路,趁着热乎劲在,说干就干。

动手实现

项目初始化

用脚手架生成一个vue3+vite项目, 下面四种命令,都能生成一个vue3+vite4项目。

npm init vite
# 等价于
npm create vite
# 使用yarn
yarn create vite
# 使用pnpm
pnpm create vite

本文使用的是pnpm,因为pnpm安装工具包既快,也节省空间。选择 vue3 + ts 模式,

同时添加声明文件 vite-env.d.ts ,处理引入 .vue 文件编码软件标示红色波浪线问题,需要确保包含在 tsconfig.json 的include中,包含 vite-env.d.ts文件路径。

 pnpm init vite vue3-virtual-list

开发功能

我们开发一个每项固定高度的虚拟滚动列表组件,要想使外层容器可以滚动,需要满足两个条件:

  • 外层容器的滚动属性要设置为overflow:auto,
  • 内层容器的高度要大于外层容器
<style lang="less">
  .virtual-list-scroll-box {
    position: relative;
    overflow: auto;
    border: 1px solid #ccc;
    cursor: default;
    // 为了美观,隐藏滚动条
    &::-webkit-scrollbar {
      display: none;
    }
  }
</style>
<template>
  <div class="virtual-list-scroll-box" :style="scrollBoxStyle" @scroll.passive="handleScroll">
    <div :style="contentBoxStyle">
      <div v-for="(item, index) in fixedList" :style="item.style" :key="index">
        <slot name="listItem" :itemData="item.itemData"></slot>
      </div>
    </div>
  </div>
</template>

实现虚拟列表的关键逻辑是容器滚动后变更可见区域的显示条目,其中具体步骤如下:

微信截图_20230614213854_result.png

  1. 计算可视区域可以显示的元素数量
  2. 计算可视区域滚动时数据的起始缓冲索引 startBuffIndex
  3. 计算可视区域滚动时数据的结束缓冲索引 endBuffIndex
  4. 计算出startBuffIndex到endBuffIndex对应每项数据在整个列表中的偏移位置 Offset,设置到列表项上
    并进行渲染

Vue版本的代码实现如下:

<script setup lang="ts">
  import { ref, computed, watchEffect } from 'vue';
  const props = withDefaults(
    defineProps<{
      data: any[];
      height: number;
      width: number;
      itemSize: number;
      itemCount: number;
    }>(),
    {
      data: () => [],
      height: 500,
      width: 200,
      itemSize: 50,
      itemCount: 10,
    }
  );

  // 记录滚动卷去的高度
  const scrollOffset = ref(0);

  // 外部容器高度
  const scrollBoxStyle = computed(() => {
    const { height, width } = props;
    return {
      width: `${width}px`,
      height: `${height}px`,
    };
  });

  // 元素撑起盒子的实际高度
  const contentBoxStyle = computed(() => {
    const { itemSize, itemCount } = props;
    return {
      height: `${itemSize * itemCount}px`,
      width: '100%',
    };
  });

  const fixedList = computed(() => {
    const arr = [];
    const { data, height, itemSize, itemCount } = props;
    const Buff_Size=2;
    // 可视区能展示的元素的最大个数
    const visibleCount = Math.ceil(height / itemSize);
    
    // 可视区起始索引
    const startIndex = Math.floor(scrollOffset.value / itemSize);
    // 缓冲区起始索引
    const startBuffIndex = Math.max(0, startIndex - Buff_Size);

    // 缓冲区结束索引
    const endBuffIndex = Math.min(itemCount - 1, startIndex + visibleCount + Buff_Size);

    // 根据上面计算的索引值,不断添加元素给container
    for (let i = startBuffIndex; i <= endBuffIndex; i++) {
      arr.push({
        style: {
          position: 'absolute',
          height: `${itemSize}px`,
          width: '100%',
          // 计算每个元素在container中的top值
          top: `${itemSize * i}px`,
        },
        itemData: data[i],
      } as const);
    }

    return arr;
  });

  // watchEffect(() => {
  //   console.log(fixedList, props.data);
  // });

  // 当触发滚动就重新计算
  const handleScroll = (evt: UIEvent) => {
    scrollOffset.value = (evt.currentTarget as HTMLDivElement).scrollTop;
  };
</script>

开发模式改进

将组件的代码和调试页面代码放在同一个工程中,每次部署的时候,得进行手动拆分,把demo演示示例功能移除,感觉不是很方便。之前看到过pnpm支持workspace功能,可以把一个项目按照功能拆分成多个子项目,每个子项目可以引用别的子项目,每个子项目可以单独运行,单独打包。这正好是我们所需要的。

我们把项目拆分成两个子项目。组件的实现放在core子项目,组件的调试放在demo子项目。
新建一个 pnpm-workspace.yaml 文件,配置内容如下:

packages:
  - "core/**"
  - "demo/**"

pnpm的workspace依赖包的安装分两种方式,一种是在根目录下安装,所有子项目都共享,另外一种是给每个子项目独自安装依赖包。特别要说明的是,一个子项目可以把另外一个子项目当做依赖包,进行安装。不同方式的安装依赖包命令如下:

# 安装公共npm依赖包
pnpm i typescript -w -D


# 给某个项目安装npm依赖包
pnpm install 包名 -r --filter 某个项目中package.json中定义的name字段


# 把A项目当做依赖包,安装到B,C项目
pnpm i A -r --filter B C

相信你也和我一样心中会有疑问:当这样的工具包被发布后,如果引用了同一个仓库下的子项目,外网如何找到形如"@A": "workspace:^1.0.0"这样的依赖包。当执行了pnpm publish后,pnpm会把基于workspace的依赖变成外部依赖,如:

// 执行pnpm publish之前  
"dependencies": {
    "@A": "workspace:^1.0.0"
 },
// 执行pnpm publish之后
"dependencies": {
    "@A": "^1.0.0"
},

另外,还要说一下pnpm的workspace模式,每个子项目的启动/打包命令是:

  pnpm -C  子项目路径 子项目的package.json中的scripts配置的命令

举例:pnpm -C ./demo start, 有了这些知识垫底,相信拆分子项目对你而言就很Easy了,如果你还不会,可以下载文末的代码。

发布自己的npm包

  1. npm 官网 注册一个账号
  2. 发布npm包时,要将自己配置的别的npm镜像源切换到npm官方镜像源
nrm ls
# 切换镜像源
nrm use npm

image.png

  1. 在终端登陆账号
    npm login
    npm notice Log in on https://registry.npmjs.org/
    Username: // 用户名
    Password: // 密码(只能手输,不能复制粘贴)
    Email: (this IS public)  // 注册邮箱
    Enter one-time password from your authenticator app: // 注册邮箱收到的EOTP code(当次有效)
    
  1. 发布

执行npm publish,看下面的错误提示,猜测是与别人发布的包重名了

image.png
去npm官网查了一下,果不其然
image.png

改个名字,重新发布,这次看到发布成功了

image.png

  1. 更新
// patch--补丁号,修复bug,小变动,如 v1.0.0->v1.0.1
npm version patch


// minor--次版本号,增加与修改功能,如 v1.0.0->v1.1.0
npm version minor


// major--主版本号,不兼容的修改,如 v1.0.0->v2.0.0
npm version major

结语

本文的代码已经分享到码云,你可以点击这里下载学习。本以为会把时间消耗在学习npm包的发布流程方面,实际开发下来,发现在pnpm+workspace这一块花费的时间最多,所以自认为是难点的地方,可能并不见得是难点。只有自己动手做一下,自己薄弱的环节才会暴露出来。在还用不上的时候提前暴露,总比事到临头,带着压力去攻克难关人在心态和开发体验上要好很多,这就是爱折腾的意义之所在。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY7p2L0a' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片