直接运行TypeScript,Deno能NodeJS也能
本文是azuo和萌妹俩技术创作之旅的第9篇原创文章,内容创作@azuo😄,精神支持@大头萌妹😂
Deno号称要来替代是Node.js。但是真的学不动了。

Deno 相比 Node.js 新增的功能特性,Node.js作为语言平台是可以通过代码实现也是扩展更多的新功能。本文将手把手带你用 Node.js 如何实现 Deno 直接运行TypeScript 模块能力。
一、了解Deno
Deno 原生支持 TypeScript 语言,可以直接运行,不必显式转码。
它的内部其实会根据文件后缀名判断,如果是.ts
后缀名,就先调用 TyepScript 编译器,将其编译成 JavaScript;如果是.js
后缀名,就直接传入 V8 引擎运行。
其实Deno也不是直接运行TypeScript代码,而是把TyepScript转成Javascript后再执行。
具体流程如下:
今天文章内容将详细讲解上面流程图绿色部分运行原理和代码实现。
二、转译TS
Node.js本身无法直接运行TypeScript,需要转译成JavaScript后再运行。TypeScript转译成Javascript ,需用到 ts-node(详细使用文档:typestrong.org/ts-node/api…)
接下来,将详细讲解 ts-node
将 ts 转译 js。
2.1 转译流程
ts-node转译 TypeScript,流程很简单,一共就两步,具体如下:
-
第一步: 使用register(详细文档点击跳转)模块创建一个 TypeScript 编译器实例【tsCompiler】:
-
第二步:调用编译器的实例【tsCompiler】将 TypeScript 转译 JavaScript。
const { register } = require('ts-node')
const tsCompiler = register()
const code = tsCompiler.compile('ts代码字符串', '文件名')
2.2 代码实现
第一步:内容准备:
- 创建目录
gan-deno
,并npm初始化; - 然后新建一个
typeScript
文件./input/ts-modeule.ts
,当成内容输入;
- npm初始化
# 初始化项目
npm init -y
- 准备一个ts文件,内容如下:
// ts-modeule.ts文件内容
export function add(a: number, b: number): number {
return a + b
}
export function subtract(a: number, b: number): number {
return a - b
}
export interface iXxxObj {
a: number
b: string
}
export const xxxObj = {
a: 1,
b: 2
}
第二步: 安装ts-node及其依赖
# 安装 typescript
npm install --save typescript
# 安装 ts-node
npm install --save ts-node
第三步:使用fs模块读取ts模块内容并执行
const fs = require('fs')
const path = require('path')
const { register } = require('ts-node')
// ts 转 js
function compile(modulePath) {
const { base } = path.parse(modulePath)
// 读取ts模块文件内容
const tsContet = fs.readFileSync(modulePath, { encoding: 'utf8' })
// 将ts代码转译成js代码
const code = register().compile(tsContet, base)
return code
}
// 转译后js代码
const code = compile(path.resolve(__dirname, './input/ts-modeule.ts'))
console.info('js code:', code)
输出结果:
ts-node
转译后的JS内容是符合CommonJS
模块规范滴。
三、运行时处理
上面已经可以完成 TypeScript
转译成 javascript
过程,
接下来,就要执行并返回TS模块。这个过程还需要两步:
- 第一步:要执行
javascript
的代码字符串即可。
在运行时运行JavaScript代码的字符串,有
eval
和new Function
两种方式。
- 第二步:模块挂载后导入。
那么如何获取获取执行后的模块,观察TypeScript转译JavaScript内容:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.xxxObj = exports.subtract = exports.add = void 0;
function add(a, b) {
return a + b;
}
exports.add = add;
function subtract(a, b) {
return a - b;
}
exports.subtract = subtract;
exports.xxxObj = {
a: 1,
b: 'bbb'
};
转译后的JS,是符合
CommonJS模块
,导出模块内容都挂在exports
。关键在于 ——— 想办法读取exports
。
3.1 eval方式
按照eval
执行特性,只需在eval
执行前定义一个 exports
变量来进行挂载即可,实现代码如下:
const fs = require('fs')
const path = require('path')
const { register } = require('ts-node')
function loadTSMoudle(modulePath) {
const { base } = path.parse(modulePath)
// 读取ts模块文件内容
const tsContet = fs.readFileSync(modulePath, { encoding: 'utf8' })
// 将ts代码转译成js代码
const code = register().compile(tsContet, base)
// 定义一个变量exports来接受
const exports = {}
// 执行转译后js模块代码
eval(code)
return exports.default || exports
}
// 获取ts 模块
const tsModule = loadTSMoudle(path.resolve(__dirname, './input/ts-modeule.ts'))
// 执行模块
console.info(
'xxxObj:', tsModule.xxxObj,
'\nadd:', tsModule.add(2, 4),
'\nsubtract:', tsModule.subtract(2, 4),
)
输出结果:
3.2 new Function 方式
遵循CommonJS
模块实现原理,写一个简单版的导出函数,内容如下:
const fs = require('fs')
const path = require('path')
const { register } = require('ts-node')
function loadTSMoudle(modulePath) {
const { base } = path.parse(modulePath)
// 读取ts模块文件内容
const tsContet = fs.readFileSync(modulePath, { encoding: 'utf8' })
// 将ts代码转译成js代码
const code = register().compile(tsContet, base)
// 构造一个极简版 CommonJS的导出函数
const loadFunc = new Function(
'exports', 'require', 'module', '__filename', '__dirname',
code
)
const exports = {}
const module = { exports }
loadFunc(exports, require, module, __filename, __dirname)
return exports.default || exports
}
// 获取ts 模块
const tsModule = loadTSMoudle(path.resolve(__dirname, './input/ts-modeule.ts'))
// 执行模块
console.info(
'xxxObj:', tsModule.xxxObj,
'\nadd:', tsModule.add(2, 4),
'\nsubtract:', tsModule.subtract(2, 4),
)
执行结果如下:
四、扩展 require
扩展
require
方法,来直接加载和运行 ts模块。给予ts模块和JS模块一样的排面!
重写 Module._extensions['.ts']
方法,在里面读取文件内容,然后调用 ts-node
来把 ts 转译成 js,之后调用 Module._compile 来处理编译后的 js模块。
代码实现如下:
const fs = require('fs')
const path = require('path')
const { register } = require('ts-node')
// 注册一个ts处理方式
require.extensions['.ts'] = function (module, filename) {
// 获取包路径
const modulePath = require.resolve(filename)
const { base } = path.parse(modulePath)
// 读取ts模块文件内容
const tsContet = fs.readFileSync(modulePath, { encoding: 'utf8' })
// 将ts代码转译成js代码
const code = register().compile(tsContet, base)
module._compile(code, base);
}
// 直接用require获取ts 模块
const tsModule = require(path.resolve(__dirname, './input/ts-modeule.ts'))
// 执行模块
console.info(
'xxxObj:', tsModule.xxxObj,
'\nadd:', tsModule.add(2, 4),
'\nsubtract:', tsModule.subtract(2, 4),
)
输出结果:
五、总结
Node.js 是一个语言平台,除了语言除本身语法和功能特性外,更重要的是围绕其自身平台建立起来的生态。截至2020年3月17日,npm为大约1200万开发人员提供了130万个软件包,这些开发人员每月下载这些软件包达750亿次。如此繁荣的生态,已经为这个语言平台打下了坚实的基础。 Deno之于 Node.js 新特性和功能,在笔者看来,有不少只具有“次要特征”,完成可以在 Node.js这个平台上通过代码实现来扩展。
本文就演示如何支持加载一个TypeScript模块。有兴趣的同学,可以参考本文的示例,来尝试使用
babel
来扩展扩展JSX
模块。
转载自:https://juejin.cn/post/7125741764333273096