基于TypeScript,发布一个npm包

概述

npm包有什么用

有的同学在开发的过程中,经常会造一些“轮子”,也就是一些复用性比较强的库(工具函数库或者组件库),那么将这些“轮子”发布成自己的一个npm包,绝对会给你带来工作效率的提升。

为什么用TypeScript

引用 TypeScrip教程 内提及的内容:

TypeScript 增加了代码的可读性和可维护性

  • 类型系统实际上是最好的文档,大部分的函数看看类型的定义就可以知道如何使用了
  • 可以在编译阶段就发现大部分错误,这总比在运行时候出错好
  • 增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、代码重构等

TypeScript 非常包容

  • TypeScript 是 JavaScript 的超集,.js 文件可以直接重命名为 .ts 即可
  • 即使不显式的定义类型,也能够自动做出类型推论
  • TypeScript 的类型系统是图灵完备的,可以定义从简单到复杂的几乎一切类型
  • 即使 TypeScript 编译报错,也可以生成 JavaScript 文件
  • 兼容第三方库,即使第三方库不是用 TypeScript 写的,也可以编写单独的类型文件供 TypeScript 读取

其中,对于npm上的包来说。在使用包内的工具时候,上述的接口提示就十分强大。

可以用JavaScript吗

有些不会TypeScrip的同学可能说,这篇不适合我看。错!

本篇内容也可以采用JavaScript的方式去写,去发布使用。完全是适配的。因为我们使用TypeScript,目的还是约束开发规范之类的问题,最终还是得通过打包成.js文件。如若用JavaScript的方式去写,完全可以的,文末也会6.2处也会提及具体相关。上述:TypeScript 是 JavaScript 的超集,.js 文件可以直接重命名为 .ts 即可也能体现两者的关系。

具体操作内容

本文采用vue-cli3工具创建TypeScript项目,目的是为了让读者更清晰看到自己写的工具方法(写组件同理)能及时的验证,让我们知悉在源码编写这一层面上是没有问题。通过写几个简单工具方法,然后打包,发布到npm上,最后在项目中安装自己发布的包。目的是:成功调用自己写的包内的方法。

本文项目地址

github地址:https://github.com/chenjing0823/util-tools
npm包地址:https://www.npmjs.com/package/common-util-tools

操作步骤

1、创建项目

vue create util-tools

然后选择BabelTypescriptUnit TestingJest其余默认
运行项目后,正常打开vue默认页后。项目创建完成,可以继续操作,关闭服务。

2、目录调整

之所以需要目录调整,是因为我们利用采用vue-cli3工具创建了TypeScript+vue的一个项目,其中在本项目内,我们只是要写一个utils工具包,而vue的页面实例仅仅是用来校验我们的工具是否可用可行。
所以目录调整的目的,是将包代码合页面实例代码区分开。

    1. 在根同目录下,创建一个example文件夹,将src移动至改文件夹内(页面实例,用于验证方法,与工具包无关)
    1. 在根目录下创建src,用于放我们写的工具包函数方法
    1. tsconfig.json内,需要修改以下内容(新增3个example/src/)
{
// ...其他配置
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "example/src/**/*.ts",
    "example/src/**/*.tsx",
    "example/src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ]
// ...其他配置
}

3、源码编写

src/index.ts:

如果对于下面4个文件存在疑惑,且比较注重发布这个过程,也可以直接在index.ts内直接export自己写的方法,不写其他几个文件方法。

import * as env from "./util-tool/env"; // 方法集合1
import * as is from "./util-tool/is"; // 方法集合2
import { Types } from "./types";
import { mixin } from "./tools/index";

function initUtils(): Types {
  const instance = Object.create(null);
  const arr = [env, is];
  mixin(instance, arr);

  return instance as Types;
}

const _utils = initUtils();

export default _utils;

src/util-tool/env

utils工具方法1

/**
 *
 * @ignore
 * @return {boolean} 判断当前浏览器是移动端(false)还是pc端(true)
 *
 */
export function getEnv(): boolean {
  const userAgent = navigator.userAgent;
  const device: string[] = [
    "Android",
    "iPhone",
    "SymbianOS",
    "Windows Phone",
    "iPad",
    "iPod"
  ];
  let flag = true;
  for (let i = 0; i < device.length; i++) {
    if (userAgent.indexOf(device[i]) !== -1) {
      flag = false;
      break;
    }
  }
  return flag;
}

src/util-tool/is

utils工具方法2

export function isArray(value: any): value is Array<any> {
  return typeof value !== "undefined" && value instanceof Array;
}

export function isObject(value: any): value is Record<string, any> {
  return value !== null && typeof value === "object";
}

src/types

interface Env {
  /**
   *
   * 判断当前浏览器是移动端还是pc端
   * @return {boolean} pc: true; mobile: false
   * @author superjing
   * ``` typescript
   * const env = utils.getEnv()
   * ```
   */
  getEnv(): boolean;
}

interface Is {
  /**
   *
   * 判断是否是数组
   * @param value 传入需要判断的变量
   * @return {boolean} true | false
   * @author superjing
   * ``` typescript
   * utils.isArray([1, 2])    // true
   * ```
   *
   */
  isArray(value: any): boolean;
}
export interface Types extends Env, Is {}

tools/index

export function mixin<T, U>(to: T, from: Array<U>): T {
  from.forEach(obj => {
    Object.getOwnPropertyNames(obj).forEach(key => {
      to[key] = obj[key];
    });
  });
  return to;
}

4、项目配置

简单的配置,易理解

/build/config.doc.js

该配置调试example文件夹中的页面实例,在这个单页面中可以直接使用以及测试编写的工具方法

const path = require("path");
const resolve = dir => path.join(__dirname, "../", dir);

console.log("run doc");

module.exports = {
  publicPath: "./",
  devServer: { port: "8000" },
  outputDir: resolve("docs"),
  pages: {
    index: {
      entry: resolve("example/src/main.ts"),
      template: "public/index.html",
      filename: "index.html",
      title: "Index Page",
      chunks: ["chunk-vendors", "chunk-common", "index"]
    }
  },
  chainWebpack: config => {
    config.plugins.delete("prefetch-index");
  }
};

/build/config.lib.js

配置打包utils工具包,打包不包括example文件夹下的示例页面,仅仅是index.ts中实现的函数,打包目标为umd格式,兼容浏览器,node以及es6模块规范;

const path = require("path");
const resolve = dir => path.join(__dirname, "../", dir);

console.log("run lib");

module.exports = {
  outputDir: resolve("dist"),
  configureWebpack: {
    entry: {
      utils: resolve("src/index.ts")
    },
    output: {
      filename: `[name].js`,
      libraryTarget: "umd",
      libraryExport: "default",
      library: "utils",
      globalObject: "this"
    }
  },
  css: {
    extract: {
      filename: `[name].css`
    }
  },
  chainWebpack: config => {
    config.optimization.delete("splitChunks");
    config.plugins.delete("copy");
    config.plugins.delete("preload");
    config.plugins.delete("prefetch");
    config.plugins.delete("html");
    config.plugins.delete("hmr");
    config.entryPoints.delete("app");
  }
};

/build/index.js

module.exports =
  process.env.NODE_ENV === "production"
    ? require("./config.lib")
    : require("./config.doc");

/vue.config.js

module.exports = require("./build/index");

到这为止,配置完成。下面进行工具调试

5、本地调试

/example/src/App.vue

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png" />
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
  </div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import HelloWorld from "./components/HelloWorld.vue";
import utils from "@/index";

@Component({
  components: {
    HelloWorld
  }
})
export default class App extends Vue {
  mounted() {
    console.log(123);
    console.log("utils.isArray([]) is array is:", utils.isArray([]));
    console.log("utils.isArray('') is array is:", utils.isArray(""));
  }
}
</script>

下图可以看到,用typescript开发的提示功能
提示.png

启动服务(确认4、项目配置无误后,方可正常启动)

npm run serve 

可以看到写的工具可以正常使用:


本地工具调用结果.png

6、打包发布

6.1 打包

在4、项目配置的config.lib.js内,已经写了打包相关内容,运行打包命令

npm run build 

在目录处生成dist文件夹,内有生成的utils.js
同样,在根目录建一个demo.html,直接引入打包生成的js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script src="./dist/utils.js"></script>
<script>
    console.log(123);
    console.log("utils.isArray([]) is array is:", utils.isArray([]));
    console.log("utils.isArray('') is array is:", utils.isArray(""));
</script>
</body>
</html>

调试结果.png

同样可以正常使用,于是工具包便算开发完成,可以自己发布了(若公司内要规范npm的包,还需要进行单元测试,我们构建项目的时候已经选择了Unit Testing、Jest

6.2 发布

通过该文件,其实也可以看到,若是用JavaScript写该插件,也是极其方便的,只要把最后打包生成的js设为mian的入口即可。对typescript有不熟悉的,也可以用JavaScript进行开发
修改package.json

{
  "name": "common-util-tools", // 包名,不能跟已有的包名有重复
  "version": "0.1.0", // 每次发布需要修改版本号
  "private": false, // 需要设置false
  "author": "chenjing0823",
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "test:unit": "vue-cli-service test:unit",
    "lint": "vue-cli-service lint"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/chenjing0823/util-tools"
  },
  "license": "MIT",
  "main": "dist/utils.js", // 入口需要正确
  "keywords": [
    "utils",
    "tools"
  ],
  "files": [
    "dist"
  ],
// ...其余代码
}

然后发布(需要注册npm账户,并先登陆自己的npm账号发布)

npm publish

发布成功后,大概在短暂的延迟过后,就可以在https://www.npmjs.com/搜到自己发布的插件了。

npmjs查询包.png

对于发布有问题的,可以参考发布npm包时遇到的一些坑

7、安装调用

到上述为止,包的创建、编写、发布已经完成了,现在来试用一下试试:

7.1 新建一个空文件夹

在该文件夹下init生成一个空项目

npm init -y

7.2 安装我们的包

npm install common-util-tools -D

可以在package.json内看到:


image.png

7.3 新建一个index.js

const utils = require('common-util-tools')
console.log(123);
console.log("utils.isArray([]) is array is:", utils.isArray([]));
console.log("utils.isArray('') is array is:", utils.isArray(""));

7.4 使用测试

node index.js
使用结果.png

到此,我们从创建项目 - > 编写代码 - > 项目配置 - > 打包发布 - > 包的使用,已经大功告成,后续读者朋友就可以写出自己的一套东西去发布使用。例如一些公共组件的编写,公共方法的编写。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容