Skip to main content

Deno Tag

A cli command that allows you to write <deno> tags in your html files.

It looks for <deno> tags in a supplied input file and replaces the <deno> tags found with the output from running deno with their attributes.

The result is written to stdout and can be piped to the desired output.

I wrote a blog post about this project that serves as a less dry description of it.

Attributes

The <deno> tag needs to have one of these two attributes present:

  • <deno bundle="file.ts" />

    Calls deno bundle file.ts and replaces the <deno> tag with the produced output.

  • <deno run="file.ts" />

    Calls deno run file.ts and replaces the <deno> tag with the produced stdout content. Any other attribute on the tag gets passed as argument to the Deno.run command. Boolean attributes get the string “true” added as value.

How to run

Call deno_tag from the command line and specify a html file:

> deno run --allow-read --allow-run --unstable https://deno.land/x/deno_tag@v1.1.0/deno_tag.ts index.html

It is necessary to have read and run permissions in order to read the file passed as an attribute on the <deno> tag and run it.

Examples

Example 1 - Simple output bundle

In its most basic usage the <deno> tag can either give you a bundle from a file and its dependencies, or just the simple output that a running file produced.

Bundle can be useful to produce the full code for your project and place it inside the HTML <script></script> tag:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Awesome Web App</title>
  </head>
  <body>
    <h1>What a great app</h1>
    <!-- Main Script -->
    <script>
      <deno bundle="index.ts" />;
    </script>
  </body>
</html>

and your index.ts could then include a lot of imports.

// index.ts
import { complexFunction } from "./complex.ts";

document.addEventListener("DOMContentLoaded", complexFunction);

and then have a really complex function that might need a ton of imports or not:

// complex.ts
function complexFunction() {
  return 42;
}
export { complexFunction };

Running deno_tag on it:

> deno run --allow-read --allow-run --unstable https://deno.land/x/deno_tag@v1.1.0/deno_tag.ts ./examples/1\ -\ simple\ output\ bundle/index.html

Produces the following output:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Awesome Web App</title>
  </head>
  <body>
    <h1>What a great app</h1>
    <!-- Main Script -->
    <script>
      function complexFunction() {
        return 42;
      }
      document.addEventListener("DOMContentLoaded", complexFunction);
    </script>
  </body>
</html>

Notice the unrolled bundle inside the <script></script> tag.

This example is available in the examples folder.

Example 2 - Running a file

You can run Deno on any file and place the output of the execution where the <deno> tag is located at.

There are many use cases for this, a simple one I can think of use is to produce single file web components. This is something that is lacking from Web Components because they require us to write code in multiple files or just place the html and styles inside the Web Component constructor which can be hard to maintain.

I don’t intend to solve this in any way (there are whole frameworks from much smarter people out there). This is however a good idea for a very simple example.

With the <deno> tag a basic single file web component approach could be done with something like this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Magic Web App</title>
  </head>

  <body>
    <magic-title>What a great app</magic-title>
    <!-- Components -->
    <deno run="deno_web_component.ts" src="components/magic-title.html" />
    <!-- Add as many as needed -->
  </body>
</html>

Where the <magic-title> component could be defined in a single magic-title.html file. Here for example purposes:

<style>
  p {
    color: indigo;
  }
</style>
<section>
  <p>
    <slot></slot>
  </p>
</section>
<script>
  console.log(`Yay Web Components!`);
</script>

Finally all the work of defining the element would be done by the deno_web_component.ts script that the <deno> is running:

// As expected, it is possible to use dependencies to do whatever
// In this case, a simple dom parser will be used as an example
import { DOMParser } from "https://deno.land/x/deno_dom/deno-dom-wasm.ts";

// Arguments get passed as in the <deno> tag, which in this case is:
// src="components/magic_title.ts"
const file = Deno.args[0].slice(
  Deno.args[0].indexOf('"') + 1,
  Deno.args[0].lastIndexOf('"')
);
const text = await Deno.readTextFile(file);
// Parse the component file and take the script code in the "code" variable
const doc = new DOMParser().parseFromString(text, "text/html")!;
const scriptTags = doc.getElementsByTagName("script")!;
let code = "";
if (scriptTags.length > 0) {
  code = scriptTags[0].textContent;
}
// Remove the `<script>...</script>` content from the file text
const template = text.slice(0, text.indexOf("<script>"));
// Get a tag name from the file name - this will allow the code bellow to
// automatically define a custom element.
const tag = file.slice(file.indexOf("/") + 1, file.indexOf("."));
// Finally output the web component creation code
console.log(`
<script>
customElements.define('${tag}',
  class extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({mode: 'open'})
      this.shadowRoot.innerHTML = \`
${template}\`;
    }
    connectedCallback() {
    ${code}
    }
  }
);
</script>`);

This is just for example purposes, don’t use this in anything serious.

The code from the <deno> tag is run with the --allow-read and --allow-run permissions set.

After running:

> deno run --allow-read --allow-run --unstable https://deno.land/x/deno_tag@v1.1.0/deno_tag.ts index.html

The output is:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Magic Web App</title>
  </head>

  <body>
    <magic-title>What a great app</magic-title>
    <!-- Components -->

    <script>
      customElements.define(
        "magic-title",
        class extends HTMLElement {
          constructor() {
            super();
            this.attachShadow({ mode: "open" });
            this.shadowRoot.innerHTML = `
    <style>
        p {
            color: indigo;
        }
    </style>
    <section>
        <p>
            <slot></slot>
        </p>
    </section>
    `;
          }
          connectedCallback() {
            console.log(`Yay Web Components!`);
          }
        }
      );
    </script>

    <!-- Add as many as needed -->
  </body>
</html>

Notice that the <deno> tag was replaced by the output of the execution of deno_web_component.ts. Where the "magic-title" web component is now being defined.