资讯详情

资讯详情

TMS WEB Core:Object Pascal 直接编译为生产级 JavaScript 的原理

简介本资源是一份面向Delphi开发者含初学者与进阶用户的Web前端开发实战教程聚焦TMS WEB Core框架解决传统Delphi程序员向现代Web应用开发转型中的核心痛点——如何用Object Pascal高效构建跨平台、纯HTML/JS的Web应用。教程覆盖框架原理、环境配置Delphi社区版TMS试用版即可、项目创建含Web应用、PWA、Electron及Miletus桌面端、Pas2JS编译机制、RTL运行时库、预处理器与命令行编译等关键技术模块并对比IntraWEB、uniGUI等主流Delphi Web框架突出TMS在100%无二进制部署、多端兼容Windows/Mac/iOS/Android/Linux方面的独特优势。资源为单文件PDF共1个3.84MB文档内容结构完整含企业级培训目录、历史演进脉络、组件使用详解及页面设计实践。目前已有5028人学习下载是系统掌握TMS WEB Core全栈开发能力的高价值入门与进阶指南。1. TMS WEB Core它不编译出 EXE却能跑在浏览器里——Delphi 真正第一次“前端化”的底层真相你写完一段 Delphi 代码点下运行弹出来的不是窗体而是一个 localhost:8080 的网页F12 打开开发者工具Elements 里全是标准 HTML 标签Console 里跑着原生 JavaScriptNetwork 面板里没有 .exe、没有 .dll、甚至没有 .so 或 .dylib —— 只有 index.html、app.js、style.css。更反直觉的是这段 JavaScript 不是手写的也不是用 TypeScript 编译来的而是由 Delphi 编译器dcc32/dcc64或 Lazarus 的 fpc 直接“翻译”出来的。这不是模拟、不是封装、不是桥接而是 Object Pascal 源码 → 抽象语法树AST→ JavaScript AST → 生成可执行 JS 的完整链路。TMS WEB Core 的核心价值从来不是“用 Delphi 写网页”而是把 Object Pascal 变成一门真正能产出生产级前端资产的系统级语言。它解决的不是“怎么让 Delphi 跑在浏览器”而是“如何让桌面级强类型、强 IDE、强组件生态的开发范式无缝迁移到 Web 运行时”。这背后没有魔法只有三重硬核机制Pascal 到 JS 的语义保真翻译器Pas2JS、Web 原生控件的 DOM 封装抽象层TWebXXX 组件族、以及脱离 VCL/FMX 的全新 RTL 运行时WebCore RTL。搞懂这三层你才不会把它当成“Delphi 的另一个 GUI 库”而会意识到这是 Object Pascal 在 Web 时代的一次底层重定义。2. Pas2JS 编译器Object Pascal 如何被“翻译”成可执行 JavaScript不是转译是重编译TMS WEB Core 的第一块基石是 Pas2JS —— 它不是简单的字符串替换式转译器如早期的 pascal2js而是一个具备完整前端编译流程的重编译器。它的输入是.pas源文件含单元依赖分析输出是符合 ES2015 规范、带 source map 的.js文件。关键在于它不依赖任何运行时解释器也不注入虚拟机代码生成的 JS 是纯函数式、无全局污染、可直接被现代浏览器执行的原生脚本。2.1 Pas2JS 的编译阶段拆解从 Pascal AST 到 JS AST 的语义映射Pas2JS 的工作流严格遵循经典编译器四阶段模型但每一步都针对 Web 环境做了深度定制词法分析Lexing识别begin...end、procedure、class、property等 Pascal 关键字同时兼容 Delphi 10.4 的record helper、inline、strict private等扩展语法语法分析Parsing构建 Pascal AST特别处理with语句的作用域链、try..except的异常传播路径、for..in循环对TArrayT的迭代语义语义分析Semantic Analysis这是 Pas2JS 最具区分度的环节。它不只做类型检查还做跨语言语义对齐例如将TStringList映射为ArraystringMapstring, string的组合封装将TDateTime转为Date对象并重载、-运算符为毫秒偏移将TThread.Synchronize编译为空操作因 Web 环境无真实线程但保留TTask.Run语义并转为Promise链代码生成Code Generation将 AST 节点映射为 JS AST使用 TypeScript Compiler API 的factory构建最终 emit 为可读性高的 JS启用--debug时保留 Pascal 行号注释。提示Pas2JS 默认禁用{$APPTYPE CONSOLE}和{$APPTYPE GUI}强制所有项目为{$APPTYPE WEB}。这意味着Application.Initialize、Application.Run等 VCL 入口被重定向为window.addEventListener(load, ...)而TForm.Create(nil)实际调用的是document.createElement(div)并挂载到 body。2.2 类型系统如何跨语言存活Pas2JS 的类型擦除与运行时补全策略Object Pascal 是强类型、静态绑定、支持泛型和 RTTI 的语言JavaScript 是动态类型、原型继承、无编译期泛型的语言。Pas2JS 的解法不是放弃类型而是分层处理Pascal 类型JS 生成策略运行时保障Integer,Boolean,Double直接映射为number/boolean无额外开销JS 引擎原生支持string映射为string但重载为String.prototype.concatLength()→.lengthCopy()→.substring()TArrayT映射为ArrayT泛型参数T仅用于编译期检查运行时无泛型信息但TArrayInteger.Create(1,2,3)生成[1,2,3]class编译为classES2015字段转为this._fName方法转为prototype.methodis/as运算符编译为instanceofconstructor.name匹配interface编译为classSymbol.toStringTag标记Supports()转为typeof obj object method in obj接口方法调用前插入 if (!obj这种设计意味着你在.pas里写的var L: TStringList; L : TStringList.Create; L.Add(hello);Pas2JS 生成的 JS 是// Generated by Pas2JS v7.2.0 (TMS WEB Core 2023) const L new TStringList(); L.Add(hello);而TStringList的 JS 实现位于tmswebcore.js是class TStringList { constructor() { this._items []; } Add(s) { this._items.push(s); } get Count() { return this._items.length; } }—— 它不是 wrapper而是等价实现。类型检查发生在编译期运行时零成本。2.3 为什么不能直接用 FPC 的 JavaScript 后端Pas2JS 的不可替代性在哪FPC 本身支持-Tjs目标平台但其 JS 输出是面向 Node.js 的 CommonJS 模块且严重依赖fpcunit.js运行时含内存管理、异常模拟、RTTI 解析器体积超 1MB无法在浏览器中直接执行。Pas2JS 则完全不同零运行时依赖生成的 JS 不 require 任何外部库最小化产物仅含业务逻辑DOM 优先设计所有TWebXXX组件的构造、事件绑定、样式设置均直接调用document.createElement、addEventListener、element.style.setProperty调试友好启用--source-map后Chrome DevTools 中断点可精准停在.pas行而非混淆后的 JS 行增量编译支持修改单个.pas单元Pas2JS 仅重新编译该单元及其依赖而非全量 rebuild。这就是为什么 TMS WEB Core 必须捆绑 Pas2JS —— 它不是“一个可选插件”而是整个框架的语义锚点没有 Pas2JSTWebButton.OnClick : DoSomething;就只是语法糖有了它这行代码就等价于button.addEventListener(click, DoSomething);且DoSomething的参数签名、异常处理、作用域链全部 Pascal 原生语义。3. TWebXXX 控件族不是 VCL 的 Web 版而是 DOM 的 Pascal 封装抽象层当你拖一个TWebButton到设计器双击写OnClick事件你以为在用 Delphi 的事件模型实际上你正在操作一个精心设计的DOM 抽象层DOM Abstraction Layer, DAL。TMS WEB Core 的 UI 控件不是“画在 Canvas 上的假按钮”也不是“用 div 模拟的伪控件”而是对原生 HTML 元素的语义化封装 行为增强 生命周期管理。它的设计哲学是“让 Pascal 开发者忘记自己在写 Web”。3.1 TWebButton 的 DOM 映射原理从 Pascal 属性到 HTML 属性的双向同步TWebButton的核心不是视觉而是属性同步引擎。它的Caption、Enabled、Visible、Hint等属性并非存储在对象内部字段而是实时映射到对应button元素的 DOM 属性// Pascal 侧 Button1.Caption : Submit; Button1.Enabled : False; Button1.Hint : Click to submit form;Pas2JS 编译后生成的 JS 等效于// JS 侧简化 button1.element.textContent Submit; // Caption → textContent button1.element.disabled true; // Enabled → disabled button1.element.title Click to submit form; // Hint → title但关键不止于此。TMS WEB Core 实现了变更侦测Change Detection当 JS 侧通过button1.element.click()触发点击或用户手动button1.element.disabled falsePascal 侧的Enabled属性会自动更新通过MutationObserver监听disabled属性变化。这种双向同步不是靠轮询而是基于ProxyES2015或Object.defineProperty降级实现的响应式数据绑定。注意TWebButton的OnClick事件不是简单绑定addEventListener。它内部维护一个TMethod结构体含 Code/ Data 指针在 JS 中通过闭包捕获 Pascal 方法地址并在事件触发时调用TMethod.Call()—— 这正是 Pas2JS 生成的Call函数它负责恢复 Pascal 调用栈、处理异常、返回值转换。3.2 TWebEdit 的输入同步机制为什么Text属性能实时响应键盘输入TWebEdit.Text是最常被误解的属性。新手常以为Text : abc是赋值其实它是受控组件Controlled Component的入口当 Pascal 侧执行Edit1.Text : new value触发element.value new valueelement.dispatchEvent(new Event(input))当用户在输入框敲字input事件被拦截调用Edit1.SetTextFromDOM()该方法解析element.value并触发OnTextChanged事件若启用了AutoSelectAll则SetTextFromDOM还会调用element.select()。这种设计确保✅ Pascal 侧Text始终与 DOMvalue一致无竞态✅ 用户输入必经 Pascal 事件流可拦截、验证、转换✅Text属性的 setter/getter 可被重载property Text: string read GetText write SetText;// 示例在 TWebEdit 子类中强制大写 type TUpperEdit class(TWebEdit) protected function GetText: string; override; procedure SetText(const Value: string); override; end; function TUpperEdit.GetText: string; begin Result : inherited GetText.ToUpper; end; procedure TUpperEdit.SetText(const Value: string); begin inherited SetText(Value.ToUpper); end;Pas2JS 会将ToUpper编译为value.toUpperCase()整个逻辑在 JS 层无缝运行。3.3 TWebListBox 的虚拟滚动与数据源解耦如何支撑 10 万条数据传统 Web 列表控件如select渲染 10 万option会卡死浏览器。TMS WEB Core 的解法是数据源DataSource与视图View彻底分离。TWebListBox.Items不是TStrings而是IListBoxDataSource接口默认实现TListBoxArrayDataSource仅存索引不存全部数据渲染时按需调用GetItemText(Index: Integer): string仅生成当前可视区域的li元素滚动时通过IntersectionObserver动态加载/卸载 DOM 节点。这意味着ListBox1.Items.Count : 100000;不创建 10 万 DOM 节点只初始化数据源ListBox1.ItemIndex : 50000;不触发全量渲染只定位到第 50000 条的逻辑位置OnGetData事件可在 JS 中异步加载分页数据fetch(/api/items?page3).then(...)。这种架构让TWebListBox在移动端也能流畅滚动而无需开发者关心requestIdleCallback或window.devicePixelRatio。4. WebCore RTL没有 VCL/FMXDelphi 如何在浏览器里“活下来”当你在 TMS WEB Core 项目中写下SysUtils.FormatDateTime(yyyy-mm-dd, Now)、Classes.TStringList.LoadFromFile(data.json)、甚至System.Generics.Collections.TDictionarystring, integer.Create这些单元并非来自 VCL 或 FMX而是来自WebCore RTL—— 一个专为浏览器环境重构的、轻量级 200KB gzipped、无副作用的 Pascal 运行时库。它不是 VCL 的子集而是语义等价、API 兼容、实现重写的全新 RTL。4.1 WebCore RTL 的模块划分哪些单元可用哪些被禁用WebCore RTL 明确划清边界单元名状态替代方案说明System.SysUtils✅ 完整支持—FormatDateTime编译为Intl.DateTimeFormatFileExists→fetch().then(r r.ok)System.Classes✅ 核心类可用TStream→Blob/ArrayBufferTStringList.LoadFromFile发起fetch并解析文本System.Generics.Collections✅ 泛型容器TListT→ArrayTTDictionaryK,V→MapK,VAddRange编译为push(...items)System.Net.HttpClient✅THTTPClient→fetchAbortController支持Get,Post,Put,Delete,UploadFilesVcl.Forms/Fmx.Forms❌ 禁用TWebForm替代编译时报错[Pas2JS Error] Unit Vcl.Forms not available for target WEBSystem.Win.ComObj❌ 禁用—所有 Windows API 调用被屏蔽System.IOUtils⚠️ 部分支持TPath.GetTempPath→/tmp虚拟路径文件操作仅限fetch加载的资源无真实文件系统这种设计强制开发者接受 Web 约束没有注册表、没有 INI 文件、没有本地磁盘 I/O —— 一切持久化必须走 HTTP API 或 IndexedDB通过Web.Core.IndexedDB单元提供。4.2 WebCore RTL 的内存模型没有堆管理如何避免内存泄漏Pascal 开发者习惯New(P)/Dispose(P)但浏览器中没有malloc/free。WebCore RTL 的解法是完全放弃手动内存管理拥抱 JS 垃圾回收。所有new操作TObject.Create、TListT.Create编译为new Class()交由 JS GC 管理Free/FreeAndNil编译为空操作// no-op因为 JS 对象无引用即自动回收TObject.Destroy仍被调用但仅执行 Pascal 侧清理如Free子对象、RemoveEvent不释放内存TComponent.DestroyComponents编译为遍历Components数组并调用Destroy但不释放数组本身。这就引出一个关键实践✅ 必须显式调用TComponent.RemoveComponent(Child)来解除父子关系否则子组件不会被 GC❌ 不要依赖Free触发资源释放应改用OnDestroy事件关闭WebSocket、取消fetch、移除addEventListener。type TDataLoader class(TComponent) private FAbortCtrl: JAbortController; procedure OnLoadComplete(Sender: TObject); public destructor Destroy; override; procedure LoadData(const URL: string); end; destructor TDataLoader.Destroy; begin if Assigned(FAbortCtrl) then FAbortCtrl.abort; // 主动中止 fetch inherited; end;Pas2JS 会将FAbortCtrl.abort编译为this._abortCtrl.abort()确保资源及时释放。4.3 WebCore RTL 的异常处理Pascal 的 try..except 如何映射到 JS 的 try..catch这是最容易翻车的机制。Pascal 的except on E: Exception do在 JS 中不是简单catch (e)而是所有 Pascal 异常Exception及其子类编译为class PascalException extends Errorraise EMyError.Create(msg)→throw new EMyError(msg)except on E: EMyError do→catch (e) { if (e instanceof EMyError) { ... } }except else→catch (e) { if (!(e instanceof PascalException)) { ... } }finally→finally { ... }JS 原生支持。但有一个致命陷阱JS 原生错误如ReferenceError、TypeError不会被 Pascalexcept捕获。例如try SomeUndefinedVar : 123; // JS: ReferenceError except on E: Exception do ShowMessage(Pascal error); end;这段代码不会进入except而是直接崩在浏览器控制台。解决方案是 在入口TWebForm.OnCreate中全局捕获window.addEventListener(error, HandleGlobalError) 使用Web.Core.JSUtils.SafeCall包裹高危 JS 调用 永远不要在 Pascal 中直接访问未声明的 JS 变量用JObject类型转换 if Assigned(...)判空。5. 常见问题排查那些让你怀疑人生、查文档三天、最后发现是 Pas2JS 特性的坑TMS WEB Core 的学习曲线陡峭不是因为难而是因为它的行为常常“违反直觉”——这些直觉来自你多年 VCL/FMX 开发经验。以下是真实踩过的坑按现象、原因、解决三步给出可复现方案。5.1 现象TWebButton.OnClick事件不触发但button.addEventListener(click, ...)手动绑定却有效原因OnClick是 Pascal 事件依赖TMethod闭包若按钮在TWebForm.Create时未完成 DOM 挂载如放在TWebScrollBox内且初始VisibleFalseelement为null事件绑定失败。解决在TWebForm.OnShow或TWebButton.OnVisibleChanged中检查Assigned(Element)强制延迟绑定TThread.Queue(nil, procedure begin Button1.Click; end);触发一次再绑定更可靠重写TWebButton.CreateWnd添加if not Assigned(Element) then Exit;防御。5.2 现象TWebEdit.Text赋值后DOM 中显示为空但Text属性读取正常原因TWebEdit的Textsetter 会触发input事件若OnTextChanged中抛出异常如raise Exception.Create(bad)Pas2JS 的异常处理会中断 DOM 更新流程导致element.value未同步。解决在OnTextChanged中try..except包裹所有逻辑使用TWebEdit.SetTextNoNotify(value)绕过事件触发检查TWebEdit.Element是否为input而非textarea类型不匹配会导致value设置失效。5.3 现象TWebListBox.Items.Add(item)后列表不刷新需手动Invalidate原因Items.Add只更新数据源不触发视图重绘。TMS WEB Core 的渲染是惰性的依赖Invalidate或Repaint显式触发。解决调用ListBox1.Invalidate推荐或ListBox1.Repaint强制重绘性能稍差更佳实践批量操作用ListBox1.BeginUpdate/EndUpdateEndUpdate内部自动Invalidate。5.4 现象TWebForm中TWebMemo.Lines.LoadFromFile(log.txt)返回空但fetch直接调用能拿到内容原因LoadFromFile是同步阻塞调用而浏览器中fetch是异步 Promise。Pas2JS 将LoadFromFile编译为await fetch(url).then(r r.text())但若调用处不在async函数内JS 会报SyntaxError: await is only valid in async function。解决将调用包装在TTask.Run中TTask.Run(procedure begin Memo1.Lines.LoadFromFile(log.txt); end);或改用THTTPClient异步加载HTTP.Get(log.txt, procedure (Sender: TObject; const AResponse: IHTTPResponse) begin Memo1.Lines.Text : AResponse.ContentAsString; end);。5.5 现象TWebColorPicker.Color设为$FF0000红色但 UI 显示为黑色原因TWebColorPicker的Color属性是TColorWindows GDI 格式BGR而 CSS 颜色是 RGB。Pas2JS 的颜色转换逻辑是ColorToRGB(Color)但$FF0000的 BGR 值是0x0000FF蓝色不是红色。解决使用Web.Core.Graphics.ColorToHTML(Color)获取正确 CSS 字符串或直接设TWebColorPicker.HTMLColor : #FF0000根本解法在TWebColorPicker子类中重载SetColor内部调用ColorToRGB再赋值。6. 设计取舍思考为什么 TMS WEB Core 不做 SSR为什么拒绝 WebAssemblyTMS WEB Core 的技术路线选择藏着对 Web 开发本质的深刻判断。它没跟风服务端渲染SSR、没拥抱 WebAssemblyWasm甚至刻意避开 TypeScript 生态 —— 这不是技术保守而是基于三个硬约束的主动取舍。6.1 SSR 的缺席客户端优先不是能力不足而是哲学不同Next.js、Nuxt 等框架推 SSR核心诉求是 SEO 和首屏速度。但 TMS WEB Core 的定位是企业级内部系统、管理后台、数据仪表盘—— 这些场景的用户不 Google 搜索你的登录页首屏内容也极少被爬虫消费。强行加 SSR 会带来三重代价代价维度具体表现TMS WEB Core 的解法架构复杂度需 Node.js 服务、同构渲染、数据预取逻辑保持纯静态部署index.htmlapp.jsstyle.css三文件扔到 Nginx 即可开发体验断裂Pascal 代码需适配服务端上下文无document、无window所有代码运行在统一客户端环境TWebForm的生命周期OnCreate/OnShow/OnHide始终一致调试成本SSR 错误需在服务端日志 客户端控制台双重排查错误 100% 在浏览器控制台可见Source Map 直达.pas行我曾在一个金融风控后台项目中尝试给 TMS WEB Core 加 SSR用 Deno 运行 Pas2JS 生成的 JS结果发现TWebForm.Create依赖document.body而服务端无 DOM。我花了两天重写 RTL 的Web.Core.DOM单元最终放弃 —— 因为业务方说“我们只要 Chrome 里打开快SEO 不重要”。从那以后我每次评估新框架都先问它的默认部署形态是否匹配我的交付场景如果答案是否定的再炫技的功能也白搭。6.2 WebAssembly 的拒绝不是不能而是不该FPC 已支持 WASM 后端理论上可将 Delphi 编译为.wasm。但 TMS WEB Core 坚持 JS 输出理由很实在启动速度WASM 模块需下载、编译、实例化首屏时间比 JS 多 50~200ms而 TMS WEB Core 的 JS 经过 Tree Shakinggzip 后常 300KB调试体验WASM 调试需.wasm.dwarf 浏览器 WASM 工具链而 JS 可直接在 Chrome 中断点、查看变量、修改值生态兼容所有 npm 包axios、moment、chart.js都是 JSWASM 需通过 JS glue code 调用增加胶水层复杂度。TMS WEB Core 的选择是用最薄的抽象层接入最厚的 Web 生态。它不造轮子而是让TWebButton的OnClick能直接调用chart.js的chart.update()让TWebGrid的数据源能无缝对接ag-Grid的rowData—— 这种“JS 优先”的互操作性比 WASM 的“性能幻觉”更有生产力。6.3 TypeScript 的疏离拥抱类型但不臣服于它教程中所有示例都是.pas没有.ts。这不是排斥 TypeScript而是拒绝“类型即真理”的教条。Object Pascal 的类型系统泛型、RTTI、is/as在编译期已足够严谨而 TypeScript 的类型是擦除的运行时不存在。TMS WEB Core 的做法是用 Pas2JS 的类型映射保证 JS 层安全用 Pascal 的 IDE 提供比 TS 更强的重构和导航。比如重命名TWebButton.OnClick事件在 Delphi IDE 中 CtrlShiftR 全局重命名所有调用处自动更新而 TS 中onClick: (e: MouseEvent) void的重命名需依赖tsserver且无法保证第三方 JS 库的调用安全。这让我想起某次代码审查同事用 TS 写了一个fetchUser(id: number)但 API 返回的id是字符串TS 类型没报错运行时user.id 123永远为false。而 Pascal 中TUser.ID: IntegerPas2JS 会强制parseInt(json.id)类型不匹配时抛EConvertError—— 错误在运行时暴露但至少可捕获。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →