A Better Development Experience with TypeScript and TSConfig
Since the beginning of this series, we've been working with TypeScript by creating .ts
files and using lang="ts"
in the script
section of Vue.js components.
However, we haven’t configured TypeScript in our project yet, so we’re not leveraging its full potential. Additionally, one of the benefits of using TypeScript in a library is to expose types to the developers who will utilize it. This enables type checking and auto-completion in their IDE.
Tip
Check the dist
folder in packages/huchet-vue
. You’ll notice only .js
files are present. We need to generate .d.ts
files to expose the types.
Configuring TypeScript
Let's start by installing TypeScript and the necessary Node.js types in packages/huchet-vue
:
pnpm add -D typescript @types/node
Thanks to the extensive Vite plugin ecosystem, we can use the vite-plugin-dts
plugin to generate .d.ts
files for our library.
Install it:
pnpm add -D vite-plugin-dts
Next, update the vite.config.ts
file in packages/huchet-vue
:
import Vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import Dts from 'vite-plugin-dts'
export default defineConfig({
plugins: [
Vue(),
Dts(),
],
build: {
// ...
}
})
Does it work now? Not yet. If it were that simple, I wouldn't write an entire article about it!
When using TypeScript, we have to configure it through a tsconfig.json
file. This file includes numerous options to inform TypeScript about the project type, including global types and variables, file paths, and even the desired code compilation method.
Writing this the first time can be tedious, but fortunately, some projects provide a base configuration to extend from.
Another issue with tsconfig.json
is that a package can have more than one. Our vite.config.ts
file and .vue
files have different contexts. The first needs access to the Node.js API, while the second needs access to the DOM and browser API.
We can resolve this by using a single tsconfig.json
file with the references
option. This allows us to split a TSConfig file into multiple files, each targeting a specific context.
Here’s the plan:
tsconfig.json
to combine the two contexts.tsconfig.node.json
for the Node.js context.tsconfig.app.json
for the Vue.js context.
The Node TypeScript Context
Create the tsconfig.node.json
file in packages/huchet-vue
:
touch tsconfig.node.json
Populate it with the following:
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"moduleDetection": "force",
"types": ["node"],
"module": "ESNext",
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"strict": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noEmit": true,
"isolatedModules": true,
"skipLibCheck": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}
This file is similar to what's provided in any Vite template, except we've added the types
option set to ["node"]
to ensure Node.js types are available in the Vite configuration file. This configuration targets only the vite.config.ts
file.
This forms our first TypeScript context.
The Vue.js TypeScript Context
The second context will target the Vue.js files and TypeScript files from our src
folder, named tsconfig.app.json
.
Create the tsconfig.app.json
file:
touch tsconfig.app.json
Include the following content:
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": [
"src/**/*.ts",
"src/**/*.vue"
]
}
Before using this setup, install the Vue.js TypeScript base configuration:
pnpm add -D @vue/tsconfig
Vue.js offers a base configuration to expand on, crucial because TypeScript evolves rapidly, and Vue.js requires specific configurations. Writing a TypeScript configuration from scratch isn’t appealing to most developers.
Tip
I recommend examining the tsconfig.json
file in the @vue/tsconfig
package. It's well documented and offers valuable insights.
Our configuration extends the Vue.js setup with the extends
option and targets the src
folder using the include
option. It's straightforward and functional.
This is our second TypeScript context.
The Global TypeScript Configuration
Now, create the tsconfig.json
file to unify the two contexts. This file doesn't define a new context or compiler options but references the two other contexts for the TypeScript compiler to use as needed.
Create the tsconfig.json
file:
touch tsconfig.json
Include the following content:
{
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.node.json"
}
]
}
The empty files
array is crucial to avoid including every project file. Each reference has its own context, so we must ensure that only one context targets a file.
This forms our global TypeScript configuration.
Tip
To verify everything works correctly, attempt to import a Node.js module in vite.config.ts
where it should be accessible and in a Vue.js file where it should not be.
Generating the Types
With our TypeScript configurations set up, we can proceed to generate the types for our library.
Since we are combining two contexts, the vite-plugin-dts
plugin needs explicit context direction.
Update the vite.config.ts
file in packages/huchet-vue
:
import { resolve } from 'node:path'
import Vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import Dts from 'vite-plugin-dts'
export default defineConfig({
plugins: [
Vue(),
Dts({
tsconfigPath: resolve(__dirname, 'tsconfig.app.json'),
})
],
build: {
// ...
},
})
We use the tsconfig.app.json
file since Vite builds Vue.js and TypeScript files from the src
folder.
Proceed to generate the types:
pnpm run build
In our dist
folder, a types
folder containing the .d.ts
files should appear.
dist
├── index.d.ts
├── index.mjs
└── Button
├── Button.vue.d.ts
└── index.d.ts
Exposing the Types
Having generated the types, the next step is to expose them to developers using our library.
Update the package.json
file in packages/huchet-vue
:
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs"
}
},
"main": "./dist/index.mjs",
"types": "dist/index.d.ts"
}
We set the types
field to point to the index.d.ts
file in the dist
folder, ensuring types are available in the IDE when developers import our library.
Important
Ensure the types
field precedes any other exports in the package.json
file.
Thanks for reading! My name is Estéban, and I love to write about web development.
I've been coding for several years now, and I'm still learning new things every day. I enjoy sharing my knowledge with others, as I would have appreciated having access to such clear and complete resources when I first started learning programming.
If you have any questions or want to chat, feel free to comment below or reach out to me on Bluesky, X, and LinkedIn.
I hope you enjoyed this article and learned something new. Please consider sharing it with your friends or on social media, and feel free to leave a comment or a reaction below—it would mean a lot to me! If you'd like to support my work, you can sponsor me on GitHub!