文章目录
概述
目的
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
步骤
创建工程
- 创建工程
 选择手动选择功能配置vue create demo-ts-lib
 选择Babel、Typescript、Unit TestingMamually select features
 接下来的除了测试选择Jest,其他的选择默认就好了;
- 初始化工程 
  - 安装依赖 npm i或者cnpm i
- 启动项目,检查有没有问题:npm run serve
  
- 检查没有问题之后,开始实现功能,并且创建类型声明文件;
 
- 安装依赖 
结构调整
为了能够打包自定义的typescript包,这里需要将包源码与实例页面代码分开。
- 在根目录下创建一个home文件夹,然后将src目录下现有的文件全部移动到home文件夹下,src目录作为包的根目录,其中只存放函数相关的文件,而home则存放示例页面相关的文件;
- 根目录下的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

 
                   
                   
                   
                   
                             本文详述如何使用TypeScript与Vue CLI3构建可复用的前端库,涵盖库的开发、类型声明、打包及发布流程。通过具体示例,如shuffle数组与extend对象功能,展示TypeScript提升开发效率与代码质量的实际应用。
本文详述如何使用TypeScript与Vue CLI3构建可复用的前端库,涵盖库的开发、类型声明、打包及发布流程。通过具体示例,如shuffle数组与extend对象功能,展示TypeScript提升开发效率与代码质量的实际应用。
           
       
           
                 
                 
                 
                 
                 
                
               
                 
                 
                 
                 
                
               
                 
                 扫一扫
扫一扫
                     
              
             
                   2740
					2740
					
 被折叠的  条评论
		 为什么被折叠?
被折叠的  条评论
		 为什么被折叠?
		 
		  到【灌水乐园】发言
到【灌水乐园】发言                                
		 
		 
    
   
    
   
             
            


 
            