How can I inject `<script>` and `<link>` elements into the `<head>` of the generated HTML files?

How can I inject <script> and <link> elements into the <head> of the generated HTML files?

target at html

When the output target is set to “html”, how should I configure it to inject <script> and <link> elements into the <head> of the generated HTML files?

// the source file
#html.elem("head")[
  #html.elem("script", attrs: (src: "script.js"))[]
  #html.elem("link", attrs: (rel: "stylesheet", href: "style.css"))[]
]

= A level 1 heading

some texts

The output file:

<!--  The output file -->

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>

    <head>
        <script src="script.js"></script>
        <link rel="stylesheet" href="style.css">
    </head>
<!-- this is not what I want. I need the script and link element in the top head element. -->

    <h2>A level 1 heading</h2>
    <p>some texts</p>
</body>

</html>

target at bundle

Also, when I set the output target to “bundle”, how can I configure it to add <script> and <link> elements to the <head> of every generated HTML file? Should I use the asset function?

#document("index.html", title: [Home])[
  #title()
  - #link(<blog>)[Go to blog]
]

#document("blog.html", title: [Blog])[
  #title()
  Welcome to my blog!

  ...

  This blog also exists as a
  #link(<blog-pdf>)[single PDF].
] <blog>

#document("blog.pdf", title: [Blog])[
  ...
] <blog-pdf>


// #asset("styles.css", read("styles.css"))


// #asset("script.js", read("script.js"))

Try

// the source file
#show: contents => {
  html.head({
    html.elem("script", attrs: (src: "script.js"))[]
    html.elem("link", attrs: (rel: "stylesheet", href: "style.css"))[]
  })
  html.body(contents)
}

= A level 1 heading

some text

It wraps everything inside a <body> element after a <head> element.

Normally, Typst will generate html, head, and body tags for you. If you instead create them with this function, Typst will omit its own tags.
Elem - Typst Documentation

As a result, we have to create <html> from scratch if we want customization.

I usually define a wrapper for the document function (instead of using show rules), because occasionally I want to make an exception.

Code
#set text(lang: "en")
#let doc(path, title: none, body) = document(path, title: title, {
  show: rest => context html.html(lang: text.lang, rest)
  html.head({
    html.meta(charset: "utf-8")
    html.meta(name: "viewport", content: "width=device-width, initial-scale=1")
    html.script(type: "module", src: "script.js")
    html.link(rel: "stylesheet", href: "style.css")
  })
  html.body(body)
})

#doc("index.html", title: [Home])[
  #title()
  - #link(<blog>)[Go to blog]
]

#doc("blog.html", title: [Blog])[
  #title()
  Welcome to my blog!

  ...

  This blog also exists as a
  #link(<blog-pdf>)[single PDF].
] <blog>

#document("blog.pdf", title: [Blog])[
  ...
] <blog-pdf>
Output
typst compile main.typ target --features bundle,html --format bundle --pretty

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script type="module" src="script.js"></script>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <h1>Home</h1>
    <ul>
      <li><a href="blog.html">Go to blog</a></li>
    </ul>
  </body>
</html>
2 Likes