tl;dr use Vite. Example.
You may be wondering if the TypeScript compiler is able to generate JavaScript code that can be used on a website. The answer is somewhat ambiguous, but in most cases, the answer is no.
For a basic example of how this works, you can see an example here. First, set outDir in tsconfig. Now when I run tsc, it generates JavaScript files that I can then import into my index.html file. This seems to work without any issues.
The problem comes when you try to use an npm package. tsc can't bundle an npm package. tsc is not a bundler, it just compiles your TypeScript files to JavaScript. In this example, I'm importing an npm package. When I run tsc, the output still contains my import line, but TypeScript didn't even try to bundle the contents of tiny-package for me.
However, Vite can easily (easily!) solve this for you. See this example. Seriously, you won't believe how easy it is. All you need to do is
npm i -D vite), andnpx vite).You're still building with tsc, but Vite is serving your files and it can handle npm packages.
In the simple example above, index.html includes index.js. index.js imports foo.js. This import happens using ESM (ECMAScript module) syntax. This works fine since browsers now support this natively.
Here, I'm outputting multiple files. My TypeScript files get generated into corresponding JavaScript files:
index.ts -> index.js
foo.ts -> foo.jsI can output a single file instead using outFile, as in this example. I need to set "module": "AMD" because I can't output a single file using ESM syntax.
AMD modules are the kind that use the require and define syntax:
define("foo", ["require", "exports"], function (require, exports) {
"use strict";
// etc
});But! If you now include the generated file in index.html, you'll see an error:
ReferenceError: Can't find variable: defineThis is because AMD syntax is not natively supported in browsers. You'll need to include require.js first to make this work. The easiest way is to add the unpkg link:
<script src="https://unpkg.com/requirejs/require.js"></script>You'll also need to explicitly require the index module to see the console.logs appear:
<script>
// or requirejs
require(["index"], function (index) {
console.log(index);
});
</script>Webpack fixes all these issues. Webpack takes all your files and combines them into a single file. It handles npm packages too.
Webpack can get complex, but this example is simple.
First install webpack:
npm install webpack webpack-cli --save-devCreate a webpack.config.js file:
const path = require("path");
module.exports = {
mode: "development",
entry: "./index.ts",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist"),
},
};Run webpack (add this to scripts in package.json):
"build": "webpack"Tada!
We already saw how vite works (here's the example again). If, instead of serving files, you want vite to make a build that you can then upload to Vercel or render.com or whatever, run npx vite build. It will look for an index.html file by default, use that as an entry point, and go through all the JavaScript files linked from there.
One change we'll need to make is, we've been linking to the generated JavaScript file from index.html, but now we link straight to the TypeScript file instead.
I'm adding this example for the sake of completeness, but the only change I've made is which js file I'm linking to in index.html.
Like Webpack, Vite bundles several different JavaScript files for you, handles npm packages, etc. The difference is Webpack was written before ECMAScript modules became a standard, and so it bundles all the files together. Vite tends to take advantage of the fact that you can import ECMAScript modules in the browser.