diff --git a/website/builder/build.py b/website/builder/build.py index e1c77d4b..dc732d6b 100644 --- a/website/builder/build.py +++ b/website/builder/build.py @@ -189,7 +189,7 @@ def build(optimize=True): dist = Path(temporary) / 'dist' dist.mkdir() shutil.copytree(SITE / 'vendor', dist / 'vendor') - for name in ('index.html', 'site.css', 'site.js', 'search.js'): + for name in ('index.html', 'site.css', 'site.js', 'search.js', 'edgeone.json'): shutil.copyfile(SITE / name, dist / name) for path in sources(): target = dist / path.relative_to(ROOT) @@ -282,10 +282,11 @@ def build(optimize=True): 'searchIndex': index_name, 'legacyRoutes': len(legacy)} (dist / 'build-info.json').write_text(json.dumps(build_info, indent=2) + '\n') entry = (dist / 'index.html').read_text() - for name in ('site.css', 'site.js', 'search.js', 'search-paths.js'): - digest = hashlib.sha256((dist / name).read_bytes()).hexdigest()[:12] - entry = entry.replace('/' + name + '"', '/' + name + '?v=' + digest + '"') + entry = bundle_entry(dist, entry) (dist / 'index.html').write_text(entry) + shell = ['/', '/_sidebar.md', '/README.md'] + re.findall(r'(?:src|href)="(/(?:app|vendor|style)-[0-9a-f]{16}\.(?:js|css))"', entry) + worker = (SITE / 'sw.js').read_text().replace('__SHELL__', json.dumps(shell)) + (dist / 'sw.js').write_text(worker.replace('__BUILD__', hashlib.sha256(json.dumps(shell).encode()).hexdigest()[:12])) write_manifest(dist, build_info) files = [p for p in dist.rglob('*') if p.is_file()] report = {'notes': len(note_paths), 'files': len(files), 'bytes': sum(p.stat().st_size for p in files), @@ -310,6 +311,32 @@ def build(optimize=True): print(json.dumps({k: v for k, v in report.items() if k not in ('imageReplacements', 'externalImageUrls')}, ensure_ascii=False, indent=2)) +SCRIPT_TAG = re.compile(r'^ \n', re.M) +STYLE_TAG = re.compile(r'^ \n', re.M) + + +def bundle_entry(dist, entry): + """入口页的脚本按原顺序合成本站脚本和依赖库两个文件,样式合成一个;文件名带内容哈希。""" + def write(names, prefix, suffix, separator): + parts = [re.sub(r'^//# sourceMappingURL=.*$', '', (dist / name).read_text(), flags=re.M).rstrip() for name in names] + data = separator.join(parts) + '\n' + name = f'{prefix}-{hashlib.sha256(data.encode()).hexdigest()[:16]}{suffix}' + (dist / name).write_text(data) + return name + scripts, styles = SCRIPT_TAG.findall(entry), STYLE_TAG.findall(entry) + own = [name for name in scripts if not name.startswith('vendor/')] + vendor = [name for name in scripts if name.startswith('vendor/')] + # 本站脚本要先执行(site.js 定义 $docsify 配置,依赖库中的插件往里面添加) + if scripts != own + vendor: + raise ValueError('入口页的脚本顺序应为本站脚本在前、依赖库在后') + tags = ''.join(f' \n' + for group, prefix in ((own, 'app'), (vendor, 'vendor')) if group) + style = f' \n' + entry = SCRIPT_TAG.sub('', entry) + entry = STYLE_TAG.sub(lambda match: style if match[1] == styles[0] else '', entry) + return entry.replace('', tags + '', 1) + + if __name__ == '__main__': parser = argparse.ArgumentParser(description='仅在本机从最新上游源码生成静态站,不下载图片、不发布。') parser.add_argument('--source', type=Path, default=ROOT)