DragonRster`s Void
Banner Image
On This Page
脚本详解:generate-archive.ps1

一脚本四产出:最新文章 + 文章归档 + 标签云 + 搜索索引


generate-archive.ps1 是整个构建流程的第一阶段。它必须在 build.ps1 之前运行,因为它负责生成那些会被"注入"到侧边栏和独立页面的数据文件。一个脚本,四种产出,是项目中最"高产"的模块。

1. 元数据扫描

脚本的第一步是遍历博客内容目录下的所有 content-*.html 文件,用正则提取三段关键元数据:



$dateMatch  = [regex]::Match($raw, '')

$titleMatch = [regex]::Match($raw, '')

$tagsMatch  = [regex]::Match($raw, '')

$isDraft    = $raw -match ''

日期格式兼容两种写法:2026-04-28(精确到日)和 2026-04-28 23:30(精确到分钟)[1]。标签用逗号分隔,例如 教程, 网站建设, 脚本 会被解析为数组 @("教程", "网站建设", "脚本")

草稿(draft: true)会被跳过,不出现在任何产出中。这意味着草稿文章对网站访问者完全不可见。

所有文章信息存入一个 PSCustomObject 数组,按日期降序排列。

2. 产出 A:最新文章组件

取前 5 篇文章,生成 src/components/latest-posts.html



$recentPosts = $posts | Select-Object -First 5

这里有一个精巧的标题截断算法,按像素宽度估算而非字符数:



foreach ($c in $title.ToCharArray()) {

    $code = [int]$c

    if ($code -gt 127) { $estWidth += 11 }    # CJK 字符 ~11px

    elseif ($c -eq ' ') { $estWidth += 4 }    # 空格 ~4px

    else { $estWidth += 6 }                    # ASCII ~6px

}

if ($estWidth -gt 110) {

    $title = $title.Substring(0, 10) + "..."  # 截断 10 字符

}

为什么用像素估算而非简单字符截断?因为侧边栏宽度只有 140px,一个中文字大约 11px 宽,英文字母大约 6px,同样的字符数可能宽度差一倍。如果不做像素估算,"AAAAAAAAAAAA" 会溢出,"中中中中中中中" 则还有余量。这种"手工宽度计算"正是 90s web 开发的典型手法[2]

输出文件使用 UTF-8 without BOM 编码——因为它会被注入到已有 BOM 的页面中。如果带了 BOM,注入位置会出现乱码字符。

3. 产出 B:文章归档页

归档页的核心逻辑是按年-月分组



$grouped = $posts | Group-Object { $_.Date.Substring(0, 7) }

                  | Sort-Object Name -Descending

日期格式统一为 YYYY-MM-DD,取前 7 个字符就是 YYYY-MM。分组按键降序排列,确保最新的月份排在最上面。

每个月份的渲染分为:

  • 月份标题:用 size="4" color="#ff99ff" 显示"2026年4月"
  • 文章表格:两列布局——日期列(140px 宽,黄色) + 标题列(青色链接)
  • 归档页输出到 dist/archive.html(中文)或 dist/en/archive.html(英文),使用与其他页面一致的表格式布局和侧边栏。

    4. 产出 C:搜索索引

    搜索索引是一个纯文本文件 data/search_index.txt,被 cgi-bin/search.py 读取用于全文搜索[3]

    
    
    TITLE: 脚本详解:build.ps1
    
    DATE: 2026-04-28 23:30
    
    LINK: blog-build-script.html
    
    TEXT: build.ps1 是整个静态站点生成系统的核心引擎...
    
    ---
    
    

    文本提取的流程是:

  • 读取 HTML 源文件
  • 用正则 <[^>]+> 剥离所有 HTML 标签
  • 替换 &nbsp; 为空格
  • 压缩连续空白为单个空格
  • 每篇文章之间用 --- 分隔,search.py 解析时按此分割为独立的文档记录。

    文本提取不会解析元数据注释——因为注释中的 datetitle 已经被结构化提取到了 TITLE 和 DATE 字段中。

    5. 产出 D:标签云页面

    标签云是四个产出中逻辑最复杂的一个,需要构建标签→文章的反向索引

    
    
    $tagMap = @{}                           # 哈希表:标签名 → 文章列表
    
    foreach ($post in $posts) {
    
        foreach ($tag in $post.Tags) {
    
            if (-not $tagMap.ContainsKey($tag)) {
    
                $tagMap[$tag] = @()
    
            }
    
            $tagMap[$tag] += $post
    
        }
    
    }
    
    

    标签云的字号分级是一个简单的启发式规则:

    
    
    $count = $tagMap[$tag].Count
    
    $size = if ($count -ge 3) { 4 }      # 3+ 篇文章:大号
    
            elseif ($count -ge 2) { 3 }  # 2 篇文章:中号
    
            else { 2 }                   # 1 篇文章:小号
    
    

    标签云下方,每个标签对应一个文章列表区域,用 <a name="tag名"> 做锚点定位,点击标签云中的标签即可跳转到对应的文章列表。

    输出到 dist/tags.html(中文)或 dist/en/tags.html(英文)。

    6. 双语支持

    脚本通过 -Lang 参数(默认 "zh")控制语言:

    
    
    $isEn = ($Lang -eq "en")
    
    $blogDir = if ($isEn) {
    
        Join-Path $projectRoot "src\content\pages\blog\en"
    
    } else {
    
        Join-Path $projectRoot "src\content\pages\blog"
    
    }
    
    

    英文模式下:

  • 博客内容从 blog/en/ 目录读取
  • 组件输出到 latest-posts-en.html
  • 页面输出到 dist/en/ 子目录
  • 界面文字("最新文章"→"Latest Posts","文章归档"→"Article Archive")自动切换
  • 搜索索引导出到 data/search_index_en.txt
  • 中文和英文的归档/标签是完全独立的——这保证了英文访问者看到的是英文的界面和文章列表,不会中英混杂。

    7. 在构建流程中的位置

    generate-archive.ps1 必须在 build.ps1 之前运行,原因是:

  • build.ps1 读取 latest-posts.html 注入侧边栏——如果还没生成,注入的就是旧数据
  • 归档页和标签页是独立页面,也需要在访问者看到之前重新构建
  • 搜索索引需要在 search.py 处理查询前更新,否则新文章搜不到
  • rebuild-all.ps1 负责按正确顺序调用这些脚本,确保数据依赖关系不被打破[4]

    小结

    generate-archive.ps1 体现了一种"数据预处理"的设计模式:将博客元数据一次性扫描,生成多种下游格式,避免每个消费者重复解析。

    如果把这个脚本拆成四个独立的脚本,代码会更模块化,但每次全站构建就需要扫描 4 次博客目录。一次扫描、四处输出的设计减少了 I/O,换来的是构建速度的提升——在这个有数十篇文章的站点上,差异可能只有几百毫秒,但它展示了"提前想好数据流"的工程思维[5]


    [1]分钟精度的日期格式主要用于"最近在干什么"这类按月更新的日志文章,精确到分钟可以更好地控制文章排序。

    [2]在 CSS text-overflow: ellipsis 出现之前,后端截断是唯一的方案。IE5.5 不支持 text-overflow,所以这个手工算法恰好是最兼容的做法。

    [3]搜索是完全免 JavaScript 的——搜索框是一个 HTML 表单,提交到 /cgi-bin/search.py,服务端读取 search_index.txt 做全文匹配,返回带高亮的结果页面。整个过程与 2002 年的搜索引擎工作原理一致。

    [4]实际上 rebuild-all.ps1 不仅管理了脚本调用顺序,还会在调用 generate-archive.ps1 之后调用 build.ps1 重建所有页面。详见关于 rebuild-all.ps1 的博文。

    [5]PowerShell 的 Group-ObjectSort-ObjectSelect-Object 等管道命令在这里发挥了类似 SQL 中 GROUP BYORDER BYLIMIT 的作用——只不过操作的是内存中的对象数组而非数据库表。


    « Guestbook Architecture · From Form to Page Home
    中文

    Search

    Latest Posts

    » Guestbook ...
    » April 2026...
    » Panlongge · Server
    » FnOS Vulne...
    » Wiki.js Setup Guide

    » Article Archive


    Tags

    教程 网站建设 脚本 更新日志


    Tools

    » RSS Feed
    » GitHub Source
    » Back to Top
    » Archive


    DRAGONRSTER
    CC BY-NC-SA
    © 2004-2026 DragonRster • Made with HTML • 本站支持IE5.5+
    最佳浏览分辨率:1024x768 • 最后更新于 2026年04月29日 03:30:45