加入收藏 | 设为首页 | 会员中心 | 我要投稿 源码门户网 (https://www.92codes.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

【第1592期】ts2php, 将你的 TypeScript 代码转换为 PHP

发布时间:2023-01-29 13:32:14 所属栏目:PHP教程 来源:未知
导读: 前言
想想这个还是有开发场景的,有点酷。今日早读文章由百度@梅旭光投稿分享。
@梅旭光,现任百度搜索前端团队高级研发工程师,参与搜索组件化渲染框架、多端统一开发框架的研发。毕业于天

前言

想想这个还是有开发场景的,有点酷。今日早读文章由百度@梅旭光投稿分享。

@梅旭光,现任百度搜索前端团队高级研发工程师,参与搜索组件化渲染框架、多端统一开发框架的研发。毕业于天津大学,对前端框架,以及前端组件的设计和实现,有较丰富的经验,致力于提升 Web 产品的用户体验。摩托骑行爱好者。个人博客:meixg.com

正文从这开始~~

我们实现了一个工具 ts2php,可以将 TypeScript 文件转换为 PHP 文件,支持了大部分的 TypeScript 语法。它的意义在于可以让同一份逻辑,在前端(通过 JS)和后端(通过 PHP)都可以执行。

又一个装 X 项目?

为什么要将 TypeScript 转换为 PHP?当然因为 PHP 是世界上最好的语言。

玩笑放在一边,技术要为业务服务,这么做的原因,就是业务需要,对业务有帮助。

我们当前的项目有以下特点:

在这种情况下,我们当前的方案是一份代码编译为 JS 和 PHP 两份文件,分别运行在前端和后端。

这种场景下,难以避免有一些逻辑需要在前后端都执行。除了 DSL 转换为 PHP ,也需要将业务逻辑转换为 php 在后端执行。ts2php 尝试将 TS 转换为 PHP 文件,作为业务逻辑在后端执行的实践。

可能很多人会觉得直接重写成 Nodejs 多好?但这需要对大量代码进行重构,相信每个程序员都有重构代码的冲动,但重构并不一定是一个好的项目决策,它会阻塞整个项目的迭代速度,重复一遍之前解决过的bug。Joel 大神在他的博客中有这样一篇讲重构的文章,我觉得讲的很好:Things You Should Never Do, Part I[1]。

很多时候渐进式的修改是一个更有效的办法。利用 ts2php ,我们可以渐进式的对现有架构进行升级。同时由于项目代码都可以使用 ts 书写,后续迁移 node 成本也会小很多。

另外,我们也通过实践证明了,虽然想法听上去有些不符合常规,但是,这确实是可行的。

为什么不是JS?

进行编程语言间的转换,一个难点在于抹平不同语言间运算语法的不一致。各语言间的运算是不能做好完全对应的,这就需要我们根据变量类型来将同一个源语言运算符转换为不同的目标语言运算符。

没有选择将 JS 转换为 php,是因为 JS 作为一个弱类型语言,编译阶段是无法判断变量类型的,这样在转换时,没办法对运算符进行取舍。举例来说,JS 中的 + 符号,当运算符两边是数字时PHP字符串变量,执行加法运算;两边是字符串时,执行字符串拼接。

console.log(123 + 456); // 579
console
.log('123' + '456'): // '123456'

但在 PHP 中,+ 符号只进行加法运算:

echo 123 + 456; // 579
echo
'123' + '456'; // 579

我们需要判断+两边有字符串时,转换为 PHP 中的.:

echo '123' . '456'; // '123456'

TypeScript

为了在编译阶段判断变量类型,我们需要给 JS 加上类型。目前 JS 社区中,有 Typescript 和 Flow 两种强类型方案,但选择 TypeScript 其实是没有难度的,TypeScript 的更新速度、生态,使用体验都相对更好,Vue 也将在 3.0 中使用 TypeScript 进行重写[2]。

有越来越多的项目使用了 Typescript 进行开发,相信 TypeScript 就不用过多介绍了,对于复杂项目的维护和开发都是很有帮助的。

我之前的一篇文章:TypeScript 编译流程及内部类简介,希望能对大家了解 TypeScript 的编译及内部类能有帮助。

进度怎么样了呢?

ts2php 支持了大部分的 TS 语法,同时它是开源的。使用方式和支持了的的语法特性列表可以见 Github 项目首页:

当前 TS 转 PHP 方案,已经在我们的线上得到应用。我们的目标是实现一个 TS 的真子集,满足业务开发。

怎么做到的?

接下来介绍一下 ts2php 是如何实现的。

语言间的转换就是一个编译过程:将源语言转换为 AST,之后根据 AST 生成目标语言。

那么首先我们需要获取 TypeScript 的 AST。

TypeScript Compiler API

要进行语言间的转换,首先要做的就是得到源语言的 AST。JS 可以在 GitHub 上找到很多解析器,但 TypeScript 目前基本上只能依赖官方提供的 API。

目前 TypeScript 的 Compiler API[3] 还不是很稳定,但是已经可以提供强大的功能,比如:

ts.createProgram

createProgram 方法可以根据输入的多个文件,创建一个 Program。Program 指一个编译单元,它是由多个 SourceFile 和一些编译选项组成的。Program 是类型系统和代码生成的总入口。将多个文件合并在一起,是为了方便进行依赖分析。

 let program = ts.createProgram(fileNames, options);

SourceFile

上面我们说 Program 是由多个 SourceFile 和一些编译选项组成的,那么 SourceFile 又是什么呢?SourceFile 本身是基础抽象语法树结构 Node 的一个扩展,他在抽象语法树的基础上增加了额外的接口,可以用来获取源文件代码、源文件地址、identifier 列表等功能。

我们可以使用 program.getSourceFiles 来得到 Program 中的所有 SourceFile 并进行遍历。

 for (const sourceFile of program.getSourceFiles()) {

}

TypeChecker

我们选用 TypeScript 的一个主要原因就是类型系统,TypeScript 提供了 TypeChecker 来帮助我们在编译阶段判断一个变量的类型。我们可以从 Program 中得到一个对应的 TypeChecker:

 let checker = program.getTypeChecker();

TypeChecker 是 ts 类型系统的核心。它负责计算不同文件中 Symbols 的关系,判断 Symbol 的类型,生成语法诊断(也就是语法错误)。

TypeChecker 可以回答以下问题:

转换流程

利用 TypeScript 的 Compiler API, ts2php 的执行路径可以抽象为一下步骤:

生成 Program。

得到 typeChecker。

获取并遍历 sourceFiles。

使用 transformer 对 sourceFile 的 AST 进行转换。

遍历 AST,使用 emitter 得到 PHP 代码。

感兴趣的同学欢迎看一下 ts2php 的代码,对 TypeScript 的编译方式,希望能有有一定的帮助。

API 的对齐

对于 Core JavaScript API,很多方法在 PHP 无法找到直接的对应,例如 String.prototype.replace 等。

ts2php 新增了一个PHP 的类库,来解决这个问题。类库中实现了一些没有对应到的方法,使用时需要在 PHP 中引入:

require_once("/path/to/ts2php/dist/runtime/Ts2Php_Helper.php");

模块化

PHP 的模块机制跟 TypeScript 有着很大的不同,PHP 的 require 与 include 相当于将代码直接插入文件中执行,会有命名冲突问题。

例如有两个 PHP 文件 file1.php 和 file2.php,都定义了一个叫做 abc 的函数,如果 file2.php 引用了 file1.php,那么会报错:

<?php
function abc() {
echo
'file1';
}

<?php
require('./file1.php'); // Fatal error: Cannot redeclare abc()

function abc() {
echo
'file2';
}

因此 import 不能直接转换为 require,ts2php 使用 namespace 来进行 TypeScript 中 import 和 export 的转换。

export 转换为定义命名空间:

export default function run(a: string) {
var b = '111';
console
.log(a, b);
}

<?php
namespace test\export;
function run($a) {
$b
= "111";
echo $a
, $b;
}

而 import 会转换为 use namespace 来使用命名空间:

import {Other_Utils as Util} from '../some-utils';
import {Some_Utils, func} from '../some-utils';

type
TplData = {
src
?: string,
title
?: string
};

const tplData: TplData = {};
tplData
.src = Some_Utils.makeTcLink('url');
tplData
.title = Some_Utils.highlight('title');
tplData
.title = Util.sample;

tplData
.title = func() + 'aa';

<?php
namespace test\import;
require_once
(realpath(dirname(__FILE__) . '/' . "../some-utils.php"));
use \Other_Utils as Util;
use \Some_Utils;
use \func;

$tplData
= array();
$tplData
["src"] = Some_Utils::makeTcLink("url");
$tplData
["title"] = Some_Utils::highlight("title");
$tplData
["title"] = Util::$sample;
$tplData
["title"] = func() . "aa";

注意事项

当然,TypeScript 转换成 PHP,一定是会有坑的,以下写法在语言转换以后,执行结果是不相同的,还未解决:

以上就是我们做 ts2php 的原因、进度以及一些实现方式了,项目地址 :

尽管是一个不太主流的方案,但我们相信,能解决业务问题的技术,就是好的技术。

相关文章

Things You Should Never Do, Part I:

Plans for the Next Iteration of Vue.js:

Using the Compiler API:

Quickest PHP equivalent of javascriptvar a = var1||var2||var3;expression:

php 字符转实体字符_php获取特定字符_PHP字符串变量

为你推荐

(编辑:源码门户网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!