typescript作为未来前端开发的主流语言,在前端开发的过程中扮演着越来越重要的角色。如果有的同学平时的主要开发内容都是处理业务逻辑,那么可能没有那么容易体会到typescript带来的好处,如果有的同学经常会造一些“轮子”,也就是一些复用性比较强的库(工具函数库或者组件库),那么typescript绝对会给你带来工作效率的提升。因为typescript的类型提示以及类型约束,能够大幅度地减少bug,使得功能结构清晰,这是ES6,ES7无法具备的优点。
本文的内容是,基于vue-cli3脚手架创建的typescript工程,编写一些简单的typescript函数,打包为umd模块,发布到npm上,然后在浏览器环境、node环境、es6模块以及typescript模块中使用这个umd包,在typescript中具有类型提示、类型约束的功能,并且在其他类型的模块中具有类型提示的功能。
本文的目标是使用typescript实现两个函数:
之后打包这两个函数(打包的内容仅有这两个函数),创建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
选择手动选择功能配置Mamually select features
选择Babel、Typescript、Unit TestingJest
,其他的选择默认就好了;为了能够打包自定义的typescript包,这里需要将包源码与实例页面代码分开。
{
"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
中默认导出的是一个类型,那么在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>
这个文件的作用是配置调试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')
},
}
这个文件的作用是配置打包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')
}
}
module.exports = process.env.NODE_ENV === 'production' ? require('./config.lib') : require('./config.doc')
module.exports = require('./build/index')
这个文件的作用是测试在html文件中直接引入打包好的js文件
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
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;
{
"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
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))
可以看到,仍然具有正确的类型提示功能: