A JavaScript Executable

Deno
Deno

There’s an interesting command line browser called Lynx. It’s an extremely useful and versatile text based browser. In lynx you can dump all links on a web page as references. That’s neat.

shell
$ lynx -dump -listonly https://example.com/

References

   1. https://www.iana.org/domains/example

I’ve been meaning to replicate this feature, generate a list of references and stick them at the end of each post. This would allow counting the number of links on a page, and visually or automatically checking for dead links (link rot) in the future.

Lynx
A fully–featured World Wide Web (WWW) client for users running cursor–addressable character–cell display devices — Lynx Users Guide

Let’s make a quick and dirty command line program that replicates the reference list feature of lynx, but to HTML instead of text.

JavaScript with Deno Runtime

JavaScript is the master at traversing the DOM (Document Object Model). Pulling down a list of links is easy with native browser JavaScript APIs (Application Programming Interfaces). Deno is a versatile runtime for JavaScript and TypeScript that sits at just the right primitives and abstractions that allow for a variety of use cases. Using Deno, we’ll compile a simple command line program written in JavaScript that generates references. The output of my deno --version is 1.23.0.

shell
$ deno --version
deno 1.23.0 (release, x86_64-unknown-linux-gnu)
v8 10.4.132.5
typescript 4.7.2

File and Directory Setup

The program will be The result of smashing a few words together with a portmanteau script until it gave something that sounded good… exoference (exothermic references?). exoference. The source directory will contain the entry–point main.ts, the TypeScript compiler option manifest tsconfig.json, and eventually a compiled binary that assumes the name of the parent A bare deno project requires virtually no boilerplate (so good). Version 1.25 adds the command deno init for peak laziness.

shell
exoference/
  |__ exoference
  |__ main.ts
  |__ tsconfig.json

Running the script inside main.ts is usually achieved with deno run.

shell
deno run --allow-net --config tsconfig.json main.ts

Compiling a program into a runnable Cross compilation is available on popular platforms. or binary uses deno compile.

shell
deno compile --allow-net --config tsconfig.json main.ts

The tsconfig.json manifest configures compiler options for TypeScript which provides TypeScript becomes extremely useful when you know for sure what you’re program is supposed to do. Your data structures or business constraints are well defined and translatable. type checking. The following is a slightly relaxed tsconfig.json that imports browser DOM API libraries.

json
{
  "compilerOptions": {
    "noFallthroughCasesInSwitch": true,
    "noImplicitAny": false,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUncheckedIndexedAccess": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "strict": true,
    "strictBindCallApply": true,
    "strictFunctionTypes": true,
    "strictNullChecks": false,
    "strictPropertyInitialization": false,
    "lib": [
      "dom",
      "dom.iterable",
      "dom.asynciterable",
      "deno.ns"
    ]
  }
}
TypeScript compiler options (tsconfig.json)

Specifics & Details

Since this is supposed to be a command line program, the first thing is setting up a boilerplate. The program name, version, help flags, and help function are declared.

javascript
const program = "exoference";
const version = "0.0.1";
const helpFlags = ["-h", "-help", "--help"];
const help = () => {
  return `
Usage: ${program} [FLAGS]... [ARGUMENTS]...

The program ${program} shall generate a list of anchor reference links
as partial HTML output.

  Command List:

    ${program} https://example.com           Dump anchor reference links from
                                             specified URL to HTML output.

    ${program} --pretty https://example.com  Dump anchor reference links from
                                             specified URL to text output.

    ${program} --help                        Show this help menu.

Version: ${version}
`.trim();
};

Import DOMparser for DOM traversal. The references function accepts a URL (Uniform Resource Locator) and finds all elements that are anchors on the page. The URL API adds implicit formatting checks to the input URL.

The try clause naively resolves relative URLs and completes them with the origin. Duplicates are removed by briefly turning the references array [] into a set {}. The spread operator expands the items (iterables) inside the set back into an array.

javascript
import { DOMParser } from "https://deno.land/x/deno_dom@v0.1.31-alpha/deno-dom-wasm.ts";

const references = async (address) => {
  const url = new URL(address);
  const page = await fetch(url.href);
  const dom = new DOMParser().parseFromString(await page.text(), "text/html");
  const anchors = dom.getElementsByTagName("a");
  const references = [];

  for (const anchor of anchors) {
    const link = anchor.attributes.href.replace(url.origin, "").replace("/#", "#",);
    const resolveRelativeFragment = new URL(url.href + link).href;
    const resolveRelativeLink = (new URL(url.origin + link).href);
    try {
      references.push(new URL(link).href);
    } catch {
      if (link.startsWith("#")) {
        references.push(resolveRelativeFragment);
      } else {
        references.push(resolveRelativeLink);
      }
    }
  }

  return [...new Set(references)];
};

The html function returns partial HTML (HyperText Markup Language) to the console as an ordered list of references.

javascript
const html = (references) => {
  console.log("<ol>");
  for (const reference in references) {
    console.log(`<li><a href="${references[reference]}">${references[reference]}</a></li>`,);
  }
  console.log("</ol>");
};

The pretty function replicates the -dump and -listonly arguments of lynx and returns a list of references as plain text. Command line flags allow switching to this function.

javascript
const prettyFlags = ["-p", "-pretty", "--pretty"];
const pretty = (references) => {
  console.log("References", "\n");
  for (const reference in references) {
    console.log((parseInt(reference) + 1) + ".", references[reference]);
  }
};

The main function handles switching between each case by reading the first, and second arguments along with the argument length. The function switches based on the helpFlags and prettyFlags array in addition to the argument position.

typescript
const firstArgument: string = Deno.args[0];
const secondArgument: string = Deno.args[1];
const numberOfArguments: number = Deno.args.length;

const main = async (numberOfArguments) => {
  if (numberOfArguments === 0) return console.log(help());
  if (helpFlags.includes(firstArgument)) return console.log(help());
  if (prettyFlags.includes(firstArgument)) {
    if (new URL(secondArgument)) {
      return pretty(await references(secondArgument));
    }
  }
  if (new URL(firstArgument)) return html(await references(firstArgument));
  return console.log(`Unkown argument ${Deno.args[0]}`);
};

main(numberOfArguments);

This is good enough to compile the If you are curious, the binary size is approximately 78MB (megabytes) on my x86_64 (64 bit version of the x86 instruction set) Linux laptop. and output a list of references for this article.

A quick run

Conclusion

This might be a very simple starting point, but the opportunities for more features are endless. Filtering, automatic link rot detection, and caching the reference sources are just a small subset of possible features.

30 August 2022 — Written
23 September 2022 — Updated
Thedro Neely — Creator
a-javascript-executable.md — Article

More Content

Openring

Web Ring

Comments

References

  1. https://thedroneely.com/git/
  2. https://thedroneely.com/
  3. https://thedroneely.com/posts/
  4. https://thedroneely.com/projects/
  5. https://thedroneely.com/about/
  6. https://thedroneely.com/contact/
  7. https://thedroneely.com/abstracts/
  8. https://ko-fi.com/thedroneely
  9. https://thedroneely.com/tags/javascript/
  10. https://thedroneely.com/posts/a-javascript-executable/#isso-thread
  11. https://thedroneely.com/posts/rss.xml
  12. https://thedroneely.com/images/a-javascript-executable.png
  13. https://deno.land/
  14. https://lynx.invisible-island.net/
  15. https://thedroneely.com/posts/a-javascript-executable/#code-block-d626e3b
  16. https://example.com/
  17. https://en.wikipedia.org/wiki/Link_rot
  18. https://lynx.invisible-island.net/lynx_help/Lynx_users_guide.html
  19. https://thedroneely.com/posts/a-javascript-executable/#javascript-with-deno-runtime
  20. https://developer.mozilla.org/en-US/docs/Web/JavaScript
  21. https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model
  22. https://www.typescriptlang.org/
  23. https://thedroneely.com/posts/a-javascript-executable/#code-block-c5f2a93
  24. https://thedroneely.com/posts/a-javascript-executable/#file-and-directory-setup
  25. https://github.com/tdro/dotfiles/blob/8ea84da5b2ade1043d05c30289ef97835c4b1499/.local/bin/portmanteau
  26. https://deno.com/blog/v1.25
  27. https://thedroneely.com/posts/a-javascript-executable/#code-block-8b74e7c
  28. https://thedroneely.com/posts/a-javascript-executable/#code-block-6792c40
  29. https://deno.land/manual@v1.25.0/tools/compiler#cross-compilation
  30. https://thedroneely.com/posts/a-javascript-executable/#code-block-aad7306
  31. https://deno.land/manual@v1.9.2/typescript/configuration
  32. https://thedroneely.com/posts/a-javascript-executable/#code-block-16a7ded
  33. https://thedroneely.com/posts/a-javascript-executable/#specifics--details
  34. https://thedroneely.com/posts/a-javascript-executable/#code-block-2aaf55d
  35. https://developer.mozilla.org/en-US/docs/Web/API/URL
  36. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
  37. https://thedroneely.com/posts/a-javascript-executable/#code-block-e8145ee
  38. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol
  39. https://thedroneely.com/posts/a-javascript-executable/#code-block-a932706
  40. https://thedroneely.com/posts/a-javascript-executable/#code-block-8f85b49
  41. https://thedroneely.com/posts/a-javascript-executable/#code-block-bb96478
  42. https://en.wikipedia.org/wiki/Linux
  43. https://thedroneely.com/posts/a-javascript-executable/#article-references
  44. https://thedroneely.com/videos/a-javascript-executable-run.mp4
  45. https://thedroneely.com/posts/a-javascript-executable/#conclusion
  46. https://www.thedroneely.com/posts/a-javascript-executable.md
  47. https://thedroneely.com/posts/declarative-user-package-management-in-nixos/
  48. https://thedroneely.com/projects/bulma-resume/
  49. https://thedroneely.com/archives/tags/
  50. https://git.sr.ht/~sircmpwn/openring
  51. https://drewdevault.com/2022/11/12/In-praise-of-Plan-9.html
  52. https://drewdevault.com/
  53. https://mxb.dev/blog/the-indieweb-for-everyone/
  54. https://mxb.dev/
  55. https://www.taniarascia.com/simplifying-drag-and-drop/
  56. https://www.taniarascia.com/
  57. https://thedroneely.com/posts/a-javascript-executable#isso-thread
  58. https://thedroneely.com/posts/a-javascript-executable#code-block-d626e3b
  59. https://thedroneely.com/posts/a-javascript-executable#javascript-with-deno-runtime
  60. https://thedroneely.com/posts/a-javascript-executable#code-block-c5f2a93
  61. https://thedroneely.com/posts/a-javascript-executable#file-and-directory-setup
  62. https://thedroneely.com/posts/a-javascript-executable#code-block-8b74e7c
  63. https://thedroneely.com/posts/a-javascript-executable#code-block-6792c40
  64. https://thedroneely.com/posts/a-javascript-executable#code-block-aad7306
  65. https://thedroneely.com/posts/a-javascript-executable#code-block-16a7ded
  66. https://thedroneely.com/posts/a-javascript-executable#specifics--details
  67. https://thedroneely.com/posts/a-javascript-executable#code-block-2aaf55d
  68. https://thedroneely.com/posts/a-javascript-executable#code-block-e8145ee
  69. https://thedroneely.com/posts/a-javascript-executable#code-block-a932706
  70. https://thedroneely.com/posts/a-javascript-executable#code-block-8f85b49
  71. https://thedroneely.com/posts/a-javascript-executable#code-block-bb96478
  72. https://thedroneely.com/posts/a-javascript-executable#article-references
  73. https://thedroneely.com/posts/a-javascript-executable#conclusion
  74. https://thedroneely.com/posts/tweaking-goaccess-for-analytics/
  75. https://thedroneely.com/archives/projects/
  76. https://thedroneely.com/abstracts/the-myth-of-the-rational-voter/
  77. https://thedroneely.com/posts/trying-out-this-website/
  78. https://thedroneely.com/posts/gitea-in-a-sub-directory-with-nginx/
  79. https://thedroneely.com/posts/bitcoin-hacked-email-scam/
  80. https://drewdevault.com/2022/09/16/Open-source-matters.html
  81. https://mxb.dev/blog/make-free-stuff/
  82. https://www.thedroneely.com/git/
  83. https://www.thedroneely.com/
  84. https://www.thedroneely.com/posts/
  85. https://www.thedroneely.com/projects/
  86. https://www.thedroneely.com/about/
  87. https://www.thedroneely.com/contact/
  88. https://www.thedroneely.com/abstracts/
  89. https://www.thedroneely.com/tags/javascript/
  90. https://www.thedroneely.com/posts/a-javascript-executable#isso-thread
  91. https://www.thedroneely.com/posts/rss.xml
  92. https://www.thedroneely.com/images/a-javascript-executable.png
  93. https://www.thedroneely.com/posts/a-javascript-executable#code-block-d626e3b
  94. https://www.thedroneely.com/posts/a-javascript-executable#javascript-with-deno-runtime
  95. https://www.thedroneely.com/posts/a-javascript-executable#code-block-c5f2a93
  96. https://www.thedroneely.com/posts/a-javascript-executable#file-and-directory-setup
  97. https://www.thedroneely.com/posts/a-javascript-executable#code-block-8b74e7c
  98. https://www.thedroneely.com/posts/a-javascript-executable#code-block-6792c40
  99. https://www.thedroneely.com/posts/a-javascript-executable#code-block-aad7306
  100. https://www.thedroneely.com/posts/a-javascript-executable#code-block-16a7ded
  101. https://www.thedroneely.com/posts/a-javascript-executable#specifics--details
  102. https://www.thedroneely.com/posts/a-javascript-executable#code-block-2aaf55d
  103. https://www.thedroneely.com/posts/a-javascript-executable#code-block-67d2632
  104. https://www.thedroneely.com/posts/a-javascript-executable#code-block-a932706
  105. https://www.thedroneely.com/posts/a-javascript-executable#code-block-8f85b49
  106. https://www.thedroneely.com/posts/a-javascript-executable#code-block-bb96478
  107. https://www.thedroneely.com/posts/a-javascript-executable#article-references
  108. https://www.thedroneely.com/videos/a-javascript-executable-run.mp4
  109. https://www.thedroneely.com/posts/a-javascript-executable#conclusion
  110. https://www.thedroneely.com/posts/website-auditing-tools/
  111. https://www.thedroneely.com/posts/lets-customize-gitea/
  112. https://www.thedroneely.com/posts/automate-vultr-snapshots-using-bash/
  113. https://drewdevault.com/2022/08/28/powerctl-a-hare-case-study.html
  114. https://www.taniarascia.com/josh/
  115. https://www.thedroneely.com/sitemap.xml
  116. https://www.thedroneely.com/index.json
  117. https://www.thedroneely.com/resume/
  118. https://gitlab.com/tdro
  119. https://github.com/tdro
  120. https://codeberg.org/tdro
  121. https://www.thedroneely.com/analytics
  122. https://www.thedroneely.com/posts/a-javascript-executable#
  123. https://creativecommons.org/licenses/by-sa/2.0/
  124. https://www.thedroneely.com/git/thedroneely/thedroneely.com
  125. https://opensource.org/licenses/GPL-3.0
  126. https://thedroneely.com/sitemap.xml
  127. https://thedroneely.com/index.json
  128. https://thedroneely.com/resume/
  129. https://thedroneely.com/analytics
  130. https://thedroneely.com/posts/a-javascript-executable#
  131. https://thedroneely.com/git/thedroneely/thedroneely.com
  132. https://thedroneely.com/posts/a-javascript-executable/#