跳到主要内容

资源管道与图片处理

Hugo Pipes 是 Hugo 的资源处理系统,提供了 CSS、JavaScript 处理和图片操作等功能。掌握资源管道可以大幅提升网站性能和开发效率。

资源基础

什么是资源?

在 Hugo 中,资源是指存放在 assets/ 目录下的文件,包括:

  • CSS 样式文件
  • JavaScript 脚本
  • 图片文件
  • JSON、YAML 等数据文件
  • 其他需要处理的静态文件

资源目录

assets/
├── css/
│ ├── main.css
│ └── components/
├── js/
│ └── main.js
├── images/
│ └── logo.png
└── data/
└── config.json

获取资源

在模板中获取资源:

<!-- 获取全局资源 -->
{{ $css := resources.Get "css/main.css" }}
{{ $image := resources.Get "images/logo.png" }}

<!-- 获取远程资源 -->
{{ $remote := resources.GetRemote "https://example.com/data.json" }}

<!-- 获取页面资源 -->
{{ $image := .Resources.Get "photo.jpg" }}

资源管道操作

链式处理

资源管道支持链式操作,按顺序处理资源:

{{ $css := resources.Get "css/main.css" | minify | fingerprint }}
<link rel="stylesheet" href="{{ $css.Permalink }}">

常用操作

操作说明
minify压缩资源
fingerprint生成内容哈希,用于缓存控制
resources.Concat合并多个资源
resources.Copy复制资源
resources.ExecuteAsTemplate执行模板

Minify 压缩

压缩 CSS、JavaScript、JSON、SVG 等资源:

{{ $css := resources.Get "css/main.css" | minify }}
{{ $js := resources.Get "js/main.js" | minify }}
{{ $svg := resources.Get "images/icon.svg" | minify }}

Fingerprint 指纹

生成基于内容的哈希值,用于缓存控制:

{{ $css := resources.Get "css/main.css" | minify | fingerprint }}
<link rel="stylesheet" href="{{ $css.Permalink }}">
<!-- 输出: /css/main.min.a1b2c3d4.css -->

文件名中包含哈希值,当内容变化时哈希值改变,浏览器会自动获取新版本。

Concat 合并

将多个资源合并为一个文件:

{{ $css1 := resources.Get "css/reset.css" }}
{{ $css2 := resources.Get "css/main.css" }}
{{ $css3 := resources.Get "css/components.css" }}

{{ $bundle := slice $css1 $css2 $css3 | resources.Concat "css/bundle.css" | minify }}
<link rel="stylesheet" href="{{ $bundle.Permalink }}">

PostCSS 处理

使用 PostCSS 处理 CSS,支持 Autoprefixer 等插件:

首先安装依赖:

npm install postcss postcss-cli autoprefixer

创建 postcss.config.js

module.exports = {
plugins: {
autoprefixer: {}
}
}

在模板中使用:

{{ $css := resources.Get "css/main.css" | postCSS | minify }}
<link rel="stylesheet" href="{{ $css.Permalink }}">

CSS 处理

Sass/SCSS 编译

Hugo Extended 内置支持 Sass/SCSS 编译:

assets/css/main.scss

// 变量定义
$primary-color: #3b82f6;
$text-color: #1f2937;
$border-radius: 8px;

// 混入
@mixin card-shadow {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}

// 嵌套
.post-card {
border: 1px solid #e5e7eb;
border-radius: $border-radius;

&:hover {
@include card-shadow;
}

&-title {
color: $text-color;
font-size: 1.25rem;
}
}

在模板中编译:

{{ $scss := resources.Get "css/main.scss" }}
{{ $css := $scss | toCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $css.Permalink }}">

SCSS 编译选项:

{{ $options := dict "targetPath" "css/style.css" "outputStyle" "compressed" }}
{{ $css := $scss | toCSS $options | minify }}

CSS 打包(css.Build)

css.Build 是 Hugo 0.150+ 引入的新功能,基于 esbuild 实现高性能的 CSS 打包、转换和压缩。相比传统的 PostCSS 方式,css.Build 无需额外依赖,处理速度更快。

基本用法

css.Build 可以递归处理 CSS 中的 @import 语句,将多个 CSS 文件合并为一个:

目录结构

assets/
└── css/
├── components/
│ ├── buttons.css
│ └── cards.css
└── main.css

main.css

/* 导入远程 CSS */
@import url('https://cdn.jsdelivr.net/npm/normalize.css');

/* 导入本地组件 */
@import './components/buttons.css';
@import './components/cards.css';

/* 自定义样式 */
.container {
max-width: 1200px;
margin: 0 auto;
}

模板中使用

{{ with resources.Get "css/main.css" | css.Build }}
{{ if hugo.IsDevelopment }}
<!-- 开发环境:不压缩,便于调试 -->
<link rel="stylesheet" href="{{ .RelPermalink }}">
{{ else }}
<!-- 生产环境:添加指纹 -->
{{ with . | minify | fingerprint }}
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
{{ end }}
{{ end }}
{{ end }}

配置选项

css.Build 支持丰富的配置选项:

{{ $opts := dict
"minify" true
"sourceMap" "linked"
"target" (slice "chrome115" "edge115" "firefox116" "safari16.4")
}}
{{ $css := resources.Get "css/main.css" | css.Build $opts }}

完整选项列表

选项类型说明
minifybool是否压缩输出
sourceMapstringSource map 类型:externalinlinelinkednone
sourcesContentbool是否在 source map 中包含源文件内容
target[]string目标浏览器版本,用于语法转换和前缀添加
targetPathstring输出文件路径
externals[]string排除打包的路径模式
loadersmap文件扩展名到加载器类型的映射
mainFields[]stringpackage.json 中用于查找入口点的字段优先级

目标浏览器配置

target 选项用于指定兼容的浏览器版本,Hugo 会自动添加必要的厂商前缀和语法转换:

{{ $target := slice 
"chrome115"
"edge115"
"firefox116"
"ios16.4"
"opera101"
"safari16.4"
}}
{{ $opts := dict "target" $target }}
{{ $css := resources.Get "css/main.css" | css.Build $opts }}

这相当于浏览器兼容性配置中的 "baseline widely available" 标准。

嵌入资源

通过 loaders 选项可以将小图片和 SVG 直接嵌入 CSS 中作为 data URL:

{{ $opts := dict
"loaders" (dict
".png" "dataurl"
".svg" "dataurl"
)
}}
{{ $css := resources.Get "css/main.css" | css.Build $opts }}

可用的加载器类型

加载器说明
css作为 CSS 文件处理
dataurl作为 Base64 编码的 data URL 嵌入
empty从打包中排除
file复制文件并重写 URL
text作为文本字符串加载

排除文件

使用 externals 选项排除特定文件,保持 @import 语句不变:

{{ $opts := dict
"externals" (slice "./vendor/*" "https://cdn.example.com/*")
}}
{{ $css := resources.Get "css/main.css" | css.Build $opts }}

引用 Node 包

css.Build 可以直接引用 Node 包中的 CSS:

/* 引用包的入口文件 */
@import "bootstrap";

/* 引用包内的特定文件 */
@import "bootstrap/dist/css/bootstrap-grid.css";

Hugo 会查找 package.json 中的 stylemain 字段来确定入口文件。可通过 mainFields 自定义:

{{ $opts := dict "mainFields" (slice "css" "style" "main") }}

完整示例

以下示例展示了生产环境下的最佳实践:

{{ with resources.Get "css/main.css" }}
{{ $opts := dict
"minify" (not hugo.IsDevelopment)
"sourceMap" (cond hugo.IsDevelopment "linked" "none")
"target" (slice "chrome115" "edge115" "firefox116" "safari16.4")
"loaders" (dict ".png" "dataurl" ".svg" "dataurl")
}}
{{ with . | css.Build $opts }}
{{ if hugo.IsDevelopment }}
<link rel="stylesheet" href="{{ .RelPermalink }}">
{{ else }}
{{ with . | fingerprint }}
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
{{ end }}
{{ end }}
{{ end }}
{{ end }}

css.Build vs PostCSS

特性css.BuildPostCSS
依赖内置,无需安装需要 npm 包
速度极快(Go 实现)较慢(Node.js)
打包原生支持 @import需要插件
厂商前缀自动添加需要 Autoprefixer
自定义处理有限插件生态丰富
适用场景常规 CSS 处理需要自定义转换

对于大多数项目,推荐使用 css.Build 获得更好的性能。如果需要特定的 PostCSS 插件功能,再考虑使用 PostCSS。

Tailwind CSS 集成

Hugo 可以与 Tailwind CSS 配合使用:

安装 Tailwind:

npm install tailwindcss
npx tailwindcss init

tailwind.config.js

module.exports = {
content: [
'./layouts/**/*.html',
'./content/**/*.md',
],
theme: {
extend: {},
},
plugins: [],
}

assets/css/main.css

@tailwind base;
@tailwind components;
@tailwind utilities;

在模板中处理:

{{ $css := resources.Get "css/main.css" | postCSS | minify }}
<link rel="stylesheet" href="{{ $css.Permalink }}">

JavaScript 处理

ESBuild 打包

Hugo 内置 ESBuild,支持 JavaScript 打包、转译和压缩:

assets/js/main.js

// ES6 模块导入
import { initMenu } from './menu.js';
import { initSearch } from './search.js';

// 初始化
document.addEventListener('DOMContentLoaded', () => {
initMenu();
initSearch();
});

在模板中使用:

{{ $js := resources.Get "js/main.js" | js.Build | minify | fingerprint }}
<script src="{{ $js.Permalink }}" defer></script>

JS Build 选项

{{ $options := dict
"targetPath" "js/bundle.js"
"minify" true
"sourceMap" "inline"
"target" "es2015"
"format" "iife"
}}
{{ $js := resources.Get "js/main.js" | js.Build $options }}

完整选项列表

选项说明默认值
targetPath输出路径-
minify是否压缩false
sourceMapSource map 类型:inlinelinkedexternal""
sourcesContent是否在 source map 中包含源文件内容false
target编译目标:es5es2015es2016es2017es2018es2019es2020es2021es2022esnextesnext
format输出格式:iifeesmcjsesm
platform运行平台:browsernodeneutralbrowser
externals外部依赖列表[]
defines定义字符串替换
drop删除特定构造:consoledebugger""
loaders文件类型加载器配置
inject自动注入的文件列表[]
shims模块替换配置[]
params传递给 JS 的参数
jsxJSX 转换方式:transformpreserveautomatictransform
jsxImportSourceJSX 导入源-

loaders 选项

loaders 选项(Hugo 0.140+)允许配置特定文件类型的加载方式:

{{ $options := dict
"loaders" (dict
".png" "dataurl"
".svg" "dataurl"
".txt" "text"
)
}}

可用的加载器类型:

加载器说明
base64Base64 编码
binary二进制数据
copy直接复制
cssCSS 文件
dataurlData URL 编码
default默认处理
empty空内容
file文件路径
jsJavaScript
jsonJSON 解析
text文本内容
tsTypeScript

platform 选项

platform 选项(Hugo 0.140+)设置目标运行环境:

{{/* 浏览器环境(默认) */}}
{{ $options := dict "platform" "browser" }}

{{/* Node.js 环境 */}}
{{ $options := dict "platform" "node" }}

{{/* 中立环境 */}}
{{ $options := dict "platform" "neutral" }}

drop 选项

drop 选项(Hugo 0.144+)在构建前删除特定代码:

{{/* 删除所有 console 语句 */}}
{{ $options := dict "drop" "console" }}

{{/* 删除所有 debugger 语句 */}}
{{ $options := dict "drop" "debugger" }}

defines 选项

defines 选项用于定义编译时替换:

{{ $options := dict
"defines" (dict
"process.env.NODE_ENV" "\"production\""
"DEBUG" "false"
)
}}

在 JavaScript 中:

if (process.env.NODE_ENV === 'production') {
// 这段代码在构建时会被保留
}

params 选项

params 选项允许将数据从模板传递到 JavaScript:

{{ $options := dict
"params" (dict
"apiEndpoint" "https://api.example.com"
"debug" false
"version" "1.0.0"
)
}}
{{ $js := resources.Get "js/main.js" | js.Build $options }}

在 JavaScript 中导入:

import * as params from '@params';

console.log(params.apiEndpoint); // https://api.example.com
console.log(params.debug); // false
console.log(params.version); // 1.0.0

注意:params 适用于小型配置数据。对于大型数据集,建议将文件放入 assets 目录并直接导入。

TypeScript 支持

assets/js/main.ts

interface Post {
title: string;
date: Date;
content: string;
}

function renderPost(post: Post): string {
return `<article><h1>${post.title}</h1></article>`;
}
{{ $js := resources.Get "js/main.ts" | js.Build | minify }}
<script src="{{ $js.Permalink }}"></script>

JSX 支持

Hugo 内置支持 JSX 和 TSX 转换。通过 jsxjsxImportSource 选项可以配置 JSX 的处理方式。

JSX 转换模式

jsx 选项支持三种模式:

模式说明
transform传统转换,需要手动导入 React(默认)
preserve保留 JSX 不转换
automatic自动导入 JSX 辅助函数(React 17+)

传统模式

{{ $options := dict "jsx" "transform" }}
{{ $js := resources.Get "js/app.jsx" | js.Build $options }}

需要手动导入 React:

import React from 'react';

function App() {
return <h1>Hello World</h1>;
}

自动模式(推荐)

{{ $options := dict 
"jsx" "automatic"
"jsxImportSource" "react"
}}
{{ $js := resources.Get "js/app.jsx" | js.Build $options }}

自动模式不需要手动导入 React:

// 无需导入 React

function App() {
return <h1>Hello World</h1>;
}

使用 Preact

Preact 是一个轻量级的 React 替代方案:

{{ $options := dict 
"jsx" "automatic"
"jsxImportSource" "preact"
}}
{{ $js := resources.Get "js/app.jsx" | js.Build $options }}

JSX 文件:

// 自动从 preact 导入 h 和 Fragment
function App() {
return (
<div class="app">
<h1>Hello Preact</h1>
</div>
);
}

export default App;

确保已安装 Preact:

npm install preact

TSX 支持

TypeScript + JSX 文件使用 .tsx 扩展名:

interface ButtonProps {
text: string;
onClick: () => void;
}

function Button({ text, onClick }: ButtonProps) {
return (
<button onClick={onClick}>
{text}
</button>
);
}

export default Button;

构建:

{{ $options := dict 
"jsx" "automatic"
"target" "es2015"
}}
{{ $js := resources.Get "js/components/Button.tsx" | js.Build $options }}

shims 选项

shims 选项允许在开发环境和生产环境中使用不同的依赖源。这在以下场景很有用:

  • 开发时使用完整的 npm 包
  • 生产时从 CDN 加载

基本用法

{{ $shims := slice
(dict "path" "react" "window" "React")
(dict "path" "react-dom" "window" "ReactDOM")
}}

{{ $options := dict
"shims" $shims
"externals" (slice "react" "react-dom")
}}

{{ $js := resources.Get "js/app.jsx" | js.Build $options }}

配置说明:

  • path:要替换的模块路径
  • window:全局变量名
  • externals:不打包的外部依赖

创建 Shim 文件

assets/js/shims/ 目录下创建 shim 文件:

assets/js/shims/react.js

// 生产环境:从全局变量获取 React
module.exports = window.React;

assets/js/shims/react-dom.js

// 生产环境:从全局变量获取 ReactDOM
module.exports = window.ReactDOM;

按环境切换

根据环境选择不同的配置:

{{ $isProduction := eq (os.Getenv "HUGO_ENV") "production" }}

{{ if $isProduction }}
{{/* 生产环境:使用 CDN */}}
{{ $options := dict
"shims" (slice
(dict "path" "react" "window" "React")
)
"externals" (slice "react")
}}
{{ else }}
{{/* 开发环境:使用 npm 包 */}}
{{ $options := dict }}
{{ end }}

{{ $js := resources.Get "js/app.jsx" | js.Build $options }}

在基础模板中加载 CDN:

<head>
{{ if eq (os.Getenv "HUGO_ENV") "production" }}
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
{{ end }}
</head>

inject 选项

inject 选项允许自动注入全局变量:

{{ $options := dict
"inject" (slice "js/shims/jquery.js")
}}
{{ $js := resources.Get "js/main.js" | js.Build $options }}

注入文件示例(assets/js/shims/jquery.js):

// 自动注入 jQuery
import $ from 'jquery';
window.$ = $;
window.jQuery = $;

之后在所有 JS 文件中都可以直接使用 $jQuery,无需手动导入。

代码分割

对于大型 JavaScript 应用,可以进行代码分割:

{{ $options := dict "format" "esm" }}
{{ $js := resources.Get "js/main.js" | js.Build $options }}

js.Batch 批量脚本处理

js.Batch 是 Hugo 0.140 引入的新功能,用于创建 JavaScript 捆绑包分组。这个功能特别适合需要在页面上动态创建多个 JavaScript 实例的场景,例如 React 组件、地图、图表等。

什么是 js.Batch?

传统的 js.Build 会将所有 JavaScript 打包成一个文件。但在某些场景下,你需要:

  • 在不同的模板中添加脚本,最后统一打包
  • 为同一个脚本创建多个实例,每个实例有不同的参数
  • 使用 Runner 模式,一个主脚本处理多个实例

js.Batch 通过 Group(分组)、Script(脚本)、Instance(实例)和 Runner(运行器)的概念来实现这些需求。

基本概念

Group(分组):将相关的脚本组织在一起

Script(脚本):定义一个 JavaScript 文件

Instance(实例):为脚本创建具有不同参数的实例

Runner(运行器):一个特殊的脚本,负责处理同一分组中的所有实例

工作流程

js.Batch 的工作流程如下:

  1. 在不同模板中使用 js.Batch 添加脚本、实例和运行器
  2. 在构建阶段,Hugo 将所有脚本打包
  3. Runner 脚本接收所有实例数据,进行统一处理

基本用法

创建批次和分组

{{ with js.Batch "js/mybundle" }}
{{ with .Group "widgets" }}
{{ with .Script "counter" }}
{{ .SetOptions (dict "resource" (resources.Get "js/counter.js")) }}
{{ end }}
{{ end }}
{{ end }}

创建实例并传递参数

{{ with js.Batch "js/mybundle" }}
{{ with .Group "widgets" }}
{{ with .Instance "counter" "instance-1" }}
{{ .SetOptions (dict "params" (dict "start" 10 "step" 2)) }}
{{ end }}
{{ end }}
{{ end }}

设置 Runner

{{ with js.Batch "js/mybundle" }}
{{ with .Group "widgets" }}
{{ with .Runner "main" }}
{{ .SetOptions (dict "resource" (resources.Get "js/runner.js")) }}
{{ end }}
{{ end }}
{{ end }}

配置构建选项

{{ with js.Batch "js/mybundle" }}
{{ with .Config }}
{{ .SetOptions (dict
"target" "es2023"
"format" "esm"
"minify" true
) }}
{{ end }}
{{ end }}

完整示例:React 组件批量渲染

这个示例展示如何使用 js.Batch 在页面上渲染多个 React 组件实例。

短代码:添加组件实例 (layouts/shortcodes/react-counter.html):

{{ $id := .Get "id" | default "counter" }}
{{ $start := .Get "start" | default 0 }}
{{ $step := .Get "step" | default 1 }}

<div id="counter-{{ $id }}"></div>

{{ with js.Batch "js/app" }}
{{ with .Group "components" }}
{{ with .Instance "Counter" $id }}
{{ .SetOptions (dict "params" (dict
"containerId" (printf "counter-%s" $id)
"start" $start
"step" $step
)) }}
{{ end }}
{{ end }}
{{ end }}

Runner 脚本 (assets/js/runner.js):

import * as ReactDOM from 'react-dom/client';
import * as React from 'react';

// 导入组件绑定
import Counter from './Counter';

export default function Run(group) {
for (const script of group.scripts) {
for (const instance of script.instances) {
const container = document.getElementById(instance.params.containerId);
if (!container) {
console.warn(`容器 ${instance.params.containerId} 未找到`);
continue;
}

const root = ReactDOM.createRoot(container);
const Component = script.binding;
root.render(React.createElement(Component, instance.params));
}
}
}

React 组件 (assets/js/Counter.jsx):

import { useState, useEffect } from 'react';

export default function Counter({ containerId, start = 0, step = 1 }) {
const [count, setCount] = useState(start);

return (
<div className="counter">
<h3>计数器 {containerId}</h3>
<p>当前值: {count}</p>
<button onClick={() => setCount(c => c + step)}>
+{step}
</button>
<button onClick={() => setCount(c => c - step)}>
-{step}
</button>
<button onClick={() => setCount(start)}>
重置
</button>
</div>
);
}

在基础模板中定义脚本和 Runner

{{ with js.Batch "js/app" }}
{{ with .Group "components" }}
{{ with .Script "Counter" }}
{{ .SetOptions (dict
"resource" (resources.Get "js/Counter.jsx")
"export" "default"
) }}
{{ end }}

{{ with .Runner "main" }}
{{ .SetOptions (dict
"resource" (resources.Get "js/runner.js")
"export" "default"
) }}
{{ end }}
{{ end }}
{{ end }}

渲染输出

{{ $group := "components" }}
{{ with (templates.Defer (dict "key" $group "data" $group)) }}
{{ with (js.Batch "js/app") }}
{{ with .Build }}
{{ with index .Groups $group }}
{{ range . }}
{{ if eq .MediaType.SubType "css" }}
<link href="{{ .RelPermalink }}" rel="stylesheet" />
{{ else }}
<script src="{{ .RelPermalink }}" type="module"></script>
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}

在 Markdown 中使用

{{< react-counter id="a" start=0 step=1 >}}

{{< react-counter id="b" start=100 step=10 >}}

{{< react-counter id="c" start=50 step=5 >}}

options 详解

Script 选项

选项说明
resource要构建的资源文件
export要绑定的导出,默认 * 表示整个命名空间
importContext解析导入的额外上下文
params传递给脚本的参数

Runner 选项

选项说明
resourceRunner 脚本文件
export导出的函数名,默认 default

Config 选项

选项说明
target语言目标,如 es2023
format输出格式,目前仅支持 esm
minify是否压缩
loaders文件类型加载器配置
params全局配置参数
jsxJSX 处理方式
jsxImportSourceJSX 库导入源

templates.Defer 的作用

由于 Hugo 的并发构建特性,不同模板可能同时向同一个批次添加内容。templates.Defer 作为同步点,确保所有内容添加完成后再进行构建:

{{ $group := "mygroup" }}
{{ with (templates.Defer (dict "key" $group "data" $group)) }}
{{ with (js.Batch "js/mybundle") }}
{{ with .Build }}
<!-- 处理构建结果 -->
{{ end }}
{{ end }}
{{ end }}

如果你的批次内容在一个模板中一次性定义完成,则不需要使用 templates.Defer

参数导入

在 JavaScript 文件中,可以通过 @params 导入参数:

// 导入实例参数
import * as params from '@params';

// 导入配置参数
import * as config from '@params/config';

console.log(params.start); // 实例参数
console.log(config.apiKey); // 配置参数

实际应用场景

交互式地图:每张地图有不同的坐标和缩放级别

图表组件:每个图表显示不同的数据集

表单验证:每个表单有不同的验证规则

计数器和计时器:每个实例有不同的初始值和步长

UI 组件库:批量渲染同一组件的多个实例

注意事项

  1. 格式限制:目前仅支持 esm 格式,不兼容非常老的浏览器
  2. 导入顺序:ESBuild 的代码分割可能有导入顺序问题,可通过定义导入顺序脚本来解决
  3. 性能考虑js.Batch 适合复杂场景,简单需求使用 js.Build 即可

图片处理

图片资源类型

Hugo 支持三种图片资源:

页面资源:存放在页面目录中

content/posts/my-post/
├── index.md
├── cover.jpg
└── gallery/
├── photo1.jpg
└── photo2.jpg

全局资源:存放在 assets/ 目录

assets/images/
├── logo.png
└── hero.jpg

远程资源:从网络获取

{{ $image := resources.GetRemote "https://example.com/image.jpg" }}

获取图片

<!-- 页面资源 -->
{{ $image := .Resources.Get "cover.jpg" }}

<!-- 全局资源 -->
{{ $image := resources.Get "images/logo.png" }}

<!-- 远程资源 -->
{{ $image := resources.GetRemote "https://example.com/image.jpg" }}

<!-- 匹配模式 -->
{{ $images := .Resources.Match "gallery/*.jpg" }}

图片输出

{{ with $image }}
<img
src="{{ .RelPermalink }}"
width="{{ .Width }}"
height="{{ .Height }}"
alt="描述">
{{ end }}

调整大小(Resize)

按指定宽度或高度调整图片大小,保持宽高比:

<!-- 按宽度调整 -->
{{ $resized := $image.Resize "800x" }}

<!-- 按高度调整 -->
{{ $resized := $image.Resize "x600" }}

<!-- 同时指定宽高(可能变形) -->
{{ $resized := $image.Resize "800x600" }}

适应尺寸(Fit)

缩放图片以适应指定尺寸,保持宽高比,图片不会超出边界:

{{ $fitted := $image.Fit "800x600" }}

如果原图是 1600x1200,结果会是 800x600。 如果原图是 1600x800,结果会是 800x400。

填充尺寸(Fill)

裁剪并调整大小,确保输出精确匹配指定尺寸:

{{ $filled := $image.Fill "800x600" }}

裁剪(Crop)

裁剪图片到指定尺寸,不进行缩放:

{{ $cropped := $image.Crop "400x300" }}

锚点位置

Fill 和 Crop 操作支持锚点参数:

{{ $image.Fill "400x300 TopLeft" }}    <!-- 左上角 -->
{{ $image.Fill "400x300 Center" }} <!-- 居中 -->
{{ $image.Fill "400x300 BottomRight" }} <!-- 右下角 -->
{{ $image.Fill "400x300 Smart" }} <!-- 智能选择 -->

可用的锚点值:

  • TopLeftTopTopRight
  • LeftCenterRight
  • BottomLeftBottomBottomRight
  • Smart(智能裁剪,自动检测重要区域)

格式转换

将图片转换为不同格式:

<!-- 转换为 WebP -->
{{ $webp := $image.Resize "800x webp" }}

<!-- 转换为 PNG -->
{{ $png := $image.Resize "800x png" }}

<!-- 转换为 JPEG -->
{{ $jpg := $image.Resize "800x jpg" }}

质量控制

控制 JPEG 和 WebP 图片的质量:

{{ $compressed := $image.Resize "800x q60" }}

质量值范围 1-100,默认 75。

旋转图片

<!-- 逆时针旋转 90 度 -->
{{ $rotated := $image.Resize "800x r90" }}

<!-- 旋转 180 度 -->
{{ $rotated := $image.Resize "800x r180" }}

<!-- 旋转 270 度 -->
{{ $rotated := $image.Resize "800x r270" }}

组合选项

多个选项可以组合使用:

{{ $image.Process "resize 800x webp q80" }}
{{ $image.Fill "400x300 Center webp q75" }}
{{ $image.Crop "200x200 Smart r90" }}

Process 方法

Process 是统一的图片处理方法:

{{ $result := $image.Process "resize 800x" }}
{{ $result := $image.Process "fill 400x300 Center" }}
{{ $result := $image.Process "crop 200x200 Smart" }}
{{ $result := $image.Process "webp q80" }} <!-- 仅转换格式 -->

图片滤镜

应用各种滤镜效果:

{{ $image := $image | images.Filter 
(images.GaussianBlur 6)
(images.Pixelate 8)
(images.Grayscale)
(images.Overlay $watermark 50 50)
}}

常用滤镜:

滤镜说明
images.Grayscale灰度化
images.GaussianBlur n高斯模糊(n 为模糊半径,通常 1-10)
images.Pixelate n像素化(n 为像素块大小)
images.Overlay img x y叠加水印图片(x, y 为位置坐标)
images.Text text添加文字水印
images.Brightness n调整亮度(-100 到 100)
images.Contrast n调整对比度(-100 到 100)
images.Saturation n调整饱和度(-100 到 100)
images.Sepia n复古效果(0 到 100)
images.Invert反转颜色
images.Padding top right bottom left color添加边距(Hugo 0.120+)

Padding 滤镜详解

Padding 滤镜是 Hugo 0.120 新增的功能,用于在图片周围添加边距(留白)。这在需要统一图片尺寸或创建相框效果时非常有用。

基本语法

{{ $padded := $image | images.Filter (images.Padding 20 20 20 20 "#ffffff") }}

参数说明:

  • 前四个参数:上、右、下、左的边距像素值
  • 第五个参数:边距区域的背景颜色(十六进制 RGB 或 RGBA 格式)

实用示例

为图片添加白色边框:

{{ $image := resources.Get "images/photo.jpg" }}
{{ $bordered := $image | images.Filter (images.Padding 10 10 10 10 "#ffffff") }}
<img src="{{ $bordered.RelPermalink }}" alt="">

创建相框效果:

{{ $image := resources.Get "images/portrait.jpg" }}
{{ $framed := $image | images.Filter
(images.Padding 20 20 40 20 "#f5f5dc")
}}
<img src="{{ $framed.RelPermalink }}" alt="相框效果">

底部添加更多空间用于文字说明:

{{ $image := resources.Get "images/product.jpg" }}
{{ $withSpace := $image | images.Filter (images.Padding 0 0 30 0 "#ffffff") }}
<div class="image-with-caption">
<img src="{{ $withSpace.RelPermalink }}" alt="产品图">
<span class="caption">产品描述文字</span>
</div>

统一不同尺寸图片的外观:

{{ $images := slice 
(resources.Get "images/img1.jpg")
(resources.Get "images/img2.jpg")
(resources.Get "images/img3.jpg")
}}

{{ range $images }}
<!-- 先缩放到统一尺寸,再添加边距 -->
{{ $resized := .Resize "300x300" }}
{{ $padded := $resized | images.Filter (images.Padding 5 5 5 5 "#e0e0e0") }}
<img src="{{ $padded.RelPermalink }}" alt="">
{{ end }}

使用透明背景(RGBA):

{{ $image := resources.Get "images/icon.png" }}
{{ $padded := $image | images.Filter (images.Padding 20 20 20 20 "rgba(0,0,0,0)") }}
<img src="{{ $padded.RelPermalink }}" alt="">

组合多个滤镜

Padding 滤镜可以与其他滤镜组合使用:

{{ $image := resources.Get "images/photo.jpg" }}

<!-- 先灰度化,再添加边框 -->
{{ $processed := $image | images.Filter
(images.Grayscale)
(images.Padding 15 15 15 15 "#2c3e50")
}}

<img src="{{ $processed.RelPermalink }}" alt="">

创建 Instagram 风格的方形图片

{{ $image := resources.Get "images/landscape.jpg" }}

<!-- 方法:先 Fit 到正方形内,再用 Padding 填充 -->
{{ $fitted := $image.Fit "600x600" }}
{{ $square := $fitted | images.Filter (images.Padding 50 50 50 50 "#000000") }}

<img src="{{ $square.RelPermalink }}" width="700" height="700" alt="">

提取主色调

{{ $colors := $image.Colors }}
{{ range first 5 $colors }}
<div style="background-color: {{ . }}">{{ . }}</div>
{{ end }}

EXIF 信息

获取图片的 EXIF 数据:

{{ with $image.Exif }}
<p>拍摄时间:{{ .Date.Format "2006-01-02" }}</p>
{{ with .Lat }}<p>纬度:{{ . }}</p>{{ end }}
{{ with .Long }}<p>经度:{{ . }}</p>{{ end }}
{{ with .Tags.Camera }}<p>相机:{{ . }}</p>{{ end }}
{{ with .Tags.FocalLength }}<p>焦距:{{ . }}</p>{{ end }}
{{ end }}

响应式图片

创建响应式图片集:

{{ $original := resources.Get "images/hero.jpg" }}
{{ $small := $original.Resize "400x webp" }}
{{ $medium := $original.Resize "800x webp" }}
{{ $large := $original.Resize "1200x webp" }}

<picture>
<source media="(max-width: 400px)" srcset="{{ $small.RelPermalink }}">
<source media="(max-width: 800px)" srcset="{{ $medium.RelPermalink }}">
<source media="(max-width: 1200px)" srcset="{{ $large.RelPermalink }}">
<img src="{{ $large.RelPermalink }}" alt="Hero image">
</picture>

或使用 srcset

<img 
src="{{ $large.RelPermalink }}"
srcset="
{{ $small.RelPermalink }} 400w,
{{ $medium.RelPermalink }} 800w,
{{ $large.RelPermalink }} 1200w"
sizes="(max-width: 800px) 100vw, 800px"
alt="Hero image">

图片类型判断

在处理图片之前,你可能需要判断图片是否可以被处理。Hugo 提供了三个判断函数:

函数说明
reflect.IsImageResource判断是否为图片资源
reflect.IsImageResourceProcessable判断图片是否可被处理(调整大小、裁剪等)
reflect.IsImageResourceWithMeta判断图片是否可提取元数据(EXIF、IPTC、XMP)

不同图片格式的支持情况:

格式IsImageResourceIsImageResourceProcessableIsImageResourceWithMeta
AVIFtruefalsetrue
BMPtruetruetrue
GIFtruetruetrue
HEICtruefalsetrue
HEIFtruefalsetrue
ICOtruefalsefalse
JPEGtruetruetrue
PNGtruetruetrue
SVGtruefalsefalse
TIFFtruetruetrue
WebPtruetruetrue

实际应用示例:

{{ range resources.Match "**" }}
{{ if reflect.IsImageResource . }}
{{ if reflect.IsImageResourceProcessable . }}
{{ with .Process "resize 300x webp" }}
<img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="">
{{ end }}
{{ else if reflect.IsImageResourceWithMeta . }}
<img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="">
{{ else }}
<img src="{{ .RelPermalink }}" alt="">
{{ end }}
{{ end }}
{{ end }}

这个例子展示了如何根据图片格式选择合适的处理方式:可处理的图片会被转换为 WebP 格式,仅支持元数据的图片直接显示原始尺寸,其他图片则不指定尺寸。

图片元数据

EXIF 信息

使用 Exif 方法获取图片的 EXIF 数据:

{{ with $image.Exif }}
<p>拍摄时间:{{ .Date.Format "2006-01-02" }}</p>
{{ with .Lat }}<p>纬度:{{ . }}</p>{{ end }}
{{ with .Long }}<p>经度:{{ . }}</p>{{ end }}
{{ with .Tags.Camera }}<p>相机:{{ . }}</p>{{ end }}
{{ with .Tags.FocalLength }}<p>焦距:{{ . }}</p>{{ end }}
{{ end }}

Meta 方法

Meta 方法(Hugo 0.155+)提供了更全面的元数据访问能力,支持从多个来源提取数据:

{{ with $image.Meta }}
<p>宽度:{{ .Width }}</p>
<p>高度:{{ .Height }}</p>

{{/* 访问 EXIF 数据 */}}
{{ with .Exif }}
<p>相机:{{ .Camera }}</p>
<p>曝光时间:{{ .ExposureTime }}</p>
{{ end }}

{{/* 访问 IPTC 数据 */}}
{{ with .IPTC }}
<p>标题:{{ .Headline }}</p>
<p>描述:{{ .Description }}</p>
<p>关键词:{{ delimit .Keywords ", " }}</p>
{{ end }}

{{/* 访问 XMP 数据 */}}
{{ with .XMP }}
<p>创建工具:{{ .CreatorTool }}</p>
{{ end }}
{{ end }}

元数据来源

  • EXIF:相机拍摄信息(快门、光圈、ISO、GPS 等)
  • IPTC:新闻行业标准(标题、描述、关键词、版权等)
  • XMP:Adobe 制定的扩展元数据平台

注意:图片经过处理后,元数据会被清除。如果需要保留元数据,请在处理前使用原始图片调用 MetaExif 方法。

图片处理配置

hugo.toml 中配置默认选项:

[imaging]
# 默认质量(JPEG/WebP)
quality = 75

# 压缩策略(Hugo 0.153+)
# 可选值: lossy(有损), lossless(无损,仅 WebP 支持)
compression = "lossy"

# 重采样滤镜
# 可选值: box, lanczos, catmullrom, mitchellnetravali, linear, nearestneighbor
resampleFilter = "lanczos"

# 智能裁剪锚点
# 可选值: Smart, TopLeft, Top, TopRight, Left, Center, Right, BottomLeft, Bottom, BottomRight
anchor = "smart"

# 背景色(用于透明图转 JPEG)
bgColor = "#ffffff"

WebP 高级配置

[imaging.webp]
# 编码预设
# 可选值: drawing, icon, photo, picture, text
hint = "photo"

# 压缩努力程度(0-6),数值越大压缩越充分但耗时越长
method = 2

# 是否使用锐利的 YUV 转换,提升清晰度但增加处理时间
useSharpYuv = false

WebP hint 选项说明

说明适用场景
drawing手绘或线条画高对比度线条图、示意图
icon小型彩色图片图标、缩略图、按钮
photo户外照片自然光照的照片(默认)
picture室内照片人像、室内拍摄、艺术照
text主要为文字截图、文档扫描、代码截图

元数据配置

[imaging.meta]
# 元数据来源
# 可选值: exif, iptc, xmp
sources = ["exif", "iptc"]

# 包含的字段(glob 模式)
# 设置 ['**'] 包含所有字段
fields = ["! *{GPS,Exif,Exposure[MPB]}*"]

[imaging.exif]
# 是否禁用日期提取
disableDate = false

# 是否禁用经纬度提取
disableLatLong = false

# 包含的 EXIF 字段(正则表达式)
includeFields = ""

# 排除的 EXIF 字段(正则表达式)
excludeFields = ""

重采样滤镜详解

不同的重采样滤镜适用于不同场景:

滤镜特点适用场景
box简单快速的均值滤镜缩小图片,速度优先
lanczos高质量,结果锐利照片,质量优先
catmullrom锐利,比 lanczos 快照片,平衡质量和速度
mitchellnetravali平滑,振铃效应少需要平滑效果的图片
linear双线性,平滑输出速度优先,质量要求不高
nearestneighbor最快,无抗锯齿像素艺术、图标

选择建议

  • 照片类图片:lanczoscatmullrom
  • 图标、Logo:nearestneighbor
  • 需要快速处理大量图片:boxlinear

WebP Hint 选项

WebP 编码提示对应不同的预设编码参数:

说明适用场景
drawing手绘或线条画高对比度线条图
icon小型彩色图片图标、缩略图
photo户外照片自然光照的照片(默认)
picture室内照片人像、室内拍摄
text主要为文字截图、文档扫描

EXIF 配置

控制图片 EXIF 数据的提取:

[imaging.exif]
# 是否禁用日期提取
disableDate = false

# 是否禁用经纬度提取
disableLatLong = false

# 包含的 EXIF 字段(正则表达式)
includeFields = ""

# 排除的 EXIF 字段(正则表达式)
excludeFields = ""

性能优化:默认情况下,Hugo 排除以下标签以提升性能和减小缓存大小:

  • ColorSpace, Contrast, Exif
  • Exposure[M|P|B], Flash, GPS
  • JPEG, Metering, Resolution
  • Saturation, Sensing, Sharp, WhiteBalance

包含所有标签

[imaging.exif]
includeFields = ".*"

排除敏感信息(如 GPS 位置):

[imaging.exif]
disableLatLong = true
excludeFields = "GPS"

完整配置示例

[imaging]
quality = 80
resampleFilter = "lanczos"
anchor = "smart"
bgColor = "#ffffff"
hint = "photo"

[imaging.exif]
disableDate = false
disableLatLong = false
includeFields = ""
excludeFields = ""

性能优化建议

1. 使用 WebP 格式

WebP 相比 JPEG 和 PNG 体积更小:

{{ $webp := $image.Resize "800x webp q80" }}

2. 添加指纹

确保缓存正确失效:

{{ $css := resources.Get "css/main.css" | minify | fingerprint }}

3. 按需生成

只在需要时处理图片:

{{ with .Params.image }}
{{ $img := resources.Get . }}
{{ if $img }}
{{ $resized := $img.Resize "800x webp" }}
<img src="{{ $resized.RelPermalink }}">
{{ end }}
{{ end }}

4. 缓存处理结果

Hugo 自动缓存处理后的资源,生成的文件存储在 resources/ 目录。将此目录纳入版本控制可以加速 CI/CD 构建。

5. 懒加载

对于非关键图片使用懒加载:

<img src="{{ $image.RelPermalink }}" loading="lazy" alt="...">

图片处理最佳实践

图片格式选择

根据内容类型选择合适的格式:

内容类型推荐格式说明
照片WebP 或 JPEG有损压缩,文件小
图标、LogoPNG 或 SVG无损压缩,支持透明
截图、文字PNG 或 WebP保持清晰度
动画GIF 或 WebP支持动画

质量与体积平衡

不同场景下的质量建议:

<!-- 缩略图:较低质量,优先体积 -->
{{ $thumb := $image.Resize "200x webp q60" }}

<!-- 内容图片:平衡质量和体积 -->
{{ $content := $image.Resize "800x webp q75" }}

<!-- 精品图片:高质量,优先效果 -->
{{ $featured := $image.Resize "1200x webp q85" }}

响应式图片最佳实践

结合媒体查询和 art direction:

{{ $original := resources.Get "images/hero.jpg" }}

<!-- 移动端:裁剪为正方形 -->
{{ $mobile := $original.Fill "400x400 Center webp" }}

<!-- 平板:适度裁剪 -->
{{ $tablet := $original.Fill "800x600 Center webp" }}

<!-- 桌面:保持原比例 -->
{{ $desktop := $original.Fit "1200x800 webp" }}

<picture>
<source media="(max-width: 400px)" srcset="{{ $mobile.RelPermalink }}">
<source media="(max-width: 800px)" srcset="{{ $tablet.RelPermalink }}">
<img src="{{ $desktop.RelPermalink }}"
alt="Hero image"
width="{{ $desktop.Width }}"
height="{{ $desktop.Height }}"
loading="eager">
</picture>

图片画廊实现

使用页面资源和图片处理创建画廊:

{{ define "main" }}
<div class="gallery">
{{ range .Resources.ByType "image" }}
{{ $thumbnail := .Resize "300x300 webp" }}
{{ $full := .Resize "1200x webp" }}

<a href="{{ $full.RelPermalink }}" class="gallery-item">
<img src="{{ $thumbnail.RelPermalink }}"
width="{{ $thumbnail.Width }}"
height="{{ $thumbnail.Height }}"
loading="lazy"
alt="{{ .Name }}">
</a>
{{ end }}
</div>
{{ end }}

提取图片主色调

用于创建占位符或配色方案:

{{ $image := resources.Get "images/cover.jpg" }}
{{ $colors := $image.Colors }}

<div class="hero" style="--accent-color: {{ index $colors 0 }}">
<img src="{{ $image.RelPermalink }}" alt="">
</div>

配合 CSS 实现平滑加载效果:

.hero {
background-color: var(--accent-color);
}

.hero img {
opacity: 0;
transition: opacity 0.3s ease;
}

.hero img.loaded {
opacity: 1;
}

智能裁剪的妙用

Smart 裁剪会自动检测图片的重要区域:

<!-- 人像照片:Smart 会优先保留面部 -->
{{ $avatar := $image.Fill "200x200 Smart webp" }}

<!-- 产品图:Smart 会优先保留主体 -->
{{ $product := $image.Fill "400x400 Smart webp" }}

也可以手动指定锚点:

<!-- Logo 通常在中心 -->
{{ $logo := $image.Crop "100x100 Center" }}

<!-- 地平线照片通常重点在上方 -->
{{ $landscape := $image.Fill "800x400 Top" }}

图片预加载

对于关键图片(如 Hero 图),使用预加载:

{{ $hero := resources.Get "images/hero.jpg" | Resize "1920x webp" }}

<head>
<link rel="preload" as="image" href="{{ $hero.RelPermalink }}">
</head>

批量图片处理

处理目录中的所有图片:

{{ $images := resources.Match "images/gallery/*.{jpg,png}" }}

<div class="gallery-grid">
{{ range $images }}
{{ $small := .Resize "400x webp" }}
{{ $large := .Resize "1200x webp" }}

<a href="{{ $large.RelPermalink }}">
<img src="{{ $small.RelPermalink }}" loading="lazy" alt="">
</a>
{{ end }}
</div>

图片 CDN 优化

结合 CDN 和图片处理:

{{ $image := resources.Get "images/photo.jpg" }}
{{ $webp := $image.Resize "800x webp q80" }}

<!-- 使用 Hugo 的 Permalink 获取 CDN URL -->
<img
src="{{ $webp.Permalink }}"
srcset="{{ ($image.Resize "400x webp").Permalink }} 400w,
{{ ($image.Resize "800x webp").Permalink }} 800w,
{{ ($image.Resize "1200x webp").Permalink }} 1200w"
sizes="(max-width: 800px) 100vw, 800px"
alt="Photo">

小结

Hugo 的资源管道提供了强大的资源处理能力:

  • CSS 处理:支持 Sass/SCSS 编译、PostCSS、压缩
  • JavaScript 处理:支持 ESBuild 打包、TypeScript、压缩
  • 图片处理:支持调整大小、裁剪、格式转换、滤镜等
  • 缓存优化:指纹生成、内容哈希

合理使用这些功能可以显著提升网站性能和开发效率。