Hugo Pipes 资源处理

Hugo Pipes 是 Hugo 的资产处理函数集,在 Hugo 的核心设计中扮演着关键角色,旨在提供速度和灵活性,使得 Hugo 能够在几秒钟内渲染完整的网站。它是 Hugo 快速资产管道的重要组成部分。

核心概念

资产处理功能集

Hugo Pipes 是 Hugo 内置的一套用于处理各种资产(如 CSS、JavaScript、图像等)的函数。这些功能通过 resources 命名空间下的方法提供,实现:

  • 资源转换:Sass 编译、JavaScript 构建、图像处理
  • 优化:压缩、指纹生成、缓存控制
  • 集成:Node.js 依赖、PostCSS 插件、外部工具

资源类型

Hugo Pipes 可以操作不同范围的资源:

全局资源 (Global Resources)

位于 assets 目录中,或通过模块挂载到 assets 目录的文件:

assets/
├── scss/
│   ├── main.scss
│   └── _variables.scss
├── js/
│   ├── main.js
│   └── components/
├── images/
│   └── hero.jpg
└── data/
    └── config.json

远程资源 (Remote Resources)

通过 HTTP 或 HTTPS 访问的远程服务器文件:

{{ $css := resources.GetRemote "https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" }}
{{ with $css }}
  <link rel="stylesheet" href="{{ .Permalink }}">
{{ end }}

页面范围资源 (Page-scoped Resources)

页面捆绑包内的资源,通过 Page.Resources 方法访问:

{{ range .Resources.ByType "image" }}
  <img src="{{ .Permalink }}" alt="{{ .Title }}">
{{ end }}

主要功能详解

CSS 处理

Sass/SCSS 编译

<!-- 基本 Sass 编译 -->
{{ $sass := resources.Get "scss/main.scss" }}
{{ $css := $sass | resources.ToCSS }}
<link rel="stylesheet" href="{{ $css.Permalink }}">

<!-- 带选项的 Sass 编译 -->
{{ $options := dict "targetPath" "css/style.css" "outputStyle" "compressed" }}
{{ $css := $sass | resources.ToCSS $options }}
<link rel="stylesheet" href="{{ $css.Permalink }}">

<!-- 开发和生产环境不同配置 -->
{{ $options := dict }}
{{ if hugo.IsProduction }}
  {{ $options = dict "outputStyle" "compressed" }}
{{ else }}
  {{ $options = dict "outputStyle" "expanded" "enableSourceMap" true }}
{{ end }}
{{ $css := $sass | resources.ToCSS $options }}

PostCSS 处理

<!-- 基本 PostCSS 处理 -->
{{ $css := resources.Get "css/main.css" }}
{{ $css = $css | resources.PostCSS }}
<link rel="stylesheet" href="{{ $css.Permalink }}">

<!-- 自定义 PostCSS 配置 -->
{{ $options := dict "config" "postcss.config.js" }}
{{ $css = $css | resources.PostCSS $options }}

PostCSS 配置文件示例:

// postcss.config.js
module.exports = {
  plugins: [
    require('autoprefixer'),
    require('cssnano')({
      preset: 'default',
    }),
  ],
}

Tailwind CSS 集成

{{ $css := resources.Get "css/main.css" }}
{{ $css = $css | resources.PostCSS (dict "config" "tailwind.config.js") }}
{{ if hugo.IsProduction }}
  {{ $css = $css | resources.Minify }}
{{ end }}
<link rel="stylesheet" href="{{ $css.Permalink }}">

JavaScript 处理

基本 JavaScript 构建

<!-- 简单 JavaScript 处理 -->
{{ $js := resources.Get "js/main.js" }}
{{ $js = $js | resources.Minify }}
<script src="{{ $js.Permalink }}"></script>

<!-- ESBuild 构建 -->
{{ $js := resources.Get "js/main.js" }}
{{ $built := $js | js.Build (dict "minify" true) }}
<script src="{{ $built.Permalink }}"></script>

高级 JavaScript 构建

{{ $options := dict 
  "minify" hugo.IsProduction
  "target" "es2018"
  "format" "esm"
  "sourcemap" (not hugo.IsProduction)
}}
{{ $js := resources.Get "js/app.js" | js.Build $options }}
<script type="module" src="{{ $js.Permalink }}"></script>

Babel 转译

{{ $js := resources.Get "js/modern.js" }}
{{ $options := dict "config" "babel.config.json" }}
{{ $js = $js | js.Babel $options }}
<script src="{{ $js.Permalink }}"></script>

文件操作

文件连接 (Concat)

<!-- 连接多个 CSS 文件 -->
{{ $css1 := resources.Get "css/normalize.css" }}
{{ $css2 := resources.Get "css/main.css" }}
{{ $css3 := resources.Get "css/custom.css" }}
{{ $combined := slice $css1 $css2 $css3 | resources.Concat "css/all.css" }}
<link rel="stylesheet" href="{{ $combined.Permalink }}">

<!-- 连接多个 JavaScript 文件 -->
{{ $libs := slice 
  (resources.Get "js/jquery.js")
  (resources.Get "js/bootstrap.js")
  (resources.Get "js/app.js")
}}
{{ $js := $libs | resources.Concat "js/bundle.js" | resources.Minify }}
<script src="{{ $js.Permalink }}"></script>

从字符串创建资源

<!-- 动态生成 CSS -->
{{ $colors := .Site.Params.colors }}
{{ $css := printf ":root { --primary: %s; --secondary: %s; }" $colors.primary $colors.secondary }}
{{ $resource := $css | resources.FromString "css/variables.css" }}
<link rel="stylesheet" href="{{ $resource.Permalink }}">

<!-- 生成 JavaScript 配置 -->
{{ $config := dict "apiUrl" .Site.Params.apiUrl "version" .Site.Params.version }}
{{ $js := printf "window.config = %s;" ($config | jsonify) }}
{{ $resource := $js | resources.FromString "js/config.js" }}
<script src="{{ $resource.Permalink }}"></script>

模板执行

<!-- 将资源作为模板执行 -->
{{ $template := resources.Get "js/config.js.tpl" }}
{{ $js := $template | resources.ExecuteAsTemplate "js/config.js" . }}
<script src="{{ $js.Permalink }}"></script>

模板文件示例:

// assets/js/config.js.tpl
window.siteConfig = {
  baseURL: '{{ .Site.BaseURL }}',
  title: '{{ .Site.Title }}',
  language: '{{ .Site.Language.Lang }}',
  params: {{ .Site.Params | jsonify }}
};

资源优化

压缩 (Minify)

<!-- CSS 压缩 -->
{{ $css := resources.Get "css/main.css" | resources.Minify }}

<!-- JavaScript 压缩 -->
{{ $js := resources.Get "js/main.js" | resources.Minify }}

<!-- HTML 压缩(在生产环境) -->
{{ if hugo.IsProduction }}
  {{ $html := resources.Get "templates/email.html" | resources.Minify }}
{{ end }}

指纹生成 (Fingerprint)

<!-- SHA256 指纹 -->
{{ $css := resources.Get "css/main.css" | resources.Minify }}
{{ $css = $css | resources.Fingerprint }}
<link rel="stylesheet" href="{{ $css.Permalink }}" integrity="{{ $css.Data.Integrity }}">

<!-- 自定义算法 -->
{{ $css = $css | resources.Fingerprint "md5" }}
<link rel="stylesheet" href="{{ $css.Permalink }}">

后期处理 (PostProcess)

<!-- 延迟处理直到构建完成 -->
{{ $css := resources.Get "css/main.css" }}
{{ $css = $css | resources.PostProcess }}
<link rel="stylesheet" href="{{ $css.Permalink }}">

图像处理

<!-- 基本图像处理 -->
{{ $image := resources.Get "images/hero.jpg" }}
{{ $resized := $image.Resize "800x600" }}
<img src="{{ $resized.Permalink }}" alt="Hero Image">

<!-- 高级图像处理 -->
{{ $options := "800x600 q85 Lanczos" }}
{{ $webp := $image.Resize $options | images.Filter (images.Format "webp") }}
<img src="{{ $webp.Permalink }}" alt="Hero Image">

<!-- 响应式图像 -->
{{ $sizes := slice "320" "640" "1024" "1200" }}
{{ range $sizes }}
  {{ $resized := $image.Resize (printf "%sx webp q85" .) }}
  <!-- 使用 $resized 生成 srcset -->
{{ end }}

缓存与性能优化

管道链缓存

Hugo Pipes 的调用基于整个"管道链"进行缓存

<!-- 这个管道链只会执行一次,后续直接使用缓存 -->
{{ $result := resources.Get "scss/main.scss" | 
  resources.ToCSS | 
  resources.Minify | 
  resources.Fingerprint }}

resources 目录缓存

处理后的资源缓存到 resources 目录:

# 推荐将 resources 目录纳入版本控制
git add resources/

# 清理未使用的缓存
hugo --gc

缓存配置

# hugo.toml
[caches]
  [caches.images]
    dir = ":resourceDir/_gen"
    maxAge = -1  # 永不过期
    
  [caches.assets]
    dir = ":resourceDir/_gen"
    maxAge = -1

实际应用示例

完整的前端构建管道

<!-- layouts/partials/head.html -->
{{ $sass := resources.Get "scss/main.scss" }}
{{ $css := $sass | resources.ToCSS }}

{{ if hugo.IsProduction }}
  {{ $css = $css | resources.PostCSS | resources.Minify | resources.Fingerprint }}
{{ end }}

<link rel="stylesheet" href="{{ $css.Permalink }}" 
  {{ if hugo.IsProduction }}integrity="{{ $css.Data.Integrity }}"{{ end }}>

{{ $js := resources.Get "js/main.js" }}
{{ if hugo.IsProduction }}
  {{ $js = $js | js.Build (dict "minify" true) | resources.Fingerprint }}
{{ else }}
  {{ $js = $js | js.Build (dict "sourcemap" "inline") }}
{{ end }}

<script src="{{ $js.Permalink }}" 
  {{ if hugo.IsProduction }}integrity="{{ $js.Data.Integrity }}"{{ end }}></script>

条件资源加载

<!-- 根据页面参数加载不同资源 -->
{{ if .Params.math }}
  {{ $mathCSS := resources.Get "css/katex.css" | resources.Minify }}
  <link rel="stylesheet" href="{{ $mathCSS.Permalink }}">
{{ end }}

{{ if .Params.chart }}
  {{ $chartJS := resources.Get "js/chart.js" | resources.Minify }}
  <script src="{{ $chartJS.Permalink }}"></script>
{{ end }}

主题资源覆盖

<!-- 允许用户覆盖主题资源 -->
{{ $css := "" }}
{{ if resources.Get "css/custom.css" }}
  {{ $css = resources.Get "css/custom.css" }}
{{ else }}
  {{ $css = resources.Get "css/default.css" }}
{{ end }}
{{ $css = $css | resources.ToCSS | resources.Minify }}
<link rel="stylesheet" href="{{ $css.Permalink }}">

与外部工具集成

Node.js 依赖集成

<!-- 使用 npm 包 -->
{{ $js := resources.Get "js/app.js" }}
{{ $built := $js | js.Build (dict 
  "external" (slice "react" "react-dom")
  "format" "esm"
  "minify" hugo.IsProduction
) }}
<script type="module" src="{{ $built.Permalink }}"></script>

自定义构建脚本

#!/bin/bash
# scripts/build-assets.sh

echo "构建前端资源..."

# 安装依赖
npm ci

# 运行自定义构建
npm run build

# Hugo 构建
hugo --minify

echo "构建完成!"

CI/CD 集成

# .github/workflows/build.yml
- name: Setup Node.js
  uses: actions/setup-node@v3
  with:
    node-version: '18'
    cache: 'npm'

- name: Install dependencies
  run: npm ci

- name: Build with Hugo
  run: hugo --minify

调试和故障排除

启用详细日志

# 显示资源处理详情
hugo server --debug --verboseLog

# 显示模板指标
hugo server --templateMetrics

检查资源状态

<!-- 在模板中调试资源 -->
{{ $resource := resources.Get "css/main.css" }}
{{ if $resource }}
  <!-- 资源存在 -->
  <p>Resource found: {{ $resource.RelPermalink }}</p>
  <p>Size: {{ $resource.Data.Size }} bytes</p>
{{ else }}
  <!-- 资源不存在 -->
  <p>Resource not found</p>
{{ end }}

常见问题解决

  1. 资源找不到:检查文件路径和 assets 目录配置
  2. 构建失败:验证外部依赖和工具版本
  3. 缓存问题:使用 hugo --gc 清理缓存
  4. 性能问题:检查管道链复杂度和缓存配置

最佳实践

1. 组织资源文件

assets/
├── scss/
│   ├── main.scss           # 主样式文件
│   ├── _variables.scss     # 变量
│   ├── _mixins.scss        # 混合
│   └── components/         # 组件样式
├── js/
│   ├── main.js            # 主脚本
│   ├── modules/           # 模块
│   └── vendor/            # 第三方库
├── images/
│   ├── src/               # 源图像
│   └── processed/         # 处理后图像
└── data/
    └── config.json        # 配置数据

2. 环境特定处理

{{ $options := dict }}
{{ if hugo.IsProduction }}
  {{ $options = dict "minify" true "sourcemap" false }}
{{ else }}
  {{ $options = dict "minify" false "sourcemap" "inline" }}
{{ end }}
{{ $js := resources.Get "js/main.js" | js.Build $options }}

3. 性能优化策略

  • 使用条件加载避免不必要的资源
  • 合理使用缓存机制
  • 在生产环境启用压缩和指纹
  • resources 目录纳入版本控制

4. 安全考虑

<!-- 使用 SRI (Subresource Integrity) -->
{{ $css := resources.Get "css/main.css" | resources.Minify | resources.Fingerprint }}
<link rel="stylesheet" href="{{ $css.Permalink }}" 
      integrity="{{ $css.Data.Integrity }}" 
      crossorigin="anonymous">

通过 Hugo Pipes 的强大功能,您可以构建高效、优化的前端资源管道,提升网站的性能和用户体验。

文章导航

章节内容

这是章节的内容页面。

章节概览