Dawn 的加载策略:按分区引入资源、首屏图片与按需媒体
本文基于 Dawn v15.3.0,源码固定到 ecb06c3(核验于 2026-10-01)。结论限于此提交;本篇为静态源码分析,未测量任何性能指标。Dawn 上游已发布 v16.0.0,本库 Dawn 正文统一以 v15.3.0 为基线,升级前需重新核对。
Dawn 没有打包工具,资源优化全部写在 Liquid 里,由服务端按页面内容决定输出哪些标签。平台层面的指标与优先级见店面性能,本文只记录 Dawn 的具体写法。
一、资源跟着分区走
全局只放公共部分。 布局固定加载 base.css 与 6 个基础脚本(theme.liquid#L31-L36、#L258),其余按全局设置决定:
| 条件 | 引入的资源 | 位置 |
|---|---|---|
animations_reveal_on_scroll | animations.js | #L38-L40 |
cart_type == 'drawer' | 抽屉相关 5 个 CSS | #L261-L267 |
| 多个国家或语言 | 本地化表单 CSS 与 JS | #L280-L283 |
predictive_search_enabled | 预测搜索 CSS(异步) | #L285-L292 |
分区引入自己的样式与脚本。 sections/ 的 52 个文件中有 39 个直接输出 stylesheet_tag。商品主分区还按商品数据进一步筛选(main-product.liquid#L12-L58):只有一个默认变体时不加载变体选择器与色板 CSS;设置了阶梯价才加载阶梯价 CSS 与两个脚本;悬停放大才加载 magnify.js;有 3D 模型才加载模型样式。页面上没有的功能,不会产生请求。
循环中只输出一次。 集合页循环渲染商品卡时,第一张卡片输出卡片样式,之后传入 skip_styles: true(main-collection-product-grid.liquid#L155-L181、card-product.liquid#L23)。
非关键样式异步加载。 media="print" onload="this.media='all'" 让样式表不阻塞渲染,加载完成后再生效(如 theme.liquid#L259)。sections/、snippets/、layout/ 中共 12 处使用这种写法。
脚本一律 defer。 不阻塞解析,按文档顺序执行;组件的服务端 HTML 在脚本执行前已经可见(见组件组织)。
二、字体
| 写法 | 位置 | 作用 |
|---|---|---|
非系统字体时 preconnect 到 fonts.shopifycdn.com | theme.liquid#L14-L16 | 提前建立连接 |
font_face: font_display: 'swap',含粗体、斜体派生 | #L44-L55 | 字体加载前先显示后备字体 |
正文与标题字体 preload | #L269-L278 | 尽早下载首屏用到的两个字重 |
预加载用 theme-check-disable AssetPreload 包住,说明这是有意绕过 Theme Check 的建议。只预加载常规字重,粗体与斜体按需加载。
三、图片
srcset 只列不超过原图的尺寸。 商品卡对 165、360、533、720、940、1066 每个宽度都先判断 featured_media.width >= N,最后补上原图宽度(card-product.liquid#L64-L83),避免让 CDN 生成放大后的图片。同时写明 width、height,浏览器可以预留空间,减少布局偏移。
sizes 由页面宽度设置计算。 页面宽度达到 settings.page_width 后,每张卡片按 (page_width - 130) / 4 计算,窄屏按 4、3、2 列计算。
首屏立即加载,其余懒加载。
- 集合页前 2 张商品卡不加
loading="lazy"(main-collection-product-grid.liquid#L157-L160); - 商品图库的主图
lazy_load: false,第 2 张起懒加载(product-media-gallery.liquid#L84、#L104-L108); - 横幅与轮播位于模板第一个分区(
section.index == 1)时设置fetchpriority: 'high'(image-banner.liquid#L48-L50、slideshow.liquid#L134-L136)。
四、按需挂载与提前加载
- 视频与 3D 模型。
DeferredMedia先只显示海报,真正的<video>、<iframe>、<model-viewer>放在<template>里,点击后才克隆插入。自托管视频另设preload: 'none'(product-thumbnail.liquid#L143)。 - 商品推荐。
ProductRecommendations用IntersectionObserver监听,rootMargin底部 400px,即离可视区还有 400px 时就发请求,用户滚到时内容大概率已就绪。
五、CartPerformance 埋点
CartPerformance 用 performance.mark / measure 记录购物车交互,名称前缀为 cart-performance:。measureFromEvent 以事件的 timeStamp 作为起点,测量的是从用户点击到界面更新的完整耗时,而不只是请求时间。加购记录 add:user-action、add:wait-for-subscribers、add:paint-updated-sections(product-form.js#L69-L108)。这些测量可在浏览器 Performance 面板中查看,也能被 PerformanceObserver 采集;主题内未找到上报代码。
失效或打折的优化
均为静态阅读,未测量:
- 缩放动画的节流没有生效。 animations.js#L64 调用
throttle(fn)时没有传入间隔,throttle中now - lastCall < undefined恒为false,每次滚动事件都会执行。每个animate--zoom-in元素还各自注册一个scroll监听,并且从不移除。 sizes固定按 4 列计算。 集合页桌面列数可设为 1 到 6(schema),商品卡的sizes却不读取这个设置:设为 2 列时浏览器会选择偏小的图片,设为 6 列时会下载偏大的图片。- 快速加购请求整页。 quick-add.js#L28 直接请求商品 URL,不带
section_id,取回包含页头、页脚在内的整页 HTML,只用其中的product-info。 - 一个埋点名称带了多余的引号。 cart.js#L165 的模板字符串是
`${eventTarget}:paint-updated-sections"`,生成的测量名称以"结尾,按名称筛选时会漏掉。
迁移到自己主题时
- 保留“分区自己引入资源,再按商品数据和设置裁剪”的做法;用了打包工具时,也应按分区拆分入口,而不是合并成一个大包。
- 首屏判断用
section.index和循环序号,不要对所有图片统一加loading="lazy"。 sizes应读取与布局相同的设置。- 给关键交互加上与
CartPerformance类似的埋点,并真正上报。
建议的验证范围
本篇均未执行:分别打开有、无变体的商品页,对比 CSS 请求列表;在集合页把列数设为 2 和 6,检查浏览器实际选择的 srcset 尺寸;首页首屏为横幅时确认 LCP 图片带 fetchpriority="high";在 Performance 面板中查看 cart-performance:* 测量。