Match each token in a multi-value link rel (#17)
A <link rel> attribute is a space-separated token list, but kage matched the whole string against its set of downloadable rels. So a tag like <link rel="preload stylesheet" href="/assets/style.css"> matched neither "preload" nor "stylesheet" and its stylesheet was left on the live web, absolute and undownloaded. VitePress ships exactly this form, and it is the only stylesheet on the page, so a cloned Vue docs site (and any other VitePress site) rendered completely unstyled offline. Tokenize the rel and match if any single token is a known asset rel, the way the HTML spec reads it. This also subsumes the old combined "shortcut icon" entry, since the "icon" token alone now matches.
This commit is contained in:
@@ -69,6 +69,7 @@ body { background: url(../img/bg.png); }
|
||||
|
||||
const htmlDoc = `<!doctype html><html><head>
|
||||
<link rel="stylesheet" href="/css/main.css">
|
||||
<link rel="preload stylesheet" href="/css/vp.css" as="style">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<link rel="canonical" href="https://ex.com/canon">
|
||||
</head><body>
|
||||
@@ -98,6 +99,7 @@ func TestRewriteHTML(t *testing.T) {
|
||||
|
||||
checks := map[string]bool{
|
||||
`href="_kage/ex.com/css/main.css"`: true, // stylesheet localised
|
||||
`href="_kage/ex.com/css/vp.css"`: true, // multi-value "preload stylesheet" rel localised
|
||||
`href="_kage/ex.com/favicon.ico"`: true, // icon localised
|
||||
`href="https://ex.com/canon"`: true, // canonical left alone
|
||||
`href="docs/intro/index.html"`: true, // internal page → local
|
||||
|
||||
+19
-5
@@ -9,13 +9,28 @@ import (
|
||||
"golang.org/x/net/html/atom"
|
||||
)
|
||||
|
||||
// stylesheetRels are <link rel> values whose href kage downloads as an asset.
|
||||
var stylesheetRels = map[string]bool{
|
||||
"stylesheet": true, "icon": true, "shortcut icon": true,
|
||||
// assetRels are the individual <link rel> tokens whose href kage downloads as
|
||||
// an asset. A rel attribute is a space-separated token list, so a single known
|
||||
// token in it (for example the "stylesheet" in "preload stylesheet") is enough.
|
||||
var assetRels = map[string]bool{
|
||||
"stylesheet": true, "icon": true,
|
||||
"apple-touch-icon": true, "apple-touch-icon-precomposed": true,
|
||||
"mask-icon": true, "manifest": true, "preload": true, "prefetch": true,
|
||||
}
|
||||
|
||||
// linkRelDownloadable reports whether a <link rel> names a resource kage should
|
||||
// download. It treats rel as the space-separated token list the HTML spec
|
||||
// defines, so "preload stylesheet", "shortcut icon", or a bare "stylesheet" all
|
||||
// match on a single recognised token.
|
||||
func linkRelDownloadable(rel string) bool {
|
||||
for _, tok := range strings.Fields(strings.ToLower(rel)) {
|
||||
if assetRels[tok] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RewriteHTML walks the parsed document and rewrites every resource and link
|
||||
// reference through sink, resolving relative URLs against base. It mutates the
|
||||
// tree in place; the caller renders it afterwards. References kage cannot handle
|
||||
@@ -40,8 +55,7 @@ func rewriteElement(n *html.Node, base *url.URL, sink RefSink) {
|
||||
case atom.Iframe, atom.Frame:
|
||||
rewriteAttr(n, "src", base, sink, pageOrAsset)
|
||||
case atom.Link:
|
||||
rel := strings.ToLower(strings.TrimSpace(attrVal(n, "rel")))
|
||||
if stylesheetRels[rel] {
|
||||
if linkRelDownloadable(attrVal(n, "rel")) {
|
||||
rewriteAttr(n, "href", base, sink, alwaysAsset)
|
||||
}
|
||||
case atom.Img:
|
||||
|
||||
Reference in New Issue
Block a user