发布一个基于typescript实现的包到npm

概述

目的

typescript作为未来前端开发的主流语言,在前端开发的过程中扮演着越来越重要的角色。如果有的同学平时的主要开发内容都是处理业务逻辑,那么可能没有那么容易体会到typescript带来的好处,如果有的同学经常会造一些“轮子”,也就是一些复用性比较强的库(工具函数库或者组件库),那么typescript绝对会给你带来工作效率的提升。因为typescript的类型提示以及类型约束,能够大幅度地减少bug,使得功能结构清晰,这是ES6,ES7无法具备的优点。

内容

本文的内容是,基于vue-cli3脚手架创建的typescript工程,编写一些简单的typescript函数,打包为umd模块,发布到npm上,然后在浏览器环境、node环境、es6模块以及typescript模块中使用这个umd包,在typescript中具有类型提示、类型约束的功能,并且在其他类型的模块中具有类型提示的功能。

目标

本文的目标是使用typescript实现两个函数:

  • shuffle:打乱数组
  • extend: 合并对象属性

之后打包这两个函数(打包的内容仅有这两个函数),创建typescript类型声明文件,发布到npm上。在新的工程中,typescript运行环境下能够直接使用这个包,并且具有类型提示以及类型约束的功能,es6以及node运行环境下具有类型提示的功能。在html中直接引入打包的js,也能够在浏览器中直接使用;

demo-ts-lib工程地址:https://gitee.com/martsforever-demo/demo-ts-lib
test-demo-ts-lib工程地址:https://gitee.com/martsforever-demo/test-demo-ts-lib

步骤

创建工程

  1. 创建工程
    vue create demo-ts-lib
    
    选择手动选择功能配置
    Mamually select features
    
    选择Babel、Typescript、Unit Testing
    接下来的除了测试选择Jest,其他的选择默认就好了;
  2. 初始化工程
    • 安装依赖 npm i 或者 cnpm i
    • 启动项目,检查有没有问题:npm run serve
    • 检查没有问题之后,开始实现功能,并且创建类型声明文件;

结构调整

为了能够打包自定义的typescript包,这里需要将包源码与实例页面代码分开。

  1. 在根目录下创建一个home文件夹,然后将src目录下现有的文件全部移动到home文件夹下,src目录作为包的根目录,其中只存放函数相关的文件,而home则存放示例页面相关的文件;
  2. 根目录下的tsconfig.json中的include增加home目录扫描正则表达式:
    {
      "compilerOptions": {
        "target": "esnext",
        "module": "esnext",
        "strict": true,
        "jsx": "preserve",
        "importHelpers": true,
        "moduleResolution": "node",
        "experimentalDecorators": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "sourceMap": true,
        "baseUrl": ".",
        "types": [
          "webpack-env",
          "jest"
        ],
        "paths": {
          "@/*": [
            "src/*"
          ]
        },
        "lib": [
          "esnext",
          "dom",
          "dom.iterable",
          "scripthost"
        ]
      },
      "include": [
        "src/**/*.ts",
        "src/**/*.tsx",
        "src/**/*.vue",
        "home/**/*.ts",
        "home/**/*.tsx",
        "home/**/*.vue",
        "tests/**/*.ts",
        "tests/**/*.tsx"
      ],
      "exclude": [
        "node_modules"
      ]
    }
    

源码实现

src/index.ts

import {DemoTsLibType, DemoTsModule} from "@/types";

const extend: DemoTsModule.extend = <T, U>(to: T, from: U) => {
    const ret = {} as any

    for (const key in to) {
        ret[key] = to[key] as any
    }
    for (const key in from) {
        ret[key] = from[key] as any
    }

    return ret as T & U
}

const shuffle: DemoTsModule.shuffle = <T>(array: T[]) => {
    if (!array) return array
    array = [...array]
    let currentIndex = array.length;
    let temporaryValue, randomIndex;
    while (0 !== currentIndex) {
        randomIndex = Math.floor(Math.random() * currentIndex);
        currentIndex -= 1;
        temporaryValue = array[currentIndex];
        array[currentIndex] = array[randomIndex];
        array[randomIndex] = temporaryValue;
    }
    return array
}

const DemoTsLib: DemoTsLibType = {
    extend,
    shuffle
}

export default DemoTsLib

src/types/index.d.ts

export namespace DemoTsModule {
    type extend = <T, U>(to: T, from: U) => T & U
    type shuffle = <T>(array: T[]) => T[]
}

export interface DemoTsLibType {
    extend: DemoTsModule.extend
    shuffle: DemoTsModule.shuffle
}

declare const DemoTsLib: DemoTsLibType;

export default DemoTsLib

这里提示两点:

  • 第一个是在类型声明文件 index.d.ts 中声明类型,然后在 index.ts 中使用根据类型实现,尽量避免类型声明与代码实现不一致的情况出现;
  • 第二点是,index.d.ts中的默认导出是一个值,不能是类型,而且类型声明文件 index.d.ts 中的默认导出也是一个值,这个值的类型要与 index.d.ts 中默认导出的值的类型要一致。否则在新项目中无法得到正确的类型提示以及类型约束;如果 index.d.ts中默认导出的是一个类型,那么在typescript将无法正常使用;

在 home/App.vue中使用定义的DemoTsLib

<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 DemoTsLib from "@/index";

    @Component({
        components: {
            HelloWorld,
        },
    })
    export default class App extends Vue {

        mounted() {
            const a = {name: '123'}
            const b = {age: 20}
            const c = DemoTsLib.extend(a, b)
            console.log(c)

            const arr = [1, 2, 3, 4, 5]
            console.log(DemoTsLib.shuffle(arr))
        }

    }
</script>

<style>
    #app {
        font-family: 'Avenir', Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
    }
</style>

可以看到,变量c能够得到正确的合并类型的提示:
在这里插入图片描述

启动以及打包配置

/build/config.doc.js

这个文件的作用是配置调试home文件夹中的单页面,在这个单页面中可以直接使用以及测试编写的DemoTsLib

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

console.log('run doc')

module.exports = {
    publicPath: './',
    devServer: {port: '8887'},
    outputDir: resolve('docs'),
    pages: {
        index: {
            entry: resolve('home/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

这个文件的作用是配置打包DemoTsLib,打包不包括home文件夹下的示例页面,仅仅是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: {
            'demo-ts-lib': resolve('src/index.ts')
        },
        output: {
            filename: `[name].js`,
            libraryTarget: 'umd',
            libraryExport: 'default',
            library: 'DemoTsLib',
            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')

/demo.html

这个文件的作用是测试在html文件中直接引入打包好的js文件

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

<script src="./dist/demo-ts-lib.js"></script>
<script>
    var a = {name: '123'}
    var b = {age: 20}
    var c = DemoTsLib.extend(a, b)
    console.log(c)

    var arr = [1, 2, 3, 4, 5]
    console.log(DemoTsLib.shuffle(arr))
</script>
</body>
</html>

启动本地调试

npm run serve

浏览器控制台结果:

在这里插入图片描述

打包

npm run build

将会在根目录下生成dist文件夹
在这里插入图片描述
直接打开demo.html

在这里插入图片描述
这个demo.html直接在ie浏览器也是能够打开的。兼容到ie10;

发布到npm

修改package.json

{
  "name": "demo-ts-lib",
  "version": "0.1.0",
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "test:unit": "vue-cli-service test:unit"
  },
  "main": "dist/demo-ts-lib.js",
  "files": [
    "src/types",
    "dist"
  ],
  "types": "src/types/index.d.ts",
  "dependencies": {
    "core-js": "^2.6.5",
    "vue": "^2.6.10",
    "vue-class-component": "^7.0.2",
    "vue-property-decorator": "^8.1.0"
  },
  "devDependencies": {
    "@types/jest": "^23.1.4",
    "@vue/cli-plugin-babel": "^3.1.1",
    "@vue/cli-plugin-typescript": "^3.1.1",
    "@vue/cli-plugin-unit-jest": "^3.1.1",
    "@vue/cli-service": "^3.1.1",
    "@vue/test-utils": "1.0.0-beta.29",
    "babel-core": "7.0.0-bridge.0",
    "ts-jest": "^23.0.0",
    "typescript": "^3.4.3",
    "vue-template-compiler": "^2.6.10"
  }
}

没有npm账号的同学自行注册;注册完之后使用npm login 登录,然后发布:

npm publish

第一个版本0.1.0

在这里插入图片描述
全局安装typescript:

npm i -g typescript

创建一个空的文件夹:test-demo-ts-lib
进入该文件夹,执行以下命令初始化:

npm init 
tsc --init
npm i
npm i tslib -D

全部回车,使用默认设置

使用ide打开目录,创建文件:src/helloworld.ts

package.json的启动脚本中增加两个启动脚本:

"build": "tsc",
"start": "tsc --watch"

安装 demo-ts-lib

npm i demo-ts-lib -S

在helloworld.ts中使用DemoTsLib:

src/helloworld.ts

import DemoTsLib from "demo-ts-lib";

const a = {name: '123'}
const b = {age: 456}

const c = DemoTsLib.extend(a, b)

console.dir(c)

const arr = [1, 2, 3, 4, 5]

console.log(DemoTsLib.shuffle(arr))

可以看到,c具有正确的类型提示功能:

在这里插入图片描述

执行 npm start 开启编译

会在helloworld.ts旁生成一个helloworld.js

使用node执行这个helloworld.js

node ./src/helloworld.js

结果:

在这里插入图片描述

根目录下创建test.js

const DemoTsLib = require('demo-ts-lib')

const a = {name: '123'}
const b = {age: 456}

const c = DemoTsLib.extend(a, b)

console.dir(c)

const arr = [1, 2, 3, 4, 5]

console.log(DemoTsLib.shuffle(arr))

可以看到,仍然具有正确的类型提示功能:

在这里插入图片描述
使用node执行这个test.js

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值