- rehydrate.js 新增 boot()/aksrt:ready 事件,支持 boot 后晚注册立即以 init 触发 - aksrt-runtime.js 初始加载改走 AksrtFrontend.boot,tooltip/倒计时注册为组件 - 约 30 个前端脚本(首页、评论、支付、路由、页面叶子、widgets) 从直接 DOMContentLoaded 迁移为 AksrtFrontend.register,并加幂等标记 - section-navs 依赖改为 aksrt-runtime,保证注册时序 - 清理死代码:删除孤儿 loader.js/main.js 及 runtime/page-navs 遗留副本 - loader.js 移除已迁移模块的 tbquire 路径映射,main.js 相应瘦身 - build-public-assets.mjs 增加 runtime/main.min.js 自动压缩步骤 - 新增 4 个契约测试(homepage-dom/rehydrate-runtime/global-dom-init/ public-bundle-parity),3 个既有 harness 适配注册表初始化方式
118 lines
4.7 KiB
JavaScript
118 lines
4.7 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const test = require('node:test');
|
|
|
|
const root = path.resolve(__dirname, '..', '..');
|
|
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
|
|
|
const publicSources = [
|
|
'assets/js/core/comment-fixed-input.js',
|
|
'assets/js/core/misc.js',
|
|
'assets/js/follow.js',
|
|
'assets/js/enhancements.js',
|
|
'assets/js/enhancements2.js',
|
|
'assets/js/color-extract.js',
|
|
'assets/js/effects.js',
|
|
'assets/js/captcha-front.js',
|
|
'assets/widgets/frontend.js',
|
|
];
|
|
|
|
const routedSources = [
|
|
'assets/js/auth.js',
|
|
'assets/js/author.js',
|
|
'assets/js/routes/user-center.js',
|
|
'assets/js/order-center.js',
|
|
'assets/js/credits.js',
|
|
'assets/js/signin.js',
|
|
'assets/js/reward.js',
|
|
'assets/js/report.js',
|
|
'assets/js/page-navs.js',
|
|
'assets/js/section-navs.js',
|
|
'assets/js/posts-navs-page.js',
|
|
'assets/js/front-submit.js',
|
|
'assets/js/mini-upload.js',
|
|
'assets/js/page-template.js',
|
|
'assets/js/page-edit.js',
|
|
'assets/js/precode.js',
|
|
'assets/js/post-share.js',
|
|
'assets/js/poster-share.js',
|
|
'assets/js/pay-vip.js',
|
|
'assets/js/video-player.js',
|
|
'assets/js/download-status.js',
|
|
'assets/js/ban-appeal-page.js',
|
|
'assets/js/docs-navigation.js',
|
|
'assets/js/input-expand.js',
|
|
'assets/js/waterfall.js',
|
|
'assets/js/yiyan.js',
|
|
'assets/js/messages.js',
|
|
'assets/js/site-notice.js',
|
|
'assets/js/user-badge-actions.js',
|
|
];
|
|
|
|
const readyPattern = /(?:document\.addEventListener\(\s*['"]DOMContentLoaded['"]|document\.readyState\s*===|jQuery\(document\)\.ready\s*\()/;
|
|
|
|
test('global DOM boot is the only initial page lifecycle owner', () => {
|
|
const runtime = read('assets/js/aksrt-runtime.js');
|
|
const registry = read('assets/js/rehydrate.js');
|
|
assert.match(registry, /boot:\s*function/);
|
|
assert.match(registry, /register:[\s\S]*?_booted/);
|
|
assert.match(runtime, /AksrtFrontend\.boot\(root\)/);
|
|
assert.match(runtime, /aksrt\.autoFun\(document, 'init'\)/);
|
|
assert.match(runtime, /reason === 'init'[\s\S]*?AksrtFrontend\.boot\(root\)[\s\S]*?else[\s\S]*?AksrtFrontend\.rehydrate\(root, reason\)/);
|
|
});
|
|
|
|
test('public bundle components register instead of owning DOM ready', () => {
|
|
for (const file of publicSources) {
|
|
const source = read(file);
|
|
assert.doesNotMatch(source, readyPattern, `${file} still owns a DOM-ready boot`);
|
|
assert.match(source, /AksrtFrontend\.register\(/, `${file} must register with the global lifecycle`);
|
|
}
|
|
});
|
|
|
|
test('active routed UI scripts register with the global lifecycle', () => {
|
|
for (const file of routedSources) {
|
|
const source = read(file);
|
|
if (/API|coordinator|runtime/i.test(source.slice(0, 500)) && !/querySelector|addEventListener/.test(source)) continue;
|
|
assert.doesNotMatch(source, readyPattern, `${file} still owns a DOM-ready boot`);
|
|
assert.match(source, /AksrtFrontend\.register\(/, `${file} must register with the global lifecycle`);
|
|
}
|
|
});
|
|
|
|
test('high-risk routed components remain idempotent under global rehydrate', () => {
|
|
const pageNavs = read('assets/js/page-navs.js');
|
|
const sectionNavs = read('assets/js/section-navs.js');
|
|
const poster = read('assets/js/poster-share.js');
|
|
|
|
assert.match(pageNavs, /__aksrtPageNavInitialized/);
|
|
assert.match(pageNavs, /var grid = self\.root\.querySelector\('\.aksrt-doc-grid'\)/);
|
|
assert.match(sectionNavs, /if \(scrollspyBound\) return/);
|
|
assert.match(sectionNavs, /if \(autoHashHandled \|\| !window\.location\.hash\) return/);
|
|
assert.match(poster, /triggers\.filter\(function \(el\) \{ return el\.dataset\.posterInit !== '1'; \}\)/);
|
|
assert.doesNotMatch(poster, /triggers\.some\(function \(el\) \{ return el\.dataset\.posterInit === '1'; \}\)/);
|
|
});
|
|
|
|
test('resource and SDK load listeners are not mistaken for page boot', () => {
|
|
const captcha = read('assets/js/captcha-front.js');
|
|
const color = read('assets/js/color-extract.js');
|
|
assert.match(captcha, /\.onload\s*=|addEventListener\(['"]load['"]/);
|
|
assert.match(color, /addEventListener\(['"]load['"]/);
|
|
assert.doesNotMatch(captcha, /DOMContentLoaded/);
|
|
assert.doesNotMatch(color, /DOMContentLoaded/);
|
|
});
|
|
|
|
test('route scripts that use the registry load after the public runtime', () => {
|
|
const scripts = read('src/Core/ScriptsPart1b.php') + read('src/Core/ScriptsPart1c.php');
|
|
const files = ['section-navs.js', 'page-navs.js', 'posts-navs-page.js', 'author.js', 'auth.js'];
|
|
for (const file of files) {
|
|
const uri = scripts.indexOf("/assets/js/" + file);
|
|
assert.notEqual(uri, -1, `${file} must be enqueued`);
|
|
const blockStart = scripts.lastIndexOf('wp_enqueue_script(', uri);
|
|
const blockEnd = scripts.indexOf(');', uri);
|
|
const block = scripts.slice(blockStart, blockEnd + 2);
|
|
assert.match(block, /aksrt-runtime/, `${file} must depend on aksrt-runtime`);
|
|
}
|
|
});
|