如何在 Hakyll 中相对化 css 文件中的 URL?

How to relativize URLs in css files in Hakyll?

在我的 Hakyll 站点中,我有一个链接到页面的样式表:

<link rel="stylesheet" type="text/css" href="/css/my.css">

此 CSS 包含链接到字体文件的 @font-face 指令:

@font-face {
 font-family: "Bla";
 src: url("/data/bla.ttf") format("truetype");
}

问题是字体的 URL 没有被 relativizeUrls 相对化,即使我将它移动到页面本身的 <script> 标签中。如何解决这个问题?

Hakyll 的 relativizeURLs 使用 TagSoup 来解析和美化打印 HTML,因此它只能在 HTML 属性中找到的 URL 起作用。我不知道有任何现有功能可以将其扩展到 CSS 而不仅仅是 HTML 属性。

相关代码遍历 TagSoup 解析的每个标签,并将函数应用于它识别为 URLs:

的属性
-- | Apply a function to each URL on a webpage
withUrls :: (String -> String) -> String -> String
withUrls f = withTags tag
  where
    tag (TS.TagOpen s a) = TS.TagOpen s $ map attr a
    tag x                = x
    attr (k, v)          = (k, if isUrlAttribute k then f v else v)

(来自 Hakyll.Web.HTML

无法通过提供的 relativizeURLs 编译器更改此遍历逻辑,因此您可能必须自己编写。幸运的是,它非常简单:它获取站点根目录(使用 toSiteRoot),然后使用 withURLs 将函数应用于每个 URL,将绝对路径转换为相对路径。

relativizeUrls item = do
    route <- getRoute $ itemIdentifier item
    return $ case route of
        Nothing -> item
        Just r  -> fmap (relativizeUrlsWith $ toSiteRoot r) item

relativizeUrlsWith root = withUrls rel
  where
    isRel x = "/" `isPrefixOf` x && not ("//" `isPrefixOf` x)
    rel x   = if isRel x then root ++ x else x

(摘自 Hakyll.Web.RelativizeURLs)。

您需要将此类过程与某种轻量级 CSS 解析器相结合。它看起来像这样(伪代码):

relativizeCssUrls root = renderCSS . fmap relativize . parseCSS
  where relativize (URL url)
          | isRel url = URL (root <> url)
          | otherwise = URL url
        relativize other = other

我没有使用过任何 CSS parsing/printing 库,所以我不能在这里给你一个好的建议,但是 css-text 似乎是一个不错的起点。

我走了一条更简单的路。我使用此代码获取相对于当前项目的根路径:

rootPath :: Compiler String
rootPath = (toSiteRoot . fromJust) <$> (getUnderlying >>= getRoute)

然后创建了一个 Context 具有常量字段:

fontCtx = do
    root <- rootPath
    return $ constField "fontRoot" root

最后,我将 @font-face 子句从 CSS 文件移到 HTML 文件中,并在那里使用了我的字段:

    <style type="text/css">
        @font-face {
          ...
          src: url("$fontRoot$/data/bla.ttf") format("truetype");
        }
    </style>

事实证明,该上下文字段在其他地方非常有用,例如 Javascript 代码中的路径字符串,我也使用它。

tl;dr – 您可以使用 Beerend Lauwers 的 hakyll-extra 包(hackage 似乎还没有),它提供了一个 relativizeUrl 宏。或者,按如下方式实现您自己的:

如果您没有太多链接,并且不想仅仅为了执行此操作而引入 CSS 解析器,您可以创建一个函数字段 - 实际上是一个宏 - 它允许您打电话,例如relativize("/some/url") 来自页面内。 (我 运行 遇到了类似的问题,因为我想将样式表的链接相对化,仅供旧版本的 Internet Explorer 使用;对于 TagSoup,链接看起来好像在评论中,所以它没有处理它们。)

首先,我们需要编写一个 relativizeUrls 版本,它只对单个 URL:

进行操作
 import Data.List as L

 -- | Relativize URL. Same logic as "relativizeUrlsWith" in
 -- Hakyll.Web.Html.RelativizeUrls, but for just one url.
 relativizeUrl :: String  -- ^ Path to the site root
                    -> String  -- ^ link to relativize
                    -> String  -- ^ Resulting link
 relativizeUrl root = rel
   where
     isRel :: String -> Bool
     isRel x = "/" `L.isPrefixOf` x && not ("//" `L.isPrefixOf` x)
     rel x   = if isRel x then root ++ x else x

然后,我们定义一个"function field"可以添加到上下文中。

 import Data.Maybe (maybe)

 -- ugh. ugly name.
 relativizeFuncField :: Context a
 relativizeFuncField = functionField "relativize" relativize
   where 
     relativize :: [String] -> Item a -> Compiler String
     relativize args item = do
       siteRoot <- getRoot <$> (getRoute $ itemIdentifier item)
       arg <- case args of 
               [arg] -> return arg
               _     -> error "relativize: expected only 1 arg"
       return $ relativizeUrl siteRoot arg

     getRoot :: Maybe String -> String
     getRoot = maybe (error "relativize: couldn't get route") toSiteRoot 

然后,在任何你想使用这个宏的地方,而不是使用,比如说,defaultContext,使用relativizeFuncField <> defaultContext。例如:

 import Data.Monoid( (<>) )

 main = 
   -- ... 

   match (fromList ["about.rst", "contact.markdown"]) $ do
       route   $ setExtension "html"
       compile $ pandocCompiler
           >>= loadAndApplyTemplate "templates/default.html" (relativizeFuncField <> defaultContext)
           >>= relativizeUrls

所以,最后,这意味着在文件中,您可以在 TagSoup 尚未相对化链接的任何位置写入 $relativize("/path/to/file")$

希望有用:)
(这是使用 Hakyll 4.9.0.0,但我假设其他 4.X 版本大致相同。)

编辑:p.s.,非常感谢 Beerend Lauwers,他在他的 post here

中解释了 Hakyll 函数字段

再次编辑:哦。我没有看到 Beerend 实际上已经在他的 hakyll-extra 包中放置了一个 relativizeUrl 函数。