Home > other >  use `document` in external TypeScript file within Astro project
use `document` in external TypeScript file within Astro project

Time:11-02

I've switched to Astro after some difficulties getting Vite Eleventy to cooperate. I have everything like I want it except that I can't have an external TypeScript module that interacts with the document. I think it may be two problems tied in a knot, or maybe a misunderstanding of the sparse documentation on external TS/JS files.

Problem 1: public/ and import paths

JavaScript files can easily live under public/, but TypeScript is not transpiled. I tried adding public to my tsconfig, but it didn't help.

Importing in the frontmatter of a page is done in "Astro scope", which lets me use paths relative to the page file. TS imported this way transpiles and works except when looking for document.

Importing a module from within a <script> tag on the page is "client scope". If my TS code elsewhere under src is getting transpiled, I still don't have a determinate, served location to use for the import path (in other words, I could probably figure it out, but it would be fragile).

Problem 2: document undefined

I must use <script is:inline> or <script type="module"> in order to have a script remain embedded in the HTML for "client scope". These scripts have access to window and document. However, they don't have access to any modules imported and transpiled in "Astro scope", nor to the Runtime API.

Import paths within an inline script tag always start looking at the root URL (i.e., public/), so I'm back to Problem 1.

(Incidentally, after a little research, it seems DOMContentLoaded isn't necessary with normal modules, but I think Astro is doing something to change that.)

my setup

tsconfig.json

{
  "extends": "astro/tsconfigs/strictest",
  "include": [
    "src/**/*",
    "public/**/*"
  ],
  "compilerOptions": {
    "baseUrl": ".",
    "types": [
      "astro/client"
    ],
    "paths": {
      "^layouts/*": [ "src/layouts/*" ],
      "^scripts/*": [ "src/scripts/*" ]
    }
  }
}

src/scripts/sample.ts

type NotJavaScript = {
    hasTypes: boolean;
};

export const callMe = () => {
    document.getElementById("greeting").textContent = "Color me your color, baby";
};

document.addEventListener("DOMContentLoaded", () => {
    document.getElementById("greeting").textContent = "Hello world";
});

src/pages/sample/sample1.astro

---
import LayoutMain from "^layouts/LayoutMain.astro";
import "^scripts/sample"
---
<LayoutMain>
<p id="greeting"></p>
</LayoutMain>

Result: ReferenceError: document is not defined

src/pages/sample/sample2.astro

---
import LayoutMain from "^layouts/LayoutMain.astro";
import "^scripts/sample"
// same result as `import { callMe } from "^scripts/sample"`
---
<LayoutMain>
<p id="greeting"></p>
</LayoutMain>

<script is:inline>
// document.addEventListener moved from TS file
document.addEventListener("DOMContentLoaded", () => {
    document.getElementById("greeting").textContent = "Hello world";
    callMe();
});
</script>

Result: "Hello world" with ReferenceError: callMe is not defined

src/scripts/sample.ts moved to public/sample.ts for samples 3 and 4

src/pages/sample/sample3.astro

---
import LayoutMain from "^layouts/LayoutMain.astro";
---
<LayoutMain>
<p id="greeting"></p>
</LayoutMain>

<script is:inline>
    import "/sample.ts";
    callMe();
</script>

Result: SyntaxError: import declarations may only appear at top level of a module

src/pages/sample/sample4.astro

---
import LayoutMain from "^layouts/LayoutMain.astro";
---
<LayoutMain>
<p id="greeting"></p>
</LayoutMain>

<script type="module">
    import { callMe } from "/sample.ts";
    callMe();
</script>

Result: Loading module from “http://localhost:3000/sample.ts” was blocked because of a disallowed MIME type (“text/html”).

I have also seen SyntaxError: unexpected token: identifier (referring to type)

desired result

Thank you for making it this far. Can you tell me how I can

  • have an external TS file
  • loaded by or within an Astro page
  • that interacts with the DOM (via document)
  • after DOMContentLoaded (or equivalent)

Relevant questions:

CodePudding user response:

I must use or in order to have a script remain embedded in the HTML for "client scope". These scripts have access to window and document.

You can actually access the document and window object within the default processed <srcipt> tags (without any attribute).

To do that, reproduce sample4.astro, but omit the type attribute on the script tag and move your sample.ts file somewhere in the src directory (^scripts for you) so that you can import it from javascript (note the last bullet point)

---
import LayoutMain from "^layouts/LayoutMain.astro";
---
<LayoutMain>
<p id="greeting"></p>
</LayoutMain>

<script type="module">
    import { callMe } from "^scripts/sample";
    callMe();
</script>

Here is a working reproduction.

  • Related