Repository: styleguidist/react-styleguidist
Pull request #1485 (2019-11-28 01:06:14)

Refactor: Rewrite loaders folder in typescript

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

| [Impacted Files](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1485?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [.../loaders/utils/getComponentPatternsFromSections.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1485/diff?src=pr&el=tree#diff-c3JjL2xvYWRlcnMvdXRpbHMvZ2V0Q29tcG9uZW50UGF0dGVybnNGcm9tU2VjdGlvbnMudHM=) | `100% <100%> (ø)` | |
| [src/loaders/utils/getAst.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1485/diff?src=pr&el=tree#diff-c3JjL2xvYWRlcnMvdXRpbHMvZ2V0QXN0LnRz) | `100% <100%> (ø)` | |
| [src/loaders/utils/removeDoclets.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1485/diff?src=pr&el=tree#diff-c3JjL2xvYWRlcnMvdXRpbHMvcmVtb3ZlRG9jbGV0cy50cw==) | `100% <100%> (ø)` | |
| [src/loaders/utils/getComponentFilesFromSections.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1485/diff?src=pr&el=tree#diff-c3JjL2xvYWRlcnMvdXRpbHMvZ2V0Q29tcG9uZW50RmlsZXNGcm9tU2VjdGlvbnMudHM=) | `100% <100%> (ø)` | |
| [src/loaders/utils/highlightCode.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1485/diff?src=pr&el=tree#diff-c3JjL2xvYWRlcnMvdXRpbHMvaGlnaGxpZ2h0Q29kZS50cw==) | `100% <100%> (ø)` | |
| [src/loaders/utils/getNameFromFilePath.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1485/diff?src=pr&el=tree#diff-c3JjL2xvYWRlcnMvdXRpbHMvZ2V0TmFtZUZyb21GaWxlUGF0aC50cw==) | `100% <100%> (ø)` | |
| [src/loaders/utils/requireIt.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1485/diff?src=pr&el=tree#diff-c3JjL2xvYWRlcnMvdXRpbHMvcmVxdWlyZUl0LnRz) | `100% <100%> (ø)` | |
| [src/loaders/utils/sortProps.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1485/diff?src=pr&el=tree#diff-c3JjL2xvYWRlcnMvdXRpbHMvc29ydFByb3BzLnRz) | `100% <100%> (ø)` | |
| [src/loaders/utils/noAutoLinkRemarkPlugin.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1485/diff?src=pr&el=tree#diff-c3JjL2xvYWRlcnMvdXRpbHMvbm9BdXRvTGlua1JlbWFya1BsdWdpbi50cw==) | `100% <100%> (ø)` | |
| [src/loaders/utils/chunkify.ts](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1485/diff?src=pr&el=tree#diff-c3JjL2xvYWRlcnMvdXRpbHMvY2h1bmtpZnkudHM=) | `91.66% <100%> (ø)` | |
| ... and [19 more](https://codecov.io/gh/styleguidist/react-styleguidist/pull/1485/diff?src=pr&el=tree-more) | |

elevatebart: Just a heads up, this might actually be a breaking change for those who have written typings for their rsg based project. 

Should I remove the publishing of typings?
elevatebart: To avoid any potential breaking changes, I postponed the publishing of the types until we have proper testing and documentation for it.

**No more potential breaking change**

Patch:
diff --git a/.travis.yml b/.travis.yml
index a579a06a..547ad634 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,7 +7,7 @@ node_js:
 addons:
   apt:
     packages:
-    - libgconf-2-4
+      - libgconf-2-4
 
 script:
   # Run tests without coverage since it's 2x faster
@@ -38,7 +38,6 @@ jobs:
 
     - stage: typecheck
       script:
-        - npm run flow check
         - npm run tscheck
 
     - stage: integration
diff --git a/package-lock.json b/package-lock.json
index acb95cb3..ba620723 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1449,6 +1449,12 @@
         "@types/webpack": "*"
       }
     },
+    "@types/doctrine": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.3.tgz",
+      "integrity": "sha1-6JLSk8ksnB0/mvcsFaVU+8fgiVo=",
+      "dev": true
+    },
     "@types/enzyme": {
       "version": "3.10.3",
       "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.3.tgz",
@@ -1459,6 +1465,12 @@
         "@types/react": "*"
       }
     },
+    "@types/escodegen": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/@types/escodegen/-/escodegen-0.0.6.tgz",
+      "integrity": "sha1-UjCpznluBCzabwhtvxnyLqMwZZw=",
+      "dev": true
+    },
     "@types/eslint-visitor-keys": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
@@ -1579,6 +1591,16 @@
       "integrity": "sha1-H+SObl4qlpyv7PDyRIWuI8cqvTA=",
       "dev": true
     },
+    "@types/loader-utils": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@types/loader-utils/-/loader-utils-1.1.3.tgz",
+      "integrity": "sha512-euKGFr2oCB3ASBwG39CYJMR3N9T0nanVqXdiH7Zu/Nqddt6SmFRxytq/i2w9LQYNQekEtGBz+pE3qG6fQTNvRg==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*",
+        "@types/webpack": "*"
+      }
+    },
     "@types/lodash": {
       "version": "4.14.144",
       "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.144.tgz",
diff --git a/package.json b/package.json
index 65211a51..134b2f5e 100644
--- a/package.json
+++ b/package.json
@@ -104,10 +104,13 @@
     "@testing-library/react": "^9.3.0",
     "@types/buble": "^0.19.2",
     "@types/copy-webpack-plugin": "^5.0.0",
+    "@types/doctrine": "0.0.3",
     "@types/enzyme": "^3.10.3",
+    "@types/escodegen": "0.0.6",
     "@types/estree": "^0.0.39",
     "@types/jest": "^24.0.21",
     "@types/keymirror": "^0.1.1",
+    "@types/loader-utils": "^1.1.3",
     "@types/lodash": "^4.14.144",
     "@types/markdown-to-jsx": "^6.9.0",
     "@types/node": "^12.12.3",
@@ -172,8 +175,8 @@
     "start:sections": "node lib/bin/styleguidist.js server --config examples/sections/styleguide.config.js",
     "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 **/__mocks__/*,**/__tests__/*,**/*.spec.*,**/*.d.ts --extensions '.js,.ts,.tsx' -d lib/ src/",
+    "compile:types": "tsc -p ./tsconfig.types.json --emitDeclarationOnly",
     "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/client/rsg-components/Argument/Argument.spec.tsx b/src/client/rsg-components/Argument/Argument.spec.tsx
index b97710bc..d70854d7 100644
--- a/src/client/rsg-components/Argument/Argument.spec.tsx
+++ b/src/client/rsg-components/Argument/Argument.spec.tsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import { ArgumentRenderer, styles } from './ArgumentRenderer';
-import classes from '../../../../test/classes';
 
 const name = 'Foo';
 const type = { name: 'Array' };
diff --git a/src/client/rsg-components/Arguments/Arguments.spec.tsx b/src/client/rsg-components/Arguments/Arguments.spec.tsx
index 7ab2a034..1cea174a 100644
--- a/src/client/rsg-components/Arguments/Arguments.spec.tsx
+++ b/src/client/rsg-components/Arguments/Arguments.spec.tsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import { ArgumentsRenderer, styles } from './ArgumentsRenderer';
-import classes from '../../../../test/classes';
 
 const props = {
 	classes: classes(styles),
diff --git a/src/client/rsg-components/Editor/Editor.tsx b/src/client/rsg-components/Editor/Editor.tsx
index 3754cb59..8a8867d4 100644
--- a/src/client/rsg-components/Editor/Editor.tsx
+++ b/src/client/rsg-components/Editor/Editor.tsx
@@ -35,7 +35,7 @@ const styles = ({ fontFamily, fontSize, color, borderRadius }: Rsg.Theme) => ({
 	},
 });
 
-interface EditorProps extends JssInjectedProps {
+export interface EditorProps extends JssInjectedProps {
 	code: string;
 	onChange: (code: string) => void;
 }
diff --git a/src/client/rsg-components/JsDoc/JsDoc.tsx b/src/client/rsg-components/JsDoc/JsDoc.tsx
index 430b52c7..c8e47392 100644
--- a/src/client/rsg-components/JsDoc/JsDoc.tsx
+++ b/src/client/rsg-components/JsDoc/JsDoc.tsx
@@ -4,7 +4,8 @@ import { TagProps, TagObject } from 'react-docgen';
 import map from 'lodash/map';
 import Markdown from 'rsg-components/Markdown';
 
-const plural = (array: any[], caption: string) => (array.length === 1 ? caption : `${caption}s`);
+const plural = (array: TagObject[], caption: string) =>
+	array.length === 1 ? caption : `${caption}s`;
 const list = (array: TagObject[]) => array.map(item => item.description).join(', ');
 const paragraphs = (array: TagObject[]) => array.map(item => item.description).join('\n\n');
 
diff --git a/src/client/rsg-components/Markdown/Checkbox/index.js b/src/client/rsg-components/Markdown/Checkbox/index.ts
similarity index 100%
rename from src/client/rsg-components/Markdown/Checkbox/index.js
rename to src/client/rsg-components/Markdown/Checkbox/index.ts
diff --git a/src/client/rsg-components/Markdown/Pre/PreRenderer.tsx b/src/client/rsg-components/Markdown/Pre/PreRenderer.tsx
index 1fe44a75..2324b24c 100644
--- a/src/client/rsg-components/Markdown/Pre/PreRenderer.tsx
+++ b/src/client/rsg-components/Markdown/Pre/PreRenderer.tsx
@@ -20,6 +20,7 @@ const styles = ({ space, color, fontSize, fontFamily, borderRadius }: Rsg.Theme)
 		borderRadius,
 		marginTop: 0,
 		marginBottom: space[2],
+		overflow: 'auto',
 		...prismTheme({ color }),
 	},
 });
diff --git a/src/client/rsg-components/Name/Name.spec.tsx b/src/client/rsg-components/Name/Name.spec.tsx
index b5dd3e79..355ce71f 100644
--- a/src/client/rsg-components/Name/Name.spec.tsx
+++ b/src/client/rsg-components/Name/Name.spec.tsx
@@ -1,8 +1,6 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import { NameRenderer, styles } from './NameRenderer';
-// TODO: simplify this path
-import classes from '../../../../test/classes';
 
 const props = {
 	classes: classes(styles),
diff --git a/src/client/rsg-components/Para/Para.spec.tsx b/src/client/rsg-components/Para/Para.spec.tsx
index 9e2424c6..e2411c3b 100644
--- a/src/client/rsg-components/Para/Para.spec.tsx
+++ b/src/client/rsg-components/Para/Para.spec.tsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import { ParaRenderer, styles } from './ParaRenderer';
-import classes from '../../../../test/classes';
 
 const props = {
 	classes: classes(styles),
diff --git a/src/client/rsg-components/Pathline/Pathline.spec.tsx b/src/client/rsg-components/Pathline/Pathline.spec.tsx
index 123e1d38..0090f4ac 100644
--- a/src/client/rsg-components/Pathline/Pathline.spec.tsx
+++ b/src/client/rsg-components/Pathline/Pathline.spec.tsx
@@ -2,7 +2,6 @@ import React from 'react';
 import copy from 'clipboard-copy';
 import { shallow, mount } from 'enzyme';
 import { PathlineRenderer, styles } from './PathlineRenderer';
-import classes from '../../../../test/classes';
 
 jest.mock('clipboard-copy');
 
diff --git a/src/client/rsg-components/Table/Table.spec.tsx b/src/client/rsg-components/Table/Table.spec.tsx
index dcf64eb6..5971e9bc 100644
--- a/src/client/rsg-components/Table/Table.spec.tsx
+++ b/src/client/rsg-components/Table/Table.spec.tsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import { TableRenderer, styles } from './TableRenderer';
-import classes from '../../../../test/classes';
 
 const columns = [
 	{
diff --git a/src/client/rsg-components/Text/Text.spec.tsx b/src/client/rsg-components/Text/Text.spec.tsx
index 24678cf3..046ead09 100644
--- a/src/client/rsg-components/Text/Text.spec.tsx
+++ b/src/client/rsg-components/Text/Text.spec.tsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import { TextRenderer, styles } from './TextRenderer';
-import classes from '../../../../test/classes';
 
 const props = {
 	classes: classes(styles),
diff --git a/src/client/rsg-components/Text/TextRenderer.tsx b/src/client/rsg-components/Text/TextRenderer.tsx
index 192ce450..b9325eca 100644
--- a/src/client/rsg-components/Text/TextRenderer.tsx
+++ b/src/client/rsg-components/Text/TextRenderer.tsx
@@ -36,7 +36,7 @@ export const styles = ({ fontFamily, fontSize, color }: Rsg.Theme) => ({
 	},
 });
 
-interface TextProps extends JssInjectedProps {
+export interface TextProps extends JssInjectedProps {
 	semantic?: 'em' | 'strong';
 	size?: 'inherit' | 'small' | 'base' | 'text';
 	color?: 'base' | 'light';
diff --git a/src/client/rsg-components/ToolbarButton/ToolbarButton.spec.tsx b/src/client/rsg-components/ToolbarButton/ToolbarButton.spec.tsx
index 08e5a814..7039178c 100644
--- a/src/client/rsg-components/ToolbarButton/ToolbarButton.spec.tsx
+++ b/src/client/rsg-components/ToolbarButton/ToolbarButton.spec.tsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import { ToolbarButtonRenderer, styles } from './ToolbarButtonRenderer';
-import classes from '../../../../test/classes';
 
 const props = {
 	classes: classes(styles),
diff --git a/src/client/rsg-components/Type/Type.spec.tsx b/src/client/rsg-components/Type/Type.spec.tsx
index c2b6f4f5..54cb9f31 100644
--- a/src/client/rsg-components/Type/Type.spec.tsx
+++ b/src/client/rsg-components/Type/Type.spec.tsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import { TypeRenderer, styles } from './TypeRenderer';
-import classes from '../../../../test/classes';
 
 const props = {
 	classes: classes(styles),
diff --git a/src/client/rsg-components/slots/IsolateButton.tsx b/src/client/rsg-components/slots/IsolateButton.tsx
index 86f01667..8d85bc06 100644
--- a/src/client/rsg-components/slots/IsolateButton.tsx
+++ b/src/client/rsg-components/slots/IsolateButton.tsx
@@ -4,7 +4,7 @@ import { MdFullscreen, MdFullscreenExit } from 'react-icons/md';
 import ToolbarButton from 'rsg-components/ToolbarButton';
 import getUrl from '../../utils/getUrl';
 
-interface IsolateButtonProps {
+export interface IsolateButtonProps {
 	name: string;
 	example?: number;
 	isolated?: boolean;
diff --git a/src/client/rsg-components/slots/UsageTabButton.tsx b/src/client/rsg-components/slots/UsageTabButton.tsx
index 22af1036..b7857b8a 100644
--- a/src/client/rsg-components/slots/UsageTabButton.tsx
+++ b/src/client/rsg-components/slots/UsageTabButton.tsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import TabButton from 'rsg-components/TabButton';
 import isEmpty from 'lodash/isEmpty';
 
-interface UsageTabButtonProps {
+export interface UsageTabButtonProps {
 	name: string;
 	onClick: (e: React.MouseEvent) => void;
 	active?: boolean;
diff --git a/src/loaders/.eslintrc b/src/loaders/.eslintrc
deleted file mode 100644
index 8fff4402..00000000
--- a/src/loaders/.eslintrc
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "extends": "tamia",
-  "rules": {
-    "valid-jsdoc": 0,
-      "no-duplicate-imports": 0
-  }
-}
diff --git a/src/loaders/examples-loader.js b/src/loaders/examples-loader.ts
similarity index 80%
rename from src/loaders/examples-loader.js
rename to src/loaders/examples-loader.ts
index 36a1936e..38930e66 100644
--- a/src/loaders/examples-loader.js
+++ b/src/loaders/examples-loader.ts
@@ -12,12 +12,12 @@ import expandDefaultComponent from './utils/expandDefaultComponent';
 import getImports from './utils/getImports';
 import requireIt from './utils/requireIt';
 
-const absolutize = filepath => path.resolve(__dirname, filepath);
+const absolutize = (filepath: string) => path.resolve(__dirname, filepath);
 
 const REQUIRE_IN_RUNTIME_PATH = absolutize('utils/client/requireInRuntime');
 const EVAL_IN_CONTEXT_PATH = absolutize('utils/client/evalInContext');
 
-export default function examplesLoader(source) {
+export default function examplesLoader(this: Rsg.StyleguidistLoaderContext, source: string) {
 	const config = this._styleguidist;
 	const { file, displayName, shouldShowDefaultExample, customLangs } =
 		loaderUtils.getOptions(this) || {};
@@ -28,7 +28,7 @@ export default function examplesLoader(source) {
 	}
 
 	const updateExample = config.updateExample
-		? props => config.updateExample(props, this.resourcePath)
+		? (props: Omit<Rsg.CodeExample, 'type'>) => config.updateExample(props, this.resourcePath)
 		: undefined;
 
 	// Load examples
@@ -39,7 +39,7 @@ export default function examplesLoader(source) {
 	// Note that we can't just use require() directly at runtime,
 	// because webpack changes its name to something like __webpack__require__().
 	const allCodeExamples = filter(examples, { type: 'code' });
-	const requiresFromExamples = allCodeExamples.reduce((requires, example) => {
+	const requiresFromExamples = allCodeExamples.reduce((requires: string[], example) => {
 		return requires.concat(getImports(example.content));
 	}, []);
 
@@ -65,18 +65,21 @@ export default function examplesLoader(source) {
 
 	// “Prerequire” modules required in Markdown examples and context so they
 	// end up in a bundle and be available at runtime
-	const allModulesCode = allModules.reduce((requires, requireRequest) => {
-		requires[requireRequest] = requireIt(requireRequest);
-		return requires;
-	}, {});
+	const allModulesCode = allModules.reduce(
+		(requires: Record<string, Rsg.RequireItResult>, requireRequest) => {
+			requires[requireRequest] = requireIt(requireRequest);
+			return requires;
+		},
+		{}
+	);
 
 	// Require context modules so they are available in an example
 	const requireContextCode = b.program(
 		flatten(
-			map(fullContext, (requireRequest, name) => [
+			map(fullContext, (requireRequest: string, name) => [
 				// const name$0 = require(path);
 				b.variableDeclaration('const', [
-					b.variableDeclarator(b.identifier(`${name}$0`), requireIt(requireRequest).toAST()),
+					b.variableDeclarator(b.identifier(`${name}$0`), requireIt(requireRequest).toAST() as any),
 				]),
 				// const name = name$0[name] || name$0.default || name$0;
 				b.variableDeclaration('const', [
@@ -98,12 +101,15 @@ export default function examplesLoader(source) {
 	);
 
 	// Stringify examples object except the evalInContext function
-	const examplesWithEval = examples.map(example => {
-		if (example.type === 'code') {
-			example.evalInContext = { toAST: () => b.identifier('evalInContext') };
+	const examplesWithEval: (Rsg.RuntimeCodeExample | Rsg.MarkdownExample)[] = examples.map(
+		example => {
+			if (example.type === 'code') {
+				return { ...example, evalInContext: { toAST: () => b.identifier('evalInContext') } as any };
+			} else {
+				return example;
+			}
 		}
-		return example;
-	});
+	);
 
 	return `
 if (module.hot) {
diff --git a/src/loaders/props-loader.js b/src/loaders/props-loader.ts
similarity index 55%
rename from src/loaders/props-loader.js
rename to src/loaders/props-loader.ts
index b3727c0b..7936e949 100644
--- a/src/loaders/props-loader.js
+++ b/src/loaders/props-loader.ts
@@ -1,18 +1,22 @@
-const path = require('path');
-const isArray = require('lodash/isArray');
-const reactDocs = require('react-docgen');
-const generate = require('escodegen').generate;
-const toAst = require('to-ast');
-const logger = require('glogg')('rsg');
-const getExamples = require('./utils/getExamples');
-const getProps = require('./utils/getProps');
-const defaultSortProps = require('./utils/sortProps');
-const consts = require('../scripts/consts');
+import path from 'path';
+import isArray from 'lodash/isArray';
+import { Handler, parse, DocumentationObject, PropDescriptor } from 'react-docgen';
+import { ASTNode } from 'ast-types';
+import { NodePath } from 'ast-types/lib/node-path';
+import { generate } from 'escodegen';
+import toAst from 'to-ast';
+import createLogger from 'glogg';
+import getExamples from './utils/getExamples';
+import getProps from './utils/getProps';
+import defaultSortProps from './utils/sortProps';
+import * as consts from '../scripts/consts';
+
+const logger = createLogger('rsg');
 
 const ERROR_MISSING_DEFINITION = 'No suitable component definition found.';
 
-module.exports = function(source) {
-	const file = this.request.split('!').pop();
+export default function(this: Rsg.StyleguidistLoaderContext, source: string) {
+	const file: string = this.request.split('!').pop() || '';
 	const config = this._styleguidist;
 
 	// Setup Webpack context dependencies to enable hot reload when adding new files or updating any of component dependencies
@@ -20,11 +24,18 @@ module.exports = function(source) {
 		config.contextDependencies.forEach(dir => this.addContextDependency(dir));
 	}
 
-	const defaultParser = (filePath, code, resolver, handlers) =>
-		reactDocs.parse(code, resolver, handlers, { filename: filePath });
+	const defaultParser = (
+		filePath: string,
+		code: string,
+		resolver: (
+			ast: ASTNode,
+			parser: { parse: (code: string) => ASTNode }
+		) => NodePath<any, any> | NodePath[],
+		handlers: Handler[]
+	) => parse(code, resolver, handlers, { filename: filePath });
 	const propsParser = config.propsParser || defaultParser;
 
-	let docs = {};
+	let docs: DocumentationObject = {};
 	try {
 		docs = propsParser(file, source, config.resolver, config.handlers(file));
 
@@ -50,28 +61,34 @@ module.exports = function(source) {
 		logger.warn(message);
 	}
 
-	docs = getProps(docs, file);
+	const tempDocs = getProps(docs, file);
+	let finalDocs: Rsg.PropsObject = { ...tempDocs, props: [] };
 
-	const componentProps = docs.props;
+	const componentProps = tempDocs.props;
 	if (componentProps) {
 		// Transform the properties to an array. This will allow sorting
 		// TODO: Extract to a module
-		const propsAsArray = Object.keys(componentProps).reduce((acc, name) => {
+		const propsAsArray = Object.keys(componentProps).reduce((acc: PropDescriptor[], name) => {
 			componentProps[name].name = name;
 			acc.push(componentProps[name]);
 			return acc;
 		}, []);
 
 		const sortProps = config.sortProps || defaultSortProps;
-		docs.props = sortProps(propsAsArray);
+		finalDocs.props = sortProps(propsAsArray);
 	}
 
 	// Examples from Markdown file
 	const examplesFile = config.getExampleFilename(file);
-	docs.examples = getExamples(file, docs.displayName, examplesFile, config.defaultExample);
+	finalDocs.examples = getExamples(
+		file,
+		finalDocs.displayName,
+		examplesFile,
+		config.defaultExample
+	);
 
 	if (config.updateDocs) {
-		docs = config.updateDocs(docs, file);
+		finalDocs = config.updateDocs(finalDocs, file);
 	}
 
 	return `
@@ -79,6 +96,6 @@ if (module.hot) {
 	module.hot.accept([])
 }
 
-module.exports = ${generate(toAst(docs))}
+module.exports = ${generate(toAst(finalDocs))}
 	`;
-};
+}
diff --git a/src/loaders/styleguide-loader.js b/src/loaders/styleguide-loader.ts
similarity index 66%
rename from src/loaders/styleguide-loader.js
rename to src/loaders/styleguide-loader.ts
index 3b2bd62a..a329d540 100644
--- a/src/loaders/styleguide-loader.js
+++ b/src/loaders/styleguide-loader.ts
@@ -1,15 +1,17 @@
-const pick = require('lodash/pick');
-const commonDir = require('common-dir');
-const generate = require('escodegen').generate;
-const toAst = require('to-ast');
-const logger = require('glogg')('rsg');
-const fileExistsCaseInsensitive = require('../scripts/utils/findFileCaseInsensitive');
-const getAllContentPages = require('./utils/getAllContentPages');
-const getComponentFilesFromSections = require('./utils/getComponentFilesFromSections');
-const getComponentPatternsFromSections = require('./utils/getComponentPatternsFromSections');
-const getSections = require('./utils/getSections');
-const filterComponentsWithExample = require('./utils/filterComponentsWithExample');
-const slugger = require('./utils/slugger');
+import pick from 'lodash/pick';
+import commonDir from 'common-dir';
+import { generate } from 'escodegen';
+import toAst from 'to-ast';
+import createLogger from 'glogg';
+import * as fileExistsCaseInsensitive from '../scripts/utils/findFileCaseInsensitive';
+import getAllContentPages from './utils/getAllContentPages';
+import getComponentFilesFromSections from './utils/getComponentFilesFromSections';
+import getComponentPatternsFromSections from './utils/getComponentPatternsFromSections';
+import getSections from './utils/getSections';
+import filterComponentsWithExample from './utils/filterComponentsWithExample';
+import slugger from './utils/slugger';
+
+const logger = createLogger('rsg');
 
 // Config options that should be passed to the client
 const CLIENT_CONFIG_OPTIONS = [
@@ -25,8 +27,8 @@ const CLIENT_CONFIG_OPTIONS = [
 	'version',
 ];
 
-module.exports = function() {};
-module.exports.pitch = function() {
+export default function() {}
+export function pitch(this: Rsg.StyleguidistLoaderContext) {
 	// Clear cache so it would detect new or renamed files
 	fileExistsCaseInsensitive.clearCache();
 
@@ -55,7 +57,7 @@ module.exports.pitch = function() {
 
 	// Setup Webpack context dependencies to enable hot reload when adding new files
 	if (config.contextDependencies) {
-		config.contextDependencies.forEach(dir => this.addContextDependency(dir));
+		config.contextDependencies.forEach((dir: string) => this.addContextDependency(dir));
 	} else if (allComponentFiles.length > 0) {
 		// Use common parent directory of all components as a context
 		this.addContextDependency(commonDir(allComponentFiles));
@@ -75,4 +77,4 @@ if (module.hot) {
 
 module.exports = ${generate(toAst(styleguide))}
 	`;
-};
+}
diff --git a/src/loaders/utils/chunkify.js b/src/loaders/utils/chunkify.ts
similarity index 69%
rename from src/loaders/utils/chunkify.js
rename to src/loaders/utils/chunkify.ts
index ec6ee785..a99c2691 100644
--- a/src/loaders/utils/chunkify.js
+++ b/src/loaders/utils/chunkify.ts
@@ -1,11 +1,15 @@
 import remark from 'remark';
 import visit from 'unist-util-visit';
 import highlightCode from './highlightCode';
-import parseExample from './parseExample';
+import parseExample, { ExampleError } from './parseExample';
 
 const PLAYGROUND_LANGS = ['javascript', 'js', 'jsx'];
 const CODE_PLACEHOLDER = '<%{#code#}%>';
 
+function isErrorExample(example: any): example is ExampleError {
+	return !!example.error;
+}
+
 /**
  * Separate Markdown and code examples that should be rendered as a playground in a style guide.
  *
@@ -14,8 +18,12 @@ const CODE_PLACEHOLDER = '<%{#code#}%>';
  * @param {Array<string>} playgroundLangs
  * @returns {Array}
  */
-export default function chunkify(markdown, updateExample, playgroundLangs = PLAYGROUND_LANGS) {
-	const codeChunks = [];
+export default function chunkify(
+	markdown: string,
+	updateExample?: (example: Omit<Rsg.CodeExample, 'type'>) => Omit<Rsg.CodeExample, 'type'>,
+	playgroundLangs = PLAYGROUND_LANGS
+): (Rsg.CodeExample | Rsg.MarkdownExample)[] {
+	const codeChunks: Rsg.CodeExample[] = [];
 
 	/*
 	 * - Highlight code in fenced code blocks with defined language (```html).
@@ -23,11 +31,11 @@ export default function chunkify(markdown, updateExample, playgroundLangs = PLAY
 	 * - Leave all other Markdown or HTML as is.
 	 */
 	function processCode() {
-		return ast => {
-			visit(ast, 'code', node => {
+		return (ast: any) => {
+			visit(ast, 'code', (node: any) => {
 				const example = parseExample(node.value, node.lang, node.meta, updateExample);
 
-				if (example.error) {
+				if (isErrorExample(example)) {
 					node.lang = undefined;
 					node.value = example.error;
 					return;
@@ -35,7 +43,10 @@ export default function chunkify(markdown, updateExample, playgroundLangs = PLAY
 
 				const lang = example.lang;
 				node.lang = lang;
-				if (!lang || (playgroundLangs.indexOf(lang) !== -1 && !example.settings.static)) {
+				if (
+					!lang ||
+					(playgroundLangs.indexOf(lang) !== -1 && !(example.settings && example.settings.static))
+				) {
 					codeChunks.push({
 						type: 'code',
 						content: example.content,
@@ -56,7 +67,7 @@ export default function chunkify(markdown, updateExample, playgroundLangs = PLAY
 		.processSync(markdown)
 		.toString();
 
-	const chunks = [];
+	const chunks: (Rsg.CodeExample | Rsg.MarkdownExample)[] = [];
 	const textChunks = rendered.split(CODE_PLACEHOLDER);
 	textChunks.forEach(chunk => {
 		chunk = chunk.trim();
diff --git a/src/loaders/utils/expandDefaultComponent.js b/src/loaders/utils/expandDefaultComponent.ts
similarity index 79%
rename from src/loaders/utils/expandDefaultComponent.js
rename to src/loaders/utils/expandDefaultComponent.ts
index d7b5a09a..57c409ac 100644
--- a/src/loaders/utils/expandDefaultComponent.js
+++ b/src/loaders/utils/expandDefaultComponent.ts
@@ -8,6 +8,6 @@ const COMPONENT_PLACEHOLDER_REGEXP = new RegExp(COMPONENT_PLACEHOLDER, 'g');
  * @param {string} componentName Name that will be used instead of a placeholder.
  * @returns {string}
  */
-module.exports = function expandDefaultComponent(source, componentName) {
+export default function expandDefaultComponent(source: string, componentName: string): string {
 	return source.replace(COMPONENT_PLACEHOLDER_REGEXP, componentName);
-};
+}
diff --git a/src/loaders/utils/filterComponentsWithExample.js b/src/loaders/utils/filterComponentsWithExample.ts
similarity index 78%
rename from src/loaders/utils/filterComponentsWithExample.js
rename to src/loaders/utils/filterComponentsWithExample.ts
index 2b19470a..3306a6e3 100644
--- a/src/loaders/utils/filterComponentsWithExample.js
+++ b/src/loaders/utils/filterComponentsWithExample.ts
@@ -4,7 +4,9 @@
  * @param {Array} sections
  * @returns {Array}
  */
-module.exports = function filterComponentsWithExample(sections) {
+export default function filterComponentsWithExample(
+	sections: Rsg.LoaderSection[]
+): Rsg.LoaderSection[] {
 	return sections
 		.map(section => ({
 			...section,
@@ -14,4 +16,4 @@ module.exports = function filterComponentsWithExample(sections) {
 		.filter(
 			section => section.components.length > 0 || section.sections.length > 0 || section.content
 		);
-};
+}
diff --git a/src/loaders/utils/getAllContentPages.js b/src/loaders/utils/getAllContentPages.ts
similarity index 56%
rename from src/loaders/utils/getAllContentPages.js
rename to src/loaders/utils/getAllContentPages.ts
index 7df80d2b..a98730f2 100644
--- a/src/loaders/utils/getAllContentPages.js
+++ b/src/loaders/utils/getAllContentPages.ts
@@ -4,8 +4,10 @@
  * @param {Array} sections
  * @returns {Array}
  */
-module.exports = function getAllContentPages(sections) {
-	return sections.reduce((pages, section) => {
+export default function getAllContentPages(
+	sections: Rsg.LoaderSection[]
+): (Rsg.MarkdownExample | Rsg.RequireItResult)[] {
+	return sections.reduce((pages: (Rsg.MarkdownExample | Rsg.RequireItResult)[], section) => {
 		if (section.content) {
 			pages = pages.concat([section.content]);
 		}
@@ -16,4 +18,4 @@ module.exports = function getAllContentPages(sections) {
 
 		return pages;
 	}, []);
-};
+}
diff --git a/src/loaders/utils/getAst.js b/src/loaders/utils/getAst.ts
similarity index 63%
rename from src/loaders/utils/getAst.js
rename to src/loaders/utils/getAst.ts
index a548d5da..8a07985c 100644
--- a/src/loaders/utils/getAst.js
+++ b/src/loaders/utils/getAst.ts
@@ -1,10 +1,9 @@
-// @flow
-import { Parser, type AcornNode } from 'acorn';
+import { Parser, Node as AcornNode, Options } from 'acorn';
 import Logger from 'glogg';
 
 const logger = Logger('rsg');
 
-export const ACORN_OPTIONS = {
+export const ACORN_OPTIONS: Options = {
 	ecmaVersion: 2019,
 	sourceType: 'module',
 };
@@ -12,7 +11,10 @@ export const ACORN_OPTIONS = {
 /**
  * Parse source code with Acorn and return AST, returns undefined in case of errors
  */
-export default function getAst(code: string, plugins?: Function[] = []): ?AcornNode {
+export default function getAst(
+	code: string,
+	plugins: ((BaseParser: typeof Parser) => typeof Parser)[] = []
+): AcornNode | undefined {
 	const parser = Parser.extend(...plugins);
 
 	try {
diff --git a/src/loaders/utils/getComponentFiles.js b/src/loaders/utils/getComponentFiles.ts
similarity index 72%
rename from src/loaders/utils/getComponentFiles.js
rename to src/loaders/utils/getComponentFiles.ts
index 4dc48110..824af746 100644
--- a/src/loaders/utils/getComponentFiles.js
+++ b/src/loaders/utils/getComponentFiles.ts
@@ -1,9 +1,9 @@
-const glob = require('glob');
-const path = require('path');
-const isFunction = require('lodash/isFunction');
-const isString = require('lodash/isString');
+import glob from 'glob';
+import path from 'path';
+import isFunction from 'lodash/isFunction';
+import isString from 'lodash/isString';
 
-const getComponentGlobs = components => {
+const getComponentGlobs = (components: string | string[] | (() => string[])): string[] => {
 	if (isFunction(components)) {
 		return components();
 	} else if (Array.isArray(components)) {
@@ -16,7 +16,7 @@ const getComponentGlobs = components => {
 	);
 };
 
-const getFilesMatchingGlobs = (components, rootDir, ignore) => {
+const getFilesMatchingGlobs = (components: string[], rootDir?: string, ignore?: string[]) => {
 	ignore = ignore || [];
 	return components
 		.map(listItem =>
@@ -37,7 +37,11 @@ const getFilesMatchingGlobs = (components, rootDir, ignore) => {
  * @param {Array} [ignore] Glob patterns to ignore.
  * @returns {Array}
  */
-module.exports = function getComponentFiles(components, rootDir, ignore) {
+export default function getComponentFiles(
+	components?: string | string[] | (() => string[]) | undefined,
+	rootDir?: string,
+	ignore?: string[]
+): string[] {
 	if (!components) {
 		return [];
 	}
@@ -52,4 +56,4 @@ module.exports = function getComponentFiles(components, rootDir, ignore) {
 	const resolvedComponentFiles = componentFiles.map(file => path.resolve(file));
 
 	return resolvedComponentFiles;
-};
+}
diff --git a/src/loaders/utils/getComponentFilesFromSections.js b/src/loaders/utils/getComponentFilesFromSections.ts
similarity index 64%
rename from src/loaders/utils/getComponentFilesFromSections.js
rename to src/loaders/utils/getComponentFilesFromSections.ts
index 27c4b455..0db05ac3 100644
--- a/src/loaders/utils/getComponentFilesFromSections.js
+++ b/src/loaders/utils/getComponentFilesFromSections.ts
@@ -1,4 +1,4 @@
-const getComponentFiles = require('./getComponentFiles');
+import getComponentFiles from './getComponentFiles';
 
 /**
  * Return absolute paths of all components in sections.
@@ -8,8 +8,12 @@ const getComponentFiles = require('./getComponentFiles');
  * @param {Array} [ignore] Glob patterns to ignore.
  * @returns {Array}
  */
-module.exports = function getComponentFilesFromSections(sections, rootDir, ignore) {
-	return sections.reduce((components, section) => {
+export default function getComponentFilesFromSections(
+	sections: Rsg.ConfigSection[],
+	rootDir?: string,
+	ignore?: string[]
+): string[] {
+	return sections.reduce((components: string[], section) => {
 		if (section.components) {
 			return components.concat(getComponentFiles(section.components, rootDir, ignore));
 		}
@@ -20,4 +24,4 @@ module.exports = function getComponentFilesFromSections(sections, rootDir, ignor
 
 		return components;
 	}, []);
-};
+}
diff --git a/src/loaders/utils/getComponentPatternsFromSections.js b/src/loaders/utils/getComponentPatternsFromSections.ts
similarity index 53%
rename from src/loaders/utils/getComponentPatternsFromSections.js
rename to src/loaders/utils/getComponentPatternsFromSections.ts
index a7fb9521..7b617cb2 100644
--- a/src/loaders/utils/getComponentPatternsFromSections.js
+++ b/src/loaders/utils/getComponentPatternsFromSections.ts
@@ -1,12 +1,13 @@
 /**
  * Return all glob patterns from all sections.
  *
+ * NOTE: a section cannot have components & subsections
  * @param {Array} sections
  * @returns {Array}
  */
-module.exports = function getComponentPatternsFromSections(sections) {
-	return sections.reduce((patterns, section) => {
-		if (section.components) {
+export default function getComponentPatternsFromSections(sections: Rsg.ConfigSection[]): string[] {
+	return sections.reduce((patterns: string[], section) => {
+		if (Array.isArray(section.components)) {
 			return patterns.concat(section.components);
 		}
 
@@ -16,4 +17,4 @@ module.exports = function getComponentPatternsFromSections(sections) {
 
 		return patterns;
 	}, []);
-};
+}
diff --git a/src/loaders/utils/getComponents.js b/src/loaders/utils/getComponents.ts
similarity index 58%
rename from src/loaders/utils/getComponents.js
rename to src/loaders/utils/getComponents.ts
index f637bcc9..d32fd830 100644
--- a/src/loaders/utils/getComponents.js
+++ b/src/loaders/utils/getComponents.ts
@@ -1,4 +1,4 @@
-const processComponent = require('./processComponent');
+import processComponent from './processComponent';
 
 /**
  * Process each component in a list.
@@ -7,6 +7,9 @@ const processComponent = require('./processComponent');
  * @param {object} config
  * @returns {object|null}
  */
-module.exports = function getComponents(components, config) {
+export default function getComponents(
+	components: string[],
+	config: Rsg.SanitizedStyleguidistConfig
+) {
 	return components.map(filepath => processComponent(filepath, config));
-};
+}
diff --git a/src/loaders/utils/getExamples.js b/src/loaders/utils/getExamples.ts
similarity index 72%
rename from src/loaders/utils/getExamples.js
rename to src/loaders/utils/getExamples.ts
index 4e33e9bc..4c122e19 100644
--- a/src/loaders/utils/getExamples.js
+++ b/src/loaders/utils/getExamples.ts
@@ -1,4 +1,3 @@
-// @flow
 import path from 'path';
 import fs from 'fs';
 import { encode } from 'qss';
@@ -9,13 +8,14 @@ const examplesLoader = path.resolve(__dirname, '../examples-loader.js');
 /**
  * Get require statement for examples file if it exists, or for default examples if it was defined.
  */
-module.exports = function getExamples(
+export default function getExamples(
 	file: string,
 	displayName: string,
-	examplesFile: string,
-	defaultExample: string
-): {} | null {
-	const examplesFileToLoad = (fs.existsSync(examplesFile) && examplesFile) || defaultExample;
+	examplesFile?: string | false,
+	defaultExample?: string | false
+): Rsg.RequireItResult | null {
+	const examplesFileToLoad =
+		(examplesFile && fs.existsSync(examplesFile) ? examplesFile : false) || defaultExample;
 	if (!examplesFileToLoad) {
 		return null;
 	}
@@ -28,4 +28,4 @@ module.exports = function getExamples(
 		shouldShowDefaultExample: !examplesFile && !!defaultExample,
 	};
 	return requireIt(`!!${examplesLoader}?${encode(query)}!${examplesFileToLoad}`);
-};
+}
diff --git a/src/loaders/utils/getImports.js b/src/loaders/utils/getImports.ts
similarity index 93%
rename from src/loaders/utils/getImports.js
rename to src/loaders/utils/getImports.ts
index 18500a5c..32230228 100644
--- a/src/loaders/utils/getImports.js
+++ b/src/loaders/utils/getImports.ts
@@ -1,4 +1,3 @@
-// @flow
 import acornJsx from 'acorn-jsx';
 import { walk } from 'estree-walker';
 import getAst from './getAst';
@@ -17,9 +16,9 @@ export default function getImports(code: string): string[] {
 		return [];
 	}
 
-	const imports = [];
-	walk(ast, {
-		enter: node => {
+	const imports: string[] = [];
+	walk(ast as any, {
+		enter: (node: any) => {
 			// import foo from 'foo'
 			// import 'foo'
 			if (node.type === 'ImportDeclaration') {
diff --git a/src/loaders/utils/getNameFromFilePath.js b/src/loaders/utils/getNameFromFilePath.ts
similarity index 63%
rename from src/loaders/utils/getNameFromFilePath.js
rename to src/loaders/utils/getNameFromFilePath.ts
index aeed02dc..6d03a205 100644
--- a/src/loaders/utils/getNameFromFilePath.js
+++ b/src/loaders/utils/getNameFromFilePath.ts
@@ -1,17 +1,17 @@
-const path = require('path');
-const { startCase } = require('lodash');
+import path from 'path';
+import startCase from 'lodash/startCase';
 
-function transformFileNameToDisplayName(displayName) {
+function transformFileNameToDisplayName(displayName: string): string {
 	// ex: your-buttonTS -> Your Button TS -> YourButtonTS
 	// ex: your_button--TS -> Your Button TS -> YourButtonTS
 	return startCase(displayName).replace(/\s/g, '');
 }
 
-module.exports = function getNameFromFilePath(filePath) {
+export default function getNameFromFilePath(filePath: string): string {
 	let fileName = path.basename(filePath, path.extname(filePath));
 	if (fileName === 'index') {
 		fileName = path.basename(path.dirname(filePath));
 	}
 
 	return transformFileNameToDisplayName(fileName);
-};
+}
diff --git a/src/loaders/utils/getProps.js b/src/loaders/utils/getProps.js
deleted file mode 100644
index 4e9104e6..00000000
--- a/src/loaders/utils/getProps.js
+++ /dev/null
@@ -1,157 +0,0 @@
-const path = require('path');
-const fs = require('fs');
-const reactDocs = require('react-docgen');
-const highlightCodeInMarkdown = require('./highlightCodeInMarkdown');
-const removeDoclets = require('./removeDoclets');
-const requireIt = require('./requireIt');
-const getNameFromFilePath = require('./getNameFromFilePath');
-const doctrine = require('doctrine');
-const _ = require('lodash');
-const logger = require('glogg')('rsg');
-
-const examplesLoader = path.resolve(__dirname, '../examples-loader.js');
-
-const JS_DOC_METHOD_PARAM_TAG_SYNONYMS = ['param', 'arg', 'argument'];
-const JS_DOC_METHOD_RETURN_TAG_SYNONYMS = ['return', 'returns'];
-const JS_DOC_ALL_SYNONYMS = [
-	...JS_DOC_METHOD_PARAM_TAG_SYNONYMS,
-	...JS_DOC_METHOD_RETURN_TAG_SYNONYMS,
-];
-
-// HACK: We have to make sure that doclets is a proper object with correct prototype to
-// work around an issue in react-docgen that breaks the build if a component has JSDoc tags
-// like @see in its description, see https://github.com/reactjs/react-docgen/issues/155
-// and https://github.com/styleguidist/react-styleguidist/issues/298
-const getDocletsObject = string => ({ ...reactDocs.utils.docblock.getDoclets(string) });
-
-const getDoctrineTags = documentation => {
-	return _.groupBy(documentation.tags, 'title');
-};
-
-const doesExternalExampleFileExist = (componentPath, exampleFile) => {
-	const exampleFilepath = path.resolve(path.dirname(componentPath), exampleFile);
-	const doesFileExist = fs.existsSync(exampleFilepath);
-
-	if (!doesFileExist) {
-		logger.warn(`An example file ${exampleFile} defined in ${componentPath} component not found.`);
-	}
-	return doesFileExist;
-};
-
-const getMergedTag = (tags, names) => {
-	return names.reduce((params, name) => [...params, ...(tags[name] || [])], []);
-};
-
-/**
- * 1. Remove non-public methods.
- * 2. Extract doclets.
- * 3. Highlight code in descriptions.
- * 4. Extract @example doclet (load linked file with examples-loader).
- *
- * @param {object} doc
- * @param {string} filepath
- * @returns {object}
- */
-module.exports = function getProps(doc, filepath) {
-	// Keep only public methods
-	doc.methods = (doc.methods || []).filter(method => {
-		const doclets = method.docblock && reactDocs.utils.docblock.getDoclets(method.docblock);
-		return doclets && doclets.public;
-	});
-
-	// Parse the docblock of the remaining methods with doctrine to retrieve
-	// the JSDoc tags
-	doc.methods = doc.methods.map(method => {
-		const allTags = getDoctrineTags(
-			doctrine.parse(method.docblock, { sloppy: true, unwrap: true })
-		);
-
-		// Merge with react-docgen information about arguments and return value
-		// with information from JSDoc
-
-		const paramTags = getMergedTag(allTags, JS_DOC_METHOD_PARAM_TAG_SYNONYMS);
-		const params =
-			method.params &&
-			method.params.map(param => ({
-				...param,
-				...paramTags.find(tagParam => tagParam.name === param.name),
-			}));
-
-		const returnTags = getMergedTag(allTags, JS_DOC_METHOD_RETURN_TAG_SYNONYMS);
-		const returns = method.returns || returnTags[0];
-
-		// Remove tag synonyms
-		const tags = _.omit(allTags, JS_DOC_ALL_SYNONYMS);
-
-		return {
-			...method,
-			...(returns && { returns }),
-			...(params && { params }),
-			tags,
-		};
-	});
-
-	if (doc.description) {
-		// Read doclets from the description and remove them
-		doc.doclets = getDocletsObject(doc.description);
-
-		const documentation = doctrine.parse(doc.description);
-		doc.tags = getDoctrineTags(documentation);
-
-		doc.description = highlightCodeInMarkdown(removeDoclets(doc.description));
-
-		let exampleFileExists = false;
-		let exampleFile = doc.doclets.example;
-
-		// doc.doclets.example might be a boolean or undefined
-		if (typeof doc.doclets.example === 'string') {
-			exampleFile = doc.doclets.example.trim();
-			exampleFileExists = doesExternalExampleFileExist(filepath, exampleFile);
-		}
-
-		if (exampleFileExists) {
-			doc.example = requireIt(`!!${examplesLoader}!${exampleFile}`);
-			delete doc.doclets.example;
-		}
-	} else {
-		doc.doclets = {};
-	}
-
-	if (doc.props) {
-		// Read doclets of props
-		Object.keys(doc.props).forEach(propName => {
-			const prop = doc.props[propName];
-			const doclets = getDocletsObject(prop.description);
-
-			// When a prop is listed in defaultProps but not in props the prop.description is undefined
-			const documentation = doctrine.parse(prop.description || '');
-
-			// documentation.description is the description without tags
-			doc.props[propName].description = documentation.description;
-			doc.props[propName].tags = getDoctrineTags(documentation);
-
-			// Remove ignored props
-			if (doclets && doclets.ignore) {
-				delete doc.props[propName];
-			}
-		});
-	}
-
-	if (!doc.displayName && filepath) {
-		// Guess the exported component's display name based on the file path
-		doc.displayName = getNameFromFilePath(filepath);
-	}
-
-	if (doc.doclets && doc.doclets.visibleName) {
-		doc.visibleName = doc.doclets.visibleName;
-
-		// Custom tag is added both to doclets and tags
-		// Removing from both locations
-		delete doc.doclets.visibleName;
-		if (doc.tags) {
-			delete doc.tags.visibleName;
-		}
-	}
-
-	return doc;
-};
diff --git a/src/loaders/utils/getProps.ts b/src/loaders/utils/getProps.ts
new file mode 100644
index 00000000..91a1042f
--- /dev/null
+++ b/src/loaders/utils/getProps.ts
@@ -0,0 +1,176 @@
+import path from 'path';
+import fs from 'fs';
+import { TagProps, TagParamObject, DocumentationObject, utils, TagObject } from 'react-docgen';
+import _ from 'lodash';
+import doctrine, { Annotation } from 'doctrine';
+import createLogger from 'glogg';
+import highlightCodeInMarkdown from './highlightCodeInMarkdown';
+import removeDoclets from './removeDoclets';
+import requireIt from './requireIt';
+import getNameFromFilePath from './getNameFromFilePath';
+
+const logger = createLogger('rsg');
+
+const examplesLoader = path.resolve(__dirname, '../examples-loader.js');
+
+const JS_DOC_METHOD_PARAM_TAG_SYNONYMS: (keyof TagProps)[] = ['param', 'arg', 'argument'];
+const JS_DOC_METHOD_RETURN_TAG_SYNONYMS: (keyof TagProps)[] = ['return', 'returns'];
+const JS_DOC_ALL_SYNONYMS: (keyof TagProps)[] = [
+	...JS_DOC_METHOD_PARAM_TAG_SYNONYMS,
+	...JS_DOC_METHOD_RETURN_TAG_SYNONYMS,
+];
+
+// HACK: We have to make sure that doclets is a proper object with correct prototype to
+// work around an issue in react-docgen that breaks the build if a component has JSDoc tags
+// like @see in its description, see https://github.com/reactjs/react-docgen/issues/155
+// and https://github.com/styleguidist/react-styleguidist/issues/298
+const getDocletsObject = (str?: string) => ({ ...utils.docblock.getDoclets(str) });
+
+const getDoctrineTags = (documentation: Annotation) => {
+	return _.groupBy(documentation.tags, 'title');
+};
+
+const doesExternalExampleFileExist = (componentPath: string, exampleFile: string) => {
+	const exampleFilepath = path.resolve(path.dirname(componentPath), exampleFile);
+	const doesFileExist = fs.existsSync(exampleFilepath);
+
+	if (!doesFileExist) {
+		logger.warn(`An example file ${exampleFile} defined in ${componentPath} component not found.`);
+	}
+	return doesFileExist;
+};
+
+const getMergedTag = (tags: TagProps, names: (keyof TagProps)[]): TagObject[] => {
+	return names.reduce((params: TagObject[], name) => [...params, ...(tags[name] || [])], []);
+};
+
+/**
+ * 1. Remove non-public methods.
+ * 2. Extract doclets.
+ * 3. Highlight code in descriptions.
+ * 4. Extract @example doclet (load linked file with examples-loader).
+ *
+ * @param {object} doc
+ * @param {string} filepath
+ * @returns {object}
+ */
+export default function getProps(doc: DocumentationObject, filepath?: string): Rsg.TempPropsObject {
+	const outDocs: Rsg.TempPropsObject = { doclets: {}, displayName: '', ...doc, methods: undefined };
+
+	// Keep only public methods
+	outDocs.methods = (doc.methods || []).filter(method => {
+		const doclets = method.docblock && utils.docblock.getDoclets(method.docblock);
+		return doclets && doclets.public;
+	}) as Rsg.MethodWithDocblock[];
+
+	// Parse the docblock of the remaining methods with doctrine to retrieve
+	// the JSDoc tags
+	// if a method is visible it must have a docblock
+	outDocs.methods = outDocs.methods.map(method => {
+		const allTags = getDoctrineTags(
+			doctrine.parse(method.docblock, { sloppy: true, unwrap: true })
+		);
+
+		// Merge with react-docgen information about arguments and return value
+		// with information from JSDoc
+
+		const paramTags = getMergedTag(
+			allTags as TagProps,
+			JS_DOC_METHOD_PARAM_TAG_SYNONYMS
+		) as TagParamObject[];
+		const params =
+			method.params &&
+			method.params.map(param => ({
+				...param,
+				...paramTags.find(tagParam => tagParam.name === param.name),
+			}));
+
+		if (params) {
+			method.params = params;
+		}
+
+		const returnTags = getMergedTag(
+			allTags as TagProps,
+			JS_DOC_METHOD_RETURN_TAG_SYNONYMS
+		) as TagParamObject[];
+		const returns = method.returns || returnTags[0];
+
+		if (returns) {
+			method.returns = returns;
+		}
+
+		// Remove tag synonyms
+		method.tags = _.omit(allTags, JS_DOC_ALL_SYNONYMS);
+
+		return method;
+	});
+
+	if (doc.description) {
+		// Read doclets from the description and remove them
+		outDocs.doclets = getDocletsObject(doc.description);
+
+		const documentation = doctrine.parse(doc.description);
+		outDocs.tags = getDoctrineTags(documentation) as TagProps;
+
+		outDocs.description = highlightCodeInMarkdown(removeDoclets(doc.description));
+
+		let exampleFileExists = false;
+		let exampleFile = outDocs.doclets.example;
+
+		// doc.doclets.example might be a boolean or undefined
+		if (typeof outDocs.doclets.example === 'string' && filepath) {
+			exampleFile = outDocs.doclets.example.trim();
+			exampleFileExists = doesExternalExampleFileExist(filepath, exampleFile);
+		}
+
+		if (exampleFileExists) {
+			outDocs.example = requireIt(`!!${examplesLoader}!${exampleFile}`);
+			delete outDocs.doclets.example;
+		}
+	} else {
+		outDocs.doclets = {};
+	}
+
+	if (doc.props) {
+		// Read doclets of props
+		Object.keys(doc.props).forEach(propName => {
+			if (!doc.props) {
+				return;
+			}
+			const prop = doc.props[propName];
+			const doclets = getDocletsObject(prop.description);
+
+			// When a prop is listed in defaultProps but not in props the prop.description is undefined
+			const documentation = doctrine.parse(prop.description || '');
+
+			// documentation.description is the description without tags
+			prop.description = documentation.description;
+			prop.tags = getDoctrineTags(documentation) as TagProps;
+
+			// Remove ignored props
+			if (doclets && doclets.ignore && outDocs.props) {
+				delete outDocs.props[propName];
+			} else if (outDocs.props) {
+				outDocs.props[propName] = prop;
+			}
+		});
+	}
+
+	if (!doc.displayName && filepath) {
+		// Guess the exported component's display name based on the file path
+		outDocs.displayName = getNameFromFilePath(filepath);
+	}
+
+	if (outDocs.doclets && outDocs.doclets.visibleName) {
+		outDocs.visibleName = outDocs.doclets.visibleName;
+
+		// Custom tag is added both to doclets and tags
+		// Removing from both locations
+		delete outDocs.doclets.visibleName;
+		if (outDocs.tags) {
+			delete outDocs.tags.visibleName;
+		}
+	}
+
+	return outDocs;
+}
diff --git a/src/loaders/utils/getSections.js b/src/loaders/utils/getSections.ts
similarity index 70%
rename from src/loaders/utils/getSections.js
rename to src/loaders/utils/getSections.ts
index 1c35c881..2e727734 100644
--- a/src/loaders/utils/getSections.js
+++ b/src/loaders/utils/getSections.ts
@@ -1,22 +1,25 @@
 // This two functions should be in the same file because of cyclic imports
 
-const fs = require('fs');
-const path = require('path');
-const _ = require('lodash');
-const requireIt = require('./requireIt');
-const getComponentFiles = require('./getComponentFiles');
-const getComponents = require('./getComponents');
-const slugger = require('./slugger');
+import fs from 'fs';
+import path from 'path';
+import _ from 'lodash';
+import requireIt from './requireIt';
+import getComponentFiles from './getComponentFiles';
+import getComponents from './getComponents';
+import slugger from './slugger';
 
 const examplesLoader = path.resolve(__dirname, '../examples-loader.js');
 
-function processSectionContent(section, config) {
-	const contentRelativePath = section.content;
-
+function processSectionContent(
+	section: Rsg.ConfigSection,
+	config: Rsg.SanitizedStyleguidistConfig
+): Rsg.RequireItResult | Rsg.MarkdownExample | undefined {
 	if (!section.content) {
 		return undefined;
 	}
 
+	const contentRelativePath = section.content;
+
 	if (_.isFunction(section.content)) {
 		return {
 			type: 'markdown',
@@ -32,6 +35,18 @@ function processSectionContent(section, config) {
 	return requireIt(`!!${examplesLoader}!${contentAbsolutePath}`);
 }
 
+const getSectionComponents = (
+	section: Rsg.ConfigSection,
+	config: Rsg.SanitizedStyleguidistConfig
+) => {
+	let ignore = config.ignore ? _.castArray(config.ignore) : [];
+	if (section.ignore) {
+		ignore = ignore.concat(_.castArray(section.ignore));
+	}
+
+	return getComponents(getComponentFiles(section.components, config.configDir, ignore), config);
+};
+
 /**
  * Return object for one level of sections.
  *
@@ -40,19 +55,15 @@ function processSectionContent(section, config) {
  * @param {number} parentDepth
  * @returns {Array}
  */
-function getSections(sections, config, parentDepth) {
+export default function getSections(
+	sections: Rsg.ConfigSection[],
+	config: Rsg.SanitizedStyleguidistConfig,
+	parentDepth?: number
+): Rsg.LoaderSection[] {
+	// eslint-disable-next-line @typescript-eslint/no-use-before-define
 	return sections.map(section => processSection(section, config, parentDepth));
 }
 
-const getSectionComponents = (section, config) => {
-	let ignore = config.ignore ? _.castArray(config.ignore) : [];
-	if (section.ignore) {
-		ignore = ignore.concat(_.castArray(section.ignore));
-	}
-
-	return getComponents(getComponentFiles(section.components, config.configDir, ignore), config);
-};
-
 /**
  * Return an object for a given section with all components and subsections.
  * @param {object} section
@@ -60,7 +71,11 @@ const getSectionComponents = (section, config) => {
  * @param {number} parentDepth
  * @returns {object}
  */
-function processSection(section, config, parentDepth) {
+export function processSection(
+	section: Rsg.ConfigSection,
+	config: Rsg.SanitizedStyleguidistConfig,
+	parentDepth?: number
+): Rsg.LoaderSection {
 	const content = processSectionContent(section, config);
 
 	let sectionDepth;
@@ -85,6 +100,3 @@ function processSection(section, config, parentDepth) {
 		external: section.external,
 	};
 }
-
-module.exports = getSections;
-module.exports.processSection = processSection;
diff --git a/src/loaders/utils/highlightCode.js b/src/loaders/utils/highlightCode.js
deleted file mode 100644
index 71599231..00000000
--- a/src/loaders/utils/highlightCode.js
+++ /dev/null
@@ -1,45 +0,0 @@
-const logger = require('glogg')('rsg');
-const Prism = require('prismjs/components/prism-core');
-require('prismjs/components/prism-clike');
-require('prismjs/components/prism-markup');
-require('prismjs/components/prism-markdown');
-require('prismjs/components/prism-css');
-require('prismjs/components/prism-css-extras');
-require('prismjs/components/prism-scss');
-require('prismjs/components/prism-javascript');
-require('prismjs/components/prism-flow');
-require('prismjs/components/prism-typescript');
-require('prismjs/components/prism-jsx');
-require('prismjs/components/prism-tsx');
-require('prismjs/components/prism-graphql');
-require('prismjs/components/prism-json');
-require('prismjs/components/prism-bash');
-require('prismjs/components/prism-diff');
-
-const IGNORED_LANGUAGES = ['extend', 'insertBefore', 'DFS'];
-const getLanguages = () => Object.keys(Prism.languages).filter(x => !IGNORED_LANGUAGES.includes(x));
-
-/**
- * Highlight code.
- *
- * @param {string} code
- * @param {string} lang
- * @returns {string}
- */
-module.exports = function highlightCode(code, lang) {
-	if (!lang) {
-		return code;
-	}
-
-	const grammar = Prism.languages[lang];
-	if (!grammar) {
-		logger.warn(
-			`Syntax highlighting for “${lang}” isn’t supported. Supported languages are: ${getLanguages().join(
-				', '
-			)}.`
-		);
-		return code;
-	}
-
-	return Prism.highlight(code, grammar, lang);
-};
diff --git a/src/loaders/utils/highlightCode.ts b/src/loaders/utils/highlightCode.ts
new file mode 100644
index 00000000..dc9c1f9a
--- /dev/null
+++ b/src/loaders/utils/highlightCode.ts
@@ -0,0 +1,48 @@
+import createLogger from 'glogg';
+import * as Prism from 'prismjs';
+
+import 'prismjs/components/prism-clike';
+import 'prismjs/components/prism-markup';
+import 'prismjs/components/prism-markdown';
+import 'prismjs/components/prism-css';
+import 'prismjs/components/prism-css-extras';
+import 'prismjs/components/prism-scss';
+import 'prismjs/components/prism-javascript';
+import 'prismjs/components/prism-flow';
+import 'prismjs/components/prism-typescript';
+import 'prismjs/components/prism-jsx';
+import 'prismjs/components/prism-tsx';
+import 'prismjs/components/prism-graphql';
+import 'prismjs/components/prism-json';
+import 'prismjs/components/prism-bash';
+import 'prismjs/components/prism-diff';
+
+const logger = createLogger('rsg');
+
+const IGNORED_LANGUAGES = ['extend', 'insertBefore', 'DFS'];
+const getLanguages = () => Object.keys(Prism.languages).filter(x => !IGNORED_LANGUAGES.includes(x));
+
+/**
+ * Highlight code.
+ *
+ * @param {string} code
+ * @param {string} lang
+ * @returns {string}
+ */
+export default function highlightCode(code: string, lang?: string): string {
+	if (!lang) {
+		return code;
+	}
+
+	const grammar = Prism.languages[lang];
+	if (!grammar) {
+		logger.warn(
+			`Syntax highlighting for “${lang}” isn’t supported. Supported languages are: ${getLanguages().join(
+				', '
+			)}.`
+		);
+		return code;
+	}
+
+	return Prism.highlight(code, grammar, lang);
+}
diff --git a/src/loaders/utils/highlightCodeInMarkdown.js b/src/loaders/utils/highlightCodeInMarkdown.js
deleted file mode 100644
index 680b70e6..00000000
--- a/src/loaders/utils/highlightCodeInMarkdown.js
+++ /dev/null
@@ -1,26 +0,0 @@
-const remark = require('remark');
-const visit = require('unist-util-visit');
-const highlightCode = require('./highlightCode');
-const noAutoLink = require('./noAutoLinkRemarkPlugin');
-
-function highlight() {
-	return ast => {
-		visit(ast, 'code', node => {
-			node.value = highlightCode(node.value, node.lang);
-		});
-	};
-}
-
-/**
- * Highlight code in code snippets in Markdown.
- *
- * @param {string} markdown
- * @returns {string}
- */
-module.exports = function highlightCodeInMarkdown(markdown) {
-	return remark()
-		.use(highlight)
-		.use(noAutoLink)
-		.processSync(markdown)
-		.toString();
-};
diff --git a/src/loaders/utils/highlightCodeInMarkdown.ts b/src/loaders/utils/highlightCodeInMarkdown.ts
new file mode 100644
index 00000000..dc162073
--- /dev/null
+++ b/src/loaders/utils/highlightCodeInMarkdown.ts
@@ -0,0 +1,26 @@
+import remark from 'remark';
+import visit from 'unist-util-visit';
+import highlightCode from './highlightCode';
+import noAutoLink from './noAutoLinkRemarkPlugin';
+
+function highlight() {
+	return (ast: any) => {
+		visit(ast, 'code', (node: any) => {
+			node.value = highlightCode(node.value, node.lang);
+		});
+	};
+}
+
+/**
+ * Highlight code in code snippets in Markdown.
+ *
+ * @param {string} markdown
+ * @returns {string}
+ */
+export default function highlightCodeInMarkdown(markdown: string): string {
+	return remark()
+		.use(highlight)
+		.use(noAutoLink)
+		.processSync(markdown)
+		.toString();
+}
diff --git a/src/loaders/utils/noAutoLinkRemarkPlugin.js b/src/loaders/utils/noAutoLinkRemarkPlugin.ts
similarity index 72%
rename from src/loaders/utils/noAutoLinkRemarkPlugin.js
rename to src/loaders/utils/noAutoLinkRemarkPlugin.ts
index 310f5673..941315b5 100644
--- a/src/loaders/utils/noAutoLinkRemarkPlugin.js
+++ b/src/loaders/utils/noAutoLinkRemarkPlugin.ts
@@ -1,8 +1,8 @@
-const visit = require('unist-util-visit');
+import visit from 'unist-util-visit';
 
 // Return a Remark AST link node's link text
-const getLinkValue = node =>
-	node.children.reduce((value, child) => {
+const getLinkValue = (node: any) =>
+	node.children.reduce((value: string, child: any) => {
 		if (child.type === 'text') {
 			value += child.value;
 		}
@@ -17,8 +17,8 @@ const getLinkValue = node =>
  *
  * @return {Object}
  */
-module.exports = function noAutoLinkRemarkPlugin() {
-	return ast => {
+export default function noAutoLinkRemarkPlugin() {
+	return (ast: any) => {
 		visit(ast, 'link', node => {
 			const value = getLinkValue(node);
 
@@ -27,4 +27,4 @@ module.exports = function noAutoLinkRemarkPlugin() {
 			}
 		});
 	};
-};
+}
diff --git a/src/loaders/utils/parseExample.ts b/src/loaders/utils/parseExample.ts
index 5c05e178..9253204b 100644
--- a/src/loaders/utils/parseExample.ts
+++ b/src/loaders/utils/parseExample.ts
@@ -3,7 +3,7 @@ import { DOCS_DOCUMENTING } from '../../scripts/consts';
 
 const hasStringModifiers = (modifiers: string): boolean => !!modifiers.match(/^[ \w]+$/);
 
-interface Error {
+export interface ExampleError {
 	error: string;
 }
 /**
@@ -13,9 +13,9 @@ export default function parseExample(
 	content: string,
 	lang?: string | null,
 	modifiers?: string,
-	updateExample: (example: Rsg.CodeExample) => Rsg.CodeExample = x => x
-): Rsg.CodeExample | Error {
-	const example: Rsg.CodeExample = {
+	updateExample: (example: Omit<Rsg.CodeExample, 'type'>) => Omit<Rsg.CodeExample, 'type'> = x => x
+): Omit<Rsg.CodeExample, 'type'> | ExampleError {
+	const example: Omit<Rsg.CodeExample, 'type'> = {
 		content,
 		lang,
 	};
diff --git a/src/loaders/utils/processComponent.js b/src/loaders/utils/processComponent.ts
similarity index 69%
rename from src/loaders/utils/processComponent.js
rename to src/loaders/utils/processComponent.ts
index c60f7d44..c73330fd 100644
--- a/src/loaders/utils/processComponent.js
+++ b/src/loaders/utils/processComponent.ts
@@ -1,8 +1,8 @@
-const fs = require('fs');
-const path = require('path');
-const getNameFromFilePath = require('./getNameFromFilePath');
-const requireIt = require('./requireIt');
-const slugger = require('./slugger');
+import fs from 'fs';
+import path from 'path';
+import getNameFromFilePath from './getNameFromFilePath';
+import requireIt from './requireIt';
+import slugger from './slugger';
 
 const propsLoader = path.resolve(__dirname, '../props-loader.js');
 
@@ -10,9 +10,9 @@ const propsLoader = path.resolve(__dirname, '../props-loader.js');
  * References the filepath of the metadata file.
  *
  * @param {string} filepath
- * @returns {object}
+ * @returns {string}
  */
-function getComponentMetadataPath(filepath) {
+function getComponentMetadataPath(filepath: string): string {
 	const extname = path.extname(filepath);
 	return filepath.substring(0, filepath.length - extname.length) + '.json';
 }
@@ -24,7 +24,10 @@ function getComponentMetadataPath(filepath) {
  * @param {object} config
  * @returns {object}
  */
-module.exports = function processComponent(filepath, config) {
+export default function processComponent(
+	filepath: string,
+	config: Rsg.SanitizedStyleguidistConfig
+): Rsg.LoaderComponent {
 	const componentPath = path.relative(config.configDir, filepath);
 	const componentName = getNameFromFilePath(filepath);
 	const examplesFile = config.getExampleFilename(filepath);
@@ -35,7 +38,7 @@ module.exports = function processComponent(filepath, config) {
 		pathLine: config.getComponentPathLine(componentPath),
 		module: requireIt(filepath),
 		props: requireIt(`!!${propsLoader}!${filepath}`),
-		hasExamples: examplesFile && fs.existsSync(examplesFile),
+		hasExamples: !!(examplesFile && fs.existsSync(examplesFile)),
 		metadata: fs.existsSync(componentMetadataPath) ? requireIt(componentMetadataPath) : {},
 	};
-};
+}
diff --git a/src/loaders/utils/removeDoclets.js b/src/loaders/utils/removeDoclets.ts
similarity index 81%
rename from src/loaders/utils/removeDoclets.js
rename to src/loaders/utils/removeDoclets.ts
index e241d56b..03e26bb2 100644
--- a/src/loaders/utils/removeDoclets.js
+++ b/src/loaders/utils/removeDoclets.ts
@@ -6,6 +6,6 @@ const DOCLET_REGEXP = /^@(\w+)(?:$|\s((?:[^](?!^@\w))*))/gim;
  * @param {string} text
  * @returns {string}
  */
-module.exports = function removeDoclets(text) {
+export default function removeDoclets(text: string) {
 	return text.replace(DOCLET_REGEXP, '');
-};
+}
diff --git a/src/loaders/utils/requireIt.js b/src/loaders/utils/requireIt.ts
similarity index 61%
rename from src/loaders/utils/requireIt.js
rename to src/loaders/utils/requireIt.ts
index 2966a33e..3ee1da8f 100644
--- a/src/loaders/utils/requireIt.js
+++ b/src/loaders/utils/requireIt.ts
@@ -1,4 +1,4 @@
-const b = require('ast-types').builders;
+import { builders as b, ASTNode } from 'ast-types';
 
 /**
  * Return a require() statement AST.
@@ -6,13 +6,13 @@ const b = require('ast-types').builders;
  * @param {string} filepath Module name.
  * @returns {object}
  */
-module.exports = function requireIt(filepath) {
+export default function requireIt(filepath: string): Rsg.RequireItResult {
 	const obj = { require: filepath };
 	Object.defineProperty(obj, 'toAST', {
 		enumerable: false,
-		value() {
+		value(): ASTNode {
 			return b.callExpression(b.identifier('require'), [b.literal(filepath)]);
 		},
 	});
-	return obj;
-};
+	return obj as Rsg.RequireItResult;
+}
diff --git a/src/loaders/utils/slugger.js b/src/loaders/utils/slugger.js
deleted file mode 100644
index 9b26ecb7..00000000
--- a/src/loaders/utils/slugger.js
+++ /dev/null
@@ -1,4 +0,0 @@
-const GithubSlugger = require('github-slugger');
-
-// Export the singleton instance of GithubSlugger
-module.exports = new GithubSlugger();
diff --git a/src/loaders/utils/slugger.ts b/src/loaders/utils/slugger.ts
new file mode 100644
index 00000000..ea9b70db
--- /dev/null
+++ b/src/loaders/utils/slugger.ts
@@ -0,0 +1,4 @@
+import GithubSlugger from 'github-slugger';
+
+// Export the singleton instance of GithubSlugger
+export default new GithubSlugger();
diff --git a/src/loaders/utils/sortProps.js b/src/loaders/utils/sortProps.ts
similarity index 73%
rename from src/loaders/utils/sortProps.js
rename to src/loaders/utils/sortProps.ts
index b121e7a4..c030ec74 100644
--- a/src/loaders/utils/sortProps.js
+++ b/src/loaders/utils/sortProps.ts
@@ -1,4 +1,5 @@
-const sortBy = require('lodash/sortBy');
+import sortBy from 'lodash/sortBy';
+import { PropDescriptor } from 'react-docgen';
 
 /**
  * Sorts an array of properties by their 'required' property first and 'name'
@@ -7,11 +8,11 @@ const sortBy = require('lodash/sortBy');
  * @param {array} props
  * @return {array} Sorted properties
  */
-function sortProps(props) {
+function sortProps(props: PropDescriptor[]) {
 	const requiredPropNames = sortBy(props.filter(prop => prop.required), 'name');
 	const optionalPropNames = sortBy(props.filter(prop => !prop.required), 'name');
 	const sortedProps = requiredPropNames.concat(optionalPropNames);
 	return sortedProps;
 }
 
-module.exports = sortProps;
+export default sortProps;
diff --git a/src/scripts/.eslintrc b/src/scripts/.eslintrc
deleted file mode 100644
index aba1776b..00000000
--- a/src/scripts/.eslintrc
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-  "extends": "tamia",
-  "overrides": [
-    {
-      "files": ["*.ts", "*.tsx"],
-      "parser": "@typescript-eslint/parser",
-      "plugins": ["@typescript-eslint"],
-      "extends": ["tamia/typescript"]
-    }
-  ]
-}
diff --git a/src/scripts/index.ts b/src/scripts/index.ts
index d3fd8273..2df0403a 100644
--- a/src/scripts/index.ts
+++ b/src/scripts/index.ts
@@ -43,7 +43,7 @@ module.exports = function(configArg?: Rsg.StyleguidistConfig | string) {
 		 * @return {ServerInfo.App} Webpack-Dev-Server.
 		 * @return {ServerInfo.Compiler} Webpack Compiler instance.
 		 */
-		server(callback: (err: Error | undefined, config: Rsg.StyleguidistConfig) => void) {
+		server(callback: (err: Error | undefined, config: Rsg.SanitizedStyleguidistConfig) => void) {
 			return server(config, err => callback(err, config));
 		},
 
diff --git a/src/scripts/schemas/config.ts b/src/scripts/schemas/config.ts
index 12cfe8c3..e334dd76 100644
--- a/src/scripts/schemas/config.ts
+++ b/src/scripts/schemas/config.ts
@@ -32,17 +32,17 @@ 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;
+export interface ConfigSchemaOptions<T> {
+	process?(value: any, config: T, rootDir: string): any;
 	default?: any;
-	required?: boolean | ((config?: Rsg.StyleguidistConfig) => string | boolean);
+	required?: boolean | ((config?: T) => string | boolean);
 	deprecated?: string;
 	removed?: string;
 	type?: string | string[];
 	example?: any;
 }
 
-const configSchema: Record<StyleguidistConfigKey, ConfigSchemaOptions> = {
+const configSchema: Record<StyleguidistConfigKey, ConfigSchemaOptions<Rsg.StyleguidistConfig>> = {
 	assetsDir: {
 		type: ['array', 'existing directory path'],
 		example: 'assets',
diff --git a/src/scripts/utils/StyleguidistOptionsPlugin.ts b/src/scripts/utils/StyleguidistOptionsPlugin.ts
index 9b6c7cf5..d8e6fd0a 100644
--- a/src/scripts/utils/StyleguidistOptionsPlugin.ts
+++ b/src/scripts/utils/StyleguidistOptionsPlugin.ts
@@ -15,10 +15,7 @@ export default class StyleguidistOptionsPlugin implements Tapable.Plugin {
 	}
 
 	public plugin(compil: compilation.Compilation) {
-		const pluginFunc = (
-			context: { _styleguidist?: Rsg.SanitizedStyleguidistConfig },
-			module: { resource?: boolean }
-		) => {
+		const pluginFunc = (context: Rsg.StyleguidistLoaderContext, module: { resource?: boolean }) => {
 			if (!module.resource) {
 				return;
 			}
diff --git a/src/scripts/utils/sanitizeConfig.ts b/src/scripts/utils/sanitizeConfig.ts
index bfbc80d9..5c6e8b8c 100644
--- a/src/scripts/utils/sanitizeConfig.ts
+++ b/src/scripts/utils/sanitizeConfig.ts
@@ -47,7 +47,7 @@ const shouldExist = (types: string[]) => types.some(type => type.includes('exist
  */
 export default function sanitizeConfig<T extends Record<string, any>>(
 	config: T,
-	schema: Record<keyof T, ConfigSchemaOptions>,
+	schema: Record<keyof T, ConfigSchemaOptions<T>>,
 	rootDir: string
 ): T {
 	// Check for unknown fields
diff --git a/src/typings/RequireItResult.d.ts b/src/typings/RequireItResult.d.ts
new file mode 100644
index 00000000..446eda30
--- /dev/null
+++ b/src/typings/RequireItResult.d.ts
@@ -0,0 +1,10 @@
+import { ASTNode } from 'ast-types';
+
+declare global {
+	namespace Rsg {
+		interface RequireItResult {
+			require: string;
+			toAST(): ASTNode;
+		}
+	}
+}
diff --git a/src/typings/RsgComponent.d.ts b/src/typings/RsgComponent.d.ts
index f31d4003..704e8728 100644
--- a/src/typings/RsgComponent.d.ts
+++ b/src/typings/RsgComponent.d.ts
@@ -4,7 +4,8 @@ import { MethodDescriptor, PropDescriptor, TagProps } from 'react-docgen';
 // see https://stackoverflow.com/questions/45099605/ambient-declaration-with-an-imported-type-in-typescript
 declare global {
 	namespace Rsg {
-		interface Component {
+		interface BaseComponent {
+			hasExamples?: boolean;
 			name?: string;
 			slug?: string;
 			href?: string;
@@ -17,6 +18,9 @@ declare global {
 			description?: string;
 			exampleMode?: string;
 			usageMode?: string;
+		}
+
+		interface Component extends BaseComponent {
 			props?: {
 				displayName?: string;
 				visibleName?: string;
@@ -27,10 +31,16 @@ declare global {
 				example?: Example[];
 				examples?: Example[];
 			};
+			module?: number;
 			metadata?: {
 				tags?: string[];
 			};
-			module?: number;
+		}
+
+		interface LoaderComponent extends BaseComponent {
+			module: RequireItResult;
+			props: RequireItResult;
+			metadata: RequireItResult | {};
 		}
 	}
 }
diff --git a/src/typings/RsgExample.d.ts b/src/typings/RsgExample.d.ts
index 7cf2e5b9..10cab756 100644
--- a/src/typings/RsgExample.d.ts
+++ b/src/typings/RsgExample.d.ts
@@ -6,13 +6,13 @@ declare namespace Rsg {
 	}
 
 	interface CodeExample {
+		type: 'code';
 		content: string;
 		lang?: string | null;
 		settings?: Record<string, any>;
 	}
 
 	interface RuntimeCodeExample extends CodeExample {
-		type: 'code';
 		evalInContext(a: string): () => any;
 	}
 
diff --git a/src/typings/RsgPropsObject.d.ts b/src/typings/RsgPropsObject.d.ts
new file mode 100644
index 00000000..844e4495
--- /dev/null
+++ b/src/typings/RsgPropsObject.d.ts
@@ -0,0 +1,22 @@
+import { DocumentationObject, MethodDescriptor, PropDescriptor } from 'react-docgen';
+
+declare global {
+	namespace Rsg {
+		interface MethodWithDocblock extends MethodDescriptor {
+			docblock: string;
+		}
+
+		interface TempPropsObject extends DocumentationObject {
+			displayName: string;
+			visibleName?: string;
+			methods?: MethodWithDocblock[];
+			doclets: Record<string, any>;
+			example?: RequireItResult | null;
+		}
+
+		interface PropsObject extends Omit<TempPropsObject, 'props'> {
+			props?: Record<string, PropDescriptor> | PropDescriptor[];
+			examples?: RequireItResult | null;
+		}
+	}
+}
diff --git a/src/typings/RsgSection.d.ts b/src/typings/RsgSection.d.ts
index 9041cf9d..e5ef8a7f 100644
--- a/src/typings/RsgSection.d.ts
+++ b/src/typings/RsgSection.d.ts
@@ -7,6 +7,7 @@ declare namespace Rsg {
 		usageMode?: EXPAND_MODE;
 		href?: string;
 		external?: string;
+		sectionDepth?: number;
 	}
 
 	interface Section extends BaseSection {
@@ -16,13 +17,19 @@ declare namespace Rsg {
 		components?: Component[];
 		sections?: Section[];
 		content?: Example[] | string;
-		sectionDepth?: number;
 	}
 
 	interface ConfigSection extends BaseSection {
-		components?: string | string[];
+		components?: string | string[] | (() => string[]);
 		sections?: ConfigSection[];
-		content?: string[] | string;
+		content?: string;
+	}
+
+	interface LoaderSection extends BaseSection {
+		slug?: string;
+		content?: RequireItResult | MarkdownExample;
+		components: LoaderComponent[];
+		sections: LoaderSection[];
 	}
 
 	interface ExampleSection extends Section {
diff --git a/src/typings/StyleguidistConfig.d.ts b/src/typings/StyleguidistConfig.d.ts
index 9264718f..5805bddc 100644
--- a/src/typings/StyleguidistConfig.d.ts
+++ b/src/typings/StyleguidistConfig.d.ts
@@ -1,5 +1,5 @@
 import WebpackDevServer from 'webpack-dev-server';
-import { Configuration } from 'webpack';
+import { Configuration, loader } from 'webpack';
 import { TransformOptions } from 'buble';
 import { Handler, DocumentationObject, PropDescriptor } from 'react-docgen';
 import { ASTNode } from 'ast-types';
@@ -9,6 +9,10 @@ declare global {
 	namespace Rsg {
 		type EXPAND_MODE = 'expand' | 'collapse' | 'hide';
 
+		interface StyleguidistLoaderContext extends loader.LoaderContext {
+			_styleguidist: SanitizedStyleguidistConfig;
+		}
+
 		interface BaseStyleguidistConfig {
 			assetsDir: string | string[];
 			compilerConfig: TransformOptions;
@@ -18,7 +22,7 @@ declare global {
 			contextDependencies: string[];
 			configureServer(server: WebpackDevServer, env: string): string;
 			dangerouslyUpdateWebpackConfig: (server: Configuration, env: string) => Configuration;
-			defaultExample: string | boolean;
+			defaultExample: string | false;
 			exampleMode: EXPAND_MODE;
 			editorConfig: {
 				theme: string;
@@ -37,8 +41,11 @@ declare global {
 			moduleAliases: Record<string, string>;
 			pagePerSection: boolean;
 			previewDelay: number;
-			printBuildInstructions(config: ProcessedStyleguidistConfig): void;
-			printServerInstructions(config: ProcessedStyleguidistConfig): void;
+			printBuildInstructions(config: SanitizedStyleguidistConfig): void;
+			printServerInstructions(
+				config: SanitizedStyleguidistConfig,
+				options: { isHttps: boolean }
+			): void;
 			propsParser(
 				filePath: string,
 				code: string,
@@ -70,8 +77,11 @@ declare global {
 			template: any;
 			theme: Theme;
 			title: string;
-			updateDocs(doc: Component, file: string): Component;
-			updateExample(props: Example, ressourcePath: string): Example;
+			updateDocs(doc: PropsObject, file: string): PropsObject;
+			updateExample(
+				props: Omit<CodeExample, 'type'>,
+				ressourcePath: string
+			): Omit<CodeExample, 'type'>;
 			updateWebpackConfig(config: Configuration): Configuration;
 			usageMode: EXPAND_MODE;
 			verbose: boolean;
@@ -87,6 +97,9 @@ declare global {
 			sections: ConfigSection[];
 		}
 
-		type StyleguidistConfig = RecursivePartial<SanitizedStyleguidistConfig>;
+		interface StyleguidistConfig
+			extends RecursivePartial<Omit<SanitizedStyleguidistConfig, 'defaultExample'>> {
+			defaultExample?: string | boolean;
+		}
 	}
 }
diff --git a/src/typings/dependencies/acorn-jsx.d.ts b/src/typings/dependencies/acorn-jsx.d.ts
new file mode 100644
index 00000000..960cd012
--- /dev/null
+++ b/src/typings/dependencies/acorn-jsx.d.ts
@@ -0,0 +1,6 @@
+declare module 'acorn-jsx' {
+	import { Parser } from 'acorn';
+
+	function acornJsx(): (BaseParser: typeof Parser) => typeof Parser;
+	export = acornJsx;
+}
diff --git a/src/typings/dependencies/common-dir.d.ts b/src/typings/dependencies/common-dir.d.ts
new file mode 100644
index 00000000..3b2af2d5
--- /dev/null
+++ b/src/typings/dependencies/common-dir.d.ts
@@ -0,0 +1,4 @@
+declare module 'common-dir' {
+	function commonDir(list: string[]): string;
+	export = commonDir;
+}
diff --git a/src/typings/dependencies/deabsdeep.d.ts b/src/typings/dependencies/deabsdeep.d.ts
new file mode 100644
index 00000000..7379f497
--- /dev/null
+++ b/src/typings/dependencies/deabsdeep.d.ts
@@ -0,0 +1,8 @@
+declare module 'deabsdeep' {
+	interface Options {
+		root?: string;
+		mask?: string;
+	}
+	function deabsdeep<T>(objectToFreze: T, opt?: Options): T;
+	export = deabsdeep;
+}
diff --git a/src/typings/dependencies/github-slugger.d.ts b/src/typings/dependencies/github-slugger.d.ts
new file mode 100644
index 00000000..a0bec727
--- /dev/null
+++ b/src/typings/dependencies/github-slugger.d.ts
@@ -0,0 +1,7 @@
+declare module 'github-slugger' {
+	class Slugger {
+		reset(): void;
+		slug(input: string): string;
+	}
+	export = Slugger;
+}
diff --git a/src/typings/dependencies/react-docgen.d.ts b/src/typings/dependencies/react-docgen.d.ts
index e4c78a04..0e01a013 100644
--- a/src/typings/dependencies/react-docgen.d.ts
+++ b/src/typings/dependencies/react-docgen.d.ts
@@ -1,4 +1,5 @@
 declare module 'react-docgen' {
+	import { Tag, Type } from 'doctrine';
 	import { ASTNode } from 'ast-types';
 	import { NodePath } from 'ast-types/lib/node-path';
 
@@ -14,16 +15,13 @@ declare module 'react-docgen' {
 		toObject(): DocumentationObject;
 	}
 
-	export interface TagObject {
-		title: string;
+	export interface TagObject extends Omit<Tag, 'description'> {
 		description?: string;
 	}
 
-	export interface TagParamObject {
-		title: string;
-		description?: string;
+	export interface TagParamObject extends TagObject {
 		name: string;
-		type?: any;
+		type?: Type | null;
 		default?: string;
 	}
 
@@ -34,11 +32,12 @@ declare module 'react-docgen' {
 		author?: TagObject[];
 		version?: TagObject[];
 		since?: TagObject[];
-		returns?: TagObject[];
+		returns?: TagParamObject[];
 		return?: TagParamObject[];
 		arg?: TagParamObject[];
 		argument?: TagParamObject[];
 		param?: TagParamObject[];
+		[title: string]: TagObject[] | undefined;
 	}
 
 	export interface PropTypeDescriptor {
@@ -83,6 +82,7 @@ declare module 'react-docgen' {
 	export interface MethodDescriptor {
 		name: string;
 		description?: string;
+		docblock?: string;
 		returns?: { name: string; [key: string]: any } | null;
 		params?: any[];
 		modifiers?: string[];
@@ -90,6 +90,9 @@ declare module 'react-docgen' {
 	}
 
 	export interface DocumentationObject {
+		displayName?: string;
+		description?: string;
+		tags?: TagProps;
 		props?: { [propName: string]: PropDescriptor };
 		methods?: MethodDescriptor[];
 		context?: { [constextName: string]: PropDescriptor };
@@ -125,6 +128,12 @@ declare module 'react-docgen' {
 		options?: Options
 	): DocumentationObject | DocumentationObject[];
 
+	export const utils: {
+		docblock: {
+			getDoclets: (str?: string) => Record<string, any>;
+		};
+	};
+
 	export const resolver: {
 		findAllComponentDefinitions(ast: ASTNode): NodePath[];
 		findAllExportedComponentDefinitions(
diff --git a/src/typings/dependencies/strip-shebang.d.ts b/src/typings/dependencies/strip-shebang.d.ts
new file mode 100644
index 00000000..713f464f
--- /dev/null
+++ b/src/typings/dependencies/strip-shebang.d.ts
@@ -0,0 +1,4 @@
+declare module 'strip-shebang' {
+	function stripShebang(input: string): string;
+	export = stripShebang;
+}
diff --git a/src/typings/dependencies/to-ast.d.ts b/src/typings/dependencies/to-ast.d.ts
new file mode 100644
index 00000000..b2836e39
--- /dev/null
+++ b/src/typings/dependencies/to-ast.d.ts
@@ -0,0 +1,6 @@
+declare module 'to-ast' {
+	import { ASTNode } from 'ast-types';
+
+	function toAST(obj: any): ASTNode;
+	export = toAST;
+}
diff --git a/tsconfig.json b/tsconfig.json
index a95ae0f7..e9259368 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,24 +1,20 @@
 {
-  "compilerOptions": {
-    "target": "esnext",
-    "module": "es2015",
-    "baseUrl": "./src",
-    "moduleResolution": "node",
-    "jsx": "react",
-    "strict": true,
-    "esModuleInterop": true,
-    "noUnusedLocals": true,
-    "allowSyntheticDefaultImports": true,
-    "outDir": "./lib",
-    // TODO: Remove 'allowJs' once all files are converted to TS.
-    // This is required now, for accessing types and to do typechecking for JS files.
-    "allowJs": true,
-    "lib": ["dom"],
-    "paths": {
-      "rsg-components/*": ["client/rsg-components/*"]
-    }
-  },
-  // Remove the 'include' section if we want entire project to be Typescript.
-  "include": ["src/**/*.ts", "src/**/*.tsx"],
-  "exclude": ["node_modules"]
+	"compilerOptions": {
+		"target": "esnext",
+		"module": "es2015",
+		"baseUrl": ".",
+		"moduleResolution": "node",
+		"jsx": "react",
+		"strict": true,
+		"esModuleInterop": true,
+		"noUnusedLocals": true,
+		"allowSyntheticDefaultImports": true,
+		"outDir": "./lib",
+		"lib": ["dom"],
+		"paths": {
+			"rsg-components/*": ["src/client/rsg-components/*"]
+		}
+	},
+	"include": ["src"],
+	"exclude": ["node_modules"]
 }
diff --git a/tsconfig.types.json b/tsconfig.types.json
new file mode 100644
index 00000000..ef276389
--- /dev/null
+++ b/tsconfig.types.json
@@ -0,0 +1,15 @@
+{
+	"extends": "./tsconfig.json",
+	"compilerOptions": {
+		// only generate typings when using this tsconfig
+		"declaration": true
+	},
+	// avoid generating typings for tests and mocks
+	"exclude": [
+		"node_modules",
+		"**/__tests__/*.ts",
+		"**/__mocks__/*.ts",
+		"**/*.spec.tsx",
+		"**/*.spec.ts"
+	]
+}


Test patch:
diff --git a/src/loaders/__tests__/.eslintrc b/src/loaders/__tests__/.eslintrc
deleted file mode 100644
index 0b2e238c..00000000
--- a/src/loaders/__tests__/.eslintrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-	"extends": "tamia/react"
-}
diff --git a/src/loaders/__tests__/examples-loader.spec.js b/src/loaders/__tests__/examples-loader.spec.ts
similarity index 95%
rename from src/loaders/__tests__/examples-loader.spec.js
rename to src/loaders/__tests__/examples-loader.spec.ts
index 08d6a899..8515ca47 100644
--- a/src/loaders/__tests__/examples-loader.spec.js
+++ b/src/loaders/__tests__/examples-loader.spec.ts
@@ -27,7 +27,7 @@ text
 		{
 			query: getQuery(),
 			_styleguidist: {},
-		},
+		} as any,
 		exampleMarkdown
 	);
 
@@ -50,11 +50,12 @@ it('should replace all occurrences of __COMPONENT__ with provided query.displayN
 		{
 			query: getQuery({ shouldShowDefaultExample: true }),
 			_styleguidist: {},
-		},
+		} as any,
 		exampleMarkdown
 	);
 	expect(result).not.toMatch(/__COMPONENT__/);
-	expect(result.match(/<div>(.*?)<\/div>/)[0]).toMatchInlineSnapshot(`
+	const componentHtml = result.match(/<div>(.*?)<\/div>/);
+	expect(componentHtml && componentHtml[0]).toMatchInlineSnapshot(`
 		<div>
 		  \\n\\t
 		  <FooComponent>
@@ -90,7 +91,7 @@ it('should pass updateExample function from config to chunkify', () => {
 			_styleguidist: {
 				updateExample,
 			},
-		},
+		} as any,
 		exampleMarkdown
 	);
 	expect(updateExample).toBeCalledWith(
@@ -118,7 +119,7 @@ Two:
 		{
 			query: getQuery(),
 			_styleguidist: {},
-		},
+		} as any,
 		exampleMarkdown
 	);
 
@@ -139,7 +140,7 @@ One:
 		{
 			query: getQuery(),
 			_styleguidist: {},
-		},
+		} as any,
 		exampleMarkdown
 	);
 
@@ -158,7 +159,7 @@ it('should work with multiple JSX element on the root level', () => {
 		{
 			query: getQuery(),
 			_styleguidist: {},
-		},
+		} as any,
 		exampleMarkdown
 	);
 
@@ -172,7 +173,7 @@ it('should prepend example code with React require()', () => {
 		{
 			query: getQuery(),
 			_styleguidist: {},
-		},
+		} as any,
 		exampleMarkdown
 	);
 
@@ -189,7 +190,7 @@ it('should prepend example code with component require()', () => {
 		{
 			query: getQuery(),
 			_styleguidist: {},
-		},
+		} as any,
 		exampleMarkdown
 	);
 
@@ -209,7 +210,7 @@ it('should allow explicit import of React and component module', () => {
 		{
 			query: getQuery(),
 			_styleguidist: {},
-		},
+		} as any,
 		exampleMarkdown
 	);
 
@@ -232,7 +233,7 @@ it('should works for any Markdown file, without a current component', () => {
 		{
 			query: '',
 			_styleguidist: {},
-		},
+		} as any,
 		exampleMarkdown
 	);
 
diff --git a/src/loaders/__tests__/props-loader.spec.js b/src/loaders/__tests__/props-loader.spec.ts
similarity index 92%
rename from src/loaders/__tests__/props-loader.spec.js
rename to src/loaders/__tests__/props-loader.spec.ts
index ac6047bb..67c08538 100644
--- a/src/loaders/__tests__/props-loader.spec.js
+++ b/src/loaders/__tests__/props-loader.spec.ts
@@ -1,6 +1,7 @@
 import vm from 'vm';
 import { readFileSync } from 'fs';
 import glogg from 'glogg';
+import { PropDescriptor } from 'react-docgen';
 
 import sortBy from 'lodash/sortBy';
 import config from '../../scripts/schemas/config';
@@ -20,7 +21,7 @@ it('should return valid, parsable JS', () => {
 		{
 			request: file,
 			_styleguidist,
-		},
+		} as any,
 		readFileSync(file, 'utf8')
 	);
 	expect(result).toBeTruthy();
@@ -33,7 +34,7 @@ it('should extract doclets', () => {
 		{
 			request: file,
 			_styleguidist,
-		},
+		} as any,
 		readFileSync(file, 'utf8')
 	);
 	expect(result).toBeTruthy();
@@ -53,7 +54,7 @@ describe('property sorting', () => {
 			{
 				request: file,
 				_styleguidist,
-			},
+			} as any,
 			readFileSync(file, 'utf8')
 		);
 		expect(result).toBeTruthy();
@@ -70,8 +71,8 @@ describe('property sorting', () => {
 		const result = propsLoader.call(
 			{
 				request: file,
-				_styleguidist: { ..._styleguidist, sortProps: props => props },
-			},
+				_styleguidist: { ..._styleguidist, sortProps: (props: any) => props },
+			} as any,
 			readFileSync(file, 'utf8')
 		);
 		expect(result).toBeTruthy();
@@ -84,9 +85,15 @@ describe('property sorting', () => {
 	});
 
 	it('should be possible to write custom sort function', () => {
-		const sortFn = props => {
-			const requiredProps = sortBy(props.filter(prop => prop.required), 'name').reverse();
-			const optionalProps = sortBy(props.filter(prop => !prop.required), 'name').reverse();
+		const sortFn = (props: any) => {
+			const requiredProps = sortBy(
+				props.filter((prop: PropDescriptor) => prop.required),
+				'name'
+			).reverse();
+			const optionalProps = sortBy(
+				props.filter((prop: PropDescriptor) => !prop.required),
+				'name'
+			).reverse();
 			return optionalProps.concat(requiredProps);
 		};
 		const file = './test/components/Price/Price.js';
@@ -94,7 +101,7 @@ describe('property sorting', () => {
 			{
 				request: file,
 				_styleguidist: { ..._styleguidist, sortProps: sortFn },
-			},
+			} as any,
 			readFileSync(file, 'utf8')
 		);
 		expect(result).toBeTruthy();
@@ -113,7 +120,7 @@ it('should work with JSDoc annnotated components', () => {
 		{
 			request: file,
 			_styleguidist,
-		},
+		} as any,
 		readFileSync(file, 'utf8')
 	);
 	expect(result).toBeTruthy();
@@ -136,7 +143,7 @@ it('should not render ignored props', () => {
 		{
 			request: file,
 			_styleguidist,
-		},
+		} as any,
 		readFileSync(file, 'utf8')
 	);
 	expect(result).toBeTruthy();
@@ -151,7 +158,7 @@ it('should attach examples from Markdown file', () => {
 		{
 			request: file,
 			_styleguidist,
-		},
+		} as any,
 		readFileSync(file, 'utf8')
 	);
 	expect(result).toBeTruthy();
@@ -171,7 +178,7 @@ it('should warn if no componets are exported', () => {
 		{
 			request: file,
 			_styleguidist,
-		},
+		} as any,
 		readFileSync(file, 'utf8')
 	);
 	expect(result).toBeTruthy();
@@ -189,7 +196,7 @@ it('should warn if a file cannot be parsed', () => {
 		{
 			request: file,
 			_styleguidist,
-		},
+		} as any,
 		readFileSync(file, 'utf8')
 	);
 	expect(result).toBeTruthy();
@@ -207,7 +214,7 @@ it('should add context dependencies to webpack from contextDependencies config o
 			request: file,
 			_styleguidist: { ..._styleguidist, contextDependencies },
 			addContextDependency,
-		},
+		} as any,
 		readFileSync(file, 'utf8')
 	);
 
@@ -224,7 +231,7 @@ it('should update the returned props object after enhancing from the updateDocs
 		{
 			request: file,
 			_styleguidist: { ..._styleguidist, updateDocs },
-		},
+		} as any,
 		readFileSync(file, 'utf8')
 	);
 
diff --git a/src/loaders/__tests__/styleguide-loader.spec.js b/src/loaders/__tests__/styleguide-loader.spec.js
deleted file mode 100644
index 7aae9c10..00000000
--- a/src/loaders/__tests__/styleguide-loader.spec.js
+++ /dev/null
@@ -1,215 +0,0 @@
-import vm from 'vm';
-import path from 'path';
-import { readFileSync } from 'fs';
-import styleguideLoader from '../styleguide-loader';
-import getConfig from '../../scripts/config';
-
-/* eslint-disable quotes */
-
-const file = path.resolve(__dirname, '../../../test/components/Button/Button.js');
-const configDir = path.resolve(__dirname, '../../../test');
-
-it('should return valid, parsable JS', () => {
-	const result = styleguideLoader.pitch.call(
-		{
-			request: file,
-			_styleguidist: {
-				sections: [{ components: 'components/**/*.js' }],
-				configDir,
-				getExampleFilename: () => 'Readme.md',
-				getComponentPathLine: filepath => filepath,
-			},
-			addContextDependency: () => {},
-		},
-		readFileSync(file, 'utf8')
-	);
-	expect(result).toBeTruthy();
-	expect(() => new vm.Script(result)).not.toThrow();
-});
-
-it('should return correct component paths: default glob pattern', () => {
-	const result = styleguideLoader.pitch.call(
-		{
-			request: file,
-			_styleguidist: {
-				...getConfig(path.resolve(__dirname, '../../../test/apps/defaults/styleguide.config.js')),
-			},
-			addContextDependency: () => {},
-		},
-		readFileSync(file, 'utf8')
-	);
-	expect(result).toBeTruthy();
-	expect(() => new vm.Script(result)).not.toThrow();
-	expect(result).toMatch(`'filepath': 'src/components/Button.js'`);
-	expect(result).toMatch(`'filepath': 'src/components/Placeholder.js'`);
-});
-
-it('should return correct component paths: glob', () => {
-	const result = styleguideLoader.pitch.call(
-		{
-			request: file,
-			_styleguidist: {
-				sections: [{ components: 'components/**/*.js' }],
-				configDir,
-				getExampleFilename: () => 'Readme.md',
-				getComponentPathLine: filepath => filepath,
-			},
-			addContextDependency: () => {},
-		},
-		readFileSync(file, 'utf8')
-	);
-	expect(result).toBeTruthy();
-	expect(() => new vm.Script(result)).not.toThrow();
-	expect(result).toMatch(`'filepath': 'components/Button/Button.js'`);
-	expect(result).toMatch(`'filepath': 'components/Placeholder/Placeholder.js'`);
-	expect(result).toMatch(`'filepath': 'components/RandomButton/RandomButton.js'`);
-});
-
-it('should return correct component paths: function returning absolute paths', () => {
-	const result = styleguideLoader.pitch.call(
-		{
-			request: file,
-			_styleguidist: {
-				sections: [
-					{
-						components: () => [
-							`${configDir}/components/Button/Button.js`,
-							`${configDir}/components/Placeholder/Placeholder.js`,
-						],
-					},
-				],
-				configDir,
-				getExampleFilename: () => 'Readme.md',
-				getComponentPathLine: filepath => filepath,
-			},
-			addContextDependency: () => {},
-		},
-		readFileSync(file, 'utf8')
-	);
-	expect(result).toBeTruthy();
-	expect(() => new vm.Script(result)).not.toThrow();
-	expect(result).toMatch(`'filepath': 'components/Button/Button.js'`);
-	expect(result).toMatch(`'filepath': 'components/Placeholder/Placeholder.js'`);
-	expect(result).not.toMatch(`'filepath': 'components/RandomButton/RandomButton.js'`);
-});
-
-it('should return correct component paths: function returning relative paths', () => {
-	const result = styleguideLoader.pitch.call(
-		{
-			request: file,
-			_styleguidist: {
-				sections: [
-					{
-						components: () => [
-							'components/Button/Button.js',
-							'components/Placeholder/Placeholder.js',
-						],
-					},
-				],
-				configDir,
-				getExampleFilename: () => 'Readme.md',
-				getComponentPathLine: filepath => filepath,
-			},
-			addContextDependency: () => {},
-		},
-		readFileSync(file, 'utf8')
-	);
-	expect(result).toBeTruthy();
-	expect(() => new vm.Script(result)).not.toThrow();
-	expect(result).toMatch(`'filepath': 'components/Button/Button.js'`);
-	expect(result).toMatch(`'filepath': 'components/Placeholder/Placeholder.js'`);
-	expect(result).not.toMatch(`'filepath': 'components/RandomButton/RandomButton.js'`);
-});
-
-it('should return correct component paths: array of of relative paths', () => {
-	const result = styleguideLoader.pitch.call(
-		{
-			request: file,
-			_styleguidist: {
-				sections: [
-					{
-						components: ['components/Button/Button.js', 'components/Placeholder/Placeholder.js'],
-					},
-				],
-				configDir,
-				getExampleFilename: () => 'Readme.md',
-				getComponentPathLine: filepath => filepath,
-			},
-			addContextDependency: () => {},
-		},
-		readFileSync(file, 'utf8')
-	);
-	expect(result).toBeTruthy();
-	expect(() => new vm.Script(result)).not.toThrow();
-	expect(result).toMatch(`'filepath': 'components/Button/Button.js'`);
-	expect(result).toMatch(`'filepath': 'components/Placeholder/Placeholder.js'`);
-});
-
-it('should filter out components without examples if skipComponentsWithoutExample=true', () => {
-	const result = styleguideLoader.pitch.call(
-		{
-			request: file,
-			_styleguidist: {
-				sections: [
-					{
-						components: () => [
-							'components/Button/Button.js',
-							'components/RandomButton/RandomButton.js',
-						],
-					},
-				],
-				configDir,
-				skipComponentsWithoutExample: true,
-				getExampleFilename: componentPath => path.join(path.dirname(componentPath), 'Readme.md'),
-				getComponentPathLine: filepath => filepath,
-			},
-			addContextDependency: () => {},
-		},
-		readFileSync(file, 'utf8')
-	);
-	expect(result).toBeTruthy();
-	expect(() => new vm.Script(result)).not.toThrow();
-	expect(result).toMatch(`'filepath': 'components/Button/Button.js'`);
-	expect(result).not.toMatch(`'filepath': 'components/RandomButton/RandomButton.js'`);
-});
-
-it('should add context dependencies to webpack from contextDependencies config option', () => {
-	const contextDependencies = ['foo', 'bar'];
-	const addContextDependency = jest.fn();
-	styleguideLoader.pitch.call(
-		{
-			request: file,
-			_styleguidist: {
-				sections: [{ components: 'components/**/*.js' }],
-				configDir,
-				getExampleFilename: () => 'Readme.md',
-				getComponentPathLine: filepath => filepath,
-				contextDependencies,
-			},
-			addContextDependency,
-		},
-		readFileSync(file, 'utf8')
-	);
-	expect(addContextDependency).toHaveBeenCalledTimes(2);
-	expect(addContextDependency).toBeCalledWith(contextDependencies[0]);
-	expect(addContextDependency).toBeCalledWith(contextDependencies[1]);
-});
-
-it('should add common parent folder of all components to context dependencies', () => {
-	const addContextDependency = jest.fn();
-	styleguideLoader.pitch.call(
-		{
-			request: file,
-			_styleguidist: {
-				sections: [{ components: 'components/**/*.js' }],
-				configDir,
-				getExampleFilename: () => 'Readme.md',
-				getComponentPathLine: filepath => filepath,
-			},
-			addContextDependency,
-		},
-		readFileSync(file, 'utf8')
-	);
-	expect(addContextDependency).toHaveBeenCalledTimes(1);
-	expect(addContextDependency).toBeCalledWith(expect.stringMatching(/test\/components\/$/));
-});
diff --git a/src/loaders/__tests__/styleguide-loader.spec.ts b/src/loaders/__tests__/styleguide-loader.spec.ts
new file mode 100644
index 00000000..aae3a6ab
--- /dev/null
+++ b/src/loaders/__tests__/styleguide-loader.spec.ts
@@ -0,0 +1,188 @@
+import vm from 'vm';
+import path from 'path';
+import * as styleguideLoader from '../styleguide-loader';
+import getConfig from '../../scripts/config';
+
+/* eslint-disable quotes */
+
+const file = path.resolve(__dirname, '../../../test/components/Button/Button.js');
+const configDir = path.resolve(__dirname, '../../../test');
+
+it('should return valid, parsable JS', () => {
+	const result = styleguideLoader.pitch.call({
+		request: file,
+		_styleguidist: {
+			sections: [{ components: 'components/**/*.js' }],
+			configDir,
+			getExampleFilename: () => 'Readme.md',
+			getComponentPathLine: (filepath: string) => filepath,
+		},
+		addContextDependency: () => {},
+	} as any);
+	expect(result).toBeTruthy();
+	expect(() => new vm.Script(result)).not.toThrow();
+});
+
+it('should return correct component paths: default glob pattern', () => {
+	const result = styleguideLoader.pitch.call({
+		request: file,
+		_styleguidist: {
+			...getConfig(path.resolve(__dirname, '../../../test/apps/defaults/styleguide.config.js')),
+		},
+		addContextDependency: () => {},
+	} as any);
+	expect(result).toBeTruthy();
+	expect(() => new vm.Script(result)).not.toThrow();
+	expect(result).toMatch(`'filepath': 'src/components/Button.js'`);
+	expect(result).toMatch(`'filepath': 'src/components/Placeholder.js'`);
+});
+
+it('should return correct component paths: glob', () => {
+	const result = styleguideLoader.pitch.call({
+		request: file,
+		_styleguidist: {
+			sections: [{ components: 'components/**/*.js' }],
+			configDir,
+			getExampleFilename: () => 'Readme.md',
+			getComponentPathLine: (filepath: string) => filepath,
+		},
+		addContextDependency: () => {},
+	} as any);
+	expect(result).toBeTruthy();
+	expect(() => new vm.Script(result)).not.toThrow();
+	expect(result).toMatch(`'filepath': 'components/Button/Button.js'`);
+	expect(result).toMatch(`'filepath': 'components/Placeholder/Placeholder.js'`);
+	expect(result).toMatch(`'filepath': 'components/RandomButton/RandomButton.js'`);
+});
+
+it('should return correct component paths: function returning absolute paths', () => {
+	const result = styleguideLoader.pitch.call({
+		request: file,
+		_styleguidist: {
+			sections: [
+				{
+					components: () => [
+						`${configDir}/components/Button/Button.js`,
+						`${configDir}/components/Placeholder/Placeholder.js`,
+					],
+				},
+			],
+			configDir,
+			getExampleFilename: () => 'Readme.md',
+			getComponentPathLine: (filepath: string) => filepath,
+		},
+		addContextDependency: () => {},
+	} as any);
+	expect(result).toBeTruthy();
+	expect(() => new vm.Script(result)).not.toThrow();
+	expect(result).toMatch(`'filepath': 'components/Button/Button.js'`);
+	expect(result).toMatch(`'filepath': 'components/Placeholder/Placeholder.js'`);
+	expect(result).not.toMatch(`'filepath': 'components/RandomButton/RandomButton.js'`);
+});
+
+it('should return correct component paths: function returning relative paths', () => {
+	const result = styleguideLoader.pitch.call({
+		request: file,
+		_styleguidist: {
+			sections: [
+				{
+					components: () => [
+						'components/Button/Button.js',
+						'components/Placeholder/Placeholder.js',
+					],
+				},
+			],
+			configDir,
+			getExampleFilename: () => 'Readme.md',
+			getComponentPathLine: (filepath: string) => filepath,
+		},
+		addContextDependency: () => {},
+	} as any);
+	expect(result).toBeTruthy();
+	expect(() => new vm.Script(result)).not.toThrow();
+	expect(result).toMatch(`'filepath': 'components/Button/Button.js'`);
+	expect(result).toMatch(`'filepath': 'components/Placeholder/Placeholder.js'`);
+	expect(result).not.toMatch(`'filepath': 'components/RandomButton/RandomButton.js'`);
+});
+
+it('should return correct component paths: array of of relative paths', () => {
+	const result = styleguideLoader.pitch.call({
+		request: file,
+		_styleguidist: {
+			sections: [
+				{
+					components: ['components/Button/Button.js', 'components/Placeholder/Placeholder.js'],
+				},
+			],
+			configDir,
+			getExampleFilename: () => 'Readme.md',
+			getComponentPathLine: (filepath: string) => filepath,
+		},
+		addContextDependency: () => {},
+	} as any);
+	expect(result).toBeTruthy();
+	expect(() => new vm.Script(result)).not.toThrow();
+	expect(result).toMatch(`'filepath': 'components/Button/Button.js'`);
+	expect(result).toMatch(`'filepath': 'components/Placeholder/Placeholder.js'`);
+});
+
+it('should filter out components without examples if skipComponentsWithoutExample=true', () => {
+	const result = styleguideLoader.pitch.call({
+		request: file,
+		_styleguidist: {
+			sections: [
+				{
+					components: () => [
+						'components/Button/Button.js',
+						'components/RandomButton/RandomButton.js',
+					],
+				},
+			],
+			configDir,
+			skipComponentsWithoutExample: true,
+			getExampleFilename: (componentPath: string) =>
+				path.join(path.dirname(componentPath), 'Readme.md'),
+			getComponentPathLine: (filepath: string) => filepath,
+		},
+		addContextDependency: () => {},
+	} as any);
+	expect(result).toBeTruthy();
+	expect(() => new vm.Script(result)).not.toThrow();
+	expect(result).toMatch(`'filepath': 'components/Button/Button.js'`);
+	expect(result).not.toMatch(`'filepath': 'components/RandomButton/RandomButton.js'`);
+});
+
+it('should add context dependencies to webpack from contextDependencies config option', () => {
+	const contextDependencies = ['foo', 'bar'];
+	const addContextDependency = jest.fn();
+	styleguideLoader.pitch.call({
+		request: file,
+		_styleguidist: {
+			sections: [{ components: 'components/**/*.js' }],
+			configDir,
+			getExampleFilename: () => 'Readme.md',
+			getComponentPathLine: (filepath: string) => filepath,
+			contextDependencies,
+		},
+		addContextDependency,
+	} as any);
+	expect(addContextDependency).toHaveBeenCalledTimes(2);
+	expect(addContextDependency).toBeCalledWith(contextDependencies[0]);
+	expect(addContextDependency).toBeCalledWith(contextDependencies[1]);
+});
+
+it('should add common parent folder of all components to context dependencies', () => {
+	const addContextDependency = jest.fn();
+	styleguideLoader.pitch.call({
+		request: file,
+		_styleguidist: {
+			sections: [{ components: 'components/**/*.js' }],
+			configDir,
+			getExampleFilename: () => 'Readme.md',
+			getComponentPathLine: (filepath: string) => filepath,
+		},
+		addContextDependency,
+	} as any);
+	expect(addContextDependency).toHaveBeenCalledTimes(1);
+	expect(addContextDependency).toBeCalledWith(expect.stringMatching(/test\/components\/$/));
+});
diff --git a/src/loaders/utils/__tests__/__snapshots__/chunkify.spec.js.snap b/src/loaders/utils/__tests__/__snapshots__/chunkify.spec.ts.snap
similarity index 100%
rename from src/loaders/utils/__tests__/__snapshots__/chunkify.spec.js.snap
rename to src/loaders/utils/__tests__/__snapshots__/chunkify.spec.ts.snap
diff --git a/src/loaders/utils/__tests__/__snapshots__/filterComponentsWithExample.spec.js.snap b/src/loaders/utils/__tests__/__snapshots__/filterComponentsWithExample.spec.ts.snap
similarity index 100%
rename from src/loaders/utils/__tests__/__snapshots__/filterComponentsWithExample.spec.js.snap
rename to src/loaders/utils/__tests__/__snapshots__/filterComponentsWithExample.spec.ts.snap
diff --git a/src/loaders/utils/__tests__/__snapshots__/getAst.spec.js.snap b/src/loaders/utils/__tests__/__snapshots__/getAst.spec.ts.snap
similarity index 100%
rename from src/loaders/utils/__tests__/__snapshots__/getAst.spec.js.snap
rename to src/loaders/utils/__tests__/__snapshots__/getAst.spec.ts.snap
diff --git a/src/loaders/utils/__tests__/__snapshots__/getComponents.spec.js.snap b/src/loaders/utils/__tests__/__snapshots__/getComponents.spec.ts.snap
similarity index 100%
rename from src/loaders/utils/__tests__/__snapshots__/getComponents.spec.js.snap
rename to src/loaders/utils/__tests__/__snapshots__/getComponents.spec.ts.snap
diff --git a/src/loaders/utils/__tests__/__snapshots__/getProps.spec.js.snap b/src/loaders/utils/__tests__/__snapshots__/getProps.spec.ts.snap
similarity index 95%
rename from src/loaders/utils/__tests__/__snapshots__/getProps.spec.js.snap
rename to src/loaders/utils/__tests__/__snapshots__/getProps.spec.ts.snap
index d99bb83d..ca826119 100644
--- a/src/loaders/utils/__tests__/__snapshots__/getProps.spec.js.snap
+++ b/src/loaders/utils/__tests__/__snapshots__/getProps.spec.ts.snap
@@ -134,6 +134,7 @@ Object {
 <span class=\\"token function\\">alert</span><span class=\\"token punctuation\\">(</span><span class=\\"token string\\">'Hello world'</span><span class=\\"token punctuation\\">)</span><span class=\\"token punctuation\\">;</span>
 \`\`\`
 ",
+  "displayName": "",
   "doclets": Object {},
   "methods": Array [],
   "tags": Object {},
@@ -144,6 +145,7 @@ exports[`should not crash when using doctrine to parse a default prop that isn't
 Object {
   "description": "The only true button.
 ",
+  "displayName": "",
   "doclets": Object {},
   "methods": Array [],
   "props": Object {
@@ -187,15 +189,21 @@ Object {
   "props": Object {
     "children": Object {
       "description": "Button label.",
+      "name": "children",
       "required": true,
       "tags": Object {},
-      "type": Object {},
+      "type": Object {
+        "name": "object",
+      },
     },
     "color": Object {
       "description": "",
+      "name": "color",
       "required": false,
       "tags": Object {},
-      "type": Object {},
+      "type": Object {
+        "name": "string",
+      },
     },
   },
   "tags": Object {},
@@ -238,9 +246,12 @@ Object {
   "props": Object {
     "children": Object {
       "description": "Button label.",
+      "name": "children",
       "required": true,
       "tags": Object {},
-      "type": Object {},
+      "type": Object {
+        "name": "object",
+      },
     },
   },
 }
diff --git a/src/loaders/utils/__tests__/__snapshots__/getSections.spec.js.snap b/src/loaders/utils/__tests__/__snapshots__/getSections.spec.ts.snap
similarity index 100%
rename from src/loaders/utils/__tests__/__snapshots__/getSections.spec.js.snap
rename to src/loaders/utils/__tests__/__snapshots__/getSections.spec.ts.snap
diff --git a/src/loaders/utils/__tests__/__snapshots__/highlightCode.spec.js.snap b/src/loaders/utils/__tests__/__snapshots__/highlightCode.spec.ts.snap
similarity index 100%
rename from src/loaders/utils/__tests__/__snapshots__/highlightCode.spec.js.snap
rename to src/loaders/utils/__tests__/__snapshots__/highlightCode.spec.ts.snap
diff --git a/src/loaders/utils/__tests__/__snapshots__/highlightCodeInMarkdown.spec.js.snap b/src/loaders/utils/__tests__/__snapshots__/highlightCodeInMarkdown.spec.ts.snap
similarity index 100%
rename from src/loaders/utils/__tests__/__snapshots__/highlightCodeInMarkdown.spec.js.snap
rename to src/loaders/utils/__tests__/__snapshots__/highlightCodeInMarkdown.spec.ts.snap
diff --git a/src/loaders/utils/__tests__/__snapshots__/processComponent.spec.js.snap b/src/loaders/utils/__tests__/__snapshots__/processComponent.spec.ts.snap
similarity index 100%
rename from src/loaders/utils/__tests__/__snapshots__/processComponent.spec.js.snap
rename to src/loaders/utils/__tests__/__snapshots__/processComponent.spec.ts.snap
diff --git a/src/loaders/utils/__tests__/chunkify.spec.js b/src/loaders/utils/__tests__/chunkify.spec.ts
similarity index 94%
rename from src/loaders/utils/__tests__/chunkify.spec.js
rename to src/loaders/utils/__tests__/chunkify.spec.ts
index 24319e6b..310f02ab 100644
--- a/src/loaders/utils/__tests__/chunkify.spec.js
+++ b/src/loaders/utils/__tests__/chunkify.spec.ts
@@ -105,11 +105,11 @@ it('should call updateExample function for example', () => {
 			settings: {},
 		},
 	];
-	const updateExample = props => {
+	const updateExample = (props: Omit<Rsg.CodeExample, 'type'>): Omit<Rsg.CodeExample, 'type'> => {
 		const content = props.content;
 		const lang = props.lang;
 		const settings = props.settings;
-		if (typeof settings.file === 'string') {
+		if (settings && typeof settings.file === 'string') {
 			delete settings.file;
 			return {
 				content: '<h1>Hello Markdown!</h1>',
diff --git a/src/loaders/utils/__tests__/expandDefaultComponent.spec.js b/src/loaders/utils/__tests__/expandDefaultComponent.spec.ts
similarity index 88%
rename from src/loaders/utils/__tests__/expandDefaultComponent.spec.js
rename to src/loaders/utils/__tests__/expandDefaultComponent.spec.ts
index b94e9bba..ae01b04e 100644
--- a/src/loaders/utils/__tests__/expandDefaultComponent.spec.js
+++ b/src/loaders/utils/__tests__/expandDefaultComponent.spec.ts
@@ -13,5 +13,5 @@ it('expandDefaultComponent() replace placeholders with component name', () => {
 	const result = expandDefaultComponent(exampleMarkdown, 'FooComponent');
 	expect(result).not.toMatch(/__COMPONENT__/);
 	expect(result).toMatch(/FooComponent/);
-	expect(result.match(/FooComponent/g).length).toBe(4);
+	expect((result.match(/FooComponent/g) || '').length).toBe(4);
 });
diff --git a/src/loaders/utils/__tests__/filterComponentsWithExample.spec.js b/src/loaders/utils/__tests__/filterComponentsWithExample.spec.ts
similarity index 98%
rename from src/loaders/utils/__tests__/filterComponentsWithExample.spec.js
rename to src/loaders/utils/__tests__/filterComponentsWithExample.spec.ts
index a5503d1a..ea448c7a 100644
--- a/src/loaders/utils/__tests__/filterComponentsWithExample.spec.js
+++ b/src/loaders/utils/__tests__/filterComponentsWithExample.spec.ts
@@ -48,7 +48,7 @@ const sections = [
 			},
 		],
 	},
-];
+] as any;
 
 it('should skip components without example file', () => {
 	const result = filterComponentsWithExample(sections);
diff --git a/src/loaders/utils/__tests__/getAllContentPages.spec.js b/src/loaders/utils/__tests__/getAllContentPages.spec.ts
similarity index 59%
rename from src/loaders/utils/__tests__/getAllContentPages.spec.js
rename to src/loaders/utils/__tests__/getAllContentPages.spec.ts
index 251b9f28..340967ae 100644
--- a/src/loaders/utils/__tests__/getAllContentPages.spec.js
+++ b/src/loaders/utils/__tests__/getAllContentPages.spec.ts
@@ -1,9 +1,19 @@
 import getAllContentPages from '../getAllContentPages';
 
-const sections = [
+const readmeContent: Rsg.MarkdownExample = {
+	type: 'markdown',
+	content: '# Readme',
+};
+
+const nestedContent: Rsg.MarkdownExample = {
+	type: 'markdown',
+	content: '# Nested',
+};
+
+const sections: Rsg.LoaderSection[] = [
 	{
 		name: 'Readme',
-		content: 'Readme.md',
+		content: readmeContent,
 		components: [],
 		sections: [],
 	},
@@ -23,7 +33,7 @@ const sections = [
 			},
 			{
 				name: 'Nested 2',
-				content: 'Nested.md',
+				content: nestedContent,
 				components: [],
 				sections: [],
 			},
@@ -33,5 +43,5 @@ const sections = [
 
 it('should return all content pages', () => {
 	const result = getAllContentPages(sections);
-	expect(result).toEqual(['Readme.md', 'Nested.md']);
+	expect(result).toEqual([readmeContent, nestedContent]);
 });
diff --git a/src/loaders/utils/__tests__/getAst.spec.js b/src/loaders/utils/__tests__/getAst.spec.ts
similarity index 100%
rename from src/loaders/utils/__tests__/getAst.spec.js
rename to src/loaders/utils/__tests__/getAst.spec.ts
diff --git a/src/loaders/utils/__tests__/getComponentFiles.spec.js b/src/loaders/utils/__tests__/getComponentFiles.spec.ts
similarity index 91%
rename from src/loaders/utils/__tests__/getComponentFiles.spec.js
rename to src/loaders/utils/__tests__/getComponentFiles.spec.ts
index ef0f3ce0..bd9f4f2d 100644
--- a/src/loaders/utils/__tests__/getComponentFiles.spec.js
+++ b/src/loaders/utils/__tests__/getComponentFiles.spec.ts
@@ -8,7 +8,7 @@ const processedComponents = components.map(c => `~/${c}`);
 const glob = 'components/**/[A-Z]*.js';
 const globArray = ['components/Annotation/[A-Z]*.js', 'components/Button/[A-Z]*.js'];
 
-const deabs = x => deabsDeep(x, { root: configDir });
+const deabs = (x: string[]) => deabsDeep(x, { root: configDir });
 
 it('getComponentFiles() should return an empty array if components is null', () => {
 	const result = getComponentFiles();
@@ -21,7 +21,7 @@ it('getComponentFiles() should accept components as a function that returns file
 });
 
 it('getComponentFiles() should accept components as a function that returns absolute paths', () => {
-	const absolutize = files => files.map(file => path.join(configDir, file));
+	const absolutize = (files: string[]) => files.map(file => path.join(configDir, file));
 	const result = getComponentFiles(() => absolutize(components), configDir);
 	expect(deabs(result)).toEqual(processedComponents);
 });
@@ -40,7 +40,7 @@ it('getComponentFiles() should accept components as an array of file names', ()
 });
 
 it('getComponentFiles() should accept components as an array of absolute paths', () => {
-	const absolutize = files => files.map(file => path.join(configDir, file));
+	const absolutize = (files: string[]) => files.map(file => path.join(configDir, file));
 	const result = getComponentFiles(absolutize(components), configDir);
 	expect(deabs(result)).toEqual(processedComponents);
 });
@@ -84,6 +84,6 @@ it('getComponentFiles() should ignore specified patterns for globs from function
 });
 
 it('getComponentFiles() should throw if components is not a function, array or a string', () => {
-	const fn = () => getComponentFiles(42, configDir);
+	const fn = () => getComponentFiles(42 as any, configDir);
 	expect(fn).toThrowError('should be string, function or array');
 });
diff --git a/src/loaders/utils/__tests__/getComponentFilesFromSections.spec.js b/src/loaders/utils/__tests__/getComponentFilesFromSections.spec.ts
similarity index 94%
rename from src/loaders/utils/__tests__/getComponentFilesFromSections.spec.js
rename to src/loaders/utils/__tests__/getComponentFilesFromSections.spec.ts
index 10007272..493bcb4b 100644
--- a/src/loaders/utils/__tests__/getComponentFilesFromSections.spec.js
+++ b/src/loaders/utils/__tests__/getComponentFilesFromSections.spec.ts
@@ -23,7 +23,7 @@ const sections = [
 	},
 ];
 
-const deabs = x => deabsDeep(x, { root: configDir });
+const deabs = (x: string[]) => deabsDeep(x, { root: configDir });
 
 it('getComponentFilesFromSections() should return a list of files', () => {
 	const result = getComponentFilesFromSections(sections, configDir);
diff --git a/src/loaders/utils/__tests__/getComponentPatternsFromSections.spec.js b/src/loaders/utils/__tests__/getComponentPatternsFromSections.spec.ts
similarity index 52%
rename from src/loaders/utils/__tests__/getComponentPatternsFromSections.spec.js
rename to src/loaders/utils/__tests__/getComponentPatternsFromSections.spec.ts
index 38d78f1d..4869cb81 100644
--- a/src/loaders/utils/__tests__/getComponentPatternsFromSections.spec.js
+++ b/src/loaders/utils/__tests__/getComponentPatternsFromSections.spec.ts
@@ -7,14 +7,26 @@ const sections = [
 	},
 	{
 		name: 'Components',
-		components: 'components/**/B*.js',
+		components: ['components/**/B*.js'],
 	},
 	{
 		name: 'Nesting',
 		sections: [
 			{
 				name: 'Nested',
-				components: 'components/**/P*.js',
+				components: ['components/**/P*.js'],
+			},
+		],
+	},
+	{
+		name: 'Nesting With Components',
+		components: ['components/**/T*.js'],
+		// is this on purpose or a bug ?
+		// a section cannot conatin `components` and nested `sections`
+		sections: [
+			{
+				name: 'Ignored Nested',
+				components: ['components/**/O*.js'],
 			},
 		],
 	},
@@ -22,5 +34,5 @@ const sections = [
 
 it('should return a list of patterns', () => {
 	const result = getComponentPatternsFromSections(sections);
-	expect(result).toEqual(['components/**/B*.js', 'components/**/P*.js']);
+	expect(result).toEqual(['components/**/B*.js', 'components/**/P*.js', 'components/**/T*.js']);
 });
diff --git a/src/loaders/utils/__tests__/getComponents.spec.js b/src/loaders/utils/__tests__/getComponents.spec.ts
similarity index 97%
rename from src/loaders/utils/__tests__/getComponents.spec.js
rename to src/loaders/utils/__tests__/getComponents.spec.ts
index b81cf5bd..51d4dd94 100644
--- a/src/loaders/utils/__tests__/getComponents.spec.js
+++ b/src/loaders/utils/__tests__/getComponents.spec.ts
@@ -7,7 +7,7 @@ it('getComponents() should return an object for components', () => {
 		configDir: path.resolve(__dirname, '../../../test'),
 		getExampleFilename: identity,
 		getComponentPathLine: identity,
-	});
+	} as any);
 
 	expect(result).toMatchSnapshot();
 });
diff --git a/src/loaders/utils/__tests__/getExamples.spec.js b/src/loaders/utils/__tests__/getExamples.spec.ts
similarity index 87%
rename from src/loaders/utils/__tests__/getExamples.spec.js
rename to src/loaders/utils/__tests__/getExamples.spec.ts
index 60d93859..e1dee8f7 100644
--- a/src/loaders/utils/__tests__/getExamples.spec.js
+++ b/src/loaders/utils/__tests__/getExamples.spec.ts
@@ -19,21 +19,21 @@ test('require an example file if component has example file', () => {
 	vol.fromJSON({ [examplesFile]: 'pizza' });
 
 	const result = getExamples(file, displayName, examplesFile);
-	expect(deabsDeep(result).require).toMatchInlineSnapshot(
+	expect(result && deabsDeep(result).require).toMatchInlineSnapshot(
 		`"!!~/src/loaders/examples-loader.js?displayName=Pizza&file=.%2F..%2Fpizza.js&shouldShowDefaultExample=false!./Pizza.md"`
 	);
 });
 
 test('require default example file if component has no example in the file system', () => {
 	const result = getExamples(file, displayName, examplesFile, defaultExample);
-	expect(deabsDeep(result).require).toMatchInlineSnapshot(
+	expect(result && deabsDeep(result).require).toMatchInlineSnapshot(
 		`"!!~/src/loaders/examples-loader.js?displayName=Pizza&file=.%2F..%2Fpizza.js&shouldShowDefaultExample=false!./Default.md"`
 	);
 });
 
 test('require default example has no example file', () => {
 	const result = getExamples(file, displayName, false, defaultExample);
-	expect(deabsDeep(result).require).toMatchInlineSnapshot(
+	expect(result && deabsDeep(result).require).toMatchInlineSnapshot(
 		`"!!~/src/loaders/examples-loader.js?displayName=Pizza&file=.%2F..%2Fpizza.js&shouldShowDefaultExample=true!./Default.md"`
 	);
 });
diff --git a/src/loaders/utils/__tests__/getImports.spec.js b/src/loaders/utils/__tests__/getImports.spec.ts
similarity index 100%
rename from src/loaders/utils/__tests__/getImports.spec.js
rename to src/loaders/utils/__tests__/getImports.spec.ts
diff --git a/src/loaders/utils/__tests__/getNameFromFilePath.spec.js b/src/loaders/utils/__tests__/getNameFromFilePath.spec.ts
similarity index 100%
rename from src/loaders/utils/__tests__/getNameFromFilePath.spec.js
rename to src/loaders/utils/__tests__/getNameFromFilePath.spec.ts
diff --git a/src/loaders/utils/__tests__/getProps.spec.js b/src/loaders/utils/__tests__/getProps.spec.ts
similarity index 94%
rename from src/loaders/utils/__tests__/getProps.spec.js
rename to src/loaders/utils/__tests__/getProps.spec.ts
index a47e17d1..e08ecf73 100644
--- a/src/loaders/utils/__tests__/getProps.spec.js
+++ b/src/loaders/utils/__tests__/getProps.spec.ts
@@ -8,12 +8,18 @@ it('should return an object for props', () => {
 		methods: [],
 		props: {
 			children: {
-				type: {},
+				name: 'children',
+				type: {
+					name: 'object',
+				},
 				required: true,
 				description: 'Button label.',
 			},
 			color: {
-				type: {},
+				name: 'color',
+				type: {
+					name: 'string',
+				},
 				required: false,
 				description: '',
 			},
@@ -28,7 +34,10 @@ it('should return an object for props without description', () => {
 		displayName: 'Button',
 		props: {
 			children: {
-				type: {},
+				name: 'children',
+				type: {
+					name: 'object',
+				},
 				required: true,
 				description: 'Button label.',
 			},
@@ -54,7 +63,7 @@ it('should remove non-public methods', () => {
 				{
 					docblock: 'Private method by default.',
 				},
-			],
+			] as any,
 		},
 		__filename
 	);
@@ -75,7 +84,7 @@ Baz method with foo param
 @returns {string} test
 `,
 				},
-			],
+			] as any,
 		},
 		__filename
 	);
@@ -96,7 +105,7 @@ Baz method with foo param
 @return {string} test
 `,
 				},
-			],
+			] as any,
 		},
 		__filename
 	);
@@ -131,7 +140,7 @@ Foo method with baz param
 						},
 					],
 				},
-			],
+			] as any,
 		},
 		__filename
 	);
@@ -210,7 +219,7 @@ it("should not crash when using doctrine to parse a default prop that isn't in t
 			crash: {
 				description: undefined,
 			},
-		},
+		} as any,
 	});
 
 	expect(result).toMatchSnapshot();
diff --git a/src/loaders/utils/__tests__/getSections.spec.js b/src/loaders/utils/__tests__/getSections.spec.ts
similarity index 92%
rename from src/loaders/utils/__tests__/getSections.spec.js
rename to src/loaders/utils/__tests__/getSections.spec.ts
index c2094adf..6fd7242f 100644
--- a/src/loaders/utils/__tests__/getSections.spec.js
+++ b/src/loaders/utils/__tests__/getSections.spec.ts
@@ -6,10 +6,11 @@ const config = {
 	configDir,
 	exampleMode: 'collapse',
 	usageMode: 'collapse',
-	getExampleFilename: a => a,
-	getComponentPathLine: a => a,
-};
-const sections = [
+	getExampleFilename: (a: string) => a,
+	getComponentPathLine: (a: string) => a,
+} as Rsg.SanitizedStyleguidistConfig;
+
+const sections: Rsg.ConfigSection[] = [
 	{
 		name: 'Readme',
 		content: 'components/Button/Readme.md',
@@ -26,7 +27,7 @@ const sections = [
 	{
 		name: 'Ignore',
 		content: () => 'Hello World',
-	},
+	} as any,
 ];
 const sectionsWithDepth = [
 	{
@@ -70,7 +71,7 @@ const sectionsWithBadDepth = [
 	},
 ];
 
-function filterSectionDepth(section) {
+function filterSectionDepth(section: Rsg.LoaderSection): Rsg.ConfigSection {
 	if (section.sections && section.sections.length) {
 		return {
 			sectionDepth: section.sectionDepth,
diff --git a/src/loaders/utils/__tests__/highlightCode.spec.js b/src/loaders/utils/__tests__/highlightCode.spec.ts
similarity index 85%
rename from src/loaders/utils/__tests__/highlightCode.spec.js
rename to src/loaders/utils/__tests__/highlightCode.spec.ts
index 4cfe9580..e67e03ea 100644
--- a/src/loaders/utils/__tests__/highlightCode.spec.js
+++ b/src/loaders/utils/__tests__/highlightCode.spec.ts
@@ -17,7 +17,7 @@ it('should warn when language not found', () => {
 	const actual = highlightCode(code, 'pizza');
 	expect(actual).toBe(code);
 	expect(warn).toBeCalledWith(
-		'Syntax highlighting for “pizza” isn’t supported. Supported languages are: clike, markup, xml, html, mathml, svg, markdown, md, css, scss, javascript, js, flow, typescript, ts, jsx, tsx, graphql, json, bash, shell, diff.'
+		'Syntax highlighting for “pizza” isn’t supported. Supported languages are: markup, xml, html, mathml, svg, css, clike, javascript, js, markdown, md, scss, flow, typescript, ts, jsx, tsx, graphql, json, bash, shell, diff.'
 	);
 });
 
diff --git a/src/loaders/utils/__tests__/highlightCodeInMarkdown.spec.js b/src/loaders/utils/__tests__/highlightCodeInMarkdown.spec.ts
similarity index 100%
rename from src/loaders/utils/__tests__/highlightCodeInMarkdown.spec.js
rename to src/loaders/utils/__tests__/highlightCodeInMarkdown.spec.ts
diff --git a/src/loaders/utils/__tests__/noAutoLinkRemarkPlugin.spec.js b/src/loaders/utils/__tests__/noAutoLinkRemarkPlugin.spec.ts
similarity index 77%
rename from src/loaders/utils/__tests__/noAutoLinkRemarkPlugin.spec.js
rename to src/loaders/utils/__tests__/noAutoLinkRemarkPlugin.spec.ts
index d5ef1f87..9e9c633b 100644
--- a/src/loaders/utils/__tests__/noAutoLinkRemarkPlugin.spec.js
+++ b/src/loaders/utils/__tests__/noAutoLinkRemarkPlugin.spec.ts
@@ -1,19 +1,19 @@
 import remark from 'remark';
 import noAutoLink from '../noAutoLinkRemarkPlugin';
 
-const transform = markdown =>
+const transform = (markdown: string) =>
 	remark()
 		.use(noAutoLink)
 		.processSync(markdown)
 		.toString();
 
-it('should not convert URLs to auto links', () => {
+it('should convert URLs to auto links', () => {
 	const markdown = 'http://example.com';
 	const result = transform(markdown);
 	expect(result.trim()).toBe('[http://example.com](http://example.com "http&#x3A;//example.com")');
 });
 
-it('should keep full inks as is', () => {
+it('should keep full links as is', () => {
 	const markdown = '[Pizza](http://example.com)';
 	const result = transform(markdown);
 	expect(result.trim()).toBe(markdown);
diff --git a/src/loaders/utils/__tests__/processComponent.spec.js b/src/loaders/utils/__tests__/processComponent.spec.ts
similarity index 51%
rename from src/loaders/utils/__tests__/processComponent.spec.js
rename to src/loaders/utils/__tests__/processComponent.spec.ts
index 59e74eb9..7ceec0c0 100644
--- a/src/loaders/utils/__tests__/processComponent.spec.js
+++ b/src/loaders/utils/__tests__/processComponent.spec.ts
@@ -3,12 +3,13 @@ import processComponent from '../processComponent';
 
 const config = {
 	configDir: __dirname,
-	getExampleFilename: componentpath => path.join(path.dirname(componentpath), 'Readme.md'),
-	getComponentPathLine: componentpath => componentpath,
+	getExampleFilename: (componentpath: string) =>
+		path.join(path.dirname(componentpath), 'Readme.md'),
+	getComponentPathLine: (componentpath: string) => componentpath,
 };
 
 it('processComponent() should return an object for section with content', () => {
-	const result = processComponent('pizza.js', config);
+	const result = processComponent('pizza.js', config as any);
 
 	expect(result).toMatchSnapshot();
 });
diff --git a/src/loaders/utils/__tests__/removeDoclets.spec.js b/src/loaders/utils/__tests__/removeDoclets.spec.ts
similarity index 100%
rename from src/loaders/utils/__tests__/removeDoclets.spec.js
rename to src/loaders/utils/__tests__/removeDoclets.spec.ts
diff --git a/src/loaders/utils/__tests__/requireIt.spec.js b/src/loaders/utils/__tests__/requireIt.spec.ts
similarity index 100%
rename from src/loaders/utils/__tests__/requireIt.spec.js
rename to src/loaders/utils/__tests__/requireIt.spec.ts
diff --git a/src/loaders/utils/__tests__/sortProps.spec.js b/src/loaders/utils/__tests__/sortProps.spec.ts
similarity index 79%
rename from src/loaders/utils/__tests__/sortProps.spec.js
rename to src/loaders/utils/__tests__/sortProps.spec.ts
index a3d99662..923b6211 100644
--- a/src/loaders/utils/__tests__/sortProps.spec.js
+++ b/src/loaders/utils/__tests__/sortProps.spec.ts
@@ -1,10 +1,12 @@
+import { PropDescriptor, PropTypeDescriptor } from 'react-docgen';
 import sortProps from '../sortProps';
 
-function makeProp(name, required, defaultValue, type) {
-	// Default values.
-	required = required === undefined ? false : required;
-	type = type === undefined ? { name: 'string' } : type;
-
+function makeProp(
+	name: string,
+	required = false,
+	defaultValue: any = undefined,
+	type: PropTypeDescriptor = { name: 'string' }
+): PropDescriptor {
 	return {
 		name,
 		required,
diff --git a/src/typings/test.Classes.d.ts b/src/typings/test.Classes.d.ts
new file mode 100644
index 00000000..eb790cbb
--- /dev/null
+++ b/src/typings/test.Classes.d.ts
@@ -0,0 +1,7 @@
+/**
+ * function used in react tests to generate
+ * mocks of JSS Class names
+ */
+declare const classes: (
+	styles: (theme: Rsg.Theme) => Record<string, any>
+) => Record<string, string>;
diff --git a/test/classes.ts b/test/classes.js
similarity index 52%
rename from test/classes.ts
rename to test/classes.js
index ede32d88..6f1fc18b 100644
--- a/test/classes.ts
+++ b/test/classes.js
@@ -1,4 +1,4 @@
 import keymirror from 'keymirror';
 import * as theme from '../src/client/styles/theme';
 
-export default (styles: (theme: Rsg.Theme) => any) => keymirror(styles(theme));
+export default styles => keymirror(styles(theme));