我在酷家乐这 4 年,项目成败与反思

引言

2023-12-06 是我在酷家乐的最后一天,想把我在酷家乐这 4 年主导落地的项目做个总结,聊聊每个项目的创立背景、结果成败,以及反思。

为了防止业务敏感信息泄漏,文中不会涉及到任何业务情况,项目结果数据,项目截图等内容。

19 相遇

时间来到 19 年 8 月,那是我加入酷家乐的日子。作为 IC 投入到酷家乐用户增长团队,当时团队主要在做激励体系、积分抽奖、酷签到、勋章等 To C 促活业务。

新业务

19 年 10 月,用户平台线成立 "设计圈" 新项目,是个 To B 的 SaaS 业务,目的是打通企业内部设计孤岛,让企业内部设计师共享、共建、共成长。后被大家戏称 "小主站",即主站的子功能 SaaS 化。

过程中我统一所有前台页面启动逻辑,增加启动的中间件机制,中间件机制也是首次被引入到页面启动流程中,对于多页、统一的场景至关重要;对于管理后台,引入当时比较前沿的 UForm(即现在的 Formily),并进行业务定制的封装,目的是简化表单、表格等场景的开发工作,而此动作也提效明显。在此感谢阿里对开源的贡献。

反思:

  1. 多页应用,需要有入口做全局逻辑的管控。而落地做法很多:html 入口/JS 统一使用固定的 boot 逻辑等等
  2. 垂直领域能做一定的技术轮子。例如:表单表格的管理后台场景,需要有垂直领域的组件来做提效,基础组件还不够
  3. 过程中担任 SM ,反推自己以全局视角考虑问题,并且关注团队成员的任务与过程

20 回归

20 年 2 月一半精力回归用户增长团队,直到 6 月份完全回归。

小程序平台

20 年 1 月,公司内部小程序业务增多,需要做一定的基础设施,以提升整体的开发效率。前端团队老大,推动成立"小程序平台"专项虚拟小组,由几个小程序的业务团队同学(设计圈也有小程序业务),以及基架组转岗过来的同学组成。

我主动负责其中的 CI/CD 部分,接入 Def(公司前端统一 CLI 工具),完成套件、构建、部署等功能。当时微信小程序还不支持 CLI 部署,只能借助 "微信小程序开发者" 工具,在 Windows/Mac 上使用,而公司已有工程化 Linux 相关的基建,完全用不了,故在 Windows 虚拟机上安装"微信小程序开发者"工具,并且本地启动 proxy server 与开发者工具互通,CLI 再调用 Windows 本地的 proxy server 完成互通。

反思:

  1. 微信开发者工具客户端等以 UI 的形式提供给使用方,对于小团队很友好,对于想集成到大团队自有工作流的系统中,很差(好在微信现在已提供 SDK 跨平台发布,以便于集成到现有系统中)
  2. 我只参与了"小程序平台"不到半年,随后平台越发庞大:包括微信公众号管理、用户管理,甚至域名管理、人员管理、运维中心、营销工具等微信本身已经提供的能力包装。投入了非常多的人力,但是我个人认为过于超前。主要原因是:
    1. 酷家乐本身各业务小程序并没有太多增长
    2. 边缘功能太多,绝大多数场景根本用不到。我理解仅需要这些核心能力:模板分发多商家小程序、CI/CD、组件库、脚手架。
  3. 基建不应太过超前,优先满足最核心、提效最高的能力。

TMS

对于 To C 的产品,用户增长当时主要靠运营驱动,借助一些营销获客、促留存的手段,而产品需求绝大部分来自于运营同学。而面向运营同学的工具,有 2 款:

  1. TMS:仿淘宝店铺装修的页面搭建平台,主要可以完成产品介绍页、营销页等功能的搭建;
  2. 云台:运营平台,面向 To C 用户的营销推送(短信、公众号、邮箱、站内信等场景)、广告位管理等核心应用场景。JS 全栈开发,包括对 MySQL、Redis 等持久层的直接调用。

对于 TMS 页面搭建平台,有个极大地痛点:所有的模块(前端开发的定制模块)很难开发与发布,所有模块都杂糅在一个 NPM 包内,所以开发一个模块的流程是这样:

  1. TMS 管理后台创建一个模块,就是元信息了拿到模块 ID
  2. Mod Package 里开发一个模块,包含展示场景和编辑场景的组件
  3. 将 NPM 包 Link 到 TMS 管理后台的仓库
  4. 启动仓库本地 debug mode (体验很差)
  5. 开发阶段结束,开始发布阶段

  6. 发布 NPM 包
  7. 分别安装到对外渲染、对内后台管理的 Repo 上
  8. 分别发布对外、对内系统

整体流程很长,导致业务开发同学更愿意 0-1 写一个静态页,而不是开发一个 TMS 模块,进而造成了业务模块并不是很多,生态不丰富。

我在开发 TMS 模块时也深感痛苦,故过程中对 "新开发一个模块" 的流程进行了改进。

整体原则就是将模块的安装、加载从主体中剥离,从 NPM 包转变为浏览器端运行时注入的模块。当时已经有了 SEED,它比较基础,且全局都有安装,它是一个运行时模块加载、管理器,可以简单类比 SeaJS。通过维护一个 Alias ,模块 key 与 JS/CSS CDN 列表的映射关系,来决定如何加载模块,而这个 Alias 本身也是通过一个大 JSON 进行保存。

那么解决思路很直观了,只需要保存一个 TMS 模块 Key 与模块打包后的 JS/CSS 产物,即可做到将模块的安装由 buildtime -> runtime,进而能做到模块的调试、打包、发布与 TMS 主系统完全隔离。

优化后的流程是这样的:

  1. TMS 管理后台创建一个模块,就是元信息了拿到模块 ID
  2. 各自业务仓库开发一个符合一定 Interface 的模块
  3. 业务仓库本地 debug (打开 TMS 测试环境,直接把模块请求代理到本地即可)
  4. 开发阶段结束,开始发布阶段

  5. 各业务仓库构建模块,产出 JS/CSS,并自动上传 CDN,修改 Alias JSON 完成发布

本地调试由于仅需要构建当前模块,所以开发体验很棒。

SEED 与微应用

但是 SEED 也有它自己的问题,Alias JSON 独立于现有其他发布平台维护,且无灰度、无回滚,是个很大的稳定性隐患。

当时公司基建(Pub)已初步成型,比较超前,核心是以最小可发布粒度的一站式解决方案,而首推的就是页面单元,能将传统的以 Repo 多页为发布单元,转为以独立页面为发布单元,且秒级发布、秒级回滚。前端微应用当时也初步成型,主要目的是拆分酷家乐工具的巨石应用,提升开发效能。

当时主站还在继续使用 SEED,前端微应用和 SEED 其实目标非常类似,核心都是独立开发、独立发布。这时产生了一个想法 "能否让前端微应用支持浏览器端运行时加载,以替代掉 SEED 模块管理部分的能力 ",达到 "All in micro " 的效果。

此时,前端微应用的输出模式是 html 片段,此片段可以注入到 page 中,最终输出完整的 page html 给到浏览器,即拼接形式。页面与微应用可独立发布,在统一的 Node.js Page Render 层进行服务端拼接,以组装成一个可以由多团队共建的完整应用。

那么,做法很清晰了,需要将仅支持在服务端 html 拼接形式使用的微应用,扩展为支持浏览器端运行时动态获取微应用 html 片段,并注入到 DOM 中去,并解决 Script 等标签无法执行与如何同步有序执行的问题,这就诞生了"Pub 微应用加载器 "。

此时已存在 Single SPA 或乾坤等库,独立发布的功能是大家共有的,沙箱&路由联动等特性是不需要的,所以也没有参考这些开源库实现。

此阶段之后就顺势推动 SEED 历史模块全量迁移 Pub 微应用,相对的好处是:

  1. 拥抱同样的基建(CI/CD),灰发&回滚等机制
  2. 无需页面预置环境
  3. 去中心化,微应用加载器分布式安装在各个微应用 or 页面 bundle 内,不到 8K (未压缩)

而 TMS 的新模块开发方式也由 SEED 模块过渡到使用 Pub 微应用模块。

公共包

基建相对比较成熟了,但是主站业务的公共包却一直比较混乱,质量也不高。"磨刀不误砍柴工",工具库、业务组件库的重要性不言而喻,这半年也开启了公共库的创建和规范:

  1. types:以业务域划分,定义业务通用的类型单元,例如方案等
  2. utils:工具函数
  3. rc:业务特定的组件库
  4. etc...

这部分内容大多数公司做的事情类似,不细讲了。

反思

  1. HTML 是组成页面的基本单元,以它为切入点,相对以 JS Entry 能做更多事;
  2. 跨团队协作,独立发布,低耦合是效能王道
  3. 开源产品能解决部分通用问题,工作流的串联,整体架构还需独立设计
  4. 秒级发布&回滚,能解决绝大多数稳定性问题
  5. 发布卡点 or 审批对于新手是保护,对于老手是枷锁

H2 开始,也带来一些新的挑战:

  1. 如何快速搭建新站点
  2. 类似的区块如何复用,是否复制是个更好的选择?

21 创新

Star

基于 20 下半年业务上各种新站点搭建带来的效率以及质量的综合挑战,21 年初我在思考"是否要造一个全司共建共享的物料共享平台 ",以打破团队间信息壁垒。

在此阶段我已经是敏捷组 TO,并且有一定的影响力,所以大家愿意跟着我的想法一起干,包括隔壁组同学。此时恰好 UED 团队同学有"设计物料共享"的想法,所以一拍即合,前端 5 人 + 设计 2 人,自建组成虚拟小组,利用业余时间创建:Star 物料平台

平台设想大而全:

  1. 开发物料:Web 端、VSCode 插件、物料开发 CLI;分为 2 大类:区块、页面模板
  2. 设计物料:Web 端、Sketch 插件

这里主要讨论下开发物料,区块和页面模板都是参考自"飞冰"的设计,利用"复制"的手段,达到复用的目的。好处就是可以任意修改,不会因为 Interface 不满足而无法使用或扩展,相似的视觉效果都能直接拿来用。

而 VSCode 和 Sketch 插件的代码分别 Fork 自开源项目 IceWork、Kitchen(好像是),进行自有系统以及物料库的集成。

整个系统全栈 TS 开发,包括 Sketch 插件,服务端采用 NestJS+MySQL+Serverless 完成。

反思:

  1. 现在看来,区块的复用方式不如组件的形式,而且也没有用起来
  2. 页面模板倒是用来做初始化页面 or 微应用的规范了,也是一种将各业务线规范落地的平台
  3. 设计物料和 Sketch 插件使用量可观,相对于原始 File 下载分发,借助 Sketch 插件自动享受最新的设计物料比较高效

所以就区块来说,Star 是失败的,所以后来又逐步优化,新增了微应用文档的接入,因为微应用的使用方必然是需要阅读文档的,Star 就是一个比较好的集成微应用使用文档的平台,直接关联微应用的唯一 Key。

登录注册

在这之前我也兼账号体系(UIC)的前端负责人。酷家乐的账号体系也许是互联网行业最复杂的系统之一,它的复杂性来源于:

  1. 面向多种产品:To C、To B
  2. 面向多种身份:设计师、业主、从业主,在这之下又有很多细分行业

登录注册链路也有一定的复杂性:

  1. 注册链路极长,三方绑定 -> 手机验证 -> 选择身份 -> 推荐设计师/业主 -> 发放奖励
  2. 登录的形式:三方、扫码,弹窗登录、登录页面
  3. 登录过程中的风控拦截,图片验证,
  4. 登录过程中的 C & B 多账号绑定
  5. etc.. 还有很多没有列出来的

面临的挑战:

  1. 整体偏过程式的写法:你可以想想一个回调函数内部写了非常长的逻辑,且牵一发动全身
  2. 数据流与执行流的混乱:Promise 可能存在一直 Pending 的状态,例如某个 callback(resolve) 一直不执行,流程中的数据传递混乱,没有一条主线
  3. 以上带来的结果就是,涉及登录注册的任务估时 x2
  4. 美间、模袋等业务的加入,需要打通账号体系,并且复用同一套登录注册能力(但是不接受走同一个页面完成 SSO,这决定了后续的架构模式)

基于此,对登录注册组件进行了彻底的重构:

  1. 更合理的分层:基础包(通用 UIC 逻辑)、核心能力(支持配置化的形式确定外部需要何种登录注册方式)、酷家乐业务场景下的微应用 以及 其他业务场景下的页面
  2. 插件化的架构模式:借助 Tapable 完成异步串行的执行场景,增加登录注册前后等超 10 个 Hook,为后续扩展奠定了基础,并解决执行流问题
  3. 全局 Store:解决数据流问题
  4. 将原有非核心链路的逻辑拆分出接近 10 个插件,完成业务逻辑

结果:

  1. 扩展性:最初设想就是未来至少 3 年不需要重构登录注册模块,目前我认为至少 5 年是没有问题的
  2. 研发效率的提升:统一群核之下的几乎所有业务线的登录注册;后续几年的实战中,对于登录注册业务上的各种大需求,都没有对核心部分造成影响,通过插件都能满足需求
  3. 整体的 ROI 还是很高的

反思:

  1. 对核心业务的架构优化是值得投入的
  2. 插件化不仅用于工程化领域,也可用于业务,需要一定复用性、扩展性的场景都可考虑
  3. 架构是为了不让复杂度指数级爆炸

开发效率与规范

21 年的以上 2 个偏全司基建或特定业务,开发效率与规范也在持续进行:

  1. 为了多仓库共享代码,造了 Rocket CLI,定位是基于 subtree 的业务线级别的代码共享
  2. 规范了业务域为单位的 Owner 机制,并且不分端(PC、H5、小程序)
  3. 规范了 lint/babel/postcss/ts config,并且基于 Rocket 可以做到及时的共享更新
  4. 规范了所有 page 的启动方式,也基于 Rocket 进行共享
  5. 规范了全局弹窗的管理器,支持优先级队列机制
  6. etc...

基于 Rocket 的基建能力,做了到所有业务仓库共享同一套 xxx config,共享同一套业务启动逻辑。

但是也带来了一些棘手的问题:

  1. Git subtree 的机制,会让 Repo 历史记录混乱,掺杂很多不相干的 commit
  2. 高版本 Git subtree 提交时,部分同学总是无法 push 上去
  3. 随着时间推移,2 年后的今天,commit 已达 2k 多条(中间应是某些同学误操作带上去的),导致后期又增加了 reset 的机制,并且把 shared Repo 给重置了,进而又导致 shared Repo 与业务 Repo history 对不上...

这些问题只能通过比较懂的同学人肉操作下,以达到可以正常 push pull。所以后期会弃用 Rocket,改回 Npm Package,但是增加一些功能让他能保持定期更新。

反思:

  1. Subtree 有其局限性,最好的协作模式我认为一个业务线采用单一的 monorepo,通过基建去直面单 Repo 的构建性能问题,部署效率问题;
  2. 对于业务线的开发团队,最优先的是制定规范、落地规范到代码里、及时更新规范,以达到开发者同一套开发思路,对于协同开发效率是极好的;
  3. 不要分端,其他端的开发成本相对团队多人的沟通成本低很多;

22 再创新

22 年底有写过《2022 年终总结》,所以这里尽量谈的更宽泛一些, 有一定的相似处。

职位的变化,21 年中开始担任 Action Mgr,22 年初转为正式 Mgr。也会有一些管理思考,但是本文不会涉及。

客户端打包平台

没错,又开始造平台了。背景是酷家乐的主要用户在 PC 端,且绝大多数都使用客户端(基于 Electron),而且其他业务线也会开发自己的客户端(例如美间)。

所以除了一些基础 Electron 扩展能力的复用之外,长远来看最好能有个工程化平台,集成端侧的构建、打包、发布、分发等一系列的能力。这就是"客户端打包平台" 也可以称之为"客户端 DevOps 平台"。

做了如下事情:

  1. 首先需要一个打包环境,不仅要打包 Windows/Mac 上的 Electron 应用,后续还支持了 Android App 的打包
  2. 其次需要一个打包管理后台,包括:应用管理、构建管理、版本管理、发布管理以及权限
  3. 最后定义一套接入规范,以 Node.js 脚本形式接入,脚本接收一些入参,根据参数构建、打包、签名产出最终的安装包(固定目录),平台进行上传并回调更新 CDN URL、版本等信息

整体逻辑并不复杂,说一些它和 Web 页面发布的区别:

  1. 存在版本,线上版本碎片
  2. 存在复杂的更新机制,也有灰发机制
  3. 存在不同渠道分发不同安装包,便于后续的安装来源统计
  4. 多种打包目标:Windows/Mac/Android,不同目标会有提供不同的打包环境

对于平台,还有很多事没做,例如:数据看板,版本分布等,但是对于近几年足够了。

有些同学可能会不理解,和 Gitlab CI 有啥区别?

借助 CI 仅能完成任务的触发,而任务是需要特定的运行环境的,除此之外:版本的管理、灰发、渠道分发都是平台特有的能力。

反思:

  1. 针对核心业务做基建更不易出错

SSR

过去几年,随着基建升级,老的 FreeMarker(JAVA) + JQuery ,慢慢转变为 Nunjucks(Node.js)+ JQuery,再转变为 Nunjucks(Node.js)+ React。而到 React 阶段,服务端直出页面关键 HTML(SSR)已不存在。产生的结果就是来自搜索引擎的流量逐渐下滑,而 SEO 对于酷家乐来说至关重要,是个非常重要的流量窗口。为了拯救 SEO,22 年上半年开始了一些 SSR 的尝试。

但是,要做 SSR ,会和业界常用方案有所不同:不会采用 Next.js 类似全栈框架,因为此类全栈框架带来的问题是每个业务都需要独立的 Node.js 服务,还需要持续的观测稳定性,出问题对于业务开发者来说是非常棘手的,对开发者的要求极高。

所以 SSR 服务需要做到的效果:

  1. 每个 SSR 页面都可以独立发布,即使他们在一个 Repo
  2. 创建 SSR 容器服务,由 SSR 服务的开发者管理服务的稳定性,业务开发者无需关心
  3. 所有 SSR 页面都运行在这个容器内
  4. 所有 SSR 页面需要有沙箱,运行上下文隔离
  5. 需要有降级到 CSR 的策略

除了 SSR 服务本身之外,也需要有其周边的工具链:

  • 针对每个 SSR 页面构建打包为独立的 Server Entry
  • Server Entry 需要符合约定的 Interface,输入请求上下文,输出渲染结果
  • TS type 包,便于接入

方案详情页是第一个接入的页面,上线前借助 Apache Benchmark 工具做了一定的压测,上线结果也是很好的,此阶段 22 年中上线。

到此阶段,还有一些工程化问题需要联合基架组一起解决,深入集成到 Pub 系统内:

  1. 本地开发:支持 SSR 和 CSR 的同步开发,以及规范的本地开发调用链
  2. 构建:自动识别哪些页面需要走 SSR ,完成 Server Entry 构建
  3. 发布:发布后,对于页面信息的变更,秒级同步到 PR 与 SSR,完成应用的自动更新
  4. 运行:集成请求链路 浏览器 -> PR -> SSR,自动降级能力;以及 Pub 上配置包、语言包等能力的打通

此阶段在 22 年下半年完成,完成后对于业务开发者来说,开发一个 SSR 页面和 CSR 一样简单,不仅是 SEO 的提升,对于首屏优化也有效。

过程中也遇到了各种问题:

  1. 一些二方包实现时没有考虑 Node 端运行场景,例如使用了很多 window/navigator 等浏览器端全局变量,使用 jsdom 注入到每个页面上下文里解决(但也带了问预料之外问题,见 3)
  2. OOM 问题:随着 SSR 流量增多,有一天触发了一端代码的临界点,即运行几天后内存溢出,服务被 K8s Pod 自动重启,反复;排查下来是一个非常基础的监控模块,在并发的 HTTP 链接达到一定数量后进入另一个分支,这个分支对缓存的清理有问题,导致持续增长的内存没有被回收
  3. GC 频繁导致 CPU 增高:根因是有个页面使用到了 react-helmet 库管理 document head 信息,helmet 又使用了 react-side-effect 来处理 props 变化引发的副作用,问题就出现在我们 Mock 了 window/document 等信息,让库误认为当前运行环境在浏览器端,进而将本应无副作用的 string 处理,变成了 DOM 处理,让 Node.js 内 new space 的空间增多,进而引发频繁 GC。

可以看到目前的 SSR 方案也并不是完美的,虽然做了沙箱,但是本质他们还是运行在同一个线程之内的,共享同一个 CPU 资源和内存资源。当出现 GC 引发的 CPU 问题,或 OOM 引发的问题,就会导致整体的不可用。

所以解决这些问题的方案就只能做多进程,一个页面的 SSR 就启动一个独立进程,由主进程管控数据分发、应用更新,这样能充分利用多核 CPU,不至于一个页面 Bug 引发整体的雪崩。

反思:

  1. 之前做的平台更偏研发效率,SSR 能解决一定的业务问题
  2. 不一定一开始就要最完美的方案,保留一定扩展性,能解决当下问题就是最好的

其他

22 年也有一些效果不错的优化:

  • 帮助中心的核心 API 性能提升 70%,帮助中心 JS 全站开发,主要优化的是对 MySQL 调用相关的业务层的逻辑优化。同时也解决其稳定性问题,之前总会因为流量的翻倍导致服务短时间不可用;
  • Star 部分也在持续优化:新增了一些页面模板,微应用的文档是在这一年做的
  • 客户端的可观测行:本地日志优化,以及用户的一键上报
  • 重写富文本编辑器,并应用在多条业务线
  • etc

23 优化

23 年主要是对现有系统的优化,年中也由主站转岗到了国际站。

SSR & Star & 客户端

  • SSR 一些接入文档,最佳实践之类的文档编写
  • Star 权限管理;支持培训物料类目
  • 客户端监控体系的建设,接入现有监控平台
  • 客户端打包平台的持续优化等

国际站

国际站的技术沉淀基本等价于 3 年前的主站,所以还有很多问题需要解决,以及一些提效工具都没用上,感觉和大多数业务线有些脱节。

国际站有很大的特点:多语言、多货币、多集群,依赖的很多三方也不同,例如登录、支付场景。以上都和前端息息相关,其中和开发方式密切度非常高的,就是多语言。

而多语言,目前公司已有基建也比较完善:语言包 CDN 化 + 配置后台 + 项目粒度管理 + VSCode 插件提效。但是也由很多问题:治理困难,例如如何清理一些无用词条;验证困难,例如如何验证其他语种的有效性等。我目前还没有想到比较好的解决手段。

Magi 配置平台

除此之外,页面配置化的能力,对于运营可快速尝试各种增长手段也至关重要。目前运营会采用上文提到的 TMS 来搭建一些营销页、产品介绍页。但也有一些是不满足需求的:SEO、性能、多语言等。

除了 SEO 之外,另外 2 条通过优化 TMS 都还能解决。因为 TMS 的整体架构决定了,想要能支持 SSR 很难,更不必说内置的或二方的组件了。除了页面编排需求之外,还有这些诉求:

  1. 开发者编写的页面也需要有配置化的能力,而针对特定功能开发特定的后台,成本极高
  2. 页面配置化需要能根据国家、人群维度进行不同的展示
  3. 分站点,例如不同国家不同站点

为了满足以上需求,计划造一个低代码配置平台,以及低代码引擎。目前还处于非常早期的阶段,仅完成整体的架构设计和部分 Core & Editor 逻辑的编写。

总结

至此,酷家乐的旅程告一段落。

这段旅程里,做了很多针对研发效率、质量方面的工作,也为其他岗位角色(UED、运营、市场)带来了人效的提升。我相信每一份努力和效率的提升,都会让酷家乐进步一点点,让我们在这个竞争激烈的市场上赢得胜利的机会多一点点。在这里收获满满,未来祝愿群核科技越来越好!

再额外聊一下关于离职,我的看法。我们常看到某些同学因为个别同事的离职,而内心动摇,也决定离职,我曾经也这样。但是在加入酷家乐前,就告诉自己,直面自己内心,不要在乎他人的去留,只要能确定自己能有成长、有收获、与自己规划相符就足够了,共勉。

2023-12-08

于 良渚

相关推荐
Justinc.5 分钟前
CSS3新增边框属性(五)
前端·css·css3
neter.asia21 分钟前
vue中如何关闭eslint检测?
前端·javascript·vue.js
~甲壳虫21 分钟前
说说webpack中常见的Plugin?解决了什么问题?
前端·webpack·node.js
光影少年40 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
As977_42 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu108301891144 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
Ocean☾1 小时前
前端基础-html-注册界面
前端·算法·html
Dragon Wu1 小时前
前端 Canvas 绘画 总结
前端
CodeToGym1 小时前
Webpack性能优化指南:从构建到部署的全方位策略
前端·webpack·性能优化
~甲壳虫1 小时前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js