
简介面向Delphi XE10.3开发者的TMS WebCore v1.1.3.0完整源码包定位于帮助熟悉VCL/FMX的开发者沿用既有技能构建响应式Web界面并解决组件行为不透明、难以按项目需求改造的问题。压缩包共1573个文件、约34.72MB主体为pas源码单元、dcu编译单元、dpr/dproj工程与包文件辅以js、html、css等前端资源和png/jpg界面素材还包含示例工程、BPL包及pas2js相关工具目录层次较完整便于按组件、示例与平台工具分类查阅。已有374人学习关注。完整源码便于追溯按钮、表格、图表、导航等组件的实现研究HTML5、CSS3、JavaScript与Delphi的衔接方式也能对Canvas动态图形、WebSocket实时通信等能力的落地形成直观认识读者可据此做自定义修改、扩展和版本差异核对并观察源码与编译产物的对应关系适合需要深入理解TMS WebCore机制、进行二次开发或评估技术可行性的中高级Delphi开发者。1. 拿到一份 WebCore v1.1.3.0 全源码先搞清楚它能帮你解决哪些问题把 WebCore v1.1.3.0 全源码包解压开第一眼是几百个.pas文件加一套 pas2js 编译器很多人会困惑这不是组件库吗怎么还带编译器。真实情况是用 Delphi 写 Web 前端这件事IDE 只负责界面外壳真正把 Object Pascal 变成浏览器里跑的 JavaScript 的是包里那套编译器和运行库。这份源码解决的是让开发者在报错时能顺着.pas一路翻到 JS 行为从而改得动、查得到而不只是装个控件拖拖拽拽。它适合两类人手上已经有 Delphi 10.3 工程、想把部分界面搬到浏览器上的老 Delphi 开发者以及被闭源组件坑过一次、坚持要看到源码的人。不适合指望零配置出成品的人。2. 源码包解剖从 .dpk 到 pas2js 的这条编译链先把一个反直觉的事实讲清楚同一批.pas文件会被两个完全不同的编译器分别读取。Delphi 的dcc32把它编译成 IDE 里能用的设计期包和运行期包pas2js 把它编译成浏览器能执行的.js。理解这一点后面九成的安装失败和“改了源码没反应”都能自己定位。2.1 先认目录哪几个文件夹决定能不能编译不同小版本的目录命名会有出入但骨架基本一致。解压后先做一件事把整个目录放到一个不含中文、不含空格的路径下比如D:\Dev\WebCore113。路径里带空格引发的命令行截断是后面一堆莫名报错的源头之一。目录里面是什么缺了会怎样Core\Source组件与框架的 Pascal 实现TWebButton、TWebForm这些类都在这工程编译时直接报找不到WEBLib.StdCtrls之类的单元RTL\SourceWeb 端运行库Web、JS等基础单元连Application.Initialize都解析不了Compilerpas2js 编译器本体和它自己的 packages 目录IDE 提示找不到编译器一个.js都生不出来PackagesDelphi 的.dpk包工程设计期与运行期各一套工具栏里始终不出现 Web 分类Demos可运行的样例工程不影响编译但少了一个能对照的基准Core\Source和RTL\Source是给两个编译器共用的源头Packages只服务于 Delphi 那一侧。所以“装不上”和“编译不出 JS”是两类完全不同的问题别混在一起查。2.2 版本对齐Delphi 10.3 与 pas2js 的匹配关系包名里的delphixe10.3指的是 RAD Studio 10.3内部版本号 20.0。这个约束是硬性的.dpk里引用的单元和requires子句是按某个 Delphi 版本的 RTL 写的换到 10.2 或 11 上编译期就会在requires那几行炸掉。真正容易忽略的是第二层对齐——pas2js 编译器版本和RTL\Source的配套关系。pas2js 迭代很快Compiler目录里的编译器和RTL\Source里的单元必须来自同一个包不能拿 A 版本的编译器去编 B 版本的 RTL否则会出现Unknown identifier这类看着像自己写错代码、其实是版本错位的报错。提示解压后先看Compiler目录里编译器的版本标识再看RTL\Source的时间戳两者差得离谱就说明包被人拼过优先换一份完整的。另外要分清两套库路径一套给 Delphi 编译器用指向Core\Source、RTL\Source供设计期包编译时解析单元另一套是 Web 工程编译到 JS 时用的源码搜索路径。前者配错装不上包后者配错工程能建但编不出 JS。2.3 安装顺序运行期包先编译设计期包后注册顺序错了会浪费很多时间。正确做法是关掉 IDE → 编译运行期包得到.bpl与.dcp→ 再编译设计期包并 Install → 重启 IDE 看工具栏。包里那套.dpk的contains列表长这样示意实际条目以包内为准package WebCore_RT; {$R *.res} {$ALIGN 8} {$ASSERTIONS ON} requires rtl, vcl; contains Web in ..\RTL\Source\Web.pas, WEBLib.Forms in ..\Core\Source\WEBLib.Forms.pas, WEBLib.Controls in ..\Core\Source\WEBLib.Controls.pas, WEBLib.StdCtrls in ..\Core\Source\WEBLib.StdCtrls.pas; end.requires写的是这个包依赖哪些别的包缺一个就会在编译时提示“找不到单元”contains是把哪些源文件打进这个包。如果你后面要往Core\Source里塞自己的控件就是在这个列表里加一行路径写相对路径最省事换机器不用改。不想开 IDE 的话命令行也能编批处理里这样写:: 用 IDE 自带的命令行编译器重编运行期包路径按自己机器改 set DCCStudio\20.0\bin\dcc32.exe %DCC% -B -Q -LE..\..\bin WebCore_RT.dpk :: -B 全量重建避免用到上一次的 .dcu :: -Q 安静模式只输出错误和警告 :: -LE 指定生成的 .bpl 输出目录必须和 IDE 的搜索路径一致编译完检查三样东西.bpl有没有生成、.dcp有没有落在 Delphi 的库路径里、IDE 的“安装组件”对话框里能不能看到这个设计期包。三样都齐了再点 Install缺一样都会出现“装了但工具栏没反应”的情况。3. 跑通第一个页面最小 Web 窗体从建工程到浏览器出字装完不等于能用。这一步的目标很明确用最少的控件验证「Pascal 源码 → pas2js → 浏览器」这条链路真的通了。链路不通的时候写业务代码等于在沙滩上盖楼。3.1 工程骨架与 HTML 模板Web 工程的目录结构和普通 VCL 工程不一样多了一个与工程同名的.html模板文件。这个模板会被复制到输出目录编译器往里面注入对生成 JS 的引用所以模板里那段脚本区不要手改改样式和容器就够了。!DOCTYPE html html langzh-CN head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 title最小验证页/title style /* 只放布局样式别在这里写死脚本引用 */ body { font-family: system-ui, sans-serif; padding: 24px; } #app { max-width: 520px; } /style /head body div idapp/div /body /html主程序文件保持最小只做三件事初始化、创建窗体、运行。program MiniWeb; uses Web, Unit1; {$R *.dpr} begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end.Application.Initialize负责把浏览器环境和 Pascal 侧的运行时接起来CreateForm才是真正在 DOM 里建出窗体。少写Run的话页面能打开但一片空白这是新手最常见的第一次卡点。{$R *.dpr}是让编译器把同名的资源文件挂进来删掉它有时候也能编过但后面用到字符串表、图标时会莫名报错别图省事。3.2 控件与事件三个组件就能验证链路一个输入框、一个按钮、一个标签足够覆盖“属性生效、事件触发、DOM 更新”三件事。unit Unit1; interface uses System.SysUtils, System.Classes, JS, Web, WEBLib.Forms, WEBLib.Controls, WEBLib.StdCtrls; type TForm1 class(TWebForm) edtName: TWebEdit; btnPing: TWebButton; lblOut: TWebLabel; procedure btnPingClick(Sender: TObject); private FCount: Integer; public end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.btnPingClick(Sender: TObject); begin Inc(FCount); if Trim(edtName.Text) then begin lblOut.Caption : 第 IntToStr(FCount) 次点击名字还没填; Exit; end; lblOut.Caption : 你好 Trim(edtName.Text) 这是第 IntToStr(FCount) 次点击; end; end.这段代码值得逐行看的地方有两处。第一处是FCount它是一个普通的 Pascal 字段但在浏览器端它活在 JS 对象里刷新页面就归零——这跟 VCL 里“窗体活着字段就活着”的直觉不一样别把它当持久化用。第二处是lblOut.Caption : ...赋值语句背后是一次 DOM 写入写得太频繁会直接影响渲染帧率循环里几千次赋值就会肉眼可见地卡。事件绑定这一层要特别注意btnPingClick的方法签名必须和.dfm里记录的一致。有人在源码里手改参数类型编译不报错运行起来点了没反应查半天以为是编译器的问题。3.3 编译产物与调试入口编译成功后输出目录里最关键的是三样.html、.js、.js.map。第三个最容易被当成垃圾文件删掉恰恰是它让浏览器 DevTools 的 Sources 面板里能直接看到.pas源文件。打开页面后按 F12Sources 里如果只能看到压缩过的一坨 JS先检查是不是切到了发布配置——很多配置默认在发布模式下关掉 source map。能看 Pascal 源文件的时候断点可以直接打在btnPingClick这一行上单步走进去能看到WEBLib.StdCtrls里的实现这就是拿到全源码最实打实的收益。还有一个细节直接双击 HTML 用file://打开和通过本地服务器打开行为不一样。涉及请求、模块加载的场景file://下必挂养成用本地静态服务器打开的习惯。4. 全源码的价值在改动一行 RTL 看它怎么进 JS只有源码、却不改它等于买了个带说明书的黑匣子。这一章做一件具体的事改一处源码验证它确实会进到浏览器里并搞清哪些改动需要重装包、哪些只要重新编译工程。4.1 从组件 Pascal 源码找到对应 JS 行为打开Core\Source\WEBLib.StdCtrls.pas找到按钮类的构造过程会看到类似这样的语句——控件在创建时通过 DOM API 造出一个真实元素// 摘自源码里的典型写法说明控件本质是 DOM 元素的一层 Pascal 包装 FElement : document.createElement(button);这一行解释了很多现象。为什么Caption改了页面上立刻变因为它最终落到元素的文本节点上。为什么某些 VCL 里惯用的属性在 Web 控件上找不到因为底层 DOM 元素根本没有对应的概念。看源码的时候不要一行行读按这个顺序找先找到类声明看它继承自哪个基类再到基类里找元素的创建和挂载最后看属性 setter 里调了哪些 DOM 方法。我一般会先在源码目录里全局搜索类名确认它定义在哪个单元再看这个单元被哪个.dpk的contains引用——这一步决定了改动会不会被打进包。4.2 一次最小改动验证编译链是否真的吃源码最省事的验证方式不是改组件而是加一个辅助单元在自己的工程里调用看它有没有出现在生成的 JS 里。unit MyWebHelpers; interface uses Web, WEBLib.StdCtrls, JS; // 给按钮打一个自定义标记方便在浏览器里用选择器定位和加样式 procedure MarkAsPrimary(AButton: TWebButton; const ARole: string); implementation procedure MarkAsPrimary(AButton: TWebButton; const ARole: string); begin if AButton.ElementHandle nil then Exit; AButton.ElementHandle.setAttribute(data-role, ARole); end; end.ElementHandle是 Web 控件暴露出来的底层 DOM 元素拿到它之后DOM 那一套能力就都能用了。这里用setAttribute而不是直接操作classList是因为属性名在不同 pas2js 版本的 DOM 声明里大小写有差异setAttribute的签名最不容易出岔子写别的 DOM 方法时在 IDE 里打点让代码补全告诉你准确的属性名比翻文档快。把这段调用挂到按钮点击里编译后到浏览器 Elements 面板看那个>unit MyWebTag; interface uses Classes, WEBLib.Controls, WEBLib.StdCtrls; type // 继承现成控件只加一个 published 属性成本最低 TWebTag class(TWebLabel) private FTag: string; published property Tag: string read FTag write FTag; end; procedure Register; implementation procedure Register; begin RegisterComponents(Web|Mine, [TWebTag]); end; end.三件事要一起做把这个单元加进Core\Source、在.dpk的contains里加一行、重编包并在 IDE 里重新 Install。少最后一步工具栏不会更新少contains那一行IDE 里能拖控件但一编译就报找不到单元。注册的字符串Web|Mine决定它出现在工具栏哪个分类下写个自己认得的分类名比塞进默认分类里好找。5. 避坑与排查五个把人卡在半天的现场下面五条按“现象 → 原因 → 解决”记都是绕不开的类型顺序基本对应从安装到上线的时间线。5.1 安装与注册阶段现象一设计期包 Install 成功工具栏里却没有 Web 分类。原因通常有两个一是 IDE 位数和包不匹配32 位 IDE 装了 64 位包注册代码根本没被执行二是装完没重启 IDE工具栏缓存还是旧的。 解决确认 IDE 位数后重编对应平台的包装完彻底关闭 IDE 再打开仍旧没有就在 IDE 的组件安装列表里确认这个包的状态是已安装而不是仅编译。现象二新建工程时提示找不到WebCore相关的工程模板或单元。原因是库路径没配全只加了Core\Source忘了RTL\Source或者加的是绝对路径换机器就失效。 解决两条路径一起加尽量用相对安装根目录的写法加完在 IDE 里随便uses Web;试编一次能过说明路径生效。5.2 编译与运行阶段现象三编译直接报找不到编译器.js一个都没生成。原因是 IDE 只知道要调 pas2js但不知道它装在哪。这个路径是独立配置项不在 Delphi 的库路径里。 解决把编译器路径指到Compiler目录下的可执行文件指完关掉工程重开命令行编译的话检查批处理里那个路径有没有被空格截断。现象四编译显示成功浏览器打开一片空白控制台报 404。最常见的原因是打开了输出目录里的 HTML但 JS 没跟着复制过去或者手工改过模板里的脚本引用路径。 解决看控制台具体 404 的是哪个文件回到输出目录确认.js与 HTML 在同一层不要手改模板脚本区让编译器自己注入。现象五改了源码页面行为没变。三个候选原因按概率排浏览器缓存了旧 JS、编译的是另一个构建配置、改的文件不在当前工程的搜索路径里。 解决先在 DevTools 的 Network 面板看请求的是不是最新文件再确认构建配置最后用全局搜索确认改的那个同名单元到底有几份。注意这套源码是商业组件的完整实现拿它做学习和内部验证没问题真要对外分发产品授权这件事单独确认别把源码包当成可以随便塞进交付物的东西。6. 进阶请求、状态、部署三件收尾事页面能跑之后真正决定这套东西能不能上生产的是三件小事怎么发请求、状态放哪、产物怎么部署。请求这块Web 环境里所有网络调用都是异步的写法上和 VCL 的同步直觉完全不同procedure TForm1.LoadList; var Req: TWebHttpRequest; begin Req : TWebHttpRequest.Create(nil); Req.URL : api/list.json; // 回调签名在不同小版本间略有差异以 IDE 代码补全给出的为准 Req.Execute( procedure(const AResponse: string; ARequest: TWebHttpRequest) begin if AResponse then lblOut.Caption : 没有拿到数据 else lblOut.Caption : 收到 IntToStr(Length(AResponse)) 字节; end); end;URL用相对路径别把localhost写死进去换环境必翻车。回调里如果直接抛异常浏览器控制台只会给一行看不懂的报错所以回调体内部自己做判空。状态这块页面一刷新所有 Pascal 字段归零这是最容易被忽略的边界。轻量配置放浏览器本地存储用一小段汇编块直连最省事function ReadKV(const AKey: string): string; begin asm Result window.localStorage.getItem(AKey); end; end;部署就是把输出目录整个搬走HTML、JS、样式、静态资源一个不能少.js.map可以不传但要单独留存出线上问题时靠它定位。静态资源记得带版本查询串否则用户浏览器缓存旧 JS你会收到一堆“我这没变化”的反馈。从那以后我每次拿到一份新的 WebCore 源码包都先编译运行期包再跑一个只有三个控件的空窗体确认这条编译链路通了才动业务代码。希望帮到你。本文还有配套的精品资源点击获取