Repository: styleguidist/react-styleguidist
Pull request #1484 (2019-11-25 23:34:21)

Refactor: Make script folder typescript

codecov[bot]: # [Codecov](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1484?src=pr&el=h1) Report
> Merging [#1484](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1484?src=pr&el=desc) into [master](https://codecov.io/gh/styleguidist/react-styleguidist/commit/e29cd4ef1130da276f19ff2878e7efbbb01fb59c?src=pr&el=desc) will **decrease** coverage by `0.15%`.
> The diff coverage is `91.86%`.

| [Impacted Files](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1484?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/scripts/utils/ensureWebpack.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1484/diff?src=pr&el=tree#diff-c3JjL3NjcmlwdHMvdXRpbHMvZW5zdXJlV2VicGFjay50cw==) | `66.66% <ø> (ø)` | |
| [src/scripts/consts.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1484/diff?src=pr&el=tree#diff-c3JjL3NjcmlwdHMvY29uc3RzLnRz) | `100% <ø> (ø)` | |
| [src/client/rsg-components/slots/index.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1484/diff?src=pr&el=tree#diff-c3JjL2NsaWVudC9yc2ctY29tcG9uZW50cy9zbG90cy9pbmRleC50cw==) | `100% <ø> (ø)` | :arrow_up: |
| [src/scripts/server.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1484/diff?src=pr&el=tree#diff-c3JjL3NjcmlwdHMvc2VydmVyLnRz) | `100% <100%> (ø)` | |
| [src/scripts/make-webpack-config.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1484/diff?src=pr&el=tree#diff-c3JjL3NjcmlwdHMvbWFrZS13ZWJwYWNrLWNvbmZpZy50cw==) | `100% <100%> (ø)` | |
| [src/scripts/utils/getUserPackageJson.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1484/diff?src=pr&el=tree#diff-c3JjL3NjcmlwdHMvdXRpbHMvZ2V0VXNlclBhY2thZ2VKc29uLnRz) | `100% <100%> (ø)` | |
| [src/scripts/utils/sanitizeConfig.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1484/diff?src=pr&el=tree#diff-c3JjL3NjcmlwdHMvdXRpbHMvc2FuaXRpemVDb25maWcudHM=) | `100% <100%> (ø)` | |
| [src/loaders/utils/parseExample.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1484/diff?src=pr&el=tree#diff-c3JjL2xvYWRlcnMvdXRpbHMvcGFyc2VFeGFtcGxlLnRz) | `100% <100%> (ø)` | |
| [src/scripts/utils/getWebpackVersion.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1484/diff?src=pr&el=tree#diff-c3JjL3NjcmlwdHMvdXRpbHMvZ2V0V2VicGFja1ZlcnNpb24udHM=) | `75% <100%> (ø)` | |
| [src/scripts/utils/mergeWebpackConfig.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1484/diff?src=pr&el=tree#diff-c3JjL3NjcmlwdHMvdXRpbHMvbWVyZ2VXZWJwYWNrQ29uZmlnLnRz) | `100% <100%> (ø)` | |
| ... and [10 more](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1484/diff?src=pr&el=tree-more) | |

elevatebart: I changed a bit the compile script as well since this was needed to avoid double mocks
Babel is annoying with globs:

https://stackoverflow.com/questions/54915206/how-do-i-tell-babel-cli-to-ignore-any-path-with-in-it
elevatebart: I believe you can merge.

Patch:
diff --git a/package-lock.json b/package-lock.json
index 4b03a4b3..acb95cb3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1391,6 +1391,16 @@
         "@babel/types": "^7.3.0"
       }
     },
+    "@types/body-parser": {
+      "version": "1.17.1",
+      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz",
+      "integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==",
+      "dev": true,
+      "requires": {
+        "@types/connect": "*",
+        "@types/node": "*"
+      }
+    },
     "@types/buble": {
       "version": "0.19.2",
       "resolved": "https://registry.npmjs.org/@types/buble/-/buble-0.19.2.tgz",
@@ -1409,6 +1419,36 @@
         "@types/node": "*"
       }
     },
+    "@types/connect": {
+      "version": "3.4.32",
+      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz",
+      "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@types/connect-history-api-fallback": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.3.tgz",
+      "integrity": "sha512-7SxFCd+FLlxCfwVwbyPxbR4khL9aNikJhrorw8nUIOqeuooc9gifBuDQOJw5kzN7i6i3vLn9G8Wde/4QDihpYw==",
+      "dev": true,
+      "requires": {
+        "@types/express-serve-static-core": "*",
+        "@types/node": "*"
+      }
+    },
+    "@types/copy-webpack-plugin": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/@types/copy-webpack-plugin/-/copy-webpack-plugin-5.0.0.tgz",
+      "integrity": "sha512-yQHocgdgES7W5Q2UyxJ5cj/E6MrV1zq3MZ8jdApS9NJKqax+rux9IE3QAbBmNCGbgivEsejrkIq3Rm76JLubkg==",
+      "dev": true,
+      "requires": {
+        "@types/minimatch": "*",
+        "@types/node": "*",
+        "@types/webpack": "*"
+      }
+    },
     "@types/enzyme": {
       "version": "3.10.3",
       "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.3.tgz",
@@ -1436,6 +1476,27 @@
       "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
       "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g=="
     },
+    "@types/express": {
+      "version": "4.17.2",
+      "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz",
+      "integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==",
+      "dev": true,
+      "requires": {
+        "@types/body-parser": "*",
+        "@types/express-serve-static-core": "*",
+        "@types/serve-static": "*"
+      }
+    },
+    "@types/express-serve-static-core": {
+      "version": "4.17.0",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.0.tgz",
+      "integrity": "sha512-Xnub7w57uvcBqFdIGoRg1KhNOeEj0vB6ykUM7uFWyxvbdE89GFyqgmUcanAriMr4YOxNFZBAWkfcWIb4WBPt3g==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*",
+        "@types/range-parser": "*"
+      }
+    },
     "@types/glob": {
       "version": "7.1.1",
       "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
@@ -1446,6 +1507,26 @@
         "@types/node": "*"
       }
     },
+    "@types/http-proxy": {
+      "version": "1.17.2",
+      "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.2.tgz",
+      "integrity": "sha512-Qfb7batJJBlI8wcrd48vHpgsOOYzQQa+OZcaIz33jkJPe8A7KktAJFmRAiR42s5BfnErdlFnOyQucq2BKy/98g==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@types/http-proxy-middleware": {
+      "version": "0.19.3",
+      "resolved": "https://registry.npmjs.org/@types/http-proxy-middleware/-/http-proxy-middleware-0.19.3.tgz",
+      "integrity": "sha512-lnBTx6HCOUeIJMLbI/LaL5EmdKLhczJY5oeXZpX/cXE4rRqb3RmV7VcMpiEfYkmTjipv3h7IAyIINe4plEv7cA==",
+      "dev": true,
+      "requires": {
+        "@types/connect": "*",
+        "@types/http-proxy": "*",
+        "@types/node": "*"
+      }
+    },
     "@types/istanbul-lib-coverage": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz",
@@ -1513,6 +1594,12 @@
         "@types/react": "*"
       }
     },
+    "@types/mime": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
+      "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==",
+      "dev": true
+    },
     "@types/minimatch": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@@ -1541,6 +1628,12 @@
       "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
       "dev": true
     },
+    "@types/range-parser": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
+      "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
+      "dev": true
+    },
     "@types/react": {
       "version": "16.9.11",
       "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.11.tgz",
@@ -1560,6 +1653,16 @@
         "@types/react": "*"
       }
     },
+    "@types/serve-static": {
+      "version": "1.13.3",
+      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz",
+      "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==",
+      "dev": true,
+      "requires": {
+        "@types/express-serve-static-core": "*",
+        "@types/mime": "*"
+      }
+    },
     "@types/source-list-map": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
@@ -1576,6 +1679,16 @@
       "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.4.tgz",
       "integrity": "sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ=="
     },
+    "@types/terser-webpack-plugin": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/@types/terser-webpack-plugin/-/terser-webpack-plugin-2.2.0.tgz",
+      "integrity": "sha512-ywqEfTm7KdKoX9aYx0zYtiFU1z6IHrIYW9FJqeay2Ea58rTPML1J0hvoztGal2Jow3bkgGKcAmEZNL+8LqUVrA==",
+      "dev": true,
+      "requires": {
+        "@types/webpack": "*",
+        "terser": "^4.3.9"
+      }
+    },
     "@types/testing-library__dom": {
       "version": "6.10.0",
       "resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-6.10.0.tgz",
@@ -1595,6 +1708,12 @@
         "@types/testing-library__dom": "*"
       }
     },
+    "@types/type-detect": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/@types/type-detect/-/type-detect-4.0.1.tgz",
+      "integrity": "sha512-0+S1S9Iq0oJ9w9IaBC5W/z1WsPNDUIAJG+THGmqR4vUAxUPCzIY+dApTvyGsaBUWjafTDL0Dg8Z9+iRuk3/BQA==",
+      "dev": true
+    },
     "@types/uglify-js": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz",
@@ -1621,6 +1740,19 @@
         "source-map": "^0.6.0"
       }
     },
+    "@types/webpack-dev-server": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/@types/webpack-dev-server/-/webpack-dev-server-3.4.0.tgz",
+      "integrity": "sha512-cNxiXfGnMxVgXOjmo/SQdsIX2Muan0A44AvPgVfz9y1PfWogOJGEy+/nFkrF/luvFxykJXT+fZYPpyuIGZtRZg==",
+      "dev": true,
+      "requires": {
+        "@types/connect-history-api-fallback": "*",
+        "@types/express": "*",
+        "@types/http-proxy-middleware": "*",
+        "@types/serve-static": "*",
+        "@types/webpack": "*"
+      }
+    },
     "@types/webpack-env": {
       "version": "1.14.1",
       "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.14.1.tgz",
@@ -6301,9 +6433,9 @@
       "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ=="
     },
     "handlebars": {
-      "version": "4.5.2",
-      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.2.tgz",
-      "integrity": "sha512-29Zxv/cynYB7mkT1rVWQnV7mGX6v7H/miQ6dbEpYTKq5eJBN7PsRB+ViYJlcT6JINTSu4dVB9kOqEun78h6Exg==",
+      "version": "4.5.3",
+      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz",
+      "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==",
       "dev": true,
       "requires": {
         "neo-async": "^2.6.0",
diff --git a/package.json b/package.json
index 6fcf3a22..65211a51 100644
--- a/package.json
+++ b/package.json
@@ -103,6 +103,7 @@
     "@testing-library/jest-dom": "^4.2.3",
     "@testing-library/react": "^9.3.0",
     "@types/buble": "^0.19.2",
+    "@types/copy-webpack-plugin": "^5.0.0",
     "@types/enzyme": "^3.10.3",
     "@types/estree": "^0.0.39",
     "@types/jest": "^24.0.21",
@@ -113,6 +114,9 @@
     "@types/prismjs": "^1.16.0",
     "@types/react": "^16.9.11",
     "@types/react-dom": "^16.9.3",
+    "@types/terser-webpack-plugin": "^2.2.0",
+    "@types/type-detect": "^4.0.1",
+    "@types/webpack-dev-server": "^3.4.0",
     "@types/webpack-env": "^1.14.1",
     "@typescript-eslint/eslint-plugin": "^2.6.0",
     "@typescript-eslint/parser": "^2.6.0",
@@ -149,6 +153,7 @@
     "react-test-renderer": "^16.11.0",
     "strip-shebang": "^1.0.2",
     "style-loader": "^1.0.0",
+    "tapable": "^1.1.3",
     "typescript": "^3.6.4",
     "url-loader": "^2.2.0",
     "webpack": "^4.41.2",
@@ -168,7 +173,7 @@
     "start:ui": "node lib/bin/styleguidist.js server",
     "lint": "eslint . --cache --fix --ext .js,.ts,.tsx",
     "flow": "flow",
-    "compile": "babel --delete-dir-on-start --ignore '**/*.spec.js','**/__*__/*' --extensions '.js,.ts,.tsx' -d lib/ src/",
+    "compile": "babel --delete-dir-on-start --ignore **/__mocks__/*,**/__tests__/*,**/*.spec.*,**/*.d.ts --extensions '.js,.ts,.tsx' -d lib/ src/",
     "compile:watch": "npm run compile -- --watch",
     "prepublishOnly": "npm run compile",
     "build:basic": "node lib/bin/styleguidist.js build --config examples/basic/styleguide.config.js",
diff --git a/src/bin/styleguidist.js b/src/bin/styleguidist.js
index 08e062f0..5306207d 100755
--- a/src/bin/styleguidist.js
+++ b/src/bin/styleguidist.js
@@ -8,10 +8,10 @@ const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
 const webpackDevServerUtils = require('react-dev-utils/WebpackDevServerUtils');
 const openBrowser = require('react-dev-utils/openBrowser');
 const logger = require('glogg')('rsg');
-const getConfig = require('../scripts/config');
-const setupLogger = require('../scripts/logger');
+const getConfig = require('../scripts/config').default;
+const setupLogger = require('../scripts/logger').default;
 const consts = require('../scripts/consts');
-const StyleguidistError = require('../scripts/utils/error');
+const StyleguidistError = require('../scripts/utils/error').default;
 
 const argv = mri(process.argv.slice(2));
 const command = argv._[0];
@@ -92,7 +92,7 @@ function updateConfig(prevConfig) {
 function commandBuild() {
 	console.log('Building style guide...');
 
-	const build = require('../scripts/build');
+	const build = require('../scripts/build').default;
 	const compiler = build(config, err => {
 		if (err) {
 			console.error(err);
@@ -119,7 +119,7 @@ function commandBuild() {
 function commandServer() {
 	let spinner;
 
-	const server = require('../scripts/server');
+	const server = require('../scripts/server').default;
 	const compiler = server(config, err => {
 		if (err) {
 			console.error(err);
diff --git a/src/client/rsg-components/Argument/index.ts b/src/client/rsg-components/Argument/index.ts
index 7de9b7bf..211aa6e5 100644
--- a/src/client/rsg-components/Argument/index.ts
+++ b/src/client/rsg-components/Argument/index.ts
@@ -1,1 +1,2 @@
-export { default, ArgumentProps } from 'rsg-components/Argument/ArgumentRenderer';
+export { default } from 'rsg-components/Argument/ArgumentRenderer';
+export * from 'rsg-components/Argument/ArgumentRenderer';
diff --git a/src/client/rsg-components/Examples/Examples.tsx b/src/client/rsg-components/Examples/Examples.tsx
index 1bd99cf2..9a835dc8 100644
--- a/src/client/rsg-components/Examples/Examples.tsx
+++ b/src/client/rsg-components/Examples/Examples.tsx
@@ -5,24 +5,17 @@ import Markdown from 'rsg-components/Markdown';
 import ExamplesRenderer from 'rsg-components/Examples/ExamplesRenderer';
 import { useStyleGuideContext } from 'rsg-components/Context';
 
-interface ExamplesRenderer {
+export interface ExamplesRenderer {
 	examples: Rsg.Example[];
 	name?: string;
 	exampleMode?: string;
 }
 
-function isTypedExample(ex: any): ex is Rsg.CodeExample | Rsg.MarkdownExample {
-	return !!ex.type;
-}
-
 const Examples: React.FunctionComponent<ExamplesRenderer> = ({ examples, name, exampleMode }) => {
 	const { codeRevision } = useStyleGuideContext();
 	return (
 		<ExamplesRenderer name={name}>
 			{examples.map((example, index) => {
-				if (!isTypedExample(example)) {
-					return null;
-				}
 				switch (example.type) {
 					case 'code':
 						return (
diff --git a/src/client/rsg-components/Markdown/Pre/index.ts b/src/client/rsg-components/Markdown/Pre/index.ts
index d94035f7..10052540 100644
--- a/src/client/rsg-components/Markdown/Pre/index.ts
+++ b/src/client/rsg-components/Markdown/Pre/index.ts
@@ -1,1 +1,2 @@
-export { default, PreProps } from 'rsg-components/Markdown/Pre/PreRenderer';
+export { default } from 'rsg-components/Markdown/Pre/PreRenderer';
+export * from 'rsg-components/Markdown/Pre/PreRenderer';
diff --git a/src/client/rsg-components/slots/index.ts b/src/client/rsg-components/slots/index.ts
index af5c6c0d..37ce27c0 100644
--- a/src/client/rsg-components/slots/index.ts
+++ b/src/client/rsg-components/slots/index.ts
@@ -9,6 +9,7 @@ export const DOCS_TAB_USAGE = 'rsg-usage';
 
 const toolbar = [IsolateButton];
 
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
 export default (config?: Rsg.ProcessedStyleguidistConfig) => {
 	return {
 		sectionToolbar: toolbar,
diff --git a/src/loaders/utils/__tests__/parseExample.spec.js b/src/loaders/utils/__tests__/parseExample.spec.ts
similarity index 100%
rename from src/loaders/utils/__tests__/parseExample.spec.js
rename to src/loaders/utils/__tests__/parseExample.spec.ts
diff --git a/src/loaders/utils/parseExample.js b/src/loaders/utils/parseExample.ts
similarity index 65%
rename from src/loaders/utils/parseExample.js
rename to src/loaders/utils/parseExample.ts
index 3d23b4fe..5c05e178 100644
--- a/src/loaders/utils/parseExample.js
+++ b/src/loaders/utils/parseExample.ts
@@ -1,39 +1,28 @@
-// @flow
 import lowercaseKeys from 'lowercase-keys';
 import { DOCS_DOCUMENTING } from '../../scripts/consts';
 
-type Example = {
-	content: string,
-	lang: ?string,
-	settings: {},
-};
-
-type Error = {
-	error: string,
-};
-
-type UpdateExampleFn = (example: Example) => Example;
-
 const hasStringModifiers = (modifiers: string): boolean => !!modifiers.match(/^[ \w]+$/);
 
+interface Error {
+	error: string;
+}
 /**
  * Split fenced code block header to lang and modifiers, parse modifiers, lowercase modifier keys, etc.
  */
 export default function parseExample(
 	content: string,
-	lang: ?string,
-	modifiers: ?string,
-	updateExample: UpdateExampleFn = x => x
-): Example | Error {
-	const example = {
+	lang?: string | null,
+	modifiers?: string,
+	updateExample: (example: Rsg.CodeExample) => Rsg.CodeExample = x => x
+): Rsg.CodeExample | Error {
+	const example: Rsg.CodeExample = {
 		content,
 		lang,
-		settings: {},
 	};
 
 	if (modifiers) {
 		if (hasStringModifiers(modifiers)) {
-			example.settings = modifiers.split(' ').reduce((obj, modifier) => {
+			example.settings = modifiers.split(' ').reduce((obj: Record<string, any>, modifier) => {
 				obj[modifier] = true;
 				return obj;
 			}, {});
@@ -51,6 +40,6 @@ export default function parseExample(
 	const updatedExample = updateExample(example);
 	return {
 		...updatedExample,
-		settings: lowercaseKeys(updatedExample.settings),
+		settings: lowercaseKeys(updatedExample.settings || {}),
 	};
 }
diff --git a/src/scripts/__mocks__/build.js b/src/scripts/__mocks__/build.js
deleted file mode 100644
index 3f9fdeec..00000000
--- a/src/scripts/__mocks__/build.js
+++ /dev/null
@@ -1,4 +0,0 @@
-module.exports = function server(config, callback) {
-	callback(null, { stats: true });
-	return {};
-};
diff --git a/src/scripts/__mocks__/build.ts b/src/scripts/__mocks__/build.ts
new file mode 100644
index 00000000..e19927c9
--- /dev/null
+++ b/src/scripts/__mocks__/build.ts
@@ -0,0 +1,7 @@
+export default function build(
+	config: Rsg.SanitizedStyleguidistConfig,
+	callback: (err: Error | null, stats: any) => void
+) {
+	callback(null, { stats: true });
+	return {};
+}
diff --git a/src/scripts/__mocks__/server.js b/src/scripts/__mocks__/server.js
deleted file mode 100644
index 85380fbf..00000000
--- a/src/scripts/__mocks__/server.js
+++ /dev/null
@@ -1,4 +0,0 @@
-module.exports = function server(config, callback) {
-	callback(null);
-	return {};
-};
diff --git a/src/scripts/__mocks__/server.ts b/src/scripts/__mocks__/server.ts
new file mode 100644
index 00000000..5b61f100
--- /dev/null
+++ b/src/scripts/__mocks__/server.ts
@@ -0,0 +1,7 @@
+export default function server(
+	config: Rsg.SanitizedStyleguidistConfig,
+	callback: (err: Error | null) => void
+) {
+	callback(null);
+	return {};
+}
diff --git a/src/scripts/build.js b/src/scripts/build.js
deleted file mode 100644
index 66f682d2..00000000
--- a/src/scripts/build.js
+++ /dev/null
@@ -1,9 +0,0 @@
-const webpack = require('webpack');
-const makeWebpackConfig = require('./make-webpack-config');
-
-module.exports = function build(config, callback) {
-	return webpack(makeWebpackConfig(config, 'production'), (err, stats) => {
-		// require('fs').writeFileSync('stats.json', JSON.stringify(stats.toJson()));
-		callback(err, stats);
-	});
-};
diff --git a/src/scripts/build.ts b/src/scripts/build.ts
new file mode 100644
index 00000000..07f22c1c
--- /dev/null
+++ b/src/scripts/build.ts
@@ -0,0 +1,12 @@
+import webpack from 'webpack';
+import makeWebpackConfig from './make-webpack-config';
+
+export default function build(
+	config: Rsg.SanitizedStyleguidistConfig,
+	callback: (err: Error, stats: webpack.Stats) => void
+) {
+	return webpack(makeWebpackConfig(config, 'production'), (err, stats) => {
+		// require('fs').writeFileSync('stats.json', JSON.stringify(stats.toJson()));
+		callback(err, stats);
+	});
+}
diff --git a/src/scripts/config.js b/src/scripts/config.ts
similarity index 81%
rename from src/scripts/config.js
rename to src/scripts/config.ts
index 44d06597..f4b06218 100644
--- a/src/scripts/config.js
+++ b/src/scripts/config.ts
@@ -9,6 +9,22 @@ import sanitizeConfig from './utils/sanitizeConfig';
 
 const CONFIG_FILENAME = 'styleguide.config.js';
 
+/**
+ * Try to find config file up the file tree.
+ *
+ * @return {string|boolean} Config absolute file path.
+ */
+function findConfigFile(): string | false {
+	let configDir;
+	try {
+		configDir = findup.sync(process.cwd(), CONFIG_FILENAME);
+	} catch (exception) {
+		return false;
+	}
+
+	return path.join(configDir, CONFIG_FILENAME);
+}
+
 /**
  * Read, parse and validate config file or passed config.
  *
@@ -16,8 +32,11 @@ const CONFIG_FILENAME = 'styleguide.config.js';
  * @param {function} [update] Change config object before running validation on it.
  * @returns {object}
  */
-function getConfig(config, update) {
-	let configFilepath;
+function getConfig(
+	config?: string | Rsg.StyleguidistConfig,
+	update?: (conf: Rsg.StyleguidistConfig) => Rsg.StyleguidistConfig
+): Rsg.SanitizedStyleguidistConfig {
+	let configFilepath: string | false = false;
 	if (isString(config)) {
 		// Load config from a given file
 		configFilepath = path.resolve(process.cwd(), config);
@@ -35,6 +54,10 @@ function getConfig(config, update) {
 		config = require(configFilepath);
 	}
 
+	if (!config || isString(config)) {
+		return {} as any;
+	}
+
 	if (update) {
 		config = update(config);
 	}
@@ -42,7 +65,7 @@ function getConfig(config, update) {
 	const configDir = configFilepath ? path.dirname(configFilepath) : process.cwd();
 
 	try {
-		return sanitizeConfig(config, schema, configDir);
+		return sanitizeConfig(config, schema, configDir) as any;
 	} catch (exception) {
 		if (exception instanceof StyleguidistError) {
 			throw new StyleguidistError(
@@ -55,20 +78,4 @@ function getConfig(config, update) {
 	}
 }
 
-/**
- * Try to find config file up the file tree.
- *
- * @return {string|boolean} Config absolute file path.
- */
-function findConfigFile() {
-	let configDir;
-	try {
-		configDir = findup.sync(process.cwd(), CONFIG_FILENAME);
-	} catch (exception) {
-		return false;
-	}
-
-	return path.join(configDir, CONFIG_FILENAME);
-}
-
-module.exports = getConfig;
+export default getConfig;
diff --git a/src/scripts/consts.js b/src/scripts/consts.ts
similarity index 100%
rename from src/scripts/consts.js
rename to src/scripts/consts.ts
diff --git a/src/scripts/create-server.js b/src/scripts/create-server.ts
similarity index 53%
rename from src/scripts/create-server.js
rename to src/scripts/create-server.ts
index 102dbe68..36eea5c8 100644
--- a/src/scripts/create-server.js
+++ b/src/scripts/create-server.ts
@@ -1,9 +1,12 @@
-const webpack = require('webpack');
-const WebpackDevServer = require('webpack-dev-server');
-const merge = require('webpack-merge');
-const makeWebpackConfig = require('./make-webpack-config');
+import webpack, { Configuration } from 'webpack';
+import WebpackDevServer from 'webpack-dev-server';
+import merge from 'webpack-merge';
+import makeWebpackConfig from './make-webpack-config';
 
-module.exports = function createServer(config, env) {
+export default function createServer(
+	config: Rsg.SanitizedStyleguidistConfig,
+	env: 'development' | 'production' | 'none'
+): { app: WebpackDevServer; compiler: webpack.Compiler } {
 	const webpackConfig = makeWebpackConfig(config, env);
 	const webpackDevServerConfig = merge(
 		{
@@ -17,11 +20,11 @@ module.exports = function createServer(config, env) {
 			},
 			watchContentBase: config.assetsDir !== undefined,
 			stats: webpackConfig.stats || {},
-		},
-		webpackConfig.devServer,
+		} as Configuration,
+		webpackConfig.devServer as Configuration,
 		{
 			contentBase: config.assetsDir,
-		}
+		} as Configuration
 	);
 
 	const compiler = webpack(webpackConfig);
@@ -29,8 +32,8 @@ module.exports = function createServer(config, env) {
 
 	// User defined customizations
 	if (config.configureServer) {
-		config.configureServer(devServer.app, env);
+		config.configureServer((devServer as any).app, env);
 	}
 
 	return { app: devServer, compiler };
-};
+}
diff --git a/src/scripts/index.js b/src/scripts/index.js
deleted file mode 100644
index 0be56c73..00000000
--- a/src/scripts/index.js
+++ /dev/null
@@ -1,54 +0,0 @@
-// Make sure user has webpack installed
-require('./utils/ensureWebpack');
-
-const build = require('./build');
-const server = require('./server');
-const makeWebpackConfig = require('./make-webpack-config');
-const getConfig = require('./config');
-const setupLogger = require('./logger');
-
-/**
- * Initialize Styleguide API.
- *
- * @param {object} [config] Styleguidist config.
- * @returns {object} API.
- */
-module.exports = function(config) {
-	config = getConfig(config, conf => {
-		setupLogger(conf.logger, conf.verbose, {});
-		return conf;
-	});
-
-	return {
-		/**
-		 * Build style guide.
-		 *
-		 * @param {Function} callback callback(err, config, stats).
-		 * @return {Compiler} Webpack Compiler instance.
-		 */
-		build(callback) {
-			return build(config, (err, stats) => callback(err, config, stats));
-		},
-
-		/**
-		 * Start style guide dev server.
-		 *
-		 * @param {Function} callback callback(err, config).
-		 * @return {ServerInfo.App} Webpack-Dev-Server.
-		 * @return {ServerInfo.Compiler} Webpack Compiler instance.
-		 */
-		server(callback) {
-			return server(config, err => callback(err, config));
-		},
-
-		/**
-		 * Return Styleguidist Webpack config.
-		 *
-		 * @param {string} [env=production] 'production' or 'development'.
-		 * @return {object}
-		 */
-		makeWebpackConfig(env) {
-			return makeWebpackConfig(config, env || 'production');
-		},
-	};
-};
diff --git a/src/scripts/index.ts b/src/scripts/index.ts
new file mode 100644
index 00000000..d3fd8273
--- /dev/null
+++ b/src/scripts/index.ts
@@ -0,0 +1,60 @@
+import webpack from 'webpack';
+// Make sure user has webpack installed
+import './utils/ensureWebpack';
+
+import makeWebpackConfig from './make-webpack-config';
+import build from './build';
+import server from './server';
+import getConfig from './config';
+import setupLogger from './logger';
+
+/**
+ * Initialize Styleguide API.
+ *
+ * NOTE: here we export an CommonJs module to avoid breaking the API
+ * TODO: in the next major version, use `export default` instead
+ *
+ * @param {object} [config] Styleguidist config.
+ * @returns {object} API.
+ */
+module.exports = function(configArg?: Rsg.StyleguidistConfig | string) {
+	const config = getConfig(configArg, conf => {
+		setupLogger(conf.logger as Record<string, (msg: string) => void>, conf.verbose, {});
+		return conf;
+	});
+
+	return {
+		/**
+		 * Build style guide.
+		 *
+		 * @param {Function} callback callback(err, config, stats).
+		 * @return {Compiler} Webpack Compiler instance.
+		 */
+		build(
+			callback: (err: Error, config: Rsg.SanitizedStyleguidistConfig, stats: webpack.Stats) => void
+		) {
+			return build(config, (err: Error, stats: webpack.Stats) => callback(err, config, stats));
+		},
+
+		/**
+		 * Start style guide dev server.
+		 *
+		 * @param {Function} callback callback(err, config).
+		 * @return {ServerInfo.App} Webpack-Dev-Server.
+		 * @return {ServerInfo.Compiler} Webpack Compiler instance.
+		 */
+		server(callback: (err: Error | undefined, config: Rsg.StyleguidistConfig) => void) {
+			return server(config, err => callback(err, config));
+		},
+
+		/**
+		 * Return Styleguidist Webpack config.
+		 *
+		 * @param {string} [env=production] 'production' or 'development'.
+		 * @return {object}
+		 */
+		makeWebpackConfig(env?: 'development' | 'production' | 'none') {
+			return makeWebpackConfig(config, env || 'production');
+		},
+	};
+};
diff --git a/src/scripts/logger.js b/src/scripts/logger.js
deleted file mode 100644
index f791854f..00000000
--- a/src/scripts/logger.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/* eslint-disable no-console */
-
-const _ = require('lodash/fp');
-const kleur = require('kleur');
-const logger = require('glogg')('rsg');
-
-const format = message => message.trim() + '\n';
-
-const printers = {
-	info: message => console.log(format(message)),
-	warn: message => console.warn(kleur.yellow(`Warning: ${format(message)}`)),
-	debug: message => console.log(format(message)),
-};
-
-/**
- * Setup up logger:
- * const logger = require('glogg')('rsg')
- * logger.info('Drinking coffee...')
- *
- * @param {Object} methods Custom methods
- * @param {bool} verbose Print debug messages
- * @param {Object} [defaults] Default methods
- */
-module.exports = function setupLogger(methods, verbose, defaults) {
-	_.flow(
-		_.defaults(defaults || printers),
-		_.omit(verbose || ['debug']),
-		_.toPairs,
-		_.forEach(printer => logger.on(printer[0], printer[1]))
-	)(methods);
-};
diff --git a/src/scripts/logger.ts b/src/scripts/logger.ts
new file mode 100644
index 00000000..92f94460
--- /dev/null
+++ b/src/scripts/logger.ts
@@ -0,0 +1,37 @@
+/* eslint-disable no-console */
+
+import _ from 'lodash/fp';
+import kleur from 'kleur';
+import loggerMaker from 'glogg';
+
+const logger = loggerMaker('rsg');
+
+const format = (message: string) => message.trim() + '\n';
+
+const printers: Record<string, (message: string) => void> = {
+	info: (message: string) => console.log(format(message)),
+	warn: (message: string) => console.warn(kleur.yellow(`Warning: ${format(message)}`)),
+	debug: (message: string) => console.log(format(message)),
+};
+
+/**
+ * Setup up logger:
+ * const logger = require('glogg')('rsg')
+ * logger.info('Drinking coffee...')
+ *
+ * @param {Object} methods Custom methods
+ * @param {bool} verbose Print debug messages
+ * @param {Object} [defaults] Default methods
+ */
+export default function setupLogger(
+	methods?: Record<string, (message: string) => void>,
+	verbose?: boolean,
+	defaults?: Record<string, (message: string) => void>
+) {
+	_.flow(
+		_.defaults(defaults || printers),
+		_.omit(verbose ? [] : ['debug']),
+		_.toPairs,
+		_.forEach((printer: any[]) => logger.on(printer[0], printer[1]))
+	)(methods);
+}
diff --git a/src/scripts/make-webpack-config.js b/src/scripts/make-webpack-config.ts
similarity index 63%
rename from src/scripts/make-webpack-config.js
rename to src/scripts/make-webpack-config.ts
index 066b5eb5..7d6a4a52 100644
--- a/src/scripts/make-webpack-config.js
+++ b/src/scripts/make-webpack-config.ts
@@ -1,22 +1,26 @@
-const path = require('path');
-const castArray = require('lodash/castArray');
-const webpack = require('webpack');
-const TerserPlugin = require('terser-webpack-plugin');
-const MiniHtmlWebpackPlugin = require('mini-html-webpack-plugin');
-const MiniHtmlWebpackTemplate = require('@vxna/mini-html-webpack-template');
-const { CleanWebpackPlugin } = require('clean-webpack-plugin');
-const CopyWebpackPlugin = require('copy-webpack-plugin');
-const merge = require('webpack-merge');
-const forEach = require('lodash/forEach');
-const isFunction = require('lodash/isFunction');
-const mergeWebpackConfig = require('./utils/mergeWebpackConfig');
-const StyleguidistOptionsPlugin = require('./utils/StyleguidistOptionsPlugin');
+import path from 'path';
+import castArray from 'lodash/castArray';
+import webpack, { Configuration } from 'webpack';
+import TerserPlugin from 'terser-webpack-plugin';
+import MiniHtmlWebpackPlugin from 'mini-html-webpack-plugin';
+import MiniHtmlWebpackTemplate from '@vxna/mini-html-webpack-template';
+import { CleanWebpackPlugin } from 'clean-webpack-plugin';
+import CopyWebpackPlugin from 'copy-webpack-plugin';
+import merge from 'webpack-merge';
+import forEach from 'lodash/forEach';
+import isFunction from 'lodash/isFunction';
+
+import StyleguidistOptionsPlugin from './utils/StyleguidistOptionsPlugin';
+import mergeWebpackConfig from './utils/mergeWebpackConfig';
 
 const RENDERER_REGEXP = /Renderer$/;
 
 const sourceDir = path.resolve(__dirname, '../client');
 
-module.exports = function(config, env) {
+export default function(
+	config: Rsg.SanitizedStyleguidistConfig,
+	env: 'development' | 'production' | 'none'
+): Configuration {
 	process.env.NODE_ENV = process.env.NODE_ENV || env;
 
 	const isProd = env === 'production';
@@ -32,7 +36,7 @@ module.exports = function(config, env) {
 		template,
 	};
 
-	let webpackConfig = {
+	let webpackConfig: Configuration = {
 		entry: config.require.concat([path.resolve(sourceDir, 'index')]),
 		mode: env,
 		output: {
@@ -60,6 +64,7 @@ module.exports = function(config, env) {
 
 	if (isProd) {
 		const minimizer = new TerserPlugin({
+			/* eslint-disable @typescript-eslint/camelcase */
 			terserOptions: {
 				ie8: false,
 				ecma: 5,
@@ -79,6 +84,7 @@ module.exports = function(config, env) {
 					keep_fnames: true,
 				},
 			},
+			/* eslint-enable @typescript-eslint/camelcase */
 		});
 		webpackConfig = merge(webpackConfig, {
 			output: {
@@ -89,7 +95,7 @@ module.exports = function(config, env) {
 				new CleanWebpackPlugin({
 					root: config.styleguideDir,
 					verbose: config.verbose === true,
-				}),
+				} as any),
 				new CopyWebpackPlugin(
 					config.assetsDir ? castArray(config.assetsDir).map(dir => ({ from: dir })) : []
 				),
@@ -117,23 +123,26 @@ module.exports = function(config, env) {
 		});
 	}
 
-	// Custom style guide components
-	if (config.styleguideComponents) {
-		forEach(config.styleguideComponents, (filepath, name) => {
-			const fullName = name.match(RENDERER_REGEXP)
-				? `${name.replace(RENDERER_REGEXP, '')}/${name}`
-				: name;
-			webpackConfig.resolve.alias[`rsg-components/${fullName}`] = filepath;
-		});
-	}
+	const alias = webpackConfig.resolve ? webpackConfig.resolve.alias : undefined;
+	if (alias) {
+		// Custom style guide components
+		if (config.styleguideComponents) {
+			forEach(config.styleguideComponents, (filepath, name) => {
+				const fullName = name.match(RENDERER_REGEXP)
+					? `${name.replace(RENDERER_REGEXP, '')}/${name}`
+					: name;
+				alias[`rsg-components/${fullName}`] = filepath;
+			});
+		}
 
-	// Add components folder alias at the end, so users can override our components
-	// to customize the style guide (their aliases should be before this one)
-	webpackConfig.resolve.alias['rsg-components'] = path.resolve(sourceDir, 'rsg-components');
+		// Add components folder alias at the end, so users can override our components
+		// to customize the style guide (their aliases should be before this one)
+		alias['rsg-components'] = path.resolve(sourceDir, 'rsg-components');
+	}
 
 	if (config.dangerouslyUpdateWebpackConfig) {
 		webpackConfig = config.dangerouslyUpdateWebpackConfig(webpackConfig, env);
 	}
 
 	return webpackConfig;
-};
+}
diff --git a/src/scripts/schemas/config.ts b/src/scripts/schemas/config.ts
index b5d51151..12cfe8c3 100644
--- a/src/scripts/schemas/config.ts
+++ b/src/scripts/schemas/config.ts
@@ -28,7 +28,21 @@ const DEFAULT_COMPONENTS_PATTERN =
 
 const logger = glogg('rsg');
 
-const configSchema = {
+export type EXPAND_MODE = 'expand' | 'collapse' | 'hide';
+
+export type StyleguidistConfigKey = keyof Rsg.SanitizedStyleguidistConfig;
+
+export interface ConfigSchemaOptions {
+	process?(value: any, config: Rsg.StyleguidistConfig, rootDir: string): any;
+	default?: any;
+	required?: boolean | ((config?: Rsg.StyleguidistConfig) => string | boolean);
+	deprecated?: string;
+	removed?: string;
+	type?: string | string[];
+	example?: any;
+}
+
+const configSchema: Record<StyleguidistConfigKey, ConfigSchemaOptions> = {
 	assetsDir: {
 		type: ['array', 'existing directory path'],
 		example: 'assets',
@@ -102,8 +116,6 @@ const configSchema = {
 				// FolderName.md when component definition file is index.js
 				path.join(path.dirname(componentPath), path.basename(path.dirname(componentPath)) + '.md'),
 			];
-			// FIXME: This looks like a bug in ESLint
-			// eslint-disable-next-line no-unused-vars
 			for (const file of files) {
 				const existingFile = fileExistsCaseInsensitive(file);
 				if (existingFile) {
diff --git a/src/scripts/server.js b/src/scripts/server.js
deleted file mode 100644
index 098426ff..00000000
--- a/src/scripts/server.js
+++ /dev/null
@@ -1,10 +0,0 @@
-const createServer = require('./create-server');
-
-module.exports = function server(config, callback) {
-	const env = 'development';
-	const serverInfo = createServer(config, env);
-
-	serverInfo.app.listen(config.serverPort, config.serverHost, callback);
-
-	return serverInfo;
-};
diff --git a/src/scripts/server.ts b/src/scripts/server.ts
new file mode 100644
index 00000000..01e083b7
--- /dev/null
+++ b/src/scripts/server.ts
@@ -0,0 +1,15 @@
+import WebpackDevServer from 'webpack-dev-server';
+import webpack from 'webpack';
+import createServer from './create-server';
+
+export default function server(
+	config: Rsg.SanitizedStyleguidistConfig,
+	callback: (error?: Error) => void
+): { app: WebpackDevServer; compiler: webpack.Compiler } {
+	const env = 'development';
+	const serverInfo = createServer(config, env);
+
+	serverInfo.app.listen(config.serverPort, config.serverHost, callback);
+
+	return serverInfo;
+}
diff --git a/src/scripts/utils/StyleguidistOptionsPlugin.js b/src/scripts/utils/StyleguidistOptionsPlugin.ts
similarity index 50%
rename from src/scripts/utils/StyleguidistOptionsPlugin.js
rename to src/scripts/utils/StyleguidistOptionsPlugin.ts
index 063608ae..9b6c7cf5 100644
--- a/src/scripts/utils/StyleguidistOptionsPlugin.js
+++ b/src/scripts/utils/StyleguidistOptionsPlugin.ts
@@ -1,28 +1,33 @@
+import { Tapable } from 'tapable';
+import { Compiler, compilation } from 'webpack';
+
 // Webpack plugin that makes Styleguidist config available for Styleguidist webpack loaders.
 // It will be available as `this._styleguidist`.
 //
 // Other working in webpack 2 way is to use LoaderOptionsPlugin, but it has problems.
 // See this issue for details: https://github.com/styleguidist/react-styleguidist/issues/328
 
-class StyleguidistOptionsPlugin {
-	constructor(options) {
+export default class StyleguidistOptionsPlugin implements Tapable.Plugin {
+	private options: Rsg.SanitizedStyleguidistConfig;
+	public constructor(options: Rsg.SanitizedStyleguidistConfig) {
 		this.options = options;
 		this.plugin = this.plugin.bind(this);
 	}
 
-	plugin(compilation) {
-		const pluginFunc = (context, module) => {
+	public plugin(compil: compilation.Compilation) {
+		const pluginFunc = (
+			context: { _styleguidist?: Rsg.SanitizedStyleguidistConfig },
+			module: { resource?: boolean }
+		) => {
 			if (!module.resource) {
 				return;
 			}
 			context._styleguidist = this.options;
 		};
-		compilation.hooks.normalModuleLoader.tap('StyleguidistOptionsPlugin', pluginFunc);
+		compil.hooks.normalModuleLoader.tap('StyleguidistOptionsPlugin', pluginFunc);
 	}
 
-	apply(compiler) {
+	public apply(compiler: Compiler) {
 		compiler.hooks.compilation.tap('StyleguidistOptionsPlugin', this.plugin);
 	}
 }
-
-module.exports = StyleguidistOptionsPlugin;
diff --git a/src/scripts/utils/ensureWebpack.js b/src/scripts/utils/ensureWebpack.ts
similarity index 83%
rename from src/scripts/utils/ensureWebpack.js
rename to src/scripts/utils/ensureWebpack.ts
index 47da2cb8..b62e8026 100644
--- a/src/scripts/utils/ensureWebpack.js
+++ b/src/scripts/utils/ensureWebpack.ts
@@ -3,9 +3,9 @@
  * usage of build tools that contains webpack as their own dependency, like Create React App.
  */
 
-const getWebpackVersion = require('./getWebpackVersion');
-const StyleguidistError = require('./error');
-const consts = require('../consts');
+import getWebpackVersion from './getWebpackVersion';
+import StyleguidistError from './error';
+import * as consts from '../consts';
 
 const MIN_WEBPACK_VERSION = 4;
 const webpackVersion = getWebpackVersion();
diff --git a/src/scripts/utils/error.js b/src/scripts/utils/error.ts
similarity index 70%
rename from src/scripts/utils/error.js
rename to src/scripts/utils/error.ts
index 5f3ff01c..428c109e 100644
--- a/src/scripts/utils/error.js
+++ b/src/scripts/utils/error.ts
@@ -1,5 +1,6 @@
 class StyleguidistError extends Error {
-	constructor(message, extra) {
+	public extra: any;
+	public constructor(message: string, extra?: any) {
 		super(message);
 		Error.captureStackTrace(this, this.constructor);
 		Object.defineProperty(this, 'name', {
@@ -11,4 +12,4 @@ class StyleguidistError extends Error {
 	}
 }
 
-module.exports = StyleguidistError;
+export default StyleguidistError;
diff --git a/src/scripts/utils/getUserPackageJson.js b/src/scripts/utils/getUserPackageJson.ts
similarity index 67%
rename from src/scripts/utils/getUserPackageJson.js
rename to src/scripts/utils/getUserPackageJson.ts
index 1b46d6be..6cd1fb7d 100644
--- a/src/scripts/utils/getUserPackageJson.js
+++ b/src/scripts/utils/getUserPackageJson.ts
@@ -1,14 +1,14 @@
-const path = require('path');
+import path from 'path';
 
 /**
  * Return user’s package.json.
  *
  * @return {object}
  */
-module.exports = function getUserPackageJson() {
+export default function getUserPackageJson() {
 	try {
 		return require(path.resolve(process.cwd(), 'package.json'));
 	} catch (err) {
 		return {};
 	}
-};
+}
diff --git a/src/scripts/utils/getWebpackVersion.js b/src/scripts/utils/getWebpackVersion.ts
similarity index 77%
rename from src/scripts/utils/getWebpackVersion.js
rename to src/scripts/utils/getWebpackVersion.ts
index b6e77992..052c154c 100644
--- a/src/scripts/utils/getWebpackVersion.js
+++ b/src/scripts/utils/getWebpackVersion.ts
@@ -3,10 +3,10 @@
  *
  * @return {number}
  */
-module.exports = function getWebpackVersion() {
+export default function getWebpackVersion() {
 	try {
 		return parseInt(require('webpack/package.json').version, 10);
 	} catch (err) {
 		return undefined;
 	}
-};
+}
diff --git a/src/scripts/utils/mergeWebpackConfig.js b/src/scripts/utils/mergeWebpackConfig.ts
similarity index 68%
rename from src/scripts/utils/mergeWebpackConfig.js
rename to src/scripts/utils/mergeWebpackConfig.ts
index 6cbe8a05..9998b994 100644
--- a/src/scripts/utils/mergeWebpackConfig.js
+++ b/src/scripts/utils/mergeWebpackConfig.ts
@@ -1,9 +1,11 @@
-const isFunction = require('lodash/isFunction');
-const omit = require('lodash/omit');
-const mergeBase = require('webpack-merge');
+import mergeBase from 'webpack-merge';
+import isFunction from 'lodash/isFunction';
+import omit from 'lodash/omit';
+import { Configuration } from 'webpack';
+import { Tapable } from 'tapable';
 
 const IGNORE_SECTIONS = ['entry', 'externals', 'output', 'watch', 'stats', 'styleguidist'];
-const IGNORE_SECTIONS_ENV = {
+const IGNORE_SECTIONS_ENV: Record<string, string[]> = {
 	development: [],
 	// For production builds, we'll ignore devtool settings to avoid
 	// source mapping bloat.
@@ -26,10 +28,12 @@ const merge = mergeBase({
 	customizeArray: mergeBase.unique(
 		'plugins',
 		IGNORE_PLUGINS,
-		plugin => plugin.constructor && plugin.constructor.name
+		(plugin: Tapable.Plugin) => plugin.constructor && plugin.constructor.name
 	),
 });
 
+type MetaConfig = Configuration | ((env?: string) => Configuration);
+
 /**
  * Merge two Webpack configs.
  *
@@ -42,8 +46,12 @@ const merge = mergeBase({
  * @param {string} env
  * @return {object}
  */
-module.exports = function mergeWebpackConfig(baseConfig, userConfig, env) {
+export default function mergeWebpackConfig(
+	baseConfig: MetaConfig,
+	userConfig: MetaConfig,
+	env = 'production'
+) {
 	const userConfigObject = isFunction(userConfig) ? userConfig(env) : userConfig;
 	const safeUserConfig = omit(userConfigObject, IGNORE_SECTIONS.concat(IGNORE_SECTIONS_ENV[env]));
 	return merge(baseConfig, safeUserConfig);
-};
+}
diff --git a/src/scripts/utils/sanitizeConfig.js b/src/scripts/utils/sanitizeConfig.ts
similarity index 64%
rename from src/scripts/utils/sanitizeConfig.js
rename to src/scripts/utils/sanitizeConfig.ts
index 41e55f7f..bfbc80d9 100644
--- a/src/scripts/utils/sanitizeConfig.js
+++ b/src/scripts/utils/sanitizeConfig.ts
@@ -1,22 +1,25 @@
-const fs = require('fs');
-const path = require('path');
-const isDirectory = require('is-directory');
-const castArray = require('lodash/castArray');
-const isBoolean = require('lodash/isBoolean');
-const isFunction = require('lodash/isFunction');
-const isPlainObject = require('lodash/isPlainObject');
-const isString = require('lodash/isString');
-const isFinite = require('lodash/isFinite');
-const map = require('lodash/map');
-const listify = require('listify');
-const kleur = require('kleur');
-const leven = require('leven');
-const stringify = require('q-i').stringify;
-const typeDetect = require('type-detect');
-const logger = require('glogg')('rsg');
-const StyleguidistError = require('./error');
-
-const typeCheckers = {
+import fs from 'fs';
+import path from 'path';
+import * as isDirectory from 'is-directory';
+import castArray from 'lodash/castArray';
+import isBoolean from 'lodash/isBoolean';
+import isFunction from 'lodash/isFunction';
+import isPlainObject from 'lodash/isPlainObject';
+import isString from 'lodash/isString';
+import isFinite from 'lodash/isFinite';
+import map from 'lodash/map';
+import listify from 'listify';
+import kleur from 'kleur';
+import leven from 'leven';
+import typeDetect from 'type-detect';
+import loggerMaker from 'glogg';
+import { stringify } from 'q-i';
+import StyleguidistError from './error';
+import { ConfigSchemaOptions } from '../schemas/config';
+
+const logger = loggerMaker('rsg');
+
+const typeCheckers: Record<string, (untypedObject: unknown) => boolean> = {
 	number: isFinite,
 	string: isString,
 	boolean: isBoolean,
@@ -29,10 +32,10 @@ const typeCheckers = {
 	'existing directory path': isString,
 };
 
-const typesList = types => listify(types, { finalWord: 'or' });
-const shouldBeFile = types => types.some(type => type.includes('file'));
-const shouldBeDirectory = types => types.some(type => type.includes('directory'));
-const shouldExist = types => types.some(type => type.includes('existing'));
+const typesList = (types: string[]) => listify(types, { finalWord: 'or' });
+const shouldBeFile = (types: string[]) => types.some(type => type.includes('file'));
+const shouldBeDirectory = (types: string[]) => types.some(type => type.includes('directory'));
+const shouldExist = (types: string[]) => types.some(type => type.includes('existing'));
 
 /**
  * Validates and normalizes config.
@@ -42,19 +45,24 @@ const shouldExist = types => types.some(type => type.includes('existing'));
  * @param {string} rootDir
  * @return {object}
  */
-module.exports = function sanitizeConfig(config, schema, rootDir) {
+export default function sanitizeConfig<T extends Record<string, any>>(
+	config: T,
+	schema: Record<keyof T, ConfigSchemaOptions>,
+	rootDir: string
+): T {
 	// Check for unknown fields
-	map(config, (value, key) => {
+	map(config, (value, keyAny: keyof T) => {
+		const key = keyAny as string;
 		if (!schema[key]) {
 			// Try to guess
 			const possibleOptions = Object.keys(schema);
-			const suggestedOption = possibleOptions.reduce((suggestion, option) => {
+			const suggestedOption = possibleOptions.reduce((suggestion: string, option: string) => {
 				const steps = leven(option, key);
 				if (steps < 2) {
 					return option;
 				}
 				return suggestion;
-			}, null);
+			}, '');
 
 			throw new StyleguidistError(
 				`Unknown config option ${kleur.bold(key)} was found, the value is:\n` +
@@ -66,8 +74,9 @@ module.exports = function sanitizeConfig(config, schema, rootDir) {
 	});
 
 	// Check all fields
-	const safeConfig = {};
-	map(schema, (props, key) => {
+	const safeConfig: Partial<T> = {};
+	map(schema, (props, keyAny: keyof T) => {
+		const key = keyAny as string;
 		let value = config[key];
 
 		// Custom processing
@@ -107,7 +116,7 @@ module.exports = function sanitizeConfig(config, schema, rootDir) {
 			});
 			if (!hasRightType) {
 				const exampleValue = props.example || props.default;
-				const example = {};
+				const example: Record<string, any> = {};
 				if (exampleValue) {
 					example[key] = exampleValue;
 				}
@@ -147,8 +156,8 @@ ${stringify(example)}`
 			}
 		}
 
-		safeConfig[key] = value;
+		safeConfig[keyAny] = value;
 	});
 
-	return safeConfig;
-};
+	return safeConfig as T;
+}
diff --git a/src/typings/RecursivePartial.d.ts b/src/typings/RecursivePartial.d.ts
index e622d98b..9c7632e7 100644
--- a/src/typings/RecursivePartial.d.ts
+++ b/src/typings/RecursivePartial.d.ts
@@ -9,6 +9,8 @@
 type RecursivePartial<T> = {
 	[P in keyof T]?: T[P] extends (infer U)[]
 		? RecursivePartial<U>[]
+		: T[P] extends Function
+		? T[P]
 		: T[P] extends object
 		? RecursivePartial<T[P]>
 		: T[P];
diff --git a/src/typings/RsgComponent.d.ts b/src/typings/RsgComponent.d.ts
index bcab3e1e..f31d4003 100644
--- a/src/typings/RsgComponent.d.ts
+++ b/src/typings/RsgComponent.d.ts
@@ -24,8 +24,8 @@ declare global {
 				methods?: MethodDescriptor[];
 				props?: PropDescriptor[];
 				tags?: TagProps;
-				example?: Example[] | string[];
-				examples?: Example[] | string[];
+				example?: Example[];
+				examples?: Example[];
 			};
 			metadata?: {
 				tags?: string[];
diff --git a/src/typings/RsgExample.d.ts b/src/typings/RsgExample.d.ts
index 6f5367bb..7cf2e5b9 100644
--- a/src/typings/RsgExample.d.ts
+++ b/src/typings/RsgExample.d.ts
@@ -6,11 +6,15 @@ declare namespace Rsg {
 	}
 
 	interface CodeExample {
-		evalInContext(a: string): () => any;
-		type: 'code';
 		content: string;
+		lang?: string | null;
 		settings?: Record<string, any>;
 	}
 
-	type Example = CodeExample | MarkdownExample | string;
+	interface RuntimeCodeExample extends CodeExample {
+		type: 'code';
+		evalInContext(a: string): () => any;
+	}
+
+	type Example = RuntimeCodeExample | MarkdownExample;
 }
diff --git a/src/typings/RsgSection.d.ts b/src/typings/RsgSection.d.ts
index dde4fb2e..9041cf9d 100644
--- a/src/typings/RsgSection.d.ts
+++ b/src/typings/RsgSection.d.ts
@@ -1,21 +1,30 @@
 declare namespace Rsg {
-	interface Section {
+	interface BaseSection {
 		name?: string;
-		slug?: string;
-		visibleName?: string;
-		components?: Component[];
-		sections?: Section[];
 		ignore?: string | string[];
-		content?: Example[] | string;
-		sectionDepth?: number;
 		description?: string;
 		exampleMode?: EXPAND_MODE;
 		usageMode?: EXPAND_MODE;
-		filepath?: string;
 		href?: string;
 		external?: string;
 	}
 
+	interface Section extends BaseSection {
+		slug?: string;
+		visibleName?: string;
+		filepath?: string;
+		components?: Component[];
+		sections?: Section[];
+		content?: Example[] | string;
+		sectionDepth?: number;
+	}
+
+	interface ConfigSection extends BaseSection {
+		components?: string | string[];
+		sections?: ConfigSection[];
+		content?: string[] | string;
+	}
+
 	interface ExampleSection extends Section {
 		content?: Example[];
 	}
diff --git a/src/typings/StyleguidistConfig.d.ts b/src/typings/StyleguidistConfig.d.ts
index b98a848b..9264718f 100644
--- a/src/typings/StyleguidistConfig.d.ts
+++ b/src/typings/StyleguidistConfig.d.ts
@@ -1,20 +1,92 @@
-declare namespace Rsg {
-	type EXPAND_MODE = 'expand' | 'collapse' | 'hide';
+import WebpackDevServer from 'webpack-dev-server';
+import { Configuration } from 'webpack';
+import { TransformOptions } from 'buble';
+import { Handler, DocumentationObject, PropDescriptor } from 'react-docgen';
+import { ASTNode } from 'ast-types';
+import { NodePath } from 'ast-types/lib/node-path';
 
-	interface ProcessedStyleguidistConfig {
-		title: string;
-		compilerConfig: object;
-		showCode: EXPAND_MODE;
-		showUsage: EXPAND_MODE;
-		components: string;
-		theme: Rsg.Theme;
-		styles: Styles;
-		pagePerSection: boolean;
-		ribbon?: {
-			text?: string;
-			url: string;
-		};
-	}
+declare global {
+	namespace Rsg {
+		type EXPAND_MODE = 'expand' | 'collapse' | 'hide';
+
+		interface BaseStyleguidistConfig {
+			assetsDir: string | string[];
+			compilerConfig: TransformOptions;
+			components: (() => string | string[]) | string | string[];
+			configDir: string;
+			context: Record<string, any>;
+			contextDependencies: string[];
+			configureServer(server: WebpackDevServer, env: string): string;
+			dangerouslyUpdateWebpackConfig: (server: Configuration, env: string) => Configuration;
+			defaultExample: string | boolean;
+			exampleMode: EXPAND_MODE;
+			editorConfig: {
+				theme: string;
+			};
+			getComponentPathLine(componentPath: string): string;
+			getExampleFilename(componentPath: string): string;
+			handlers: (componentPath: string) => Handler[];
+			ignore: string[];
+			logger: {
+				info(message: string): void;
+				warn(message: string): void;
+				debug(message: string): void;
+			};
+			minimize: boolean;
+			mountPointId: string;
+			moduleAliases: Record<string, string>;
+			pagePerSection: boolean;
+			previewDelay: number;
+			printBuildInstructions(config: ProcessedStyleguidistConfig): void;
+			printServerInstructions(config: ProcessedStyleguidistConfig): void;
+			propsParser(
+				filePath: string,
+				code: string,
+				resolver: (
+					ast: ASTNode,
+					parser: { parse: (code: string) => ASTNode }
+				) => NodePath<any, any> | NodePath[],
+				handlers: Handler[]
+			): DocumentationObject;
+			require: string[];
+			resolver(
+				ast: ASTNode,
+				parser: { parse: (code: string) => ASTNode }
+			): NodePath<any, any> | NodePath[];
+			ribbon?: {
+				text?: string;
+				url: string;
+			};
+			serverHost: string;
+			serverPort: number;
+			showCode: boolean;
+			showUsage: boolean;
+			showSidebar: boolean;
+			skipComponentsWithoutExample: boolean;
+			sortProps(props: PropDescriptor[]): PropDescriptor[];
+			styleguideComponents: Record<string, string>;
+			styleguideDir: string;
+			styles: Styles;
+			template: any;
+			theme: Theme;
+			title: string;
+			updateDocs(doc: Component, file: string): Component;
+			updateExample(props: Example, ressourcePath: string): Example;
+			updateWebpackConfig(config: Configuration): Configuration;
+			usageMode: EXPAND_MODE;
+			verbose: boolean;
+			version: string;
+			webpackConfig: Configuration | ((env?: string) => Configuration);
+		}
+
+		interface ProcessedStyleguidistConfig extends BaseStyleguidistConfig {
+			sections: Section[];
+		}
 
-	type StyleguidistConfig = RecursivePartial<ProcessedStyleguidistConfig>;
+		interface SanitizedStyleguidistConfig extends BaseStyleguidistConfig {
+			sections: ConfigSection[];
+		}
+
+		type StyleguidistConfig = RecursivePartial<SanitizedStyleguidistConfig>;
+	}
 }
diff --git a/src/typings/deepfreeze.d.ts b/src/typings/dependencies/deepfreeze.d.ts
similarity index 100%
rename from src/typings/deepfreeze.d.ts
rename to src/typings/dependencies/deepfreeze.d.ts
diff --git a/src/typings/dependencies/findup.d.ts b/src/typings/dependencies/findup.d.ts
new file mode 100644
index 00000000..39ca2dca
--- /dev/null
+++ b/src/typings/dependencies/findup.d.ts
@@ -0,0 +1,6 @@
+declare module 'findup' {
+	const findup: {
+		sync(cwd: string, path: string): string;
+	};
+	export = findup;
+}
diff --git a/src/typings/glogg.d.ts b/src/typings/dependencies/glogg.d.ts
similarity index 76%
rename from src/typings/glogg.d.ts
rename to src/typings/dependencies/glogg.d.ts
index ba1250d2..368c00b4 100644
--- a/src/typings/glogg.d.ts
+++ b/src/typings/dependencies/glogg.d.ts
@@ -5,6 +5,8 @@ declare module 'glogg' {
 		warn(msg: string): void;
 		error(msg: string): void;
 		on(event: string | symbol, listener: (...args: any[]) => void): void;
+		once(actionName: string, action: (msg: string) => void): void;
+		removeAllListeners(): void;
 	}
 	function getLogger(namespace: string): GloggLogger;
 	export = getLogger;
diff --git a/src/typings/dependencies/is-directory.d.ts b/src/typings/dependencies/is-directory.d.ts
new file mode 100644
index 00000000..0bddec91
--- /dev/null
+++ b/src/typings/dependencies/is-directory.d.ts
@@ -0,0 +1,7 @@
+declare module 'is-directory' {
+	const isDirectory: {
+		(pathToCheck: string, callback: (isDir: boolean) => void): void;
+		sync(pathToCheck: string): boolean;
+	};
+	export = isDirectory;
+}
diff --git a/src/typings/dependencies/listify.d.ts b/src/typings/dependencies/listify.d.ts
new file mode 100644
index 00000000..43a73858
--- /dev/null
+++ b/src/typings/dependencies/listify.d.ts
@@ -0,0 +1,7 @@
+declare module 'listify' {
+	interface ListifyOptions {
+		finalWord: string;
+	}
+	function listify(list: any[], opt?: ListifyOptions): string;
+	export = listify;
+}
diff --git a/src/typings/dependencies/mini-html-webpack-plugin.d.ts b/src/typings/dependencies/mini-html-webpack-plugin.d.ts
new file mode 100644
index 00000000..2a82590d
--- /dev/null
+++ b/src/typings/dependencies/mini-html-webpack-plugin.d.ts
@@ -0,0 +1,9 @@
+declare module 'mini-html-webpack-plugin' {
+	import { Plugin, loader } from 'webpack';
+
+	class HtmlPlugin extends Plugin {
+		constructor(options: any);
+	}
+
+	export = HtmlPlugin;
+}
diff --git a/src/typings/dependencies/mini-html-webpack-template.d.ts b/src/typings/dependencies/mini-html-webpack-template.d.ts
new file mode 100644
index 00000000..d54b8b69
--- /dev/null
+++ b/src/typings/dependencies/mini-html-webpack-template.d.ts
@@ -0,0 +1,4 @@
+declare module '@vxna/mini-html-webpack-template' {
+	function template(...args: any[]): string;
+	export = template;
+}
diff --git a/src/typings/dependencies/q-i.d.ts b/src/typings/dependencies/q-i.d.ts
new file mode 100644
index 00000000..811f4724
--- /dev/null
+++ b/src/typings/dependencies/q-i.d.ts
@@ -0,0 +1,3 @@
+declare module 'q-i' {
+	export const stringify: (obj: any) => string;
+}
diff --git a/src/typings/react-docgen.d.ts b/src/typings/dependencies/react-docgen.d.ts
similarity index 100%
rename from src/typings/react-docgen.d.ts
rename to src/typings/dependencies/react-docgen.d.ts
diff --git a/src/typings/stripHtmlComments.d.ts b/src/typings/dependencies/stripHtmlComments.d.ts
similarity index 100%
rename from src/typings/stripHtmlComments.d.ts
rename to src/typings/dependencies/stripHtmlComments.d.ts
diff --git a/src/typings/dependencies/webpack-merge.d.ts b/src/typings/dependencies/webpack-merge.d.ts
new file mode 100644
index 00000000..2edff39b
--- /dev/null
+++ b/src/typings/dependencies/webpack-merge.d.ts
@@ -0,0 +1,21 @@
+declare module 'webpack-merge' {
+	import { Tapable } from 'tapable';
+	import { Configuration } from 'webpack';
+
+	type MetaConfig = Configuration | ((env?: string) => Configuration);
+	type mergeFunction = (...configs: MetaConfig[]) => Configuration;
+	type customizeArrayFuntion = () => any[];
+	interface WebpackMergeOptions {
+		customizeArray: customizeArrayFuntion;
+	}
+	const webpackMerge: {
+		(options: WebpackMergeOptions): mergeFunction;
+		(...configs: MetaConfig[]): Configuration;
+		unique(
+			key: string,
+			uniques: string[],
+			getter?: (plugin: Tapable.Plugin) => string | undefined | false
+		): customizeArrayFuntion;
+	};
+	export = webpackMerge;
+}


Test patch:
diff --git a/src/client/utils/__tests__/__snapshots__/getRouteData.spec.ts.snap b/src/client/utils/__tests__/__snapshots__/getRouteData.spec.ts.snap
index 5bf3c08a..f99c3546 100644
--- a/src/client/utils/__tests__/__snapshots__/getRouteData.spec.ts.snap
+++ b/src/client/utils/__tests__/__snapshots__/getRouteData.spec.ts.snap
@@ -12,8 +12,15 @@ Object {
           "props": Object {
             "displayName": "Button",
             "examples": Array [
-              "example 0",
-              "example 1",
+              Object {
+                "content": "const a = 0",
+                "evalInContext": [Function],
+                "type": "code",
+              },
+              Object {
+                "content": "# title",
+                "type": "markdown",
+              },
             ],
           },
         },
@@ -55,8 +62,15 @@ Object {
           "props": Object {
             "displayName": "Button",
             "examples": Array [
-              "example 0",
-              "example 1",
+              Object {
+                "content": "const a = 0",
+                "evalInContext": [Function],
+                "type": "code",
+              },
+              Object {
+                "content": "# title",
+                "type": "markdown",
+              },
             ],
           },
         },
@@ -100,7 +114,10 @@ Object {
           "props": Object {
             "displayName": "Button",
             "examples": Array [
-              "example 1",
+              Object {
+                "content": "# title",
+                "type": "markdown",
+              },
             ],
           },
         },
@@ -120,7 +137,10 @@ Object {
     Object {
       "components": Array [],
       "content": Array [
-        "example 1",
+        Object {
+          "content": "# title",
+          "type": "markdown",
+        },
       ],
       "exampleMode": "collapse",
       "name": "Section",
@@ -140,8 +160,15 @@ Object {
     Object {
       "components": Array [],
       "content": Array [
-        "example 0",
-        "example 1",
+        Object {
+          "content": "const a = 0",
+          "evalInContext": [Function],
+          "type": "code",
+        },
+        Object {
+          "content": "# title",
+          "type": "markdown",
+        },
       ],
       "exampleMode": "collapse",
       "name": "Section",
@@ -161,8 +188,15 @@ Object {
     Object {
       "components": Array [],
       "content": Array [
-        "example 0",
-        "example 1",
+        Object {
+          "content": "const a = 0",
+          "evalInContext": [Function],
+          "type": "code",
+        },
+        Object {
+          "content": "# title",
+          "type": "markdown",
+        },
       ],
       "exampleMode": "collapse",
       "name": "Section",
diff --git a/src/client/utils/__tests__/filterComponentExamples.spec.ts b/src/client/utils/__tests__/filterComponentExamples.spec.ts
index 8c6afec6..27259c5e 100644
--- a/src/client/utils/__tests__/filterComponentExamples.spec.ts
+++ b/src/client/utils/__tests__/filterComponentExamples.spec.ts
@@ -1,9 +1,11 @@
 import deepfreeze from 'deepfreeze';
 import filterComponentExamples from '../filterComponentExamples';
 
+const examples: Rsg.Example[] = ['a', 'b', 'c', 'd'].map(x => ({ type: 'markdown', content: x }));
+
 const component = deepfreeze({
 	props: {
-		examples: ['a', 'b', 'c', 'd'],
+		examples,
 	},
 	other: 'info',
 });
@@ -13,7 +15,7 @@ describe('filterComponentExamples', () => {
 		const result = filterComponentExamples(component, 2);
 		expect(result).toEqual({
 			props: {
-				examples: ['c'],
+				examples: [{ type: 'markdown', content: 'c' }],
 			},
 			other: 'info',
 		});
diff --git a/src/client/utils/__tests__/getRouteData.spec.ts b/src/client/utils/__tests__/getRouteData.spec.ts
index 17f4ae5c..749dc28a 100644
--- a/src/client/utils/__tests__/getRouteData.spec.ts
+++ b/src/client/utils/__tests__/getRouteData.spec.ts
@@ -2,6 +2,17 @@ import deepfreeze from 'deepfreeze';
 import getRouteData from '../getRouteData';
 import { DisplayModes } from '../../consts';
 
+const example0: Rsg.Example = {
+	type: 'code',
+	content: 'const a = 0',
+	evalInContext: () => () => 3,
+};
+
+const example1: Rsg.Example = {
+	type: 'markdown',
+	content: '# title',
+};
+
 const sections: Rsg.Section[] = deepfreeze([
 	{
 		sections: [
@@ -13,7 +24,7 @@ const sections: Rsg.Section[] = deepfreeze([
 						name: 'Button',
 						props: {
 							displayName: 'Button',
-							examples: ['example 0', 'example 1'],
+							examples: [example0, example1],
 						},
 						module: 1,
 					},
@@ -33,7 +44,7 @@ const sections: Rsg.Section[] = deepfreeze([
 			{
 				name: 'Section',
 				slug: 'section',
-				content: ['example 0', 'example 1'],
+				content: [example0, example1],
 				components: [],
 				sections: [],
 				exampleMode: 'collapse',
diff --git a/src/scripts/__tests__/.eslintrc b/src/scripts/__tests__/.eslintrc
deleted file mode 100644
index 0b2e238c..00000000
--- a/src/scripts/__tests__/.eslintrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-	"extends": "tamia/react"
-}
diff --git a/src/scripts/__tests__/__snapshots__/make-webpack-config.spec.js.snap b/src/scripts/__tests__/__snapshots__/make-webpack-config.spec.ts.snap
similarity index 100%
rename from src/scripts/__tests__/__snapshots__/make-webpack-config.spec.js.snap
rename to src/scripts/__tests__/__snapshots__/make-webpack-config.spec.ts.snap
diff --git a/src/scripts/__tests__/config.spec.js b/src/scripts/__tests__/config.spec.ts
similarity index 96%
rename from src/scripts/__tests__/config.spec.js
rename to src/scripts/__tests__/config.spec.ts
index e264cfdc..9b5242c6 100644
--- a/src/scripts/__tests__/config.spec.js
+++ b/src/scripts/__tests__/config.spec.ts
@@ -44,7 +44,7 @@ it('should throw if config has errors', () => {
 	try {
 		getConfig({
 			components: 42,
-		});
+		} as any);
 	} catch (err) {
 		expect(err.message).toMatch('should be string, function, or array');
 	}
@@ -138,19 +138,24 @@ it('should throw if assetsDir does not exist', () => {
 	expect(fn).toThrow();
 });
 
-it('should use embedded default example template if defaultExample=true', () => {
+it('should use embedded default example template if defaultExample=true', done => {
 	const result = getConfig({
 		defaultExample: true,
 	});
 	expect(typeof result.defaultExample).toEqual('string');
-	expect(fs.existsSync(result.defaultExample)).toBeTruthy();
+	if (typeof result.defaultExample === 'string') {
+		expect(fs.existsSync(result.defaultExample)).toBeTruthy();
+	} else {
+		done.fail();
+	}
+	done();
 });
 
 it('should absolutize defaultExample if it is a string', () => {
 	const result = getConfig({
 		defaultExample: 'src/components/Button.md',
 	});
-	expect(result.defaultExample[0]).toEqual('/');
+	expect(result.defaultExample).toMatch(/^\//);
 });
 
 it('should throw if defaultExample does not exist', () => {
diff --git a/src/scripts/__tests__/create-server.spec.js b/src/scripts/__tests__/create-server.spec.ts
similarity index 55%
rename from src/scripts/__tests__/create-server.spec.js
rename to src/scripts/__tests__/create-server.spec.ts
index 674f8cf9..af0b0103 100644
--- a/src/scripts/__tests__/create-server.spec.js
+++ b/src/scripts/__tests__/create-server.spec.ts
@@ -1,3 +1,4 @@
+import { Output } from 'webpack';
 import createServer from '../create-server';
 import getConfig from '../config';
 
@@ -14,22 +15,38 @@ test('createServer should return an object containing a server instance', () =>
 	expect(result.app).toBeTruthy();
 });
 
-test('createServer should return an object containing a production Webpack compiler', () => {
+test('createServer should return an object containing a production Webpack compiler', done => {
 	process.chdir('test/apps/basic');
 	const config = getConfig();
 	const result = createServer(config, 'production');
 	expect(result).toBeTruthy();
 	expect(result.compiler).toBeTruthy();
-	expect(result.compiler.options.output.filename).toBe('build/bundle.[chunkhash:8].js');
-	expect(result.compiler.options.output.chunkFilename).toBe('build/[name].[chunkhash:8].js');
+	let output: Output;
+	if (result.compiler && result.compiler.options && result.compiler.options.output) {
+		output = result.compiler.options.output;
+	} else {
+		done.fail('no output');
+		return;
+	}
+	expect(output.filename).toBe('build/bundle.[chunkhash:8].js');
+	expect(output.chunkFilename).toBe('build/[name].[chunkhash:8].js');
+	done();
 });
 
-test('createServer should return an object containing a development Webpack compiler', () => {
+test('createServer should return an object containing a development Webpack compiler', done => {
 	process.chdir('test/apps/basic');
 	const config = getConfig();
 	const result = createServer(config, 'development');
 	expect(result).toBeTruthy();
 	expect(result.compiler).toBeTruthy();
-	expect(result.compiler.options.output.filename).toBe('build/[name].bundle.js');
-	expect(result.compiler.options.output.chunkFilename).toBe('build/[name].js');
+	let output: Output;
+	if (result.compiler && result.compiler.options && result.compiler.options.output) {
+		output = result.compiler.options.output;
+	} else {
+		done.fail('no output');
+		return;
+	}
+	expect(output.filename).toBe('build/[name].bundle.js');
+	expect(output.chunkFilename).toBe('build/[name].js');
+	done();
 });
diff --git a/src/scripts/__tests__/index.spec.js b/src/scripts/__tests__/index.spec.ts
similarity index 77%
rename from src/scripts/__tests__/index.spec.js
rename to src/scripts/__tests__/index.spec.ts
index 6b2dc722..db00ff5d 100644
--- a/src/scripts/__tests__/index.spec.js
+++ b/src/scripts/__tests__/index.spec.ts
@@ -1,10 +1,13 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
+import { Configuration } from 'webpack';
 import last from 'lodash/last';
-import styleguidist from '../index';
+
+const styleguidist = require('../index');
 
 jest.mock('../build');
 jest.mock('../server');
 
-const getDefaultWebpackConfig = () => styleguidist().makeWebpackConfig();
+const getDefaultWebpackConfig = (): any => styleguidist().makeWebpackConfig();
 
 const cwd = process.cwd();
 afterEach(() => {
@@ -24,16 +27,16 @@ describe('makeWebpackConfig', () => {
 		const api = styleguidist();
 		const result = api.makeWebpackConfig('development');
 		expect(result).toBeTruthy();
-		expect(result.output.filename).toBe('build/[name].bundle.js');
-		expect(result.output.chunkFilename).toBe('build/[name].js');
+		expect(result.output && result.output.filename).toBe('build/[name].bundle.js');
+		expect(result.output && result.output.chunkFilename).toBe('build/[name].js');
 	});
 
 	it('should return production Webpack config', () => {
 		const api = styleguidist();
 		const result = api.makeWebpackConfig('production');
 		expect(result).toBeTruthy();
-		expect(result.output.filename).toBe('build/bundle.[chunkhash:8].js');
-		expect(result.output.chunkFilename).toBe('build/[name].[chunkhash:8].js');
+		expect(result.output && result.output.filename).toBe('build/bundle.[chunkhash:8].js');
+		expect(result.output && result.output.chunkFilename).toBe('build/[name].[chunkhash:8].js');
 	});
 
 	it('should merge webpackConfig config option', () => {
@@ -45,7 +48,7 @@ describe('makeWebpackConfig', () => {
 				},
 			},
 		});
-		const result = api.makeWebpackConfig();
+		const result = api.makeWebpackConfig() as any;
 
 		expect(result).toBeTruthy();
 		expect(result.resolve.extensions.length).toEqual(
@@ -68,16 +71,18 @@ describe('makeWebpackConfig', () => {
 		});
 		const result = api.makeWebpackConfig();
 
-		expect(result.output.filename).toEqual(defaultWebpackConfig.output.filename);
+		expect(result.output && result.output.filename).toEqual(
+			defaultWebpackConfig.output && defaultWebpackConfig.output.filename
+		);
 	});
 
 	it('should merge webpackConfig config option as a function', () => {
 		const api = styleguidist({
-			webpackConfig: env => ({
+			webpackConfig: (env: string) => ({
 				_env: env,
 			}),
 		});
-		const result = api.makeWebpackConfig();
+		const result = api.makeWebpackConfig() as any;
 
 		expect(result).toBeTruthy();
 		expect(result._env).toEqual('production');
@@ -86,12 +91,14 @@ describe('makeWebpackConfig', () => {
 	it('should apply updateWebpackConfig config option', () => {
 		const defaultWebpackConfig = getDefaultWebpackConfig();
 		const api = styleguidist({
-			dangerouslyUpdateWebpackConfig: (webpackConfig, env) => {
-				webpackConfig.resolve.extensions.push(env);
+			dangerouslyUpdateWebpackConfig: (webpackConfig: Configuration, env: string) => {
+				if (webpackConfig.resolve && webpackConfig.resolve.extensions) {
+					webpackConfig.resolve.extensions.push(env);
+				}
 				return webpackConfig;
 			},
 		});
-		const result = api.makeWebpackConfig();
+		const result = api.makeWebpackConfig() as any;
 
 		expect(result).toBeTruthy();
 		expect(result.resolve.extensions.length).toEqual(
@@ -126,7 +133,7 @@ describe('makeWebpackConfig', () => {
 				StyleGuideRenderer: 'styleguide/components/StyleGuide',
 			},
 		});
-		const result = api.makeWebpackConfig();
+		const result = api.makeWebpackConfig() as any;
 
 		expect(result.resolve.alias).toMatchObject({
 			'rsg-components/Wrapper': 'styleguide/components/Wrapper',
diff --git a/src/scripts/__tests__/logger.spec.js b/src/scripts/__tests__/logger.spec.ts
similarity index 100%
rename from src/scripts/__tests__/logger.spec.js
rename to src/scripts/__tests__/logger.spec.ts
diff --git a/src/scripts/__tests__/make-webpack-config.spec.js b/src/scripts/__tests__/make-webpack-config.spec.ts
similarity index 78%
rename from src/scripts/__tests__/make-webpack-config.spec.js
rename to src/scripts/__tests__/make-webpack-config.spec.ts
index 61151b26..0e81a6fb 100644
--- a/src/scripts/__tests__/make-webpack-config.spec.js
+++ b/src/scripts/__tests__/make-webpack-config.spec.ts
@@ -1,22 +1,28 @@
-import webpack, { validate } from 'webpack';
+import webpack, { Configuration } from 'webpack';
+import { Tapable } from 'tapable';
 import CopyWebpackPlugin from 'copy-webpack-plugin';
 import makeWebpackConfig from '../make-webpack-config';
 
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const { validate } = require('webpack');
+
 jest.mock('copy-webpack-plugin');
 
-const styleguideConfig = {
+const styleguideConfig = ({
 	styleguideDir: __dirname,
 	require: [],
 	title: 'Style Guide',
-};
+} as unknown) as Rsg.SanitizedStyleguidistConfig;
 
-const getClasses = (plugins, name) => plugins.filter(x => x.constructor.name === name);
-const getClassNames = plugins => plugins.map(x => x.constructor.name);
+const getClasses = (plugins: Tapable.Plugin[] = [], name: string): Tapable.Plugin[] =>
+	plugins.filter(x => x.constructor.name === name);
+const getClassNames = (plugins: Tapable.Plugin[] = []): string[] =>
+	plugins.map(x => x.constructor.name);
 
 const process$env$nodeEnv = process.env.NODE_ENV;
 
 beforeEach(() => {
-	CopyWebpackPlugin.mockClear();
+	(CopyWebpackPlugin as jest.Mock).mockClear();
 });
 
 afterEach(() => {
@@ -30,7 +36,7 @@ it('should return a development config', () => {
 	const errors = validate(config);
 	expect(errors).toHaveLength(0);
 
-	const plugins = getClassNames(config.plugins);
+	const plugins = getClassNames(config.plugins || []);
 	expect(plugins).toContain('HotModuleReplacementPlugin');
 
 	expect(config).toMatchObject({
@@ -59,12 +65,14 @@ it('should return a production config', () => {
 	expect(config).toMatchObject({
 		mode: env,
 	});
-	expect(getClasses(config.optimization.minimizer, 'TerserPlugin')).toHaveLength(1);
+	expect(
+		getClasses(config.optimization && config.optimization.minimizer, 'TerserPlugin')
+	).toHaveLength(1);
 });
 
 it('should set aliases', () => {
 	const result = makeWebpackConfig(styleguideConfig, 'development');
-	expect(result.resolve.alias).toMatchSnapshot();
+	expect(result.resolve && result.resolve.alias).toMatchSnapshot();
 });
 
 it('should set aliases from moduleAliases option', () => {
@@ -77,7 +85,7 @@ it('should set aliases from moduleAliases option', () => {
 		},
 		'development'
 	);
-	expect(result.resolve.alias).toMatchSnapshot();
+	expect(result.resolve && result.resolve.alias).toMatchSnapshot();
 });
 
 it('should set aliases from styleguideComponents option', () => {
@@ -90,7 +98,7 @@ it('should set aliases from styleguideComponents option', () => {
 		},
 		'development'
 	);
-	expect(result.resolve.alias).toMatchSnapshot();
+	expect(result.resolve && result.resolve.alias).toMatchSnapshot();
 });
 
 it('should prepend requires as webpack entries', () => {
@@ -103,7 +111,7 @@ it('should prepend requires as webpack entries', () => {
 
 it('should enable verbose mode in CleanWebpackPlugin', () => {
 	const result = makeWebpackConfig({ ...styleguideConfig, verbose: true }, 'production');
-	expect(getClasses(result.plugins, 'CleanWebpackPlugin')[0].verbose).toBe(true);
+	expect((getClasses(result.plugins, 'CleanWebpackPlugin')[0] as any).verbose).toBe(true);
 });
 
 it('should set from with assetsDir in CopyWebpackPlugin', () => {
@@ -126,7 +134,7 @@ it('should merge user webpack config', () => {
 		{ ...styleguideConfig, webpackConfig: { resolve: { alias: { foo: 'bar' } } } },
 		'development'
 	);
-	expect(result.resolve.alias).toMatchSnapshot();
+	expect(result.resolve && result.resolve.alias).toMatchSnapshot();
 });
 
 it('should not owerwrite user DefinePlugin', () => {
@@ -155,14 +163,16 @@ it('should update webpack config', () => {
 	const result = makeWebpackConfig(
 		{
 			...styleguideConfig,
-			dangerouslyUpdateWebpackConfig: c => {
-				c.resolve.extensions = extensions;
+			dangerouslyUpdateWebpackConfig: (c: Configuration) => {
+				if (c.resolve) {
+					c.resolve.extensions = extensions;
+				}
 				return c;
 			},
 		},
 		'development'
 	);
-	expect(result.resolve.extensions).toEqual(extensions);
+	expect(result.resolve && result.resolve.extensions).toEqual(extensions);
 });
 
 it('should pass template context to HTML plugin', () => {
@@ -220,7 +230,7 @@ it('should pass specified mountPointId to HTML plugin', () => {
 		},
 		'development'
 	);
-	expect(getClasses(result.plugins, 'MiniHtmlWebpackPlugin')[0].options.context.container).toEqual(
-		'foo-bar'
-	);
+	expect(
+		(getClasses(result.plugins, 'MiniHtmlWebpackPlugin')[0] as any).options.context.container
+	).toEqual('foo-bar');
 });
diff --git a/src/scripts/__tests__/server.spec.js b/src/scripts/__tests__/server.spec.ts
similarity index 84%
rename from src/scripts/__tests__/server.spec.js
rename to src/scripts/__tests__/server.spec.ts
index 20efe272..57b57929 100644
--- a/src/scripts/__tests__/server.spec.js
+++ b/src/scripts/__tests__/server.spec.ts
@@ -4,8 +4,8 @@ import getConfig from '../config';
 jest.mock('../create-server', () => () => {
 	return {
 		app: {
-			listen: (port, host, cb) => cb(),
-			close: cb => cb(),
+			listen: (port: number, host: string, cb: () => void) => cb(),
+			close: (cb: () => void) => cb(),
 		},
 		compiler: {},
 	};
diff --git a/src/scripts/utils/__tests__/.eslintrc b/src/scripts/utils/__tests__/.eslintrc
deleted file mode 100644
index 0b2e238c..00000000
--- a/src/scripts/utils/__tests__/.eslintrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-	"extends": "tamia/react"
-}
diff --git a/src/scripts/utils/__tests__/StyleguidistOptionsPlugin.spec.js b/src/scripts/utils/__tests__/StyleguidistOptionsPlugin.spec.ts
similarity index 60%
rename from src/scripts/utils/__tests__/StyleguidistOptionsPlugin.spec.js
rename to src/scripts/utils/__tests__/StyleguidistOptionsPlugin.spec.ts
index 449ee5cc..c6dea829 100644
--- a/src/scripts/utils/__tests__/StyleguidistOptionsPlugin.spec.js
+++ b/src/scripts/utils/__tests__/StyleguidistOptionsPlugin.spec.ts
@@ -1,19 +1,22 @@
 import StyleguidistOptionsPlugin from '../StyleguidistOptionsPlugin';
 
-const options = {
+const options: any = {
 	foo: 42,
 };
 
 it('should attach Styleguidist config when webpack 4 is used', () => {
-	const context = {};
+	const context: { _styleguidist?: Rsg.StyleguidistConfig } = {};
 	const compiler = {
 		hooks: {
 			compilation: {
-				tap: (name, callback) => {
+				tap: (name: string, callback: (opt: any) => void) => {
 					callback({
 						hooks: {
 							normalModuleLoader: {
-								tap: (moduleName, compilationCallback) => {
+								tap: (
+									moduleName: string,
+									compilationCallback: (context: any, opt: any) => void
+								) => {
 									compilationCallback(context, { resource: 'pizza' });
 								},
 							},
@@ -24,20 +27,23 @@ it('should attach Styleguidist config when webpack 4 is used', () => {
 		},
 	};
 	const plugin = new StyleguidistOptionsPlugin(options);
-	plugin.apply(compiler);
+	plugin.apply(compiler as any);
 	expect(context._styleguidist).toEqual(options);
 });
 
 it('should do nothing when resource is empty', () => {
-	const context = {};
+	const context: { _styleguidist?: Rsg.StyleguidistConfig } = {};
 	const compiler = {
 		hooks: {
 			compilation: {
-				tap: (name, callback) => {
+				tap: (name: string, callback: (opt: any) => void) => {
 					callback({
 						hooks: {
 							normalModuleLoader: {
-								tap: (moduleName, compilationCallback) => {
+								tap: (
+									moduleName: string,
+									compilationCallback: (context: any, opt: any) => void
+								) => {
 									compilationCallback(context, {});
 								},
 							},
@@ -48,7 +54,7 @@ it('should do nothing when resource is empty', () => {
 		},
 	};
 	const plugin = new StyleguidistOptionsPlugin(options);
-	plugin.apply(compiler);
+	plugin.apply(compiler as any);
 
 	expect(context._styleguidist).toBeFalsy();
 });
diff --git a/src/scripts/utils/__tests__/getUserPackageJson.spec.js b/src/scripts/utils/__tests__/getUserPackageJson.spec.ts
similarity index 100%
rename from src/scripts/utils/__tests__/getUserPackageJson.spec.js
rename to src/scripts/utils/__tests__/getUserPackageJson.spec.ts
diff --git a/src/scripts/utils/__tests__/getWebpackVersion.spec.js b/src/scripts/utils/__tests__/getWebpackVersion.spec.ts
similarity index 100%
rename from src/scripts/utils/__tests__/getWebpackVersion.spec.js
rename to src/scripts/utils/__tests__/getWebpackVersion.spec.ts
diff --git a/src/scripts/utils/__tests__/mergeWebpackConfig.spec.js b/src/scripts/utils/__tests__/mergeWebpackConfig.spec.ts
similarity index 59%
rename from src/scripts/utils/__tests__/mergeWebpackConfig.spec.js
rename to src/scripts/utils/__tests__/mergeWebpackConfig.spec.ts
index 3ec00575..a5a1ef98 100644
--- a/src/scripts/utils/__tests__/mergeWebpackConfig.spec.js
+++ b/src/scripts/utils/__tests__/mergeWebpackConfig.spec.ts
@@ -1,17 +1,28 @@
 import mergeWebpackConfig from '../mergeWebpackConfig';
 
-class TerserPlugin {}
-class MyPlugin {}
-class MiniHtmlWebpackPlugin {}
+class TerserPlugin {
+	public apply() {}
+}
+class MyPlugin {
+	public apply() {}
+}
+class MiniHtmlWebpackPlugin {
+	public apply() {}
+}
 
 it('should merge two objects', () => {
-	const result = mergeWebpackConfig({ a: 0, b: 0 }, { b: 1 });
-	expect(result).toEqual({ a: 0, b: 1 });
+	const result = mergeWebpackConfig(
+		{ entry: 'main.js', devtool: 'cheap-source-map' },
+		{ devtool: 'inline-source-map' }
+	);
+	expect(result).toEqual({ entry: 'main.js', devtool: 'cheap-source-map' });
 });
 
 it('should merge an object and a function', () => {
-	const result = mergeWebpackConfig({ a: 0, b: 0 }, () => ({ b: 1 }));
-	expect(result).toEqual({ a: 0, b: 1 });
+	const result = mergeWebpackConfig({ entry: 'main.js', devtool: 'cheap-source-map' }, () => ({
+		devtool: 'inline-source-map',
+	}));
+	expect(result).toEqual({ entry: 'main.js', devtool: 'cheap-source-map' });
 });
 
 it('should pass an environment to a user config', () => {
@@ -22,11 +33,14 @@ it('should pass an environment to a user config', () => {
 });
 
 it('should ignore certain sections', () => {
-	const result = mergeWebpackConfig({ entry: 0 }, () => ({ entry: 1, module: 1 }));
-	expect(result).toEqual({ entry: 0, module: 1 });
+	const result = mergeWebpackConfig({ entry: 'main' }, () => ({
+		entry: 'other',
+		module: { rules: [] },
+	}));
+	expect(result).toEqual({ entry: 'main', module: { rules: [] } });
 });
 
-it('should ignore certain Webpack plugins', () => {
+it('should ignore certain Webpack plugins', done => {
 	const baseInstance = new TerserPlugin();
 	const userInstance = new TerserPlugin();
 	const result = mergeWebpackConfig(
@@ -37,10 +51,16 @@ it('should ignore certain Webpack plugins', () => {
 			plugins: [userInstance, new MyPlugin(), new MiniHtmlWebpackPlugin()],
 		}
 	);
+	// this test is necessary as some results can contain no plugins
+	if (!result || !result.plugins) {
+		done.fail();
+		return;
+	}
 	expect(result.plugins).toHaveLength(2);
 	expect(result.plugins[0]).toBe(baseInstance);
 	expect(result.plugins[0].constructor.name).toBe('TerserPlugin');
 	expect(result.plugins[1].constructor.name).toBe('MyPlugin');
+	done();
 });
 
 it('should pass devtool settings in development', () => {
diff --git a/src/scripts/utils/__tests__/sanitizeConfig.spec.js b/src/scripts/utils/__tests__/sanitizeConfig.spec.ts
similarity index 94%
rename from src/scripts/utils/__tests__/sanitizeConfig.spec.js
rename to src/scripts/utils/__tests__/sanitizeConfig.spec.ts
index 0bfaa291..ecb940d0 100644
--- a/src/scripts/utils/__tests__/sanitizeConfig.spec.js
+++ b/src/scripts/utils/__tests__/sanitizeConfig.spec.ts
@@ -13,20 +13,22 @@ it('should return non-empty required field as is', () => {
 			food: {
 				required: true,
 			},
-		}
+		},
+		''
 	);
 	expect(result).toBeTruthy();
 	expect(result.food).toBe('pizza');
 });
 
 it('should return default value for empty non-required field', () => {
-	const result = sanitizeConfig(
+	const result = sanitizeConfig<{ food?: string }>(
 		{},
 		{
 			food: {
 				default: 'pizza',
 			},
-		}
+		},
+		''
 	);
 	expect(result.food).toBe('pizza');
 });
@@ -40,7 +42,8 @@ it('should return actual value for non-empty field with default value', () => {
 			food: {
 				default: 'pizza',
 			},
-		}
+		},
+		''
 	);
 	expect(result.food).toBe('burger');
 });
@@ -54,7 +57,8 @@ it('should accept required as a function', () => {
 			food: {
 				required: () => true,
 			},
-		}
+		},
+		''
 	);
 	expect(result.food).toBe('pizza');
 });
@@ -67,7 +71,8 @@ it('should throw if required field is undefined', () => {
 				food: {
 					required: true,
 				},
-			}
+			},
+			''
 		);
 	expect(fn).toThrowError('config option is required');
 });
@@ -80,7 +85,8 @@ it('should throw with custom message returned by required function', () => {
 				food: {
 					required: () => 'Not good',
 				},
-			}
+			},
+			''
 		);
 	expect(fn).toThrowError('Not good');
 });
@@ -95,7 +101,8 @@ it('should throw when type in schema is incorrect', () => {
 				food: {
 					type: 'pizza',
 				},
-			}
+			},
+			''
 		);
 	expect(fn).toThrowError('Wrong type');
 });
@@ -109,7 +116,8 @@ it('should check type for number', () => {
 			food: {
 				type: 'number',
 			},
-		}
+		},
+		''
 	);
 	expect(result.food).toBe(42);
 });
@@ -124,7 +132,8 @@ it('should throw when field is not a number', () => {
 				food: {
 					type: 'number',
 				},
-			}
+			},
+			''
 		);
 	expect(fn).toThrowError('config option should be');
 });
@@ -138,7 +147,8 @@ it('should check type for string', () => {
 			food: {
 				type: 'string',
 			},
-		}
+		},
+		''
 	);
 	expect(result.food).toBe('pizza');
 });
@@ -153,7 +163,8 @@ it('should throw when field is not a string', () => {
 				food: {
 					type: 'string',
 				},
-			}
+			},
+			''
 		);
 	expect(fn).toThrowError('config option should be');
 });
@@ -167,7 +178,8 @@ it('should check type for boolean', () => {
 			food: {
 				type: 'boolean',
 			},
-		}
+		},
+		''
 	);
 	expect(result.food).toBe(true);
 });
@@ -182,7 +194,8 @@ it('should throw when field is not a boolean', () => {
 				food: {
 					type: 'boolean',
 				},
-			}
+			},
+			''
 		);
 	expect(fn).toThrowError('config option should be');
 });
@@ -196,7 +209,8 @@ it('should check type for array', () => {
 			food: {
 				type: 'array',
 			},
-		}
+		},
+		''
 	);
 	expect(result.food).toEqual([1, 2]);
 });
@@ -211,7 +225,8 @@ it('should throw when field is not an array', () => {
 				food: {
 					type: 'array',
 				},
-			}
+			},
+			''
 		);
 	expect(fn).toThrowError('config option should be');
 });
@@ -225,7 +240,8 @@ it('should check type for function', () => {
 			food: {
 				type: 'function',
 			},
-		}
+		},
+		''
 	);
 	expect(typeof result.food).toBe('function');
 });
@@ -240,7 +256,8 @@ it('should throw when field is not a function', () => {
 				food: {
 					type: 'function',
 				},
-			}
+			},
+			''
 		);
 	expect(fn).toThrowError('config option should be');
 });
@@ -254,7 +271,8 @@ it('should check type for object', () => {
 			food: {
 				type: 'object',
 			},
-		}
+		},
+		''
 	);
 	expect(result.food).toEqual({ a: 42 });
 });
@@ -269,7 +287,8 @@ it('should throw when field is not an object', () => {
 				food: {
 					type: 'object',
 				},
-			}
+			},
+			''
 		);
 	expect(fn).toThrowError('config option should be');
 });
@@ -360,7 +379,8 @@ it('should throw with correct type name', () => {
 				food: {
 					type: 'object',
 				},
-			}
+			},
+			''
 		);
 	expect(fn).toThrowError('config option should be object, received null');
 });
@@ -375,7 +395,8 @@ it('should pass value to a custom process function', () => {
 				type: ['boolean', 'string'],
 				process: val => (val === true ? 'pizza' : val),
 			},
-		}
+		},
+		''
 	);
 	expect(result.food).toEqual('pizza');
 });
@@ -389,7 +410,8 @@ it('should not throw if process function returns value for undefined required fi
 					required: true,
 					process: () => 'pizza',
 				},
-			}
+			},
+			''
 		);
 	expect(fn).not.toThrowError('config option is required');
 });
@@ -412,28 +434,30 @@ it('should throw when directory does not exist', () => {
 
 it('should throw for unknown options', () => {
 	const fn = () =>
-		sanitizeConfig(
+		sanitizeConfig<{ drink?: any; food?: any }>(
 			{
 				book: 'hobbit',
-			},
+			} as any,
 			{
 				drink: {},
 				food: {},
-			}
+			},
+			''
 		);
 	expect(fn).toThrowError('Unknown config option');
 });
 
 it('should throw for unknown options with suggestion', () => {
 	const fn = () =>
-		sanitizeConfig(
+		sanitizeConfig<{ drink?: any; food?: any }>(
 			{
 				dring: 'pizza',
-			},
+			} as any,
 			{
 				drink: {},
 				food: {},
-			}
+			},
+			''
 		);
 	expect(fn).toThrowError('Did you mean');
 });
@@ -450,7 +474,8 @@ it('should warn for deprecated options', () => {
 			food: {
 				deprecated: 'Don’t use!',
 			},
-		}
+		},
+		''
 	);
 	expect(result.food).toBe('pizza');
 	expect(warn).toBeCalledWith(expect.stringMatching('config option is deprecated. Don’t use!'));
@@ -466,7 +491,8 @@ it('should throw for removed options', () => {
 				food: {
 					removed: 'Don’t use!',
 				},
-			}
+			},
+			''
 		);
 	expect(fn).toThrowError('was removed');
 });