接手过一个"考古型"维护任务:一套运行了近十年的内部业务系统,前端跑在Silverlight上。打开解决方案的那一刻,我人有点懵——XAML、.xap、WCF调用、JS桥接,这些词像是从上一个技术时代寄来的明信片。更难受的是,想查资料时发现官方文档早已下线,社区帖子大量沉底,能搜到的博客不是404就是断章取义。我不甘心对着反编译工具一点点反推,于是花了好几个周末,把所有能接触到的Silverlight相关资料按技术维度做了系统梳理,顺手把过程中的经验也汇总成了文档。这篇文章算是那份汇总的公开版,给同样需要维护老系统、做技术考古,或者单纯想弄清楚"浏览器插件时代的前端到底是怎么写的"的人。
我会尽量用"遇到了什么问题 -> 为什么会出现 -> 怎么解决"的脉络来讲,这样不管是新手还是老手,都能直接拿里面的思路去用。文里会涉及一些代码示例和配置文件写法,都是我实测过的路径,照着落地基本不会跑偏。
1. Silverlight是什么:浏览器插件时代的技术切片
1.1 极其精简的运行时:CoreCLR与XAML
Silverlight的本质,是浏览器里的一套轻量级运行时。它搭载了一个经过裁剪的.NET运行时(官方叫CoreCLR),配合专用的XAML解析器、矢量图形引擎和媒体管道,让开发者可以用C#在浏览器里写"富客户端应用"。
这句话拆开理解就是:传统网页用HTML+CSS+JS三件套,Silverlight则是用XAML描述界面、用C#写业务逻辑。XAML这套声明式语言负责布局、样式、动画和数据绑定,C#负责事件处理、服务调用、数据加工。它的心智模型更接近桌面开发,而不是Web开发。
这里有个容易踩的认知误区:Silverlight的XAML和WPF的XAML虽然长得几乎一模一样,但两者不是同一套东西。Silverlight的XAML做了大量裁剪,没有WPF里那些重量级框架组件,控件的默认样式也更偏"简洁实用"。如果你把WPF项目里的XAML整段粘过来,会碰到一堆"属性不存在"的编译错误。这个教训我在整理资料时遇到了不止一次,后面会专门说排查方法。
1.2 从风光到退场:为什么老系统还赖在Silverlight上
Silverlight曾经是微软押注的浏览器插件方向之一,定位是直接对标当时的Flash,打的牌是"浏览器里的.NET"。在那个RIA(富互联网应用)概念最火的年代,它被大量用在数据看板、在线播放器、复杂表单、企业内部管理系统上。后来移动互联网崛起,浏览器端HTML5标准逐渐成熟,插件技术整体退场,Silverlight在2021年10月正式停止支持,官方文档和下载渠道基本都关了。
但现实是,金融、工业控制、医疗信息、政企内部办公这些领域,仍有一大批系统跑在Silverlight上。它们不是不想升级,而是业务逻辑复杂、配套服务深、替换成本高。这些系统大多还能稳定运行,只是维护它们的人越来越少,资料也越来越难找。这份汇总的初衷,就是把这些"挺老但还没死"的技术经验留下来。
从技术演进的角度看,Silverlight现代意义其实超出它自身:它把声明式UI、数据驱动界面、事件异步模型、浏览器与本地代码的桥接这些理念带进了很多开发者的视野。那些后来在HTML5、Angular、Vue里面被反复验证的思路,不少能在Silverlight时代找到影子。研究它,不只是拆一台"技术化石",更是在理解前端框架演进中的几条主线。
1.3 同类技术横评:Silverlight、Flash、ActiveX与现代HTML5
为了把Silverlight放在正确的坐标里,我整理过一个对比表格,按运行形态、语言、渲染方式、安全模型、现状五个维度列了一遍:
| 技术 | 运行形态 | 开发语言 | 界面渲染 | 安全模型 | 现状 |
|---|---|---|---|---|---|
| Silverlight | 浏览器插件(ActiveX/NPAPI)+ OOB桌面模式 | C#/VB.NET + XAML | 基于XAML的矢量渲染,自绘UI | 沙箱隔离,可申请提升权限 | 已停止支持,遗留系统仍在运行 |
| Flash | 浏览器插件 | ActionScript + MXML | 基于时间轴的矢量渲染 | 沙箱隔离,存在大量历史漏洞 | 已停止支持,基本退出市场 |
| ActiveX | 浏览器插件(仅IE系) | C++/COM为主 | 本地窗口/自绘 | 权限极高,风险大 | 随IE退役,被Edge的IE模式替代 |
| 现代HTML5 | 浏览器原生功能 | HTML+CSS+JS/TS | 浏览器标准渲染管线 | 同源策略、权限API、CSP等 | 当前主流,持续演进 |
这张表的核心信息是:Silverlight的渲染方式与页面其他部分是完全分离的,它不是一个可以被CSS影响的DOM元素,而是一块"插件画布"。这就决定了它和周边页面的交互必须靠桥接机制,也决定了它在布局上有自己的坐标系统。理解这一点,后面看部署兼容性、迁移方案时才会顺。
提示:Silverlight插件页面上通常是一个
object标签,指定了xap包路径。如果你在某个老系统里看到type="application/x-silverlight-2"这样的写法,基本可以确认这块区域就是插件画布。
2. 资料分类法:把Silverlight文章按技术维度归堆
整理资料时我踩过一个坑:一开始按"项目A的资料""项目B的文档"这样归档,结果发现同一个技术问题散落在十几个文件夹里,想查一个跨域策略配置得来回翻。后来我改成按技术维度分类,所有资料围绕"界面层、业务逻辑层、通信层、桥接层"四条主线归堆,找起来舒服多了。下面是我整理出来的四块内容重点。
2.1 界面层:XAML、控件模板与依赖属性
Silverlight界面开发的核心是XAML,但它和WPF有一个重要区别:Silverlight不强调完整的逻辑树/视觉树分离,控件的自定义程度也相对保守。多数开发场景是直接用内置控件拼页面,配合布局容器(Grid、StackPanel、Canvas),需要改样式时再写Style和ControlTemplate。
实际写起来,几个高频关键词是:
- DataTemplate:定义ListBox、DataGrid这类列表控件里"单项数据长什么样",绑定上下文是当前数据项。
- TemplateBinding:在ControlTemplate里绑定到控件自身属性的手段,做自定义控件时必备。
- 依赖属性(DependencyProperty):Silverlight控件属性体系的基础。它支持绑定、样式继承和默认值机制。但要注意,Silverlight里依赖属性系统是裁剪版的,很多高级触发行为(如属性触发器)并不支持。
一段非常典型的XAML代码像这样:
<Grid x:Name="LayoutRoot"> <ListBox x:Name="UserList"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding UserName}" FontWeight="Bold" /> <TextBlock Text=" - " /> <TextBlock Text="{Binding Role}" Foreground="Gray" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid>这段代码里最值得注意的地方是{Binding UserName}。Silverlight的数据绑定默认情况下不会为开发者提供太显眼的错误提示,如果绑定路径写错、或者数据上下文的某个属性叫UserName而实际存在的是Username,界面就静默地什么都不显示。这种事在Silverlight时代坑了无数人,后面专门有一节讲怎么让绑定错误浮出水面。
2.2 业务逻辑层:异步模型与事件驱动的代码形态
Silverlight的C#层有一个人尽皆知的"规矩":不允许在UI线程上做同步阻塞等待。因为这个平台的事件循环本质上是异步的,任何阻塞UI线程的操作都可能直接卡死整个插件。所以,Silverlight时代的代码风格是强异步的,用了大量的回调、事件和委托。
举个例子,从服务器拉取一段文本的经典写法:
private void LoadTextButton_Click(object sender, RoutedEventArgs e) { var client = new WebClient(); client.DownloadStringCompleted += (s, args) => { if (args.Error != null) { ResultText.Text = "加载失败:" + args.Error.Message; return; } ResultText.Text = args.Result; }; client.DownloadStringAsync(new Uri("http://example.com/data.txt", UriKind.Absolute)); }这段代码放在当时很标准,但放在今天看,嵌套回调的体验确实糟糕。Silverlight后来在.NET框架里也引入了async/await的支持,但很多老项目因为运行时版本和工具链的限制,还停留在事件+回调的模式。如果你在读老代码时看到大把的Completed += lambda,不用怀疑,这就是那个时代的"标准写法"。
此外,业务逻辑层还很爱用LINQ。原因很简单:Silverlight的客户端拿到的大多是集合数据,在内存里做筛选、排序、分组,用LINQ比写循环干净得多。这个习惯到了现代C#开发里也完全不过时。
2.3 通信层:WCF、REST与跨域那个"著名"文件
Silverlight应用和服务器通信的方式主要有三种:
第一种是WCF服务调用。这也是企业内部系统的主流方式,开发流程里通过"添加服务引用"自动生成客户端代理类,然后调用方法。配置文件里要关注binding、timeout、maxReceivedMessageSize这些参数。经常遇到的问题是:取数量大一点就报通信异常,多半是默认的消息大小上限太小了。
<system.serviceModel> <bindings> <basicHttpBinding> <binding name="LargeDataBinding" maxReceivedMessageSize="5242880" receiveTimeout="00:10:00" sendTimeout="00:10:00" /> </basicHttpBinding> </bindings> </system.serviceModel>第二种是REST风格调用。用WebClient或HttpWebRequest发请求,靠JSON或XML交换数据。相比WCF更轻,但需要自己处理序列化和错误码。
第三种绕不开的,就是跨域访问。Silverlight在浏览器里运行时受同源策略限制,跨域请求需要服务器根目录放一个clientaccesspolicy.xml。这个文件是Silverlight的"跨域许可证",当年几乎所有联调问题都跟它有关。
<?xml version="1.0" encoding="utf-8"?> <access-policy> <cross-domain-access> <policy> <allow-from http-request-headers="*"> <domain uri="*" /> </allow-from> <grant-to> <resource path="/" include-subpaths="true" /> </grant-to> </policy> </cross-domain-access> </access-policy>注意:这个文件必须放在域名的根路径下,不是放在应用子目录。我见过太多团队把文件放错位置,排查半天才意识到服务端根本没返回这个策略。调试方法很简单:浏览器直接访问
http://目标域名/clientaccesspolicy.xml,能看到内容且状态码200,才算过关。
2.4 浏览器桥接与OOB模式:Silverlight与宿主环境的关系
Silverlight不是封闭的孤岛,它可以和宿主网页双向通信:
- 从Silverlight调用JavaScript:通过
HtmlPage.Window.Invoke("methodName", args)。 - 从JavaScript调用Silverlight方法:先给C#方法加
[ScriptableMember]特性,再注册HtmlPage.RegisterScriptableObject("bridge", this),页面里就能以object.bridge.MethodName()的方式调用。
[ScriptableMember] public string GetVersion() { return "1.0.0"; } public Page() { InitializeComponent(); HtmlPage.RegisterScriptableObject("appBridge", this); }OOB模式(Out-of-Browser)则是一个容易被忽略的功能。它允许Silverlight应用脱离浏览器窗口直接运行,变成一个类似桌面的应用。这对内部系统非常有用——用户不用开浏览器,直接双击图标就能进入系统。OOB应用还能请求更高的信任权限,比如直接读写本地文件(需要证书配合)。代价是OOB部署时安装流程、版本更新都要自己控制,体验比Web模式笨重不少。
整理桥接这块资料时,我最大的体会是:Silverlight时代就有人在思考"浏览器里跑的代码和页面其他部分怎么通信"这个问题,后来流行的WebView桥、JS Bridge这些方案,本质上都还是在解决同一件事。
3. .xap打包与部署:老系统能否"安稳运行"的细节库
如果说界面和逻辑是Silverlight的"面子",那打包和部署就是"里子"。这部分资料如果不整理清楚,项目跑不起来时连从哪儿下手都不知道。
3.1 .xap包的结构与加载流程
Silverlight项目的最终产物是一个.xap文件,本质是一个zip压缩包。里面的核心成员是AppManifest.xaml(程序集清单)、若干个编译好的dll、一个可选的pdb调试文件,以及嵌入的资源文件。浏览器加载Silverlight时,插件事先拿到.xap,解包后读取AppManifest.xaml,定位入口程序集和启动类型,再去执行应用初始化。
这个加载机制直接影响两个运维判断:
一是体积问题。xap包每次更新都是全量下载,所以包内dll数量、资源体积都会影响首次加载速度。优化手段通常是裁剪引用、把不常用的资源拆到按需加载的程序集。别小看这个,一个20MB的xap和2MB的xap,在老旧内网里的体感差异是天壤之别。
二是更新问题。Silverlight没有像现代前端那样方便的增量更新,发布新版本时就是重新部署xap文件,然后用户刷新页面让浏览器重新拉取。如果用户端有缓存,还得配合服务器端设置或版本号策略来强制绕过。这里有个实操经验:在部署新版本后,可以直接在服务器上修改xap文件的最后修改时间,或者在引用地址后面加版本参数?v=20250601,大多数场景下能有效避免用户加载旧包。
3.2 浏览器兼容矩阵:插件没了怎么办
这是我在整理部署资料时最"扎心"的部分。Silverlight插件在IE里走ActiveX通道,在Firefox、Chrome、Safari这些浏览器里走NPAPI通道。随着Google Chrome逐步禁用NPAPI、Firefox跟进、Edge直接不内置支持,Silverlight在现代浏览器里基本"无处安放"。
那张兼容性结论并不复杂:
| 浏览器 | Silverlight支持情况 | 替代访问思路 |
|---|---|---|
| IE 8/9/10/11(老环境) | 完整支持ActiveX插件 | 保留低版本浏览器环境 |
| Edge(Chromium内核) | 不支持 | 使用IE模式或独立插件容器 |
| Chrome 43+ | 不支持NPAPI | 通过企业策略或老版本浏览器 |
| Firefox 52+ | 不支持NPAPI | 通过旧版或插件容器 |
| 企业定制浏览器 | 部分提供内置模块 | 直接用对应厂商方案 |
实际运维中,我见过几个相对稳妥的落地做法:第一,保留一台老版本Windows + IE11 + Silverlight插件的访问机,专供内部系统使用;第二,用远程桌面或虚拟桌面方案,把老系统运行在服务器端的浏览器里,用户端只负责显示桌面;第三,利用某些国产浏览器的"兼容模式"来跑ActiveX。这三种都不是长久之计,但能撑住过渡期。
这里必须提醒一句:不要指望让用户装第三方插件或改浏览器FLAG来破局,一方面安全风险不可控,另一方面随着浏览器版本迭代,这些土办法一一失效的概率极高。早一点规划迁移,比什么都强。
3.3 安全与信任:提升权限、证书与混合内容
Silverlight部署中还有一类问题不算高频,但一旦碰到就是大事:权限与证书。
- 提升权限模式:Silverlight应用默认在沙箱中运行,访问本地文件、跨域敏感操作都会受限。如果需要提升权限,必须在OOB模式下配合签名的证书,并且用户安装时要确认信任关系。
- 证书链问题:内部系统常用的自签名证书在Silverlight里常常不被直接信任,导致OOB安装或请求HTTPS服务时失败。解决路径基本是:把证书导入客户端受信任根,或者统一用企业CA签发。
- 混合内容拦截:HTTPS页面里嵌了HTTP的Silverlight xap,现代浏览器会直接拦截或提示"不安全内容",导致插件完全无法加载。这个坑在老旧系统里特别常见,因为当年部署时可能还没有全站HTTPS的强制要求。
整理安全这块资料的收获是:Silverlight时代总结的"沙箱、信任、混合内容"三件套,到今天依然适用。现代Web的CSP、HTTPS-only、权限API,本质上都在讲同一件事:浏览器里的代码不能想干嘛就干嘛。
4. 高频异常排查:从踩坑记录里提炼的检查清单
在整理同事的踩坑记录、再加上自己动手复现之后,我发现Silverlight的高频异常其实就那几类。把它们整理成检查清单,排查效率能提高一个量级。
4.1 调试工具链:XAML绑定错误怎么看见
Silverlight项目在Visual Studio里可以断点调试C#代码,但XAML层的绑定错误不会以异常形式弹出,只会吐到调试输出窗口。默认情况下你什么都看不到,因为绑定引擎的错误输出级别很低。
想要"看见"绑定错误,有两个手段:
第一种,在调试时观察输出窗口里的System.Windows.Data Error消息。这类消息会标明绑定路径、目标元素、数据上下文类型,是定位绑定问题的最直接线索。
第二种,显式开启绑定跟踪:
Binding binding = new Binding("UserName"); binding.Source = this.DataContext; binding.TraceLevel = BindingTraceLevel.High; binding.TraceDestination = BindingTraceDestination.Console; targetTextBlock.SetBinding(TextBlock.TextProperty, binding);这样可以把绑定解析过程输出到控制台。虽然写法啰嗦,但在查"为什么这个字段没显示"时非常管用。
还有一个不得不提的基础排查工具:全局异常处理器。在App.xaml.cs中挂上Application_UnhandledException事件,把异常详情记录到日志,很多生产环境里看不见摸不着的崩溃就有迹可循了。
private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e) { LogHelper.Write(e.ExceptionObject.ToString()); e.Handled = true; }4.2 几类经典异常的完整排查链路
我把高频异常分成了四组,每组给出建议的排查顺序。
第一组:XamlParseException。这类异常大多出现在应用启动阶段,常见原因是资源查找失败、StaticResource引用了尚未定义的资源、或者在模板里写了不存在的属性。排查时先看内部异常,里面通常会指出具体的XAML行号和属性名;如果内部信息不明确,就用"二分注释法",把XAML代码里大段内容注释掉,逐步缩小范围。这个办法听着笨,但在复杂页面里比肉眼找有效率得多。
第二组:跨域通信异常。报错内容里经常出现AG_E_NETWORK或者SecurityException,或者干脆是"远程服务器返回错误"。排查顺序应该是:先确认服务端有没有返回clientaccesspolicy.xml,再确认该文件内容里的domain和resource配置是否覆盖目标地址,最后确认网络层是否被防火墙或代理拦截。我见过最隐蔽的情况是CDN缓存了旧的策略文件,导致策略改了不生效,折腾了一整天。
第三组:通信超时和消息大小问题。这类报错一般是TimeoutException或CommunicationException。排查顺序:先看配置文件里binding的超时设置和消息大小上限;再确认服务端实际处理时间;最后检查服务器日志里有没有线程池饥饿的迹象。老系统最常见的问题不是网络不通,而是某个方法处理时间偶尔超过默认的10秒超时,把超时调到合理范围往往立竿见影。
第四组:内存持续增长。Silverlight的生命周期管理很脆弱,事件处理器挂接后没有解除、静态集合里持有控件引用、视觉树移除但资源未释放,都会造成内存只增不减。排查方式:用Windbg或者Visual Studio的托管内存分析工具抓快照,比较两个时间点的对象数量。常见的元凶包括CompositionTarget.Rendering事件(每帧触发,不解除必然泄漏)、DataContext未置空。
4.3 性能和内存:老项目最容易被忽略的两座山
排查高发之外,整理资料时我还专门归纳过性能优化清单,挑三个见效最快的说。
第一,列表控件的虚拟化。Silverlight的ListBox、DataGrid默认支持UI虚拟化,但前提是外层容器给它们足够的高度约束,而且没有被人为设置成ScrollViewer嵌套结构。如果发现页面加载很慢且内存暴涨,先去看是不是列表项一次性全部生成了。
第二,图片资源加载。Silverlight里如果直接绑定大图原图,解码开销会非常夸张。最佳实践是服务端处理成多尺寸版本,客户端只加载当前展示需要的尺寸。这和现代前端里srcset、loading="lazy"的思路完全一致。
第三,Layout抖动。Silverlight布局是依赖属性驱动的,任何依赖属性变化都可能触发重新布局。频繁更新某个TextBlock的Text、或者大批量改变元素宽高,都会导致Layout刷新的风暴。解决办法是合并UI更新(类似现代框架的批处理机制),或者使用Canvas在绝对坐标模式下做局部更新,避免触发整树重排。
5. 迁移路线图:老系统搬家前要做的四件事
整理Silverlight资料绕不开一个终极问题:这套东西还能活多久?几乎所有维护者最后都会走到迁移这件事上。我的建议是,迁移之前先把下面四件事做完,否则很容易被现实吊打。
5.1 盘点资产:先列清单再谈重构
很多人一提迁移就急着选新技术、画架构图,结果连"系统到底有多少个功能点"都没数清楚。我建议先做四张清单:
| 清单类型 | 要收集的内容 | 用途 |
|---|---|---|
| 功能清单 | 页面/模块/操作流/权限点 | 确定迁移范围与优先级 |
| 控件清单 | 用到的Silverlight控件、自定义组件、第三方控件 | 评估对应现代替代品的匹配度 |
| 接口清单 | 服务方法、参数、消息结构、鉴权方式 | 决定服务端是否需要调整 |
| 环境清单 | 部署位置、域名、证书、浏览器要求、访问方式 | 规划过渡期运行方案 |
资产盘点是最枯燥但最值钱的一步。做完它,你对"迁移量"会有完全不同的体感——很多团队以为界面重写是大头,实际上数据接口和权限逻辑往往更耗时。
5.2 概念映射:把Silverlight语言翻译成现代语言
为了方便讨论迁移方案,我做过一张概念映射表,把Silverlight时代的核心概念翻译到现代技术栈,团队沟通起来顺畅很多:
| Silverlight概念 | 现代对应物 | 说明 |
|---|---|---|
| XAML界面 | HTML+CSS / Blazor的Razor组件 / 其他前端框架模板 | 声明式UI理念一脉相承,但语法完全不同 |
| 依赖属性 | Signal / 响应式绑定机制 | 数据驱动UI的底层机制 |
| DataTemplate | 前端框架的列表模板 / Angular的ng-template | 定义数据项如何渲染 |
| App.xaml全局资源 | 全局状态管理 / 设计系统令牌 | 统一管理主题、样式、共享配置 |
| WCF服务引用 | REST API / gRPC客户端 | 通信方式从SOAP转向轻量API |
| 事件+回调异步模型 | async/await | 从"回调地狱"走向顺序化代码 |
| OOB模式 | 桌面端架构 / PWA | 脱离浏览器运行的思路延续 |
这张表的价值在于,它告诉团队"老系统中的大部分设计思想是可以平移的",迁移的难点在语法和工具链,而不是重新发明架构。
5.3 三条路径:对等重写、渐进迁移与混合嵌入
盘点完资产、做完概念映射,接下来就是选路。我见过并参与过的路线基本有三条:
第一条是对等重写。选一个现代前端框架(或桌面框架),把Silverlight应用按模块逐一重写,全部完成后切换。优点是技术栈统一、边界干净;缺点是周期长、期间系统要同时维护两套,团队需要具备完整的前端能力。
第二条是渐进迁移。保持老系统继续运行,新功能用现代技术栈开发,通过导航层或微前端方式把新旧模块拼接在一起。优点是每步都有交付成果、业务风险小;缺点是需要设计好两个技术栈之间的导航与身份会话共享,复杂度不低。
第三条是混合嵌入。现代页面为主,老模块通过iframe或专用浏览器容器嵌入。这是过渡味道最浓的路线,但也最省力,适合"先稳住业务,再慢慢替换"的场景。需要注意的是,嵌入意味着老模块的登录态、权限、交互边界都要提前约定好,不然用户在两个系统间跳来跳去会疯掉。
5.4 迁移中的隐形坑:看起来能平移,实际会翻车的地方
最后提醒几个我在迁移讨论中反复遇到的坑。
第一个坑是尝试用工具自动把XAML翻译成HTML。市面上确实有转换工具,但产出物大多是"能看不能用"的静态代码,离可用差着十万八千里。界面重写必须按交互设计重新来,别指望机器翻译。
第二个坑是依赖属性到绑定的平移。Silverlight的DependencyProperty支持样式继承和默认值,而现代框架的响应式绑定通常需要显式声明。如果只是机械地把"依赖属性"改成"属性",很容易漏掉样式依赖关系,导致界面行为和原来不一致。
第三个坑是异步模型转换。老代码里的回调嵌套在迁移到async/await后确实更清晰,但回调里隐藏的错误处理和资源释放逻辑,在重写时最容易丢。建议逐方法比对,别只看编译通过就以为万事大吉。
第四个坑是数据上下文。Silverlight时代大家习惯把DataContext直接赋给根元素,子控件自动继承。迁移到现代框架后,如果仍然照搬"全局状态一顿传"的思路,会出现大量无链接、难以追踪的隐式依赖。正确的做法是显式传递、显式注入。
提示:迁移不是"技术升级",而是"业务重写"。衡量标准应该是:功能等价、权限一致、性能不劣化、运维可接管。别为了用新框架而强行引入不必要的复杂度。
最后分享一个我整理资料时固定下来的习惯:每个技术条目最终都套用"五要素"模板——功能描述、操作路径、技术要点、常见故障、更新记录。这个模板帮了我大忙,半年后回看自己写的文档,依然能三分钟定位到知识点,而不是在堆砌的文字里翻半天。另一个体会是:Silverlight会过时,但它踩过的数据绑定、异步通信、内存泄漏、跨域限制这些坑,在现代前端框架里换个名字照样存在。整理老技术资料,表面上是为旧系统续命,实际上也是在帮自己建立一套"问题类型"的排查直觉——这套直觉,放到哪个技术栈里都不会贬值。