使用rollup打包ts+react缓存组件发布npm

news2024/12/22 22:40:38

新建一个项目目录比如叫root,下面新建一个rollup的配置文件: rollup.config.ts 因为rollup良好支持ts和esmodule 所以用ts配置文件

Setup

生成一个package.json文件,这里用pnpm生成:

pnpm init

安装rollup和Typescript:

pnpm add rollup
pnpm add typescript

配置package.json的pnpm build命令:

{
  "scripts": {
    "build": "rollup --c --configPlugin typescript2 --bundleConfigAsCjs",
    "build:dev": "cross-env NODE_ENV=development pnpm build",
    "build:prod": "cross-env NODE_ENV=production pnpm build"
  },
  "type": "module"
}

rollup是跑在node环境的,node的模块化是commonjs不支持esmodule的 所以要在package.json的运行脚本里配置一个rollup命令–bundleConfigAsCjs将esmodule代码解析成commonjs让nodejs能够认识 然后package.json还要加上type:module支持esmodule

build命令解释:

  1. –c是指定rollup读取项目根目录下的rollup.config配置文件进行构建
  2. –configPlugin是指定rollup构建时要使用的插件 包括对rollup配置文件的处理 这里指定用typescript2这个插件来处理ts的配置文件 防止rollup配置文件读取报错
  3. –bundleConfigAsCjs是rollup的一个命令用来将esmodule转换成commonjs便于node环境中使用
  4. cross-env是一个插件用来抹平不同操作系统中设置环境变量的方式,不同操作系统环境变量设置方式是不一样的 我们不能一个个去弄 所以用来实现跨平台的设置环境变量
  5. build:dev和build:prod用来根据获取环境变量注入的值process.env.NODE_ENV来做不同的操作
  6. type:module也是配置支持esmodule的一步 要加上

rollup.config.ts配置文件

文件要求导出一个RollupOptions对象/RollupOptions[]对象数组 一个对象就是一个文件的打包配置 要打包多少就多少个配置对象 这里我就指定一个入口文件对外暴露三个接口就行 rollup会根据入口文件的导入引用去查找文件进行构建
rollup配置文件配置:

import nodeResolve from '@rollup/plugin-node-resolve'
import typescript2 from 'rollup-plugin-typescript2'
// @ts-ignore
import babel from 'rollup-plugin-babel'
import commonjs from '@rollup/plugin-commonjs'
import { join, resolve } from 'path'
import { readdir } from 'fs/promises'
import { RollupOptions, defineConfig } from 'rollup'
import { IOptions } from 'rollup-plugin-typescript2/dist/ioptions'
import { existsSync } from 'fs'
import { unlink, rmdir, lstat } from 'fs/promises'
const commonPlugins = [
  nodeResolve({
    extensions: ['.ts', '.tsx'], // 告诉node要解析的文件扩展名
  }),
  typescript2({
    tsConfig: resolve(__dirname, 'tsconfig.json'), // 指定ts配置文件位置
    // useTsconfigDeclarationDir: true, // 使用配置文件里的DeclarationDir 不开启默认强制生成在和文件同级目录同名文件
  } as Partial<IOptions>),
  babel({
    babelrc: true, // 使用.babelrc配置文件
  }),
  commonjs(), // 这个插件比如加 用来转换成commonjs 然后注入react17新的jsx组件转换函数_JSX react17+不再用createElement 不用这个插件只用babel处理会报错
]
/**
 * @description 根据路径删除目录
 * @param dirs 删除的目录路径
 */
const removeDir = async (...dirs: string[]) => {
  for (const dir of dirs) {
    const absolutePath = resolve(__dirname, dir)
    if (existsSync(absolutePath)) {
      const dirStack = [absolutePath]
      while (dirStack.length > 0) {
        const initPath = dirStack[dirStack.length - 1]
        const fileStat = await lstat(initPath)
        if (fileStat.isDirectory()) {
          const files = await readdir(initPath)
          if (files.length > 0) {
            dirStack.push(...files.map((e) => join(initPath, e)))
          } else {
            await rmdir(initPath)
            dirStack.pop()
          }
        } else if (fileStat.isFile()) {
          await unlink(initPath)
          dirStack.pop()
        }
      }
    }
  }
}
const resolveRollupOptions = async () => {
  const results: RollupOptions[] = []
  const dirStack = [resolve(__dirname, 'src')]
  while (dirStack.length > 0) {
    const initPath = dirStack.shift()!
    const fileStat = await lstat(initPath)
    if (fileStat.isDirectory()) {
      const files = await readdir(initPath)
      if (files.length > 0) {
        dirStack.push(...files.map((e) => join(initPath, e)))
      }
    } else if (fileStat.isFile()) {
      const rollupOption: RollupOptions =
        process.env.NODE_ENV === 'development'
          ? {
              input: initPath,
              treeshake: false,
              external: ['react', 'react-dom'],
              output: {
                file: initPath
                  .replace(/src/, 'lib')
                  .replace(/\.(tsx|ts)/, '.js'),
                format: 'esm',
                sourcemap: true,
              },
              plugins: commonPlugins,
            }
          : {
              input: initPath,
              treeshake: true,
              external: ['react', 'react-dom'],
              output: {
                file: initPath
                  .replace(/src/, 'lib')
                  .replace(/\.(tsx|ts)/, '.min.js'),
                format: 'esm',
                sourcemap: false,
              },
              plugins: [...commonPlugins],
            }
      results.push(rollupOption)
    }
  }
  return results
}
export default defineConfig(async (/* commandLineArgs */) => {
  // 每次构建前先删除上一次的产物
  await removeDir('es', 'lib')
  // 生成两个产物 一个esmodule模块 一个umd通用模块
  return [
    {
      input: resolve(__dirname, 'src/index.ts'), // 指定入口文件
      treeshake: true, // 开启treeshaking
      external: ['react', 'react-dom'], // 第三方库使用外部依赖
      output: {
        name: 'ReactAlive', // 这个name用于打包成umd/iife模块时模块挂到全局对象上的key
        file: resolve(__dirname, 'es/index.js'), // 构建的产物输出位置和文件名
        format: 'esm', // 构建产物的模块化类型
        sourcemap: false, // 关闭sourcemap
        // 指定被排除掉的外部依赖在全局对象上的key
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
        },
      },
      plugins: commonPlugins,
    },
    {
      input: resolve(__dirname, 'src/index.ts'),
      treeshake: true,
      external: ['react', 'react-dom'],
      output: {
        name: 'ReactAlive',
        file: resolve(__dirname, 'lib/index.js'),
        format: 'umd',
        sourcemap: false,
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
        },
      },
      plugins: commonPlugins,
    },
  ] as RollupOptions[]
})

root/src/index.ts:入口文件声明好这个库要对外暴露的接口:

import KeepAliveScope from './components/keepalive-scope'
import KeepAliveItem, { useCacheDestroy } from './components/keepalive-item'
export { KeepAliveItem, KeepAliveScope, useCacheDestroy }

root/global.d.ts:为process.env.NODE_ENV提供类型声明 这样就有代码提示了:

declare namespace NodeJS {
  interface ProcessEnv {
    NODE_ENV: 'development' | 'production'
  }
}

root/tsconfig.json:

{
  "compilerOptions": {
    "target": "ESNext",
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": false,
    "skipLibCheck": true, // 跳过第三方库类型声明文件的检查
    "esModuleInterop": true, // 开启将esm代码编译成cjs
    "allowSyntheticDefaultImports": true, // 启用默认导出 .default访问默认导出的module.exports内容
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "sourceMap": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "types": ["node"],
    "experimentalDecorators": true, // 开启装饰器语法
    "jsx": "react-jsx", // react17+这里可以改成react-jsx 17+后会自动引入一个编译jsx函数 配置babel的automatic
    "baseUrl": ".",
    // "paths": {
    //   "@/*": ["src/*"]
    // },
    "declaration": true // 是否生成类型声明文件
    // "declarationDir": "lib/types" // 类型声明文件默认生成在对应ts文件同级目录 指定一个目录统一生成
  },
  "exclude": ["node_modules"],
  "include": ["src"]
}

root/.babelrc:给babel插件提供配置:

{
  "presets": [
    "@babel/preset-env",
    [
      "@babel/preset-react",
      {
        "runtime": "automatic"
      }
    ]
  ],
  "extensions": [".ts", ".tsx"],
  "include": ["src"],
  "exclude": ["node_modules"]
}

.babelrc文件解释:

  1. @babel/preset-env:babel根据我们环境的不同来调整babel的自身配置的预设
  2. @babel/preset-react:babel用于在react项目环境中调整自己的配置的预设 用来转换jsx propstype检查之类的 预设里的配置项runtime:automatic是指运行时自动在react组件里注入jsx的转换函数 react17之前我们需要在组件开头导入React才能用React对象上的createElement创建组件 v17之后的新特性允许我们不用导入React就能自动注入jsx的转换函数 这件事最终是由@babel/plugin-transform-react-jsx来做的
  3. extensions:告诉babel要处理的文件的文件后缀 我的组件就只有tsx和ts文件
    .npmignore文件: 告诉npm publish时哪些文件忽略不用发布到npm包里
    这是我的项目要忽略的checklist:
node_modules
src
.babelrc
.gitignore
.npmignore
.prettierrc
rollup.config.ts
test
pnpm-lock.yaml
global.d.ts
tsconfig.json
.DS_Store
test-project

除了.npmignore里的文件都会被上传到npm

发布npm包

首先注册一个npm账号 然后去首页搜索包的头部会有个提示让你绑定2FA安全校验策略 根据提示走完流程就行 大概就是要绑定一个otp(one-time-password)一次性密码 这个下载一个google authentication的app就能生成 扫网页上的码绑定账号然后跟着提示走就行 这个一次性密码在第一次npm login和npm publish的时候会让你输入otp 后面就直接让你去浏览器验证就行

每次发布前要先修改一下package.json的version版本号 不能发布相同的版本 会报错

发布前最好去搜搜包名(package.json的name就是npm包的包名)是否已经存在了 不然取存在的名字会报没有权限更新别人的包 最好就是用@自己的名字/包名这个格式 加个用户名称前缀相当于一个namespace 重复的几率就会小很多

root/package.json完整代码示例:

{
  "name": "@williamyi74/react-keepalive",
  "version": "1.1.2",
  "description": "基于react18+的缓存组件,拥有类似vue的KeepAlive组件功能效果",
  "main": "index.js",
  "scripts": {
    "build": "rollup --c --configPlugin typescript2 --bundleConfigAsCjs",
    "build:dev": "cross-env NODE_ENV=development pnpm build",
    "build:prod": "cross-env NODE_ENV=production pnpm build"
  },
  "keywords": [
    "alive",
    "keep alive",
    "react",
    "react keep alive",
    "react keep alive item",
    "react keep alive scope",
    "react keep scope",
    "react hooks keep alive"
  ],
  "author": "williamyi",
  "license": "ISC",
  "peerDependencies": {
    "react": ">=18.2.0",
    "react-dom": ">=18.2.0",
    "typescript": ">=5.0.4"
  },
  "devDependencies": {
    "@babel/core": "^7.21.4",
    "@babel/preset-env": "^7.21.4",
    "@babel/preset-react": "^7.18.6",
    "@rollup/plugin-commonjs": "^24.1.0",
    "@rollup/plugin-node-resolve": "^15.0.2",
    "@types/react": "^18.2.0",
    "@types/react-dom": "^18.2.1",
    "cross-env": "^7.0.3",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "rollup": "^3.21.0",
    "rollup-plugin-babel": "^4.4.0",
    "rollup-plugin-typescript2": "^0.34.1",
    "typescript": "^5.0.4"
  },
  "type": "module"
}

root/package.json解释:

  1. keywords: 搜索你的包时的关键词
  2. description: 搜索包时略缩图下的简述
  3. license: 项目的协议,ISC是MIT的一个简化版本 这个说明就是别人能自由的修改和用你的项目
  4. peerDependencies: 项目里用到的外部依赖要求 告诉使用这个包的项目这些依赖版本要符合这个要求 否则会报错 用这个包的项目下载依赖时会下载peerDep 而不是我们项目里去下载 external里的包都写到这

首先执行npm login前先切换到npm源不要在淘宝源 否则会报错 输入信息跟着走完

登录成功后执行npm publish --access public发布

–access public是告诉npm发布公开的包 因为默认是不能发布私有包的 忘了私有包是要收费还是啥 指定了这个命令参数就不会报权限错误了

一路没报错后去看npm的package就能看到自己发布上去的包了:
发布成功的npm包

文档

使用文档就是根目录下的README.md文件 完善这个文件就可以了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/493429.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android 自定义View 之 简易输入框

简易输入框 前言正文① 构造方法② XML样式③ 测量④ 绘制1. 绘制方框2. 绘制文字 ⑤ 输入1. 键盘布局2. 键盘接口3. 键盘弹窗4. 显示键盘5. 相关API 四、使用自定义View五、源码 前言 在日常工作开发中&#xff0c;我们时长会遇到各种各样的需求&#xff0c;不部分需求是可以通…

在云服务器上部署jupyter服务

1.准备一台云服务器&#xff0c;阿里云、腾讯云都可以&#xff0c;并且远程登陆&#xff0c;在云服务器的安全组中配置8888的访问端口&#xff0c;因为jupyter默认的访问端口是8888&#xff0c;如下述步骤&#xff1b;以阿里云服务器的centos系统为例 2.使用以下命令在服务器…

golang 微服务容错处理是如何做的?

随着微服务的规模越来越大&#xff0c;各个微服务之间可能会存在错综复杂的调用关系 在我们实际工作中&#xff0c;确实慢慢的也出现了很多问题&#xff0c;整个系统的弊端的慢慢的展现出来 例如就会有这样的情况&#xff1a; 服务 A 去请求服务B&#xff0c;服务 B 还需要去…

HTB-Silo

HTB-Silo 信息收集立足root哈希传递攻击 信息收集 分别对smb和rpc都进行guest用户和空密码测试。 1521的Oracle TNS listener 11.2.0.2.0。 搜索可能存在的漏洞。 得到一个CVE编号cve-2012-1675。同时我们可以对其进行SID枚举&#xff0c;SID说简单点就是数据库的名字。 简单…

错题汇总04

1.以下C语言指令&#xff1a; int a[5] {1,3,5,7,9}; int *p (int *)(&a1); printf(“%d,%d”,*(a1)&#xff0c;*(p-1)); 运行结果是什么&#xff1f; A 2,1 B 3,1 C 3,9 D 运行时崩溃 数组名只有在&与sizeof之后&#xff0c;才表明数组本身&#xff0c;其余表…

平均情况时间复杂度

// n表示数组array的长度 int find(int[] array, int n, int x) {int i 0;int pos -1;for (; i < n; i) {if (array[i] x){ pos i; break;}}return pos; } 通过以上代码&#xff0c;我们分析一下平均情况时间复杂度。 以上代码要查找的变量 x 在数组中的位置&#xff…

并发编程02:CompletableFuture

文章目录 2.1 Future接口理论知识2.2 Future接口常用实现类FutureTask异步任务2.2.1 Future接口能干什么2.2.2 Future接口相关架构2.2.3 Future编码实战和优缺点分析2.2.4 完成一些复杂的任务 2.3 CompletableFuture对Future的改进2.3.1 CompletableFuture为什么会出现2.3.2 Co…

Redis持久化篇

文章目录 持久化篇1、AOF持久化是怎么实现的&#xff1f;1.1、AOF日志1.2、三种写回策略1.3、AOF重写机制1.4、AOF后台重写 2、RDB快照是怎么实现的&#xff1f;2.1、快照怎么使用2.2、执行快照时&#xff0c;数据能被修改吗&#xff1f;2.3、RDB和AOF合体 3、Redis大key对持久…

自动驾驶行业观察之2023上海车展-----智驾供应链(2)

传感器供应链发展 图达通&#xff1a;展示长距Lidar“Falcon”&#xff0c;和DeepWay签署定点协议 产品&#xff1a;主视激光雷达 Falcon 猎鹰&#xff08;2023CES曾亮相&#xff09; 核心亮点&#xff1a; • 核心性能&#xff1a;最远探测距离可达 500 米&#xff0c;为智…

《计算机网络—自顶向下方法》 第一章Wireshark实验:Wireshark软件的安装和入门

要深入理解网络协议&#xff0c;需要仔细观察协议实体之间交换的报文序列。为探究协议操作细节&#xff0c;可使协议实体执行某些动作&#xff0c;观察这些动作及其影响。这些任务可以在仿真环境下或在如因特网这样的真实网络环境中完成。观察在正在运行协议实体间交换报文的基…

万字长文详解linux内存管理,值得收藏

一、Linux内存管理概述 Linux内存管理是指对系统内存的分配、释放、映射、管理、交换、压缩等一系列操作的管理。在Linux中&#xff0c;内存被划分为多个区域&#xff0c;每个区域有不同的作用&#xff0c;包括内核空间、用户空间、缓存、交换分区等。Linux内存管理的目标是最…

经典常用的脚本讲解

目录 一&#xff1a;echo 语句 二&#xff1a;while read命令​编辑 三&#xff1a;猴子摘香蕉问题 四:斐波拉切数求前10个数的和 ​五&#xff1a;随机生成8位数的密码 六&#xff1a;二进制转换 &#xff08;1&#xff09;余数倒排法 &#xff08;2&#xff09;减法正…

Google - ISLR 比赛总结

引言 本篇主要想总结一下最近打的GISLR比赛&#xff0c;本来是没想写的&#xff0c;比赛前期感觉赛题很有意思&#xff0c;做了eda以及根据一些base改了改自己的方案&#xff0c;取得了还不错的结果&#xff0c;但因为中途被各种琐事缠身&#xff0c;发生了很多变故&#xff0…

【Linux】Libevent库

Libevent——高性能I/O框架库 底层封装了select&#xff0c;poll&#xff0c;epoll&#xff0c;便于使用 I/O框架库以库函数的形式&#xff0c;封装了较为底层的系统调用&#xff0c;给应用程序提供了一组更便于使用的接口。 特点&#xff1a;1.跨平台&#xff0c;2.统一事件源…

c++面向对象之封装、继承、和多态

一、封装 把客观事物封装成类&#xff0c;而且可以把自己的数据和方法设置为只能让可信的类或者对象操作&#xff0c;对不可信的信息进行隐藏&#xff08;利用public,private,protected,friend)实现 二、继承 2.1类与类的关系 has-a &#xff1a;描述一个类由多个部件类构成…

SpringCloud全面学习笔记之初尝美妙篇

目录 前言初识微服务单体架构分布式架构微服务架构初见SpringCloud微服务治理分布式服务架构案例 微服务组件及使用Eureka注册中心提供者和消费者Eureka的结构和作用搭建Eureka服务注册服务服务发现Eureka注册服务总结 Ribbon负载均衡原理负载均衡原理负载均衡策略懒加载 Nacos…

Qt quick基础2(包含平移旋转放缩以及qml控件大写开头啊)

Qt quick基础2&#xff08;包含平移旋转放缩以及qml控件大写开头啊&#xff09; 目录 Qt quick基础2&#xff08;包含平移旋转放缩以及qml控件大写开头啊&#xff09;前言简单的平移、旋转和放缩其他元素的一些基本使用qml文件作为控件时&#xff0c;务必以大写字母开头命名小结…

力扣题库刷题笔记682-棒球比赛

1、题目如下&#xff1a; 2、个人Python代码实现如下&#xff1a; 代码如下&#xff1a; class Solution: def calPoints(self, operations: List[str]) -> int: i 0 #用于遍历元素的下标 while i < len(operations): …

【Python入门篇】——Python基础语法(数据类型与数据类型转换)

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; Python入门&#xff0c;本专栏主要内容为Python的基础语法&#xff0c;Python中的选择循环语句…

数据结构与算法导学

文章目录 数据结构和算法导学认识数据结构认识算法总结 数据结构和算法导学 程序 数据结构 算法 认识数据结构 什么是数据结构&#xff1f; 数据结构是一门研究计算机中数据存储和数据操作的学科。 为什么要学习数据结构&#xff1f; 学习数据结构能让我们写出更加优秀的代码…