nshew13
nshew13

Reputation: 3097

use `document` in external TypeScript file within Astro project

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

Relevant questions:

Upvotes: 2

Views: 1589

Answers (1)

HappyDev
HappyDev

Reputation: 410

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>
    import { callMe } from "^scripts/sample";
    callMe();
</script>

Here is a working reproduction.

Upvotes: 2

Related Questions