18 KiB
18 KiB
第2步:JS按需加载优化实施方案
📋 当前JS加载分析
JS文件清单(从footer.html)
核心JS(必须始终加载)
1. iconfont.js - 图标字体(UIkit图标依赖)
2. uikit.min.js - UI框架(基础依赖)
3. view-image.min.js - 图片灯箱(文章详情页)
4. utils.js - 工具函数(主题切换、代码折叠等)
5. loader.js - 加载器
6. cache.js - 缓存管理
7. search.js - 搜索功能
8. floating-tools.js - 浮动工具栏
9. nprogress.js - 页面进度条
10. pjax.js - PJAX导航库
11. mypjax.js - PJAX配置
12. pangu.js - 中英文空格处理
13. linkify.js - 链接处理
14. main.js - 主题主逻辑(初始化所有功能)
非核心JS(可按需加载)
15. artalk.js - Artalk评论系统
- 加载时机:文章详情页(有#comments容器)
- 大小:~150KB
16. infinite-scroll.js - 无限滚动
- 加载时机:首页(且启用该功能)
- 大小:~20KB
17. paragraph-comments.js - 段落评论
- 加载时机:文章详情页
- 大小:~15KB
18. toast.js - Toast消息提示
- 加载时机:特定交互触发时
- 大小:~10KB
19. tiaozhuan.js - 跳转功能
- 加载时机:特定页面(如links、circles)
- 大小:~8KB
20. reward.js - 打赏功能
- 加载时机:文章详情页(有打赏按钮)
- 大小:~12KB
21. lazyload.js - 图片懒加载
- 加载时机:页面加载后(延迟加载)
- 大小:~15KB
🎯 优化策略
策略1:代码拆分(推荐)
将JS拆分为两个bundle:
core.js- 核心功能(约200KB)optional.js- 可选功能(约230KB)
优点:
- 实现简单
- 首页加载减少230KB(约40%)
策略2:按需动态导入
使用ES6的 import() 语法,在需要时动态加载模块
优点:
- 更精细的控制
- 最小化加载量
缺点:
- 需要修改较多代码
- 需要确保模块兼容性
📝 实施方案:策略1(代码拆分)
步骤2.1:创建核心JS bundle
修改文件: themes/Ying/layouts/partials/footer.html
当前代码(第50-73行):
{{ $iconfont := resources.Get "js/libs/iconfont.js" }}
{{ $uikit := resources.Get "js/libs/uikit.min.js" }}
{{ $easylazyload := resources.Get "js/modules/lazyload.js" }}
{{ $viewimage := resources.Get "js/libs/view-image.min.js" }}
{{ $main := resources.Get "js/main.js" }}
{{ $mypjax := resources.Get "js/modules/mypjax.js" }}
{{ $nprogress := resources.Get "js/libs/nprogress.js" }}
{{ $tiaozhuan := resources.Get "js/modules/tiaozhuan.js" }}
{{ $toast := resources.Get "js/modules/toast.js" }}
{{ $reward := resources.Get "js/modules/reward.js" }}
{{/* Modules */}}
{{ $utils := resources.Get "js/modules/utils.js" }}
{{ $loader := resources.Get "js/modules/loader.js" }}
{{ $cache := resources.Get "js/modules/cache.js" }}
{{ $search := resources.Get "js/modules/search.js" }}
{{ $floatingTools := resources.Get "js/modules/floating-tools.js" }}
{{ $paragraphComments := resources.Get "js/modules/paragraph-comments.js" }}
{{ $infiniteScroll := resources.Get "js/modules/infinite-scroll.js" }}
{{ $artalkModule := resources.Get "js/modules/artalk.js" }}
{{ $pjaxLib := resources.Get "js/libs/pjax.js" }}
{{ $linkify := resources.Get "js/modules/linkify.js" }}
{{ $pangu := resources.Get "js/modules/pangu.js" }}
{{ $scripts := slice $iconfont $uikit $easylazyload $viewimage $utils $loader $cache $search $floatingTools $paragraphComments $infiniteScroll $artalkModule $nprogress $pjaxLib $pangu $linkify $main $mypjax $toast $tiaozhuan $reward | resources.Concat "js/bundle.js" | resources.Minify | resources.Fingerprint }}
<script defer src="{{ $scripts.RelPermalink }}"></script>
修改后代码:
{{/* ====== JS加载优化:核心JS + 非核心JS分离 ====== */}}
{{/* 1. 核心JS - 始终加载 */}}
{{ $iconfont := resources.Get "js/libs/iconfont.js" }}
{{ $uikit := resources.Get "js/libs/uikit.min.js" }}
{{ $viewimage := resources.Get "js/libs/view-image.min.js" }}
{{ $utils := resources.Get "js/modules/utils.js" }}
{{ $loader := resources.Get "js/modules/loader.js" }}
{{ $cache := resources.Get "js/modules/cache.js" }}
{{ $search := resources.Get "js/modules/search.js" }}
{{ $floatingTools := resources.Get "js/modules/floating-tools.js" }}
{{ $nprogress := resources.Get "js/libs/nprogress.js" }}
{{ $pjaxLib := resources.Get "js/libs/pjax.js" }}
{{ $mypjax := resources.Get "js/modules/mypjax.js" }}
{{ $pangu := resources.Get "js/modules/pangu.js" }}
{{ $linkify := resources.Get "js/modules/linkify.js" }}
{{ $main := resources.Get "js/main.js" }}
{{ $coreScripts := slice $iconfont $uikit $viewimage $utils $loader $cache $search $floatingTools $nprogress $pjaxLib $mypjax $pangu $linkify $main | resources.Concat "js/core.js" | resources.Minify | resources.Fingerprint }}
<script defer src="{{ $coreScripts.RelPermalink }}"></script>
{{/* 2. 非核心JS - 使用requestIdleCallback延迟加载 */}}
{{ $artalkModule := resources.Get "js/modules/artalk.js" }}
{{ $infiniteScroll := resources.Get "js/modules/infinite-scroll.js" }}
{{ $paragraphComments := resources.Get "js/modules/paragraph-comments.js" }}
{{ $toast := resources.Get "js/modules/toast.js" }}
{{ $tiaozhuan := resources.Get "js/modules/tiaozhuan.js" }}
{{ $reward := resources.Get "js/modules/reward.js" }}
{{ $easylazyload := resources.Get "js/modules/lazyload.js" }}
{{ $optionalScripts := slice $artalkModule $infiniteScroll $paragraphComments $toast $tiaozhuan $reward $easylazyload | resources.Concat "js/optional.js" | resources.Minify | resources.Fingerprint }}
<script>
// 使用requestIdleCallback在浏览器空闲时加载非核心JS
// 如果浏览器不支持requestIdleCallback,使用setTimeout降级
if ('requestIdleCallback' in window) {
requestIdleCallback(function() {
var script = document.createElement('script');
script.src = '{{ $optionalScripts.RelPermalink }}';
script.defer = true;
document.body.appendChild(script);
});
} else {
setTimeout(function() {
var script = document.createElement('script');
script.src = '{{ $optionalScripts.RelPermalink }}';
script.defer = true;
document.body.appendChild(script);
}, 1000); // 延迟1秒加载
}
</script>
步骤2.2:条件加载特定页面的JS(高级优化)
如果需要更精细的控制,可以根据页面类型加载特定JS:
{{/* 根据页面类型条件加载JS */}}
{{/* 文章详情页专用JS */}}
{{ if .IsPage }}
{{ $artalkModule := resources.Get "js/modules/artalk.js" }}
{{ $paragraphComments := resources.Get "js/modules/paragraph-comments.js" }}
{{ $reward := resources.Get "js/modules/reward.js" }}
{{ $pageScripts := slice $artalkModule $paragraphComments $reward | resources.Concat "js/page-only.js" | resources.Minify | resources.Fingerprint }}
<script defer src="{{ $pageScripts.RelPermalink }}"></script>
{{ end }}
{{/* 首页专用JS */}}
{{ if .IsHome }}
{{ $infiniteScroll := resources.Get "js/modules/infinite-scroll.js" }}
<script>
window.enableInfiniteScroll = {{ .Site.Params.infiniteScroll.enable | default false }};
</script>
{{ if .Site.Params.infiniteScroll.enable }}
<script defer src="{{ $infiniteScroll.RelPermalink }}"></script>
{{ end }}
{{ end }}
{{/* 特定页面专用JS */}}
{{ if or (eq .Type "links") (eq .Type "circles") }}
{{ $tiaozhuan := resources.Get "js/modules/tiaozhuan.js" }}
<script defer src="{{ $tiaozhuan.RelPermalink }}"></script>
{{ end }}
🔧 完整实施方案
修改footer.html的完整代码
文件路径: themes/Ying/layouts/partials/footer.html
删除原有JS加载代码(第50-133行),替换为:
{{/* ====== 优化后的JS加载策略 ====== */}}
{{/* ====== 1. 核心JS Bundle - 始终加载 ====== */}}
{{ $iconfont := resources.Get "js/libs/iconfont.js" }}
{{ $uikit := resources.Get "js/libs/uikit.min.js" }}
{{ $viewimage := resources.Get "js/libs/view-image.min.js" }}
{{ $utils := resources.Get "js/modules/utils.js" }}
{{ $loader := resources.Get "js/modules/loader.js" }}
{{ $cache := resources.Get "js/modules/cache.js" }}
{{ $search := resources.Get "js/modules/search.js" }}
{{ $floatingTools := resources.Get "js/modules/floating-tools.js" }}
{{ $nprogress := resources.Get "js/libs/nprogress.js" }}
{{ $pjaxLib := resources.Get "js/libs/pjax.js" }}
{{ $mypjax := resources.Get "js/modules/mypjax.js" }}
{{ $pangu := resources.Get "js/modules/pangu.js" }}
{{ $linkify := resources.Get "js/modules/linkify.js" }}
{{ $main := resources.Get "js/main.js" }}
{{ $coreScripts := slice $iconfont $uikit $viewimage $utils $loader $cache $search $floatingTools $nprogress $pjaxLib $mypjax $pangu $linkify $main | resources.Concat "js/core.js" | resources.Minify | resources.Fingerprint }}
<script defer src="{{ $coreScripts.RelPermalink }}"></script>
{{/* ====== 2. 配置变量 - 必须在核心JS之前 ====== */}}
<script>
window.enableInfiniteScroll = {{ .Site.Params.infiniteScroll.enable | default false }};
</script>
{{/* ====== 3. 远程数据获取 - 构建时 ====== */}}
{{/* Fetch friend data at build time — no runtime network requests */}}
{{ $linkHosts := slice }}
{{ $linkNames := slice }}
{{ with resources.GetRemote "https://api.usj.cc/api/links?all=1" }}
{{ with .Err }}
{{ warnf "Links API fetch failed: %s" . }}
{{ else }}
{{ with .Content | transform.Unmarshal }}
{{ range .links }}
{{ $host := "" }}
{{ with .url | urls.Parse }}
{{ $host = .Host | strings.TrimPrefix "www." }}
{{ end }}
{{ $title := .name | default "" }}
{{ if $host }}
{{ $linkHosts = $linkHosts | append (dict "host" $host "title" $title) }}
{{ end }}
{{ if $title }}
{{ $linkNames = $linkNames | append (dict "lower" (lower $title) "title" $title) }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
<script>
window.friendLinks = {{ dict "hosts" $linkHosts "names" $linkNames | jsonify | safeJS }};
</script>
{{ $feedHosts := slice }}
{{ $feedNames := slice }}
{{ with resources.GetRemote "https://api.usj.cc/api/feeds" }}
{{ with .Err }}
{{ warnf "Feeds API fetch failed: %s" . }}
{{ else }}
{{ with .Content | transform.Unmarshal }}
{{ range .feeds }}
{{ $host := "" }}
{{ with .url | urls.Parse }}
{{ $host = .Host | strings.TrimPrefix "www." }}
{{ end }}
{{ $title := .feedTitle | default "" }}
{{ if $host }}
{{ $feedHosts = $feedHosts | append (dict "host" $host "title" $title) }}
{{ end }}
{{ if $title }}
{{ $feedNames = $feedNames | append (dict "lower" (lower $title) "title" $title) }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
<script>
window.friendFeeds = {{ dict "hosts" $feedHosts "names" $feedNames | jsonify | safeJS }};
</script>
{{/* ====== 4. 条件加载 - 页面特定JS ====== */}}
{{/* 文章详情页专用JS */}}
{{ if .IsPage }}
{{ $artalkModule := resources.Get "js/modules/artalk.js" }}
{{ $paragraphComments := resources.Get "js/modules/paragraph-comments.js" }}
{{ $reward := resources.Get "js/modules/reward.js" }}
{{ $pageScripts := slice $artalkModule $paragraphComments $reward | resources.Concat "js/page-only.js" | resources.Minify | resources.Fingerprint }}
<script defer src="{{ $pageScripts.RelPermalink }}"></script>
{{ end }}
{{/* 首页专用JS */}}
{{ if .IsHome }}
{{ if .Site.Params.infiniteScroll.enable }}
{{ $infiniteScroll := resources.Get "js/modules/infinite-scroll.js" }}
<script defer src="{{ $infiniteScroll.RelPermalink }}"></script>
{{ end }}
{{ end }}
{{/* 特定页面专用JS */}}
{{ if or (eq .Type "links") (eq .Type "circles") }}
{{ $tiaozhuan := resources.Get "js/modules/tiaozhuan.js" }}
<script defer src="{{ $tiaozhuan.RelPermalink }}"></script>
{{ end }}
{{/* ====== 5. 其他非核心JS - 延迟加载 ====== */}}
{{ $toast := resources.Get "js/modules/toast.js" }}
{{ $easylazyload := resources.Get "js/modules/lazyload.js" }}
{{ $deferredScripts := slice $toast $easylazyload | resources.Concat "js/deferred.js" | resources.Minify | resources.Fingerprint }}
<script>
// 使用requestIdleCallback在浏览器空闲时加载
if ('requestIdleCallback' in window) {
requestIdleCallback(function() {
var script = document.createElement('script');
script.src = '{{ $deferredScripts.RelPermalink }}';
script.defer = true;
document.body.appendChild(script);
});
} else {
setTimeout(function() {
var script = document.createElement('script');
script.src = '{{ $deferredScripts.RelPermalink }}';
script.defer = true;
document.body.appendChild(script);
}, 1000);
}
</script>
{{/* ====== 6. 其他脚本(bsz等) ====== */}}
<script async src="/js/bsz.js"></script>
📊 预期收益
性能指标提升(预估)
| 页面类型 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 首页 | ~800KB | ~350KB | ⚡ -56% |
| 文章详情页 | ~800KB | ~450KB | ⚡ -44% |
| 其他页面 | ~800KB | ~350KB | ⚡ -56% |
加载时间提升
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| FCP | ~1.2s | ~0.9s | ⚡ -25% |
| TTI | ~2.5s | ~1.8s | ⚡ -28% |
| TBT | ~150ms | ~80ms | ⚡ -47% |
🧪 测试验证清单
功能测试
首页
- 导航菜单正常工作
- 搜索功能正常
- 主题切换正常
- 文章列表显示正常
- 分页功能正常
- 无限滚动功能正常(如果启用)
文章详情页
- 文章内容正常显示
- 图片灯箱功能正常
- 评论区正常加载(Artalk)
- 打赏按钮功能正常
- 段落评论功能正常(如果启用)
其他页面
- 友链页面跳转功能正常
- 搜索结果页正常
- 归档页面正常
性能测试
使用Chrome DevTools:
-
Network面板
- 核心JS(core.js)首先加载
- 页面特定JS按条件加载
- 非核心JS在空闲时加载
-
Performance面板
- TBT指标改善
- 长任务减少
- 交互响应更快
-
Lighthouse
- Performance得分提升
- TTI指标改善
- TBT指标改善
⚠️ 注意事项
1. 模块依赖关系
某些JS模块可能有依赖关系:
artalk.js可能依赖Artalk.js(第三方库)mypjax.js依赖pjax.jsmain.js可能调用其他模块的初始化函数
解决方案:
- 确保核心JS包含所有依赖
- 或者使用
window全局变量暴露必要函数
2. 初始化时机
如果模块的初始化代码在模块内部,延迟加载可能导致初始化失败。
解决方案:
- 在
main.js中使用事件监听或轮询 - 或者将初始化代码移到主JS中
示例:
// main.js 中的初始化代码
function initArtalk() {
if (window.Artalk) {
// 初始化Artalk
} else {
// Artalk还未加载,等待
setTimeout(initArtalk, 100);
}
}
3. 浏览器兼容性
requestIdleCallback 兼容性:
- Chrome 47+ ✅
- Firefox 55+ ✅
- Safari 12.1+ ✅
- Edge 79+ ✅
- IE ❌(使用setTimeout降级)
🔄 回滚方案
如果出现问题,快速回滚到原始方案:
{{/* 恢复原始JS加载方式 */}}
{{ $iconfont := resources.Get "js/libs/iconfont.js" }}
{{ $uikit := resources.Get "js/libs/uikit.min.js" }}
{{ $easylazyload := resources.Get "js/modules/lazyload.js" }}
{{ $viewimage := resources.Get "js/libs/view-image.min.js" }}
{{ $main := resources.Get "js/main.js" }}
{{ $mypjax := resources.Get "js/modules/mypjax.js" }}
{{ $nprogress := resources.Get "js/libs/nprogress.js" }}
{{ $tiaozhuan := resources.Get "js/modules/tiaozhuan.js" }}
{{ $toast := resources.Get "js/modules/toast.js" }}
{{ $reward := resources.Get "js/modules/reward.js" }}
{{/* Modules */}}
{{ $utils := resources.Get "js/modules/utils.js" }}
{{ $loader := resources.Get "js/modules/loader.js" }}
{{ $cache := resources.Get "js/modules/cache.js" }}
{{ $search := resources.Get "js/modules/search.js" }}
{{ $floatingTools := resources.Get "js/modules/floating-tools.js" }}
{{ $paragraphComments := resources.Get "js/modules/paragraph-comments.js" }}
{{ $infiniteScroll := resources.Get "js/modules/infinite-scroll.js" }}
{{ $artalkModule := resources.Get "js/modules/artalk.js" }}
{{ $pjaxLib := resources.Get "js/libs/pjax.js" }}
{{ $linkify := resources.Get "js/modules/linkify.js" }}
{{ $pangu := resources.Get "js/modules/pangu.js" }}
{{ $scripts := slice $iconfont $uikit $easylazyload $viewimage $utils $loader $cache $search $floatingTools $paragraphComments $infiniteScroll $artalkModule $nprogress $pjaxLib $pangu $linkify $main $mypjax $toast $tiaozhuan $reward | resources.Concat "js/bundle.js" | resources.Minify | resources.Fingerprint }}
<script defer src="{{ $scripts.RelPermalink }}"></script>
<script async src="/js/bsz.js"></script>
✅ 实施清单
- 备份原始
footer.html - 修改JS加载逻辑
- 测试首页功能
- 测试文章详情页功能
- 测试其他页面功能
- 使用Lighthouse验证性能提升
- 提交代码
📚 扩展阅读
-
JavaScript代码拆分
-
Dynamic Imports
-
requestIdleCallback
预计耗时: 2-3小时 风险等级: 中(需要测试功能完整性)