In the past few days, when I started to dig into ambient modules, I also come across the concept of TypeScript configuration. Obviously it’s not a new concept, but I’m still far from being mastering it. So I think it will be worth to have a separate blog discussing the popular configuration options — in tsconfig.json
.
I put here 3 examples of tsconfig, and will go through the properties in each of them:
//example.1
{
“compilerOptions”: {
“declaration”: true,
“declarationDir”: “types”,
“esModuleInterop”: true,
“jsx”: “react”,
“module”: “es6”,
“target”: “es6”,
“baseUrl”: “.”
},
“include”: [“src/types/**/*.ts”]
}//example.2{
“compilerOptions”: {
“allowSyntheticDefaultImports”: true,
“declaration”: true,
“declarationDir”: “types”,
“esModuleInterop”: true,
“jsx”: “react”,
“module”: “commonjs”,
“moduleResolution”: “node”,
“strict”: true,
“target”: “es5”,
“lib”: [“es5”, “es6”, “es7”, “es2017”, “dom”],
“baseUrl”: “.”,
“paths”: {
“@myPackage/internal-components”: [“src”]
}
},
“include”: [“src/**/*”],
“exclude”: [“**/*.spec.js”, “**/*.spec.ts”, “**/*.spec.tsx”, “**/*.stories.tsx” ]
}//example.3{
“compilerOptions”: {
“esModuleInterop”: true,
“jsx”: “preserve”,
“module”: “esnext”,
},
“include”: [
“src/**/*.ts”,
“src/**/*.tsx”
],
“exclude”: [
“**/*.spec.ts”,
“**/*.spec.tsx”,
],
“files”:
“src/ambient/window.d.ts”
]
}
Files
By default, TypeScript compiles recursively searches for all files in the root directory. But this is not encouraged, since without constraint, when another project references yours, it would have to read and parse every file in the compilation to figure out where to find the .d.ts file for any particular input. It would also have to do this to see if the project was up-to-date or not
However, we can explicitly define where TS search for files, and this is throughfiles
option.
“files”: [
“src/ambient/window.d.ts”
]
Note if window.d.ts
has a top level import then the imported file will be compiled as well.
include & exlude
Instead of listing each file manually, we can use include
option to specify directories with file patterns. For example, we can include every .ts
file in folder src/types
“include”: [“src/types/**/*.ts”]
And we can do the same to exclude any file following a pattern using exclude
keyword as well.
Note that the priority for the 3 options above are Files> exclude > include
, meaning that the files listed in files
will be compiled even it’s excluded in exclude
option. And if it’s both listed in exclude
and include
then it will be excluded.
Note that the pattern **/*
simply means all folder and any files with extensions .ts
/.tsx
will be included.
target
When TS compiles into JavaScript, we want to make sure the version of JavaScript we get. So we can use target
option.
“target”: “es6”
module
If we want to transpile ES6 modules into a different module system: CommonJS, AMD, etc, we can use module
option. By default the module
transpiles to ES6
if target
is ES6
, or CommonJS
otherwise.
“module”: “commonjs”
lib
To be able to use classes from the ES standard libraries in your TS sources, you should use lib
option and specify all standard ES libraries interfaces used in your sources. Note that some libraries are included by default likeDOM,ES6,DOM.Iterable,ScriptHost
for ES6
, but when you specify lib
option you overwrite it, and need to manually define it again.
“lib”: [“es5”, “es6”, “es7”, “es2017”, “dom”]
moduleResolution
In TS, modules are resolved differently based on whether the module reference is relative or non-relative. It will be set to node
if module:CommonJS
and classic
by default.
“moduleResolution”: “node”,
With node, if you have a relative import like import moduleA from “./moduleA”
in source file /src/myComponent.ts
, TS will look up declaration in paths like:
/src/moduleA.ts
/src/moduleA.tsx
/src/moduleA.d.ts
/src/moduleA/package.json
/src/moduleA/index.ts
/src/moduleA/index.tsx
/src/moduleA/index.d.ts
With a non-relative import like import moduleA from “moduleA”
in source file /src/myComponent.ts
, TS will look up paths like below in /src/node_modules
and /node_modules
/src/node_modules/moduleA.ts
/src/node_modules/moduleA.tsx
/src/node_modules/moduleA.d.ts
/src/node_modules/moduleA/package.json
/src/node_modules/@types/moduleA.d.ts
/src/node_modules/moduleA/index.ts
/src/node_modules/moduleA/index.tsx
/src/node_modules/moduleA/index.d.tsx.
/src/node_modules/@types/moduleA.d.ts
When Classic
it’s much simpler. With relative import, TS will look for
/pathToModule/moduleA.ts
/pathToModule/moduleA.d.ts
With non-relative path import in a file /src/components/app.ts
, TS will look up files in paths:
/src/components/moduleA.ts
/src/components/moduleA.d.ts
/components/moduleA.ts
/components/moduleA.d.ts
/src/moduleA.ts
/src/moduleA.d.ts
/moduleA.ts
/moduleA.d.ts
baseUrl & paths
Note that when options is set to node
, TS looks up node_modules
folder for non relative module import. But it can happen that your imported module is in another folder, so you will need the flexibility to tell TS to look for other routes:
baseUrl”: “.”,
“paths”: {
“dateJS”: [“src”]
This is to tell TS for module @myPackage/internal-components
in src folder instead of node_modules folder. TS will look for dateJS.ts
or dateJS.d.ts
in src first and if not found, will look inside src/dateJS
directory. It will first try to locate package.json
file with typings
property specifying the main file, and if not found will default to src/dateJS/index.ts
or src/dateJS/index.d.ts
.
In order to incliude any modules not only dateJS, using asterisk wildcard like below and when you reference modules do something like “ import ModuleA from src/ModuleA
"baseUrl": ".",
"paths": {
"*": [
"src/*" //* maps any files under src.
]
}
declaration & declarationDir
There are time when we need to create ambient files to add declaration for a 3rd party libraries or any imported assets without declarations. You will most likely need to generate and consume declaration files yourself. But sometimes especially when you want to publish your NPM package, you should include the .js
and the .d.ts
files - and not the .ts
files. This makes your package really portable, and ensures it can be used by both TypeScript projects and plain old JavaScript projects. This is when declaration
comes handy
"declaration": true
What this do is to instruct the TS compiler to output declaration files (.d.ts).
Sometimes it is convenient to output declaration files into one place (the types
folder )using declarationDir
option and put into one file together:
{
"declaration": true,
"declarationDir": "types"
"outFile": "types/index.d.ts"
}
}
typeRoots
When typeRoots
is specified, TypeScript will only look for declaration files defined in the path of typeRoots
.
{
"typeRoots": [
"./node_modules/@types",//include this otherwise will
//overwrite default behaviour
"./types"
]
}
allowSyntheticDefaultImports
When allowSyntheticDefaultImports
is set to true
, you no longer need to include * in import.
So instead of import * as moduelA from "moduleA"
, you can write import moduleA from "moduleA"
.
esModuleInterop
When esModuleInterop
is set to true
, you don’t need to use the old require
keyword to import libraries .
There is a lot more to explore other than what is listed above, when I put more focus on compilation, but checkout for full list of configuration here in the documentation.
Happy reading!