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 topublic/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:
- Do modules prevent the need to use the DOMContentLoaded listener?
- How to use document and window element in astro JS?
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.