tsconfig.json
Version: TypeScript 5.8
exactOptionalPropertyTypes
.erasableSyntaxOnly
.I never felt confident about my tsconfig.json
. To change that, I went through the official documentation, collected all common options, and documented them in this blog post:
This knowledge will enable you to write a tsconfig.json
that is cleaner and that you’ll fully understand.
If you don’t have the time to read the post, you can jump to the summary at the end where I show the tsconfig.json
that I use now – along with recommendations for adapting it to different use cases (npm package, app, etc.).
I also link to the tsconfig.json
recommendations by several well-known TypeScript programmers. (I went through them when I researched this post.)
I’m curious what your experiences with tsconfig.json
are: Do you agree with my choices?
This blog post only describes how to set up projects whose local modules are all ESM. It does give tips for importing CommonJS, though.
Not explained here:
allowJs
and checkJs
.composite
etc. For more information on this topic, see:
For showing computed and inferred types in source code, I use the npm package asserttt
– e.g.:
// Types of values
assertType<string>('abc');
assertType<number>(123);
// Equality of types
type Pair<T> = [T, T];
type _ = Assert<Equal<
Pair<string>, [string,string]
>>;
I’m often using trailing commas in my JSON because that’s supported for tsconfig.json
and because it helps with rearranging, copying, etc.
extends
This option lets us refer to an existing tsconfig.json
via a module specifier (as if we imported a JSON file). That file becomes the base that our tsconfig extends. That means that our tsconfig has all the option of the base, but can override any of them and can add options not mentioned in the base.
The GitHub repository tsconfig/bases
lists bases that are available under the npm namespace @tsconfig
and can be used like this (after they were installed locally via npm):
{
"extends": "@tsconfig/node-lts/tsconfig.json",
}
Alas, none of these files suit my needs. But they can serve as an inspiration for your tsconfig.
{
"include": ["src/**/*"],
}
On one hand, we have to tell TypeScript what the input files are. These are the available options:
files
: an exhaustive array of all input filesinclude
: Specifies the input files via an array of patterns with wildcards that are interpreted as relative to tsconfig.json
.exlude
: Specifies which files should be excluded from the include
set of files – via an array of patterns."compilerOptions": {
"rootDir": "src",
"outDir": "dist",
}
How TypeScript determines where to write an output file:
tsconfig.json
),rootDir
andoutDir
.As an example, consider the following tsconfig.json
:
{
"include": ["src/**/*"],
"compilerOptions": {
// Specify explicitly (don’t derive from source file paths):
"rootDir": "src",
"outDir": "dist",
// ···
}
}
Consequences of these settings:
src/util.ts
dist/util.js
src/test/integration_test.ts
dist/test/integration_test.js
src/
and test/
next to each other I like the idea of having a separate directory test/
that is a sibling of src/
. However then the output files in dist/
are more deeply nested inside the project’s directory than the input files in src/
and test/
. That means that we can’t access files such as package.json
via relative module specifiers.
tsconfig.json
:
{
"include": ["src/**/*", "test/**/*"],
"compilerOptions": {
"rootDir": ".",
"outDir": "dist",
// ···
}
}
Consequences of these settings:
src/util.ts
dist/src/util.js
test/integration_test.ts
dist/test/integration_test.js
rootDir
The default value of rootDir
depends on the input file paths. I find that too unpredictable and always specify it explicitly. It is the longest common prefix of the input file paths.
Example 1: Default value is 'src'
(relative to the project directory)
tsconfig.json
:
{
"include": ["src/**/*"],
}
Files:
/tmp/my-proj/
tsconfig.json
src/
main.ts
test/
test.ts
dist/
main.js
test/
test.js
Example 2: Default value is 'src/core/cli'
tsconfig.json
:
{
"include": ["src/**/*"],
}
Files:
/tmp/my-proj/
tsconfig.json
src/
core/
cli/
main.ts
test/
test.ts
dist/
main.js
test/
test.js
Example 3:
tsconfig.json
: Default value is '.'
{
"include": ["src/**/*", "test/**/*"],
}
Files:
/tmp/my-proj/
tsconfig.json
src/
main.ts
test/
test.ts
dist/
src/
main.js
test/
test.js
"compilerOptions": {
"sourceMap": true,
}
sourceMap
produces source map files that point from the transpiled JavaScript to the original TypeScript. That helps with debugging and is usually a good idea.
.d.ts
files (e.g. for libraries) If we want TypeScript code to consume our transpiled TypeScript code, we usually should include .d.ts
files:
"compilerOptions": {
"declaration": true,
"declarationMap": true, // enables importers to jump to source
}
Optionally, we can include the TypeScript source code in our npm package and activate declarationMap
. Then importers can, e.g., click on types or go to the definition of a value and their editor will send them to the original source code.
declarationDir
By default, each .d.ts
file is put next to its .js
file. If you want to change that, you can use option declarationDir
.
"compilerOptions": {
"newLine": "lf",
"removeComments": false,
}
The values shown above are the defaults.
newLine
configures the line endings for emitted files. Allowed values are:
"lf"
: "\n" (Unix)"crlf"
: "\r\n" (Windows)removeComments
: If active, all comments in TypeScript files are omitted in transpiled JavaScript files. I’m weakly in favor of sticking with the default and not removing comments:
"compilerOptions": {
"target": "ESNext", // sets up "lib" accordingly
"skipLibCheck": true,
}
target
target
determines which newer JavaScript syntax is transpiled to older syntax. For example, if the target is "ES5"
then an arrow function () => {}
is transpiled to a function expression function () {}
. Values can be:
"ESNext"
"ES5"
"ES6"
"ES2015"
(same as "ES6"
)"ES2016"
"ESNext"
means that nothing is ever transpiled. I find that setting easiest to deal with. It’s also the best setting if you don’t use tsc
and use type stripping (which never transpiles anything either).
"ES20YY"
target If we want to transpile, we have to pick an ECMAScript version that works for our target platforms. There are two tables that provide good overviews:
compat-table.github.io
node.green
Additionally, the official tsconfig bases all provide values for target
.
lib
lib
determines which types for built-in APIs are available – e.g. Math
or methods of built-in types:
There are categories such as "ES2024"
and "DOM"
and subcategories such as "DOM.Iterable"
and "ES2024.Promise"
.
Which values are available? We can look them up here:
The values are case-insensitive: Visual Studio Code’s autocompletion suggestions contain many capital letters; the filenames contain none. lib
values can be written either way.
When does TypeScript support a given API? It must be “available un-prefixed/flagged in at least 2 browser engines (i.e. not just 2 chromium browsers)” (source).
lib
via target
target
determines the default value of lib
: If the latter is omitted and target
is "ES20YY"
then "ES20YY.Full"
is used. However, that is not a value we can use ourselves. If we want to replicate what removing lib
does, we have to enumerate the contents of (e.g.) es2024.full.d.ts
in the TypeScript source code repository ourselves:
/// <reference lib="es2024" />
/// <reference lib="dom" />
/// <reference lib="webworker.importscripts" />
/// <reference lib="scripthost" />
/// <reference lib="dom.iterable" />
/// <reference lib="dom.asynciterable" />
In this file, we can observe an interesting phenomenon:
"ES20YY"
usually includes all of its subcategories."DOM"
doesn’t – e.g., subcategory "DOM.Iterable"
is not yet part of it.Among other things, "DOM.Iterable"
enables iteration over NodeLists – e.g.:
for (const x of document.querySelectorAll('div')) {}
skipLibCheck
skipLibCheck:false
– By default, TypeScript type-checks all .d.ts
files. This is normally not necessary but helps when a project contains hand-written .d.ts
files.
skipLibCheck:true
– If we switch it off, then TypeScript will only type-check library functionality we use in our code. That saves time – which is why I went with true
.
The types for the Node.js APIs must be installed via an npm package:
npm install @types/node
These options affect how TypeScript looks for imported modules:
"compilerOptions": {
"module": "NodeNext",
"noUncheckedSideEffectImports": true,
}
module
With this option, we specify systems for handling modules. If we set it up correctly, we also take care of the related option moduleResolution
, for which it provides good defaults. The TypeScript documentation recommends either of the following two values:
"NodeNext"
supports both CommonJS and the latest ESM features.
"moduleResolution": "NodeNext"
"NodeNext"
: It’s a moving target. But generally, functionality is only added."NodeNext"
: It supports a good mix of features – for example (source):
"Node16"
does not support import attributes (which are needed for importing JSON files)."Node18"
and "Node20"
support the outdated import assertions.require(esm)
(which is only relevant for CommonJS code, not for ESM code) is only supported by "Node20"
and "NodeNext"
."Preserve"
supports both CommonJS and the latest ESM features. It matches what most bundlers do.
"moduleResolution": "bundler"
Given that bundlers mostly mimic what Node.js does, I’m always using "NodeNext"
and haven’t encountered any issues.
Note that in both cases, TypeScript forces us to mention the complete names of local modules we import. We can’t omit filename extensions as was frequent practice when Node.js was only compiled to CommonJS. The new approach mirrors how pure-JavaScript ESM works.
module:NodeNext
implies target:ESNext
but in this case, I prefer to manually set up target
because module
and target
are not as closely related as module
and moduleResolution
. Furthermore, module:Bundler
does not imply anything.
noUncheckedSideEffectImports
By default, TypeScript does not complain if an empty import does not exist. The reason for this behavior is that this is a pattern supported by some bundlers to associate non-TypeScript artifacts with modules. And TypeScript only sees TypeScript files. This is what such an import looks like:
import './component-styles.css';
Interestingly, TypeScript normally is also OK with emptily imported TypeScript files that don’t exist. It only complains if we import something from a non-existent file.
import './does-not-exist.js'; // no error!
Setting noUncheckedSideEffectImports
to true
changes that. I’m explaining an alternative for importing non-TypeScript artifacts later.
"compilerOptions": {
"allowImportingTsExtensions": true,
// Only needed if compiling to JavaScript:
"rewriteRelativeImportExtensions": true,
}
Most non-browser JavaScript platforms now can run TypeScript code directly, without transpiling it.
This mainly affects what filename extension we use when we import a local module. Traditionally, TypeScript does not change module specifiers and we have to use the filename extension .js
in ESM modules (which is what works in the JavaScript that our TypeScript is compiled to):
import {someFunc} from './lib/utilities.js';
If we run TypeScript directly, that import statement looks like this:
import {someFunc} from './lib/utilities.ts';
This is enabled via the following settings:
allowImportingTsExtensions
: If this option is active, TypeScript won’t complain if we use the filename extension .ts
.
rewriteRelativeImportExtensions
: With this option, we can also transpile TypeScript code that is meant to be run directly. By default, TypeScript does not change the module specifiers of imports. This option comes with a few caveats:
baseUrl
and paths
into consideration (which are beyond the scope of this blog post)."exports"
and "imports"
properties in package.json
don’t look like relative paths and are therefore not rewritten either.Related option:
noEmit
option.Node.js now supports TypeScript via type stripping:
erasableSyntaxOnly
that helps with it"compilerOptions": {
"resolveJsonModule": true,
}
The option resolveJsonModule
enables us to import JSON files:
import data from './data.json' with {type: 'json'};
console.log(data.version);
Whenever we import a file basename.ext
whose extension ext
TypeScript doesn’t know, it looks for a file basename.d.ext.ts
. If it can’t find it, it raises an error. The TypeScript documentation has a good example of what such a file can look like.
There are two ways in which we can prevent TypeScript from raising errors for unknown imports.
First, we can use option allowArbitraryExtensions
to prevent any kind of error reporting in this case.
Second, we can create an ambient module declaration with a wildcard specifier – a .d.ts
file that has to be somewhere among the files that TypeScript is aware of. The following example suppresses errors for all imports with the filename extension .css
:
// ./src/globals.d.ts
declare module "*.css" {}
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
}
strict
is a must, in my opinion. With the remaining settings, you have to decide for yourself if you want the additional strictness for your code. You can start by adding all of them and see which ones cause too much trouble for your taste. In this section, we’ll ignore settings that are covered by strict
(such as noImplicitAny
).
noFallthroughCasesInSwitch
: If true
, non-empty switch
cases must end with break
, return
or throw
.
noImplicitOverride
: If true
then methods that override superclass methods must have the override
modifier.
noImplicitReturns
: If true
then an “implicit return” (the function or method ending) is only allowed if the return type is void
.
exactOptionalPropertyTypes
If true
then .colorTheme
can only be omitted and not be set to undefined
:
interface Settings {
// Absent property means “system”
colorTheme?: 'dark' | 'light';
}
const obj1: Settings = {}; // allowed
// @ts-expect-error: Type '{ colorTheme: undefined; }' is not
// assignable to type 'Settings' with
// 'exactOptionalPropertyTypes: true'. Consider adding 'undefined'
// to the types of the target's properties.
const obj2: Settings = { colorTheme: undefined };
This option also prevents optional tuple elements being undefined
(vs. missing):
const tuple1: [number, string?] = [1];
const tuple2: [number, string?] = [1, 'hello'];
// @ts-expect-error: Type '[number, undefined]' is not assignable to
// type '[number, string?]'.
const tuple3: [number, string?] = [1, undefined];
exactOptionalPropertyTypes
prevents useful patterns I’m ambivalent about this option: On one hand, enabling it prevents useful patterns such as:
type Obj = {
num?: number,
};
function createObj(num?: number): Obj {
// @ts-expect-error: Type '{ num: number | undefined; }' is not
// assignable to type 'Obj' with
// 'exactOptionalPropertyTypes: true'.
return { num };
}
exactOptionalPropertyTypes
produces better types: spreading On the other hand, it does better reflect how JavaScript works – e.g., spreading distinguishes missing properties and properties whose values are undefined
:
const optionDefaults: { a: number } = { a: 1 };
// This assignment is an error with `exactOptionalPropertyTypes`
const options: { a?: number } = { a: undefined }; // (A)
const result = { ...optionDefaults, ...options };
assertType<
{ a: number }
>(result);
assert.deepEqual(
result, { a: undefined }
);
If we had assigned an empty object in line A then the value of result
would be {a:1}
and match its type.
Object.assign()
works similarly to spreading.
exactOptionalPropertyTypes
produces better types: in
operator function f(obj: {prop?: number}): void {
if ('prop' in obj) {
// Without `exactOptionalPropertyTypes`, the type would be:
// number | undefined
assertType<number>(obj.prop);
}
}
noPropertyAccessFromIndexSignature
If true
then for types such as the following one, we cannot use the dot notation for unknown properties, only for known ones:
interface ObjectWithId {
id: string,
[key: string]: string;
}
function f(obj: ObjectWithId) {
const value1 = obj.id; // allowed
const value2 = obj['unknownProp']; // allowed
// @ts-expect-error: Property 'unknownProp' comes from an index
// signature, so it must be accessed with ['unknownProp'].
const value3 = obj.unknownProp;
}
noUncheckedIndexedAccess
noUncheckedIndexedAccess
and objects If noUncheckedIndexedAccess
is true
then the type of an unknown property is the union of undefined
and the type of the index signature:
interface ObjectWithId {
id: string,
[key: string]: string;
}
function f(obj: ObjectWithId): void {
assertType<string>(obj.id);
assertType<undefined | string>(obj['unknownProp']);
}
noUncheckedIndexedAccess
does the same for Record
(which is a mapped type):
function f(obj: Record<string, number>): void {
// Without `noUncheckedIndexedAccess`, this type would be:
// number
assertType<undefined | number>(obj['hello']);
}
noUncheckedIndexedAccess
and Arrays Option noUncheckedIndexedAccess
also affects how Arrays are handled:
const arr = ['a', 'b'];
const elem = arr[0];
// Without `noUncheckedIndexedAccess`, this type would be:
// string
assertType<undefined | string>(elem);
One common pattern for Arrays is to check the length before accessing an element. However, that pattern becomes inconvenient with noUncheckedIndexedAccess
:
function logElemAt0(arr: Array<string>) {
if (0 < arr.length) {
const elem = arr[0];
assertType<undefined | string>(elem);
console.log(elem);
}
}
Therefore, it makes more sense to use a different pattern:
function logElemAt0(arr: Array<string>) {
if (0 in arr) {
const elem = arr[0];
assertType<string>(elem);
console.log(elem);
}
}
By default, the following options produce warnings in editors, but we can also choose to produce compiler errors or ignore problems:
allowUnreachableCode
allowUnusedLabels
noUnusedLocals
noUnusedParameters
The TypeScript compiler tsc performs three tasks:
Nowadays, external tools have become popular that do #2 and #3 much faster. These have two needs:
The following subsections describe configuration options that help with those needs. They only enforce constraints via compiler errors and do not change what is emitted.
"compilerOptions": {
"noEmit": true,
}
Sometimes, we want to use tsc only for type checking – e.g., if we run TypeScript directly or use external tools for compiling TypeScript files (to JavaScript files, declaration files, etc.):
noEmit
: If true
, we can run tsc and it will only type-check the TypeScript code, it won’t emit any files.Whether or not you additionally want to remove output-related options depends on which ones of them are used by your external tools.
.js
files via transpilation "compilerOptions": {
"verbatimModuleSyntax": true, // implies "isolatedModules"
}
When compiling TypeScript to JavaScript, we need to remove the TypeScript parts. Most of those parts are easy to detect. The exception are imports: Without a (relatively simple) semantic analysis, we don’t know if an import is a (TypeScript) type or a (JavaScript) value.
If verbatimModuleSyntax
is active, we are forced to add the keyword type
to type-only imports – e.g.:
// Input: TypeScript
import {type SomeInterface, SomeClass} from './my-module.js';
// Output: JavaScript
import {SomeClass} from './my-module.js';
Note that a class is both a value and a type. In that case, no type
keyword is needed because that part of the syntax can stay in plain JavaScript.
We also need to add type
if we mention a type in an export clause:
interface MyInterface {}
export {type MyInterface};
Alternatively, we can use an inline export:
export interface MyInterface {}
isolatedModules
Activating verbatimModuleSyntax
also activates isolatedModules
, which is why we only need the former setting. The latter prevents us from using some relatively obscure features that are also problematic.
As an aside, this option enables esbuild to compile TypeScript to JavaScript (source).
.js
files via type stripping: erasableSyntaxOnly
"compilerOptions": {
"erasableSyntaxOnly": true, // TS 5.8+: type stripping
"verbatimModuleSyntax": true, // implies "isolatedModules"
}
Type stripping is the simplest way of compiling TypeScript to JavaScript:
The second point means that several TypeScript features are not supported – e.g.:
<someType>someValue // not allowed
However, the alternative as
is universally better anyway:someValue as someType // allowed
If we set erasableSyntaxOnly
to true
then TypeScript will complain during editing when we use those features.
Additionally, we should set "verbatimModuleSyntax"
to true
which performs other checks that are important for type stripping (more information).
.d.ts
files: isolatedDeclarations
"compilerOptions": {
// Only allowed if `declaration` or `composite` are true
"isolatedDeclarations": true,
}
isolatedDeclarations
mainly forces us to add return type annotations to exported functions and methods. That means that external tools won’t have to infer return types.
I’d love to always use isolatedDeclarations
, but TypeScript only allows it if option declaration
or option composite
are active. Jake Bailey explains why that is:
At the implementation level,
isolatedDeclarations
diagnostics are extra declaration diagnostics produced by the declaration transformer, which we only run whendeclaration
is enabled.Theoretically it could be implemented such that
isolatedDeclarations
enables those checks (the diagnostics actually come from us running the transformer and then throwing away the resulting AST), but it is a change from the original design.
Further reading: The TypeScript 5.5 release notes have a comprehensive section on isolated declarations.
One key issue affects importing a CommonJS module from an ESM module:
.default
of the module namespace object.module.exports
to a function.Let’s look at two options that help.
allowSyntheticDefaultImports
: type-checking default imports of CommonJS modules This option only affects type checking, not the JavaScript code emitted by TypeScript: If active, a default import of a CommonJS module refers to module.exports
(not module.exports.default
) – but only if there is no module.exports.default
.
This reflects how Node.js handles default imports of CommonJS modules (source): “When importing CommonJS modules, the module.exports
object is provided as the default export. Named exports may be available, provided by static analysis as a convenience for better ecosystem compatibility.”
Do we need this option? Yes, but it’s automatically activated if moduleResolution
is "bundler"
or if module
is "NodeNext"
(which activates esModuleInterop
which activates allowSyntheticDefaultImports
).
esModuleInterop
: better compilation of TypeScript to CommonJS code This option affects emitted CommonJS code:
false
:
import * as m from 'm'
is compiled to const m = require('m')
.import m from 'm'
is (roughly) compiled to const m = require('m')
and every access of m
is compiled to m.default
.true
:
import * as m from 'm'
assigns a new object to m
that has the same properties as module.exports
plus a property .default
that refers to module.exports
.import m from 'm'
assigns a new object to m
that has a single property .default
that refers to module.exports
. Every access of m
is compiled to m.default
..__esModule
then it is always imported as if esModuleInterop
were switched off.Do we need this option? No, since we only author ESM modules.
We can usually ignore this option:
moduleDetection
: This option configures how TypeScript determines whether a file is a script or a module. It can usually be omitted because its default "auto"
works well in most cases. You only need to explicitly set it to "force"
if your codebase has a module that has neither imports nor exports. If module
is "NodeNext"
and package.json
has "type":"module"
then even those files are interpreted as modules.If you are unhappy with the module specifiers for local imports in automatically created imports then you can take a look at the following two settings:
javascript.preferences.importModuleSpecifierEnding
typescript.preferences.importModuleSpecifierEnding
By default, VSC should now be smart enough to add filename extensions where necessary.
This is a starter tsconfig.json
file with all settings. The following subsections explain which parts you can remove – depending on your needs.
Alternatively, you can use my tsconfig configurator via the command line or online.
{
"include": ["src/**/*"],
"compilerOptions": {
// Specified explicitly (not derived from source file paths)
"rootDir": "src",
"outDir": "dist",
//========== Target and module ==========
// Nothing is ever transpiled
"target": "ESNext", // sets up "lib" accordingly
"module": "NodeNext", // sets up "moduleResolution"
// Don’t check .d.ts files
"skipLibCheck": true,
// Emptily imported modules must exist
"noUncheckedSideEffectImports": true,
// Allow importing JSON
"resolveJsonModule": true,
//========== Type checking ==========
// Essential: activates several useful options
"strict": true,
// Beyond "strict": less important
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
//========== Only JS at non-type level (enables type stripping) ==========
// Forbid non-JavaScript language constructs such as:
// JSX, enums, constructor parameter properties, namespaces
"erasableSyntaxOnly": true,
// Enforce keyword `type` for type imports etc.
"verbatimModuleSyntax": true, // implies "isolatedModules"
//========== Use filename extension .ts in imports ==========
"allowImportingTsExtensions": true,
// Only needed if compiling to JavaScript
"rewriteRelativeImportExtensions": true, // from .ts to .js
//========== Emitted files ==========
// tsc only type-checks, doesn’t emit any files
"noEmit": true,
//----- Output: .js -----
"sourceMap": true, // .js.map files
//----- Output: .d.ts -----
"declaration": true, // .d.ts files
// “Go to definition” jumps to TS source etc.
"declarationMap": true, // .d.ts.map files
// - Enforces constraints that enable efficient .d.ts generation:
// no inferred return types for exported functions etc.
// - Even though this option would be generally useful, it requires
// that `declaration` and/or `composite` are true.
"isolatedDeclarations": true,
}
}
TypeScript can transpile new JavaScript features to code that only uses older “target” features. That can help support older JavaScript engines. If that’s what you want, you must change "target"
:
"compilerOptions": {
// Transpile new JavaScript to old JavaScript
"target": "ES20YY", // sets up "lib" accordingly
}
In other words: Should all non-JavaScript syntax be erasable? If yes, then these are the main features that are forbidden: JSX, enums, constructor parameter properties, and namespaces.
The starter tsconfig only allows erasable syntax. If you want to use any of the aforementioned features, then remove section “Only JS at non-type level”.
The starter tsconfig enables .ts
in imports. If you want to use .js
, you can remove section “Use filename extension .ts in imports”.
tsc
should emit some files, remove property "noEmit"
.tsc
should not emit JavaScript, remove subsection “Output: .js”.tsc
should not emit declarations, remove subsection “Output: .d.ts”.If no files are emitted, you can remove the following properties:
"rootDir"
"outDir"
tsconfig.json
recommendations by other people base.json
tsconfig.json
Some of the sources were already mentioned earlier. These are additional sources I used: