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. It 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": {
    "experimentalDecorators": true,
    "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

On the Web

Comments

References

  1. https://www.thedroneely.com/git/
  2. https://www.thedroneely.com/
  3. https://www.thedroneely.com/posts/
  4. https://www.thedroneely.com/projects/
  5. https://www.thedroneely.com/about/
  6. https://www.thedroneely.com/contact/
  7. https://www.thedroneely.com/abstracts/
  8. https://ko-fi.com/thedroneely
  9. https://www.thedroneely.com/tags/javascript/
  10. https://www.thedroneely.com/posts/a-javascript-executable/#isso-thread
  11. https://www.thedroneely.com/posts/rss.xml
  12. https://www.thedroneely.com/images/a-javascript-executable.png
  13. https://deno.land/
  14. https://lynx.invisible-island.net/
  15. https://www.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://www.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://www.thedroneely.com/posts/a-javascript-executable/#code-block-c5f2a93
  24. https://www.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://www.thedroneely.com/posts/a-javascript-executable/#code-block-8b74e7c
  28. https://www.thedroneely.com/posts/a-javascript-executable/#code-block-6792c40
  29. https://deno.land/manual@v1.25.0/tools/compiler#cross-compilation
  30. https://www.thedroneely.com/posts/a-javascript-executable/#code-block-aad7306
  31. https://deno.land/manual@v1.9.2/typescript/configuration
  32. https://www.thedroneely.com/posts/a-javascript-executable/#code-block-16a7ded
  33. https://www.thedroneely.com/posts/a-javascript-executable/#specifics--details
  34. https://www.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://www.thedroneely.com/posts/a-javascript-executable/#code-block-67d2632
  38. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol
  39. https://www.thedroneely.com/posts/a-javascript-executable/#code-block-a932706
  40. https://www.thedroneely.com/posts/a-javascript-executable/#code-block-8f85b49
  41. https://www.thedroneely.com/posts/a-javascript-executable/#code-block-bb96478
  42. https://en.wikipedia.org/wiki/Linux
  43. https://www.thedroneely.com/posts/a-javascript-executable/#article-references
  44. https://www.thedroneely.com/videos/a-javascript-executable-run.mp4
  45. https://www.thedroneely.com/posts/a-javascript-executable/#conclusion
  46. https://www.thedroneely.com/posts/a-javascript-executable.md
  47. https://www.thedroneely.com/posts/website-auditing-tools/
  48. https://www.thedroneely.com/posts/lets-customize-gitea/
  49. https://www.thedroneely.com/posts/automate-vultr-snapshots-using-bash/
  50. https://git.sr.ht/~sircmpwn/openring
  51. https://drewdevault.com/2022/08/28/powerctl-a-hare-case-study.html
  52. https://drewdevault.com/
  53. https://www.taniarascia.com/josh/
  54. https://www.taniarascia.com/
  55. https://mxb.dev/blog/make-free-stuff/
  56. https://mxb.dev/
  57. https://www.thedroneely.com/sitemap.xml
  58. https://www.thedroneely.com/index.json
  59. https://www.thedroneely.com/resume/
  60. https://gitlab.com/tdro
  61. https://github.com/tdro
  62. https://codeberg.org/tdro
  63. https://www.thedroneely.com/analytics
  64. https://www.thedroneely.com/posts/a-javascript-executable/#
  65. https://creativecommons.org/licenses/by-sa/2.0/
  66. https://www.thedroneely.com/git/thedroneely/thedroneely.com
  67. https://opensource.org/licenses/GPL-3.0