Hugo 不完美教程 - IV: Hugo Pipes 管道處理

前言

shot.jpg

本靜態(tài)站點用于演示之用,使用 Hugo 構建,以及 Markdown 供應內(nèi)容。

流行的靜態(tài)站點框架有以下幾個:

演示站點有兩個訪問入口:

此站點提供了一篇關于 Hugo 靜態(tài)站點生成框架的入門教程。

代碼倉庫地址如下,查看 hugo-project 分支是原文件,master 分支是發(fā)布的靜態(tài)站點文件:


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 }}
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容