Example using TypeScript, Vue, and @pika/web, a new ESM bundler. Uses Sass for styling.
npm i
npm start
It’ll run a multi-page app at localhost:5000
.
Because we’re running Vue as an ESM package, there are a few key things we’ll have to do:
- We can’t use the default
vue
package; we’ll have to use the browser ESM version (/dist/vue.esm.browser.js
) - TypeScript doesn’t know what the types are for the browser ESM version, so
we’ll have to trick TypeScript into using
vue
but swapping it out after TypeScript runs. - TypeScript’s JSX conversion doesn’t work for Vue, so we need
@vue/babel-preset-app
to convert it for us.
Let’s step through these one-by-one.
TypeScript demands we use the following:
import Vue from "/web_modules/vue.js";
However we need /web_modules/vue/dist/vue.esm.browser.js
for our ES Modules
app. Fortunately babel-plugin-transform-rename-import
was made exactly for
this kind of situation. We’ll install that and modify .babelrc
:
{
"plugins": [
[
"transform-rename-import",
{
"replacements": [
{
"original": "/web_modules/vue.js",
"replacement": "/web_modules/vue/dist/vue.esm.browser.js"
}
]
}
]
]
}
And we’ll tell @pika/web to load that one instead in package.json
:
"@pika/web": {
"webDependencies": [
"vue/dist/vue.esm.browser.js",
]
},
We’ll set up Babel to run in the next step.
TypeScript’s JSX conversion doesn’t work well with Vue’s compiler.
Fortunately Vue ships a @vue/babel-preset-app
Babel preset that will take
care of everything. To use that we need to configure TypeScript to understand
JSX without transforming it. That’s what the { "jsx": "preserve" }
option
is for in tsconfig.json
:
"compilerOptions": {
"jsx": "preserve",
}
Now if we run tsc
, we end up with a whole bunch of .jsx
files in our
directory:
public
├── components
│ ├── Home.jsx
│ └── Meredith.jsx
└── index.jsx
That’s actually a blessing in disguise that the browser can’t read those.
Next, we install @vue/babel-preset-app
and add it to .babelrc
:
{
+ "presets": ["@vue/babel-preset-app"],
"plugins": [
[
"transform-rename-import",
{
"replacements": [
{
"original": "/web_modules/vue.js",
"replacement": "/web_modules/vue/dist/vue.esm.browser.js"
}
]
}
]
]
}
Let’s target our new .jsx
files and nothing else. In package.json
:
"scripts": {
"build:js": "babel public --out-dir public -x .jsx",
"build:js:watch": "babel --watch public --out-dir public -x .jsx"
}
This will allow us to run npm run build:js
to convert all .jsx
files to
.js
:
public
├── components
│ ├── Home.js
│ ├── Home.jsx
│ ├── Meredith.js
│ └── Meredith.jsx
├── index.js
└── index.jsx
We can even --watch
those .jsx
files with Babel and we won’t enter an
endless loop! Because in this setup, TypeScript will only convert .tsx
➡️
.jsx
and Babel will only convert .jsx
➡️ .js
so there’s no overlap (nor
will either compiler recompile their own files over and over again).
You can use TypeScript’s Babel plugin in situations like this, and it will save a step. However, you don’t get TypeScript’s full typechecking features if you use Babel. And personally, that’s not worth it for me. A few extra steps are always worth type safety.