一篇基于rollup+typescript开发npm包的干货

6,471 阅读8分钟

本文已参加【新人创作礼】活动,一起开启掘金创作之路。

前言

相信你在点进这篇文章的时候,不管你现在是什么水平,你一定和我一样曾经想过这样一个问题🙏

我在写业务项目的时候引入过那么多的工具库,组件,插件,他们都是怎么做的,我能不能也做一个,以后让别人也能用上我开发的轮子

是的,作为前端,我相信大家都用过lodash,moment,ant-design,element-ui等等数不胜数好用的别人封装的东西。也许以后我们不一定能成为千星star,万星star的开源大佬,但是至少我们已经在走大佬们最初走过的路了,作为程序员,动手一定是非常重要的一环。所以如果你现在还不会的话,跟着我一步一步动起手来,做一个自己的ts工具库,也希望这篇文章能带给你的不只是这一点小知识而是让你养成一个动手实操的习惯🔥🔥

给轮子定个小目标

如题所述,我们要基于typescript开发一个工具方法库,来列一下我们的工具库需要达到什么样的标准

  • 本地开发可模拟安装调试
  • 用rollup打包构建
  • 打包编译新特性为浏览器可识别的语言
  • 发布到npm供他人使用
  • 支持es6模块化引入
  • 支持cdn方式引入
  • 引入时有代码提示,有ts声明文件

初始化项目

先去github上面创建一个仓库,添加协议文件,这里一般选择MIT协议

image.png

clone至本地以后cd进目录npm init -y初始化一下,把信息都填写一下,按照如下目录结构创建目录和文件

utilibs
├─ .babelrc.json    //babel配置文件
├─ .gitignore       
├─ LICENSE
├─ package-lock.json
├─ package.json
├─ README.md
├─ rollup.config.js  //rollup构建配置文件
├─ src
│  ├─ deepClone.ts
│  ├─ index.ts      //主入口文件
│  ...
└─ tsconfig.json    //ts配置文件 可以用tsc命令生成

安装依赖

//ts相关包
npm i -D typescript tslib 

//rollup相关包
npm i -D rollup @rollup/plugin-node-resolve rollup-plugin-commonjs rollup-plugin-typescript

//babel相关
npm i -D @rollup/plugin-babel 
npm i -D @babel/core @babel/preset-env

配置tscofig.json

全局安装typescript,用tsc命令生成tscofig.json文件,你也可以自己手动新建

npm i -g typescript
tsc --init
tsconfig.json
{
  "compilerOptions": {
    "target": "es5",  // 要改成es5,不然babel转换es6的时候有些转换不了
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitAny": false,
    "noImplicitThis": false,
    "skipLibCheck": true
  }
}

配置bebel

.babelrc.json
{
  "presets": [
    "@babel/env"
  ]
}

配置rollup

rollup.config.js
import resolve from "@rollup/plugin-node-resolve";
import babel from "@rollup/plugin-babel";
import commonjs from "rollup-plugin-commonjs";
import typescript from "rollup-plugin-typescript";

export default {
  input: "src/index.ts", // 打包入口
  output: {
    // 打包出口
    file: "dist/index.js",
    format: "umd", // umd是兼容amd/cjs/iife的通用打包格式,适合浏览器
    name: "utilibs", // cdn方式引入时挂载在window上面用的就是这个名字
    sourcemap: true,
  },
  plugins: [
    // 打包插件
    resolve(), // 查找和打包node_modules中的第三方模块
    commonjs(), // 将 CommonJS 转换成 ES2015 模块供 Rollup 处理
    typescript(), // 解析TypeScript
    babel({ babelHelpers: "bundled" }), // babel配置,编译es6
  ],
};

写个方法示例打通流程

接下来我们手写一个递归深拷贝来打通流程,这里我们故意用es6的箭头函数来写,后面测试babel有没有把他编译成es5的语法

src/deepClone.ts
/**
 * 深拷贝
 * @param obj 需要深拷贝的对象
 * @returns
 */
const deepClone = (obj: Object) => {
  // 不是引用类型或者是null的话直接返回
  if (typeof obj !== "object" || typeof obj == null) {
    return obj;
  }
  // 初始化结果
  let result: object;
  if (obj instanceof Array) {
    result = [];
  } else {
    result = {};
  }

  for (let key in obj) {
    // 保证不是原型上的属性
    if (obj.hasOwnProperty(key)) {
      // 递归调用
      result[key] = deepClone(obj[key]);
    }
  }
  return result;
};

export default deepClone;


在主入口文件处引入刚刚写的深拷贝

src/index.ts

import deepClone from "./deepClone";

export {
  deepClone
};

配置一下package.json

  • 增加一个main属性,到时候打包发布到npm以后在项目里import引入时的主入口文件,和rollup的打包出口文件相对应
  • 增加script打包命令
{
  "name": "utilibs",
  "version": "1.1.1",
  "description": "js通用方法库",
  "main": "index.js",   //主入口文件
  "scripts": {
    "build": "rollup --config",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/szqlovepk/utilibs.git"
  },
  "keywords": [
    "toolkit",
    "rollup",
    "typescript"
  ],
  "author": "szqlovepk",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/szqlovepk/utilibs/issues"
  },
  "homepage": "https://github.com/szqlovepk/utilibs#readme",
  "devDependencies": {
    "@babel/core": "^7.17.10",
    "@babel/preset-env": "^7.17.10",
    "@rollup/plugin-babel": "^5.3.1",
    "@rollup/plugin-node-resolve": "^13.3.0",
    "rollup": "^2.70.2",
    "rollup-plugin-commonjs": "^10.1.0",
    "rollup-plugin-typescript": "^1.0.1",
    "tslib": "^2.3.1",
    "typescript": "^4.6.3"
  }
}

打包编译看看

npm run build

看到成功创建

image.png

看看dist目录下的产物 👋

  • 可以看到确实生成了一个index.js对应我们rollup配置的打包出口文件,这也是我们package.json中配置的main属性到时候导入要用到的入口文件

  • 刚才故意用箭头函数写的深拷贝也成功被转成了es5的语法

image.png

本地调试模拟npm包安装导入验证

准备一个正常的项目,我这里用自己开发的脚手架工具vue2-admin-cli生成一个vue-admin项目来演示

全局安装脚手架
npm install -g vue2-admin-cli
# or
yarn global add vue2-admin-cli

vue2-admin-cli init <project_name> // 创建项目

yarn serve // 启动项目

image.png

把utilibs根目录下的package.json和README.md拷贝到dist目录下

image.png

用npm link链接到全局

cd dist
npm link

image.png

提示成功,可以去自己全局安装的npm的node_modules下找一找是不是有

image.png

接下里去vue-admin项目里link这个包, 这里注意要带上包名(package.json里面配置的name)

npm link utilibs

可以看到vue-admin项目的node_modules已经成功安装到我们的utilibs包了

image.png

这里用npm link引入以后有eslint的报错的话加个.eslintignore文件忽略链接到的这个地址的eslint检查

.eslintignore
E:\project\utilibs\dist

在vue-admin的main.ts里面引入写上这样一段代码测试一下有没有生效

src/main.ts
import { deepClone } from "utilibs";

const obj: any = { a: 100, b: { c: 200 } };
const objCopy = deepClone(obj);
obj.b.c = 300;
console.log("obj:", obj);
console.log("objCopy", objCopy);

可以看到控制台的打印,我们的深拷贝生效了,非常奈斯💖💖

image.png

但是这里import引入utilibs时没有代码补全提示,并且启动有找不到模块的报错,虽然不影响功能,但是强迫症的我很难受

image.png

有过ts项目开发经验的我知道这里是因为utilibs没有ts的声明文件的原因,如果是单纯的想在vue-admin里面解决这个报错的话,其实可以很粗鲁的在shims-vue.d.ts文件里声明一下

src/shims-vue.d.ts
declare module "utilibs";

这样去解决治标不治本,引入utilibs的时候代码提示没有,并且别人在用我的包里的方法时也没有享受到我们用ts开发的作用(规范不到用户的传参),那岂不是ts用的意义就不大了。所以还是从源头解决吧!!

  1. tsconfig.json配置修改
{
  "compilerOptions": {
    "target": "es5",  // 要改成es5,不然babel转换es6的时候有些转换不了
    "module": "commonjs",
    "declaration": true, // 根据ts文件自动生成.d.ts声明文件和js文件
    "emitDeclarationOnly": true, // 只输出.d.ts声明文件,不生成js文件
    "outDir": "./dist", // 输出目录
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitAny": false,
    "noImplicitThis": false,
    "skipLibCheck": true
  }
}
  1. 修改package.json,增加scripts脚本和types属性
"types": "index.d.ts",  // 声明文件的主入口
"scripts": {
    "build:types": "tsc",
    "build": "npm run build:types && rollup --config",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  1. 重新去执行npm run build,就可以生成声明文件了

image.png

  1. 再去vue-admin里看看,就有引入时的提示了,并且按住ctrl点击也可以跳到我们的声明文件里去了,这样也充当了一个api的作用

image.png

image.png

发布到npm

本地验证结束,自然是要发布到npm上去了,小伙伴们先去npm官网上面注册一个npm的账号

  • 注意dist目录下要有package.json和README.md, npm官网上显示的信息都是这里面读取;
  • package.json里的version每次发布都要比之前的大
cd dist
npm addUser // 添加账户输入账号密码邮箱和邮箱验证码

如果不知道当前登录的账号可以用who命令查看身份:

npm who am i

还可以用下面命令退出当前账号

npm logout

登录成功就可以将我们的包推送到npm上去了

npm publish

发布完收到成功邮件,刷新npm官网就能看到自己的npm包了

image.png

在vue-admin项目引入

(先用npm unlink utilibs解除之前本地调试创建的链接)

  1. es module方式引入
yarn add utilibs

src/main.ts
import { deepClone } from "utilibs";
console.log(deepClone)

引入成功👏👏

image.png

  1. cdn方式引入,这里我用的是第三方的jsdelivr

访问cdn.jsdelivr.net/npm/包名/ 可以看到所有文件

刚发布的可以用purge.jsdelivr.net/npm/包名/ 刷新cdn缓存

image.png

用script标签引入cdn

public/index.html
<script src="https://cdn.jsdelivr.net/npm/utilibs@1.1.1/index.js"></script>
<script>
  console.log(window.utilibs)
</script>

验证windows下可以拿到utilibs👏👏

image.png

问题汇总

1.使用rollup打包报错 You must supply "output.name" for UMD bundles that have exports so that the exports are accessible in environments without a module loader.

有export的时候必须在rollup.config.js的output中指定name

2.发布npm包报错 You do not have permission to publish "utilib". Are you logged in as the correct user?

包名字重名了 需要换一个

3.没有ts声明文件和代码提示

文中本地调试模拟npm包安装导入验证中已写

4.配置了babel没有编译es6成功

tsconfig.json { "compilerOptions": { "target": "es5" } }

5.cdn引入提示跨域请求返回类型是text/plain

image.png 打包输出文件改为js文件,不要用ts文件

6.本地npm link调试时eslint报错

添加.eslintignore文件

项目地址

  • utilibs ——js通用方法库
  • vue-admin —— vue 中后台系统解决方案
  • vue2-admin-cli—— vue2-admin-cli是vue-admin的cli脚手架工具,支持快速搭建企业级中后台项目模板

总结

历时三个小时,写了三千多个字,尽量把我踩过的坑都描述了一遍,本意是希望在看这篇文章的你如果对这些不是很熟悉能够跟着一起做一遍把坑踩一遍加深印象,同时也是自己对这些知识的一个总结归纳,前端路漫漫,与大家共勉😄😄,最后欢迎大家在评论区交流学习。至此,完结撒花❤️❤️❤️❤️