# 第2步:JS按需加载优化实施方案 ## 📋 当前JS加载分析 ### JS文件清单(从footer.html) #### 核心JS(必须始终加载) ```javascript 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(可按需加载) ```javascript 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行):** ```gohtml {{ $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 }} ``` **修改后代码:** ```gohtml {{/* ====== 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 }} {{/* 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 }} ``` --- ### 步骤2.2:条件加载特定页面的JS(高级优化) 如果需要更精细的控制,可以根据页面类型加载特定JS: ```gohtml {{/* 根据页面类型条件加载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 }} {{ end }} {{/* 首页专用JS */}} {{ if .IsHome }} {{ $infiniteScroll := resources.Get "js/modules/infinite-scroll.js" }} {{ if .Site.Params.infiniteScroll.enable }} {{ end }} {{ end }} {{/* 特定页面专用JS */}} {{ if or (eq .Type "links") (eq .Type "circles") }} {{ $tiaozhuan := resources.Get "js/modules/tiaozhuan.js" }} {{ end }} ``` --- ## 🔧 完整实施方案 ### 修改footer.html的完整代码 **文件路径:** `themes/Ying/layouts/partials/footer.html` **删除原有JS加载代码(第50-133行)**,替换为: ```gohtml {{/* ====== 优化后的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 }} {{/* ====== 2. 配置变量 - 必须在核心JS之前 ====== */}} {{/* ====== 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 }} {{ $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 }} {{/* ====== 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 }} {{ end }} {{/* 首页专用JS */}} {{ if .IsHome }} {{ if .Site.Params.infiniteScroll.enable }} {{ $infiniteScroll := resources.Get "js/modules/infinite-scroll.js" }} {{ end }} {{ end }} {{/* 特定页面专用JS */}} {{ if or (eq .Type "links") (eq .Type "circles") }} {{ $tiaozhuan := resources.Get "js/modules/tiaozhuan.js" }} {{ 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 }} {{/* ====== 6. 其他脚本(bsz等) ====== */}} ``` --- ## 📊 预期收益 ### 性能指标提升(预估) | 页面类型 | 优化前 | 优化后 | 提升 | |---------|--------|--------|------| | **首页** | ~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: 1. **Network面板** - [ ] 核心JS(core.js)首先加载 - [ ] 页面特定JS按条件加载 - [ ] 非核心JS在空闲时加载 2. **Performance面板** - [ ] TBT指标改善 - [ ] 长任务减少 - [ ] 交互响应更快 3. **Lighthouse** - [ ] Performance得分提升 - [ ] TTI指标改善 - [ ] TBT指标改善 --- ## ⚠️ 注意事项 ### 1. 模块依赖关系 某些JS模块可能有依赖关系: - `artalk.js` 可能依赖 `Artalk.js`(第三方库) - `mypjax.js` 依赖 `pjax.js` - `main.js` 可能调用其他模块的初始化函数 **解决方案:** - 确保核心JS包含所有依赖 - 或者使用 `window` 全局变量暴露必要函数 ### 2. 初始化时机 如果模块的初始化代码在模块内部,延迟加载可能导致初始化失败。 **解决方案:** - 在 `main.js` 中使用事件监听或轮询 - 或者将初始化代码移到主JS中 示例: ```javascript // 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降级) --- ## 🔄 回滚方案 如果出现问题,快速回滚到原始方案: ```gohtml {{/* 恢复原始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 }} ``` --- ## ✅ 实施清单 - [ ] 备份原始 `footer.html` - [ ] 修改JS加载逻辑 - [ ] 测试首页功能 - [ ] 测试文章详情页功能 - [ ] 测试其他页面功能 - [ ] 使用Lighthouse验证性能提升 - [ ] 提交代码 --- ## 📚 扩展阅读 1. **JavaScript代码拆分** - https://web.dev/reduce-javascript-payloads-with-code-splitting/ 2. **Dynamic Imports** - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import 3. **requestIdleCallback** - https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback --- **预计耗时:** 2-3小时 **风险等级:** 中(需要测试功能完整性)