资源管道与图片处理
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 }}
完整选项列表:
| 选项 | 类型 | 说明 |
|---|---|---|
minify | bool | 是否压缩输出 |
sourceMap | string | Source map 类型:external、inline、linked、none |
sourcesContent | bool | 是否在 source map 中包含源文件内容 |
target | []string | 目标浏览器版本,用于语法转换和前缀添加 |
targetPath | string | 输出文件路径 |
externals | []string | 排除打包的路径模式 |
loaders | map | 文件扩展名到加载器类型的映射 |
mainFields | []string | package.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 中的 style 或 main 字段来确定入口文件。可通过 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.Build | PostCSS |
|---|---|---|
| 依赖 | 内置,无需安装 | 需要 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 |
sourceMap | Source map 类型:inline、linked、external | "" |
sourcesContent | 是否在 source map 中包含源文件内容 | false |
target | 编译目标:es5、es2015、es2016、es2017、es2018、es2019、es2020、es2021、es2022、esnext | esnext |
format | 输出格式:iife、esm、cjs | esm |
platform | 运行平台:browser、node、neutral | browser |
externals | 外部依赖列表 | [] |
defines | 定义字符串替换 | |
drop | 删除特定构造:console、debugger | "" |
loaders | 文件类型加载器配置 | |
inject | 自动注入的文件列表 | [] |
shims | 模块替换配置 | [] |
params | 传递给 JS 的参数 | |
jsx | JSX 转换方式:transform、preserve、automatic | transform |
jsxImportSource | JSX 导入源 | - |
loaders 选项
loaders 选项(Hugo 0.140+)允许配置特定文件类型的加载方式:
{{ $options := dict
"loaders" (dict
".png" "dataurl"
".svg" "dataurl"
".txt" "text"
)
}}
可用的加载器类型:
| 加载器 | 说明 |
|---|---|
base64 | Base64 编码 |
binary | 二进制数据 |
copy | 直接复制 |
css | CSS 文件 |
dataurl | Data URL 编码 |
default | 默认处理 |
empty | 空内容 |
file | 文件路径 |
js | JavaScript |
json | JSON 解析 |
text | 文本内容 |
ts | TypeScript |
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 转换。通过 jsx 和 jsxImportSource 选项可以配置 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 的工作流程如下:
- 在不同模板中使用
js.Batch添加脚本、实例和运行器 - 在构建阶段,Hugo 将所有脚本打包
- 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 选项:
| 选项 | 说明 |
|---|---|
resource | Runner 脚本文件 |
export | 导出的函数名,默认 default |
Config 选项:
| 选项 | 说明 |
|---|---|
target | 语言目标,如 es2023 |
format | 输出格式,目前仅支持 esm |
minify | 是否压缩 |
loaders | 文件类型加载器配置 |
params | 全局配置参数 |
jsx | JSX 处理方式 |
jsxImportSource | JSX 库导入源 |
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 组件库:批量渲染同一组件的多个实例
注意事项
- 格式限制:目前仅支持
esm格式,不兼容非常老的浏览器 - 导入顺序:ESBuild 的代码分割可能有导入顺序问题,可通过定义导入顺序脚本来解决
- 性能考虑:
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" }} <!-- 智能选择 -->
可用的锚点值:
TopLeft、Top、TopRightLeft、Center、RightBottomLeft、Bottom、BottomRightSmart(智能裁剪,自动检测重要区域)
格式转换
将图片转换为不同格式:
<!-- 转换为 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) |
不同图片格式的支持情况:
| 格式 | IsImageResource | IsImageResourceProcessable | IsImageResourceWithMeta |
|---|---|---|---|
| AVIF | true | false | true |
| BMP | true | true | true |
| GIF | true | true | true |
| HEIC | true | false | true |
| HEIF | true | false | true |
| ICO | true | false | false |
| JPEG | true | true | true |
| PNG | true | true | true |
| SVG | true | false | false |
| TIFF | true | true | true |
| WebP | true | true | true |
实际应用示例:
{{ 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 制定的扩展元数据平台
注意:图片经过处理后,元数据会被清除。如果需要保留元数据,请在处理前使用原始图片调用 Meta 或 Exif 方法。
图片处理配置
在 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 | 最快,无抗锯齿 | 像素艺术、图标 |
选择建议:
- 照片类图片:
lanczos或catmullrom - 图标、Logo:
nearestneighbor - 需要快速处理大量图片:
box或linear
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 | 有损压缩,文件小 |
| 图标、Logo | PNG 或 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、压缩
- 图片处理:支持调整大小、裁剪、格式转换、滤镜等
- 缓存优化:指纹生成、内容哈希
合理使用这些功能可以显著提升网站性能和开发效率。