前言

本靜態(tài)站點用于演示之用,使用 Hugo 構建,以及 Markdown 供應內(nèi)容。
流行的靜態(tài)站點框架有以下幾個:
- Jekyll (基于 Ruby 容易上手) https://www.jekyll.com.cn/docs/home/
- Hexo (基于 Node.js 容易上手) https://hexo.io/docs/
- Hugo (基于 Go) https://gohugo.io/documentation/
演示站點有兩個訪問入口:
此站點提供了一篇關于 Hugo 靜態(tài)站點生成框架的入門教程。
- Hugo 不完美教程 - IX: Menus 菜單組織
- Hugo 不完美教程 - VIII: Functions 內(nèi)置函數(shù)
- Hugo 不完美教程 - VII: Variables 對象變量
- Hugo 不完美教程 - VI: Multilingual 多語言支持
- Hugo 不完美教程 - V: Templates 模板機制
- Hugo 不完美教程 - V: Templates 其它模板
- Hugo 不完美教程 - IV: Hugo Pipes 管道處理
- Hugo 不完美教程 - III: Hugo Modules 模塊
- Hugo 不完美教程 - II: Hugo 目錄組織
- Hugo 不完美教程 - I: Hugo Web Framework
代碼倉庫地址如下,查看 hugo-project 分支是原文件,master 分支是發(fā)布的靜態(tài)站點文件:
- https://github.com/jimboyeah/jimboyeah.github.io/tree/hugo-project
-
https://gitee.com/jimbowhy/jimbowhy/tree/hugo-project/
shot.jpg
title: "IV: Hugo Pipes 管道處理"
description: "堅果的 Hugo 教程"
date: 2020-08-06T20:14:08-04:00
featured_image_: "/assets/IMG_20181101_233654_s.jpg"
thumb_image_: "/assets/micro_s.png"
summary: Hugo Pipes 是一組織處理資源目錄下的文件函數(shù)集,資源目錄可以通過 assetDir 配置項指定,默認是 assets,這些資源通過管道處理生成最終需要的文件,比如 SCSS 通過管道的工具處理生成 CSS,其中一個工具就是 PostCSS。
tags: ["hugo"]
目錄:
[TOC]
Hugo Pipes 管道處理
Hugo Pipes 是一組織處理資源目錄下的文件函數(shù)集,資源目錄可以通過 assetDir 配置項指定,默認是 assets。
涉及處理的內(nèi)容:
- 樣式腳本 SASS / SCSS
- 構建后期 PostProcess
- 樣式加工 PostCSS
- 腳本轉譯 JavaScript Building
- 腳本轉譯 Babel
- 資源壓縮 Asset minification
- 資源打包 Asset bundling
- 資源指紋 Fingerprinting and SRI
- 資源生成 Resource from Template
- 資源生成 Resource from String
管道處理還應用于模板中串聯(lián)函數(shù)的調(diào)用,如,生成 5 個數(shù)再將順序打亂:
{{ shuffle (seq 1 5) }}
使用管道的語法:
{{ (seq 1 5) | shuffle }}
資源對象的提供的屬性變量或方法參考 Page Resources 文檔:
{{<table>}}
| 屬性 | 說明 |
|---|---|
| ResourceType | 資源 MIME 類型,如 image/jpeg 對應 ResourceType image
|
| Name | 資源文件名,相對于當前頁面,可以在 front matter 設置 |
| Title | 默認和 .Name 一樣,也可以在 front matter 設置 |
| Permalink | 資源絕對 URL,對于 page 資源空值 |
| RelPermalink | 資源相對 URL,對于 page 資源空值 |
| Content | 資源內(nèi)容,通常是字符串內(nèi)容 |
| MediaType | MIME 類型,如 image/jpeg
|
| MediaType.MainType | 主要 MIME 類型,如 application/pdf 的 MainType 就是 application
|
| MediaType.SubType | 次要 MIME 類型,上面 pdf 的 SubType 是 pdf,而 PPT 文件是 vnd.mspowerpoint
|
| MediaType.Suffixes | 可能 MIME 列表,切片數(shù)據(jù)類型 |
{{<table>}}
{{</table>}}
如果,有 Go 語言基礎,可以試著讀 Hugo 源代碼,這也是開源的一大好片處,似乎不搞源代碼開源就沒有意義了:
// Resource represents a linkable resource, i.e. a content page, image etc.
type Resource interface {
ResourceTypeProvider
MediaTypeProvider
ResourceLinksProvider
ResourceMetaProvider
ResourceParamsProvider
ResourceDataProvider
}
使用資源對象方法:
<script>{{ (.Resources.GetMatch "myscript.js").Content | safeJS }}</script>
<img src="{{ (.Resources.GetMatch "mylogo.png").Content | base64Encode }}">
如果,文件不一定會存在,那么就需要加條件判斷:
{{ $style := resources.Get "theme/css/main.css" | resources.PostCSS }}
{{ if $css }}
{{ printf $style.Content }}
{{ end }}
處理 SCSS 樣式腳本資源
先將資源文件讀入使用:
{{ $style := resources.Get "sass/main.scss" }}
在 resources.Get 函數(shù)讀取 SCSS 樣式腳本后,就要使用擴展將其轉換為 CSS。
資源文件會被打包到 /public 目錄下,如果使用了 .Permalink 或 .RelPermalink,即意味使用了資源,Hugo 就會將將其打包。
{{ $style := resources.Get "sass/main.scss" | resources.ToCSS | resources.Minify | resources.Fingerprint }}
<link rel="stylesheet" href="{{ $style.Permalink }}">
{{ $style := resources.Get "sass/main.scss" | toCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $style.Permalink }}">
每個 Hugo Pipes 資源轉換方法都使用駝峰式 camelCased 別名,如 toCSS 表示 resources.ToCSS,非轉換方法就沒有這樣的別名,如 resources.Get, resources.FromString, resources.ExecuteAsTemplate, resources.Concat 等。
整個 Hugo Pipes 管道鏈是基于緩存的,即前一級生成的內(nèi)容緩存后進入下一級處理:
{{ $mainJs := resources.Get "js/main.js" | js.Build "main.js" | minify | fingerprint }}
在 Hugo 構建站點時,管道鏈在首次調(diào)用后建立,所有資源都會從緩存中獲取,不必擔心模板執(zhí)行反反復復地使用資源而引起構建性能問題。
通過 toCSS 進行轉換時,可以設置以下參數(shù):
{{<table>}}
| 選項 | 類型 | 說明 |
|---|---|---|
| targetPath | [string] | 指定輸出路徑,默認只修改原 SASS/SCSS 的擴展名為 .css; |
| outputStyle | [string] | 默認是 nested,其它輸出風格有 expanded, compact, compressed 等; |
| precision | [int] | 浮點值處理精度 |
| enableSourceMap | [bool] | 是不使用 source map 調(diào)試信息映射文件 |
| includePaths | [string slice] | 添加 SCSS/SASS 包含目錄,注意要使用工程目錄中的相對路徑 |
{{</table>}}
示范,給 SCSS/SASS 添加 includePaths 路徑參數(shù),注意使用了 dict 字典對象:
{{ $options := (dict "targetPath" "style.css" "outputStyle" "compressed" "enableSourceMap" true "includePaths" (slice "node_modules/myscss")) }}
{{ $style := resources.Get "sass/main.scss" | resources.ToCSS $options }}
設置 outputStyle 為 compressed 可以獲得比 resources.Minify 更好的 SASS/SCSS 壓縮效果。
SASS / SCSS 作為 CSS 的升級版,實現(xiàn)以樣式腳本的方式來定義樣式表,掌握它們可以極大提高效率。但是學習曲線陡峭,另一個選擇是使用 PostCSS 工具。
構建后期 PostProcess
允許在構建生成后延遲資源到 /public 的轉移,使用 resources.PostProcess 標記資源資源后,延遲轉移生成后的任何文件,通常轉換鏈中的一個或多個步驟取決于生成的結果。
一個基本的后期處理是使用 PostCSS 對樣式進行清理,當前有兩個限制:
- 只能處理 .html 模板文件;
- 不可以操縱由資源對象方法返回的值;
例如,示例中 upper 函數(shù)的調(diào)用就不能正常得到正確結果:
{{ $css := resources.Get "css/main.css" }}
{{ $css = $css | resources.PostCSS | minify | fingerprint | resources.PostProcess }}
{{ $css.RelPermalink | upper }}
配合 PostCSS 清理 CSS,有多種方法實現(xiàn),考慮簡單的實現(xiàn)方法,避免使用 resources.PostProcess 從模板中提取關鍵字,可以參考 tailwindcss 文檔示例。
下面的配置寫入項目根目錄下的 hugo_stats.json,如果只想在發(fā)布時有效,可以將其保存到 config/production 目錄。
[build]
writeStats = true
配置腳本:
const purgecss = require('@fullhuman/postcss-purgecss')({
content: [ './hugo_stats.json' ],
defaultExtractor: (content) => {
let els = JSON.parse(content).htmlElements;
return els.tags.concat(els.classes, els.ids);
}
});
module.exports = {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
...(process.env.HUGO_ENVIRONMENT === 'production' ? [ purgecss ] : [])
]
};
上面配置為發(fā)布時清理,那么在頁面模板中也要相應使用條件進行環(huán)境判斷:
{{ $css := resources.Get "css/main.css" }}
{{ $css = $css | resources.PostCSS }}
{{ if hugo.IsProduction }}
{{ $css = $css | minify | fingerprint | resources.PostProcess }}
{{ end }}
<link href="{{ $css.RelPermalink }}" rel="stylesheet" />
樣式加工 PostCSS
Hugo Pipes 可以使用 PostCSS 處理樣式文件,這是一個非常實用的 CSS 工具。
PostCSS 官方介紹插件功能特性:
- 增加代碼可讀性 → autoprefixer
- 使用先進的 CSS 樣式,Use tomorrow's CSS, today! → postcss-cssnext
- 全局樣式 Global CSS 終結者 → postcss-modules
- 保證樣式正確性 → stylelint
- 強大的 grid CSS → LostGrid
一些支持的功能:
- 片斷引入 partial imports
- 變量 variables
- 嵌套 nesting
- 混合宏 mixins
- 擴展 extend
- 占位符 placeholder classes
- 顏色函數(shù) darken and rgba color functions
- 壓縮 compression
語法支持參考各個插件,大數(shù)預處理器由 Syntaxes 語法擴展而來。事實上,Sass、Stylus 和 LESS 很多功能都可以通過 PostCSS 語言的擴展實現(xiàn),比如說添加 mixin,變量,條件,循環(huán),嵌套和擴展等。
例如,定義以下一個樣式:
:fullscreen { ... }
經(jīng)過 PostCSS autoprefixer 處理后,自動添加了瀏覽器前綴:
:-webkit-full-screen { ... }
:-ms-fullscreen { ... }
:fullscreen { ... }
Lost Grid 是一個強大的 PostCSS 網(wǎng)格系統(tǒng),可與任何預處理器甚至是原生 CSS 一起使用。
在這里有非常好的 demo 展示:http://lostgrid.org/lostgrid-example.html
以下例子根據(jù)不同的設備屏幕大小來調(diào)整網(wǎng)格的每行格子數(shù),小屏幕一行一格,中小屏幕一行三格,大屏幕一行六格:
.ColumnSection__grid div {
lost-column: 1/1;
}
@media (min-width: 400px) {
.ColumnSection__grid div {
lost-column: 1/3;
}
}
@media (min-width: 900px) {
.ColumnSection__grid div {
lost-column: 1/6;
}
}
postcss-nested 實現(xiàn)類似 Sass 功能:
.phone {
&_title {
width: 500px;
@media (max-width: 500px) { width: auto; }
body.is_dark & { color: white; }
}
img { display: block; }
}
.title {
font-size: var( --font );
@at-root html { --font: 16px }
}
@at-root 相應為上級節(jié)點定義一個新的樣式,var (--font) 這樣的表示引用變量,只有上級節(jié)點的值才能有效引用。
轉譯生成:
.phone_title { width: 500px; }
@media (max-width: 500px) {
.phone_title { width: auto; }
}
body.is_dark .phone_title { color: white; }
.phone img { display: block; }
.title { font-size: var(--font); }
html { --font: 16px }
postcss-nested & postcss-mixins 結合實現(xiàn) Sass 中最常用的特性:
@define-mixin clearfix{
&:after{
display: table;
clear: both;
content: " ";
}
}
.column-container{
color: #333;
@mixin clearfix;
}
編譯后:
.column-container{
color: #333;
}
.column-container:after{
display: table;
clear: both;
content: " ";
}
postcss-cssnext 語法:
:root {
--fontSize: 1rem;
--mainColor: #12345678;
--centered: {
display: flex;
align-items: center;
justify-content: center;
};
}
body {
color: var(--mainColor);
font-size: var(--fontSize);
line-height: calc(var(--fontSize) * 1.5);
padding: calc((var(--fontSize) / 2) + 1px);
}
.centered {
@apply --centered;
}
生成瀏覽器可用語法:
body {
color: rgba(18, 52, 86, 0.47059);
font-size: 16px;
font-size: 1rem;
line-height: 24px;
line-height: 1.5rem;
padding: calc(0.5rem + 1px);
}
.centered {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
可以在 baseof.html 模板中引入樣式資源:
{{ $css := resources.Get "css/main.css" }}
{{ $style := $css | resources.PostCSS }}
或者指定配置:
{{ $style := resources.Get "css/main.css" | resources.PostCSS (dict "config" "customPostCSS.js" "noMap" true) }}
{{<table>}}
| 屬性 | 類型 | 說明 |
|---|---|---|
| config | [string] | 指定 PostCSS 配置文件,默認 postcss.config.js |
| noMap | [bool] | 默認 true 不生成調(diào)試映射文件 |
| inlineImports | [bool] | 默認 false,啟用 @import "..." 或 @import url("...") 語句 |
| use | [string] | 使用的 PostCSS 插件列表 |
| parser | [string] | 指定 PostCSS parser |
| stringifier | [string] | 指定 PostCSS stringifier |
| syntax | [string] | 指定 postcss syntax |
{{</table>}}
安裝 postcss-cli 或相應插件模塊,以下為全局安裝,建議在工程中安裝,即去掉 -g 參數(shù):
npm install -g postcss-cli
npm install -g autoprefixer postcss-cssnext postcss-import postcss-apply postcss-nested postcss-mixin postcss-sass
使用 Hugo Snap package 則需要在項目中安裝 PostCSS 而不是全局安裝:
npm install postcss-cli
安裝后可以按以下命令格式試試樣式的編譯:
postcss --use autoprefixer -c options.json -o main.css css/*.css
postcss input.css -o output.css
postcss src/**/*.css --base src --dir build
cat input.css | postcss -u autoprefixer > output.css
可以在 postcss.config.js 進行配置,通過 Node 上下文指定環(huán)境。
module.exports = {
plugins: [
require('autoprefixer'),
require('postcss-cssnext'),
require('postcss-nested'),
...process.env.HUGO_ENVIRONMENT === 'production'
? [purgecss]
: []
]
}
參考 https://github.com/postcss/postcss#usage
JavaScript Building 腳本打包
Hugo Pipes 使用 ESBuild 來轉譯 JavaScript 腳本,Tree Shaking 算法可以有效清除死代碼,這是一個高效 JavaScript 轉譯器:
{{ $built := resources.Get "js/index.js" | js.Build "main.js" }}
可以通過 target [string] 指定 es5, es2015, es2016, es2017, es2018, es2019, es2020, esnext 行目標輸出規(guī)范,默認是 esnext。
或使用其它選項,使用 dict 關鍵字定義兩個字典來傳入?yún)?shù):
{{ $externals := slice "react" "react-dom" }}
{{ $defines := dict "process.env.NODE_ENV" `"development"` }}
{{ $opts := dict "targetPath" "main.js" "externals" $externals "defines" $defines }}
{{ $built := resources.Get "scripts/main.js" | js.Build $opts }}
<script type="text/javascript" src="{{ $built.RelPermalink }}" defer></script>
Babel 腳本轉譯
Hugo Pipes 也可以通過 Babel 來轉譯腳本,任意版本的 JavaScript 可以轉譯為另一個版本規(guī)范。
Babel 使用了 babel cli,需要先進行安裝,全局安裝或作為工程依賴安裝:
npm install -g @babel/cli @babel/core
npm install @babel/preset-env --save-dev
如果使用了 Hugo Snap 包則需要在工程中安裝,而不是全局安裝:
npm install @babel/cli @babel/core --save-dev
{{- $transpiled := resources.Get "scripts/main.js" | babel -}}
{{ $opts := dict "noComments" true }}
{{- $transpiled := resources.Get "scripts/main.js" | babel $opts -}}
默認地,Babel 會使用工程中 babel.config.js 作為配置文件。
Asset minification
Hugo Pipes 可以使用 resources.Minify 壓縮 CSS, JS, JSON, HTML, SVG, XML 等資源:
{{ $css := resources.Get "css/main.css" }}
{{ $style := $css | resources.Minify }}
如果需要壓縮最終輸出到 /public 目錄的 HTML 文件,可以使用 hugo --minify 命令。
Asset bundling
Hugo Pipes 可以將任意資源打包在一起,相同的 MIME 類型文件就只可以打包為一個文件以減少瀏覽器請求。
{{ $plugins := resources.Get "js/plugins.js" }}
{{ $global := resources.Get "js/global.js" }}
{{ $js := slice $plugins $global | resources.Concat "js/bundle.js" }}
為資源文件生成指紋 Fingerprinting
通過 resources.Fingerprint 方法生成 sha256 哈希摘要,可以指定其它,如 sha384, sha512, md5 等。
處理后的資源對象會在 .Data.Integrity 屬性保存摘要數(shù)據(jù),由生成摘要的函數(shù)名和摘要數(shù)據(jù)的 Base64 編碼用連字符拼接組成。
{{ $js := resources.Get "js/global.js" }}
{{ $secureJS := $js | resources.Fingerprint "sha512" }}
<script src="{{ $secureJS.Permalink }}" integrity="{{ $secureJS.Data.Integrity }}"></script>
SRI - Subresource Integrity 子資源完整性,用它可以確保站點在客戶端運行時,加載的是未經(jīng)篡改的原始資源。
大部分運營商被劫持,都是因為插入廣告代碼的需求。如果網(wǎng)站啟用了 SRI,篡改后的文件就無法執(zhí)行,這很可能讓頁面變得完全不可用。所以 SRI 給我的感覺是:寧為玉碎不為瓦全。
使用 CSP - Content Security Policy 外鏈白名單機制可以在現(xiàn)代瀏覽器下減小 XSS 風險。但針對 CDN 內(nèi)容被篡改而導致的 XSS,CSP 并不能防范,因為網(wǎng)站所使用的 CDN 域名,肯定在 CSP 白名單之中。而 SRI 通過對資源進行摘要簽名機制,保證外鏈資源的完整性。
例如,要引入以下這個資源,并啟用 SRI 策略:
https://example.com/static/js/other/zepto.js
可以使用 sha256 算法生成摘要簽名,并進行 Base64 編碼:
curl https://example.com/static/js/other/zepto.js | openssl dgst -sha256 -binary | openssl enc -base64 -A
b/TAR5GfYbbQ3gWQCA3fxESsvgU4AbP4rZ+qu1d9CuQ=
最終的代碼如下:
<script crossorigin="anonymous" integrity="sha256-b/TAR5GfYbbQ3gWQCA3fxESsvgU4AbP4rZ+qu1d9CuQ=" src="https://example.com/static/js/other/zepto.js"></script>
瀏覽器拿到資源內(nèi)容之后,會使用 integrity 所指定的簽名算法計算結果,并與 integrity 提供的摘要簽名比對,如果二者不一致,就不會執(zhí)行這個資源。
動態(tài)加載的資源使用 SRI 也是類似的,需要指定 crossOrigin 和 integrity 屬性。例如:
var s = document.createElement('script');
s.crossOrigin = 'anonymous';
s.integrity = 'sha256-b/TAR5GfYbbQ3gWQCA3fxESsvgU4AbP4rZ+qu1d9CuQ=';
s.src = 'https://example.com/static/js/other/zepto.js';
document.head.appendChild(s);
從字符串中創(chuàng)建資源
示范生成 JS 腳本:
{{ $string := (printf "var rootURL: '%s'; var apiURL: '%s';" (absURL "/") (.Param "API_URL")) }}
{{ $targetPath := "js/vars.js" }}
{{ $vars := $string | resources.FromString $targetPath }}
{{ $global := resources.Get "js/global.js" | resources.Minify }}
<script type="text/javascript" src="{{ $vars.Permalink }}"></script>
<script type="text/javascript" src="{{ $global.Permalink }}"></script>
從模板中獲取創(chuàng)建資源
使用 resources.ExecuteAsTemplate 只將資源作為模板執(zhí)行:
// assets/sass/template.scss
$backgroundColor: {{ .Param "backgroundColor" }};
$textColor: {{ .Param "textColor" }};
body{
background-color:$backgroundColor;
color: $textColor;
}
// [...]
// some md file
{{ $sassTemplate := resources.Get "sass/template.scss" }}
{{ $style := $sassTemplate | resources.ExecuteAsTemplate "main.scss" . | resources.ToCSS }}
