Repository navigation
Description
🔍 Search Terms
I often develop applications for the web without a bundler - just using browser ESM and JIT type stripping by the web server.
This setup is toolless - so no npm vite *pack next or anything. Just TypeScript, CSS and a brower.
The wrinkle is that TypeScript does not support remote declaration types which forces me to have to do an npm install despite only needing types. node_modules then fills up with half a million runtime dependencies I need to maintain in audits despite never using.
There is also no practical way to vendor declaration files for packages with TypeScript (I have tried API-extractor and even recursing through a package + its dependencies to copy out the declaration files and rename the imports to relative file-paths).
My current workflow uses browser importmap and Deno's LSP [example] (Deno is only used for type checking and nothing runtime related because it has support for remote declaration files).
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
<script type="importmap">
{
"imports": {
"preact": "https://esm.sh/preact@11",
"preact/hooks": "https://esm.sh/preact@11/hooks",
"preact/jsx-runtime": "https://esm.sh/preact@11/jsx-runtime",
"preact/compat": "https://esm.sh/preact@11/compat",
"preact/devtools": "https://esm.sh/preact@11/devtools"
}
}
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>// deno.json
{
"compilerOptions": {
"strict": true,
"lib": ["dom","dom.iterable","esnext"],
"jsx": "react",
"jsxFactory": "h"
},
"imports": {
"preact": "https://esm.sh/preact@11",
"preact/hooks": "https://esm.sh/preact@11/hooks",
"preact/jsx-runtime": "https://esm.sh/preact@11/jsx-runtime",
"preact/compat": "https://esm.sh/preact@11/compat",
"preact/devtools": "https://esm.sh/preact@11/devtools"
}
}Deno's type checker gets declarations from the x-typescript-types header.
Example; https://esm.sh/preact@11 give you https://esm.sh/preact@11.1.1/src/index.d.ts
With Deno going end of life - that's a significant loss of function for pretty much all of my workflows.
✅ Viability Checklist
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
⭐ Suggestion
It would be amazing if TypeScript added remote declaration resolution to tsconfig,json
{
"compilerOptions": {
"lib": ["DOM", "ESNext"],
"module": "nodenext",
"moduleResolution": "nodenext",
"types": [],
"allowImportingTsExtensions": true,
"noEmit": true,
"jsx": "react",
"jsxFactory": "h",
"remoteDeclarations": true,
"paths": {
"preact": ["https://esm.sh/preact@11"],
"preact/hooks": ["https://esm.sh/preact@11/hooks"],
"preact/jsx-runtime": ["https://esm.sh/preact@11/jsx-runtime"],
"preact/compat": ["https://esm.sh/preact@11/compat"],
"preact/devtools": ["https://esm.sh/preact@11/devtools"]
}
}
}Or even
{
"compilerOptions": {
"paths": {
"preact": ["https://esm.sh/preact@11.1.1/src/index.d.ts"],
//...
}
}
}Or even
{
"compilerOptions": {
"remoteDeclarations": {
"preact": "https://esm.sh/preact@11.1.1/src/index.d.ts",
//...
}
}
}📃 Motivating Example
Developing a frontend application and server scripts with no npm or build tools.
Very performance sensitive applications obviously can't do this - but 95% of the applications I build are absolutely fine tool-less.
Honestly, there are bigger sins in the FE space than shipping unbundled ESM - plus you can mitigate a lot of the performance issues with some good design decisions. I'm surprised it's not more common tbh.
💻 Use Cases
- Developing a frontend application with no build tools.
- Embedding a frontend application within a native Go/Rust application as TypeScript without needing a build step that requires a separate build pipeline, copying dist files into the backend, proxies for development, etc.
- Writing node based shell scripts without an
npm installfor@node/types. Simply add an/// <includeat the top of the file or from the tsconfig
Activity