diff --git a/.eslintignore b/.eslintignore
index 916c8d42900362c81622dc0b88a4c968fbb61bc3..dd358e38fe3be7b963ed943ce80e6e607ce98105 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -10,3 +10,6 @@ coverage
 **/__mocks__/**
 **/*.d.ts
 /config.js
+
+# generated typescript code
+**/*.generated.ts
diff --git a/.eslintrc.js b/.eslintrc.js
index b8cecc2854547a88898058e57dc887e2cc107d52..6499509be7eededff17442a1462f0ae4ebafd938 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -19,7 +19,7 @@ module.exports = {
   parserOptions: {
     ecmaVersion: 9,
     tsconfigRootDir: __dirname,
-    project: ['./tsconfig.json'],
+    project: ['./tsconfig.lint.json'],
     extraFileExtensions: ['.mjs'],
   },
   rules: {
diff --git a/.gitignore b/.gitignore
index 77f3cfb0fb37a4e4feccd3e3852eaf06f8164d4f..1a12fbfd8637ac64d54d1d3fb915113741f3b542 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,6 @@ renovate-0.0.0-semantic-release.tgz
 junit.xml
 /test-results
 renovate-schema.json
+
+**/*.generated.ts
 test/datasource/nuget/_fixtures/obj
diff --git a/.prettierignore b/.prettierignore
index f74b40cc589e9878c341f1fff2453c177c605959..1edda6b04ce61838aa69248071ba11c7c6ea6359 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -12,3 +12,6 @@ package.json
 **/__fixtures__/
 **/__mocks__/
 bin/yarn*
+
+# generated typescript code
+**/*.generated.ts
diff --git a/.babelrc b/babel.config.json
similarity index 88%
rename from .babelrc
rename to babel.config.json
index 1caf31a8b372456d2af2a06a048e2e5ccb5e71f1..71ffbe280854e38f180888b3b6a6ed888d93e11f 100644
--- a/.babelrc
+++ b/babel.config.json
@@ -17,7 +17,6 @@
   "sourceMaps": true,
   "retainLines": true,
 
-  // https://github.com/facebook/jest/issues/5920
   "env": {
     "test": {
       "plugins": ["dynamic-import-node"]
diff --git a/lib/datasource/common.ts b/lib/datasource/common.ts
index a7eea21274b972fa32d7cc3c300325f2d36ff143..d4c12a676fe10f592897eab75c3c0e1e6445366f 100644
--- a/lib/datasource/common.ts
+++ b/lib/datasource/common.ts
@@ -50,6 +50,7 @@ export interface ReleaseResult {
 }
 
 export interface Datasource {
+  id: string;
   getDigest?(config: DigestConfig, newValue?: string): Promise<string | null>;
   getPkgReleases(config: PkgReleaseConfig): Promise<ReleaseResult | null>;
 }
diff --git a/lib/datasource/index.spec.ts b/lib/datasource/index.spec.ts
index 7f0a7ea63fd54d331c55a57544c36913c6c3eb14..717c6ad314ffc724d5ca062db0a0ff57a2880071 100644
--- a/lib/datasource/index.spec.ts
+++ b/lib/datasource/index.spec.ts
@@ -4,6 +4,7 @@ import * as datasourceDocker from './docker';
 import * as datasourceGithubTags from './github-tags';
 import * as datasourceNpm from './npm';
 import { mocked } from '../../test/util';
+import { loadModules } from '../util/modules';
 
 jest.mock('./docker');
 jest.mock('./npm');
@@ -15,9 +16,32 @@ describe('datasource/index', () => {
     expect(datasource.getDatasources()).toBeDefined();
     expect(datasource.getDatasourceList()).toBeDefined();
   });
-  it('returns if digests are supported', () => {
+  it('validates dataource', async () => {
+    function validateDatasource(
+      module: datasource.Datasource,
+      name: string
+    ): boolean {
+      if (!module.getPkgReleases) {
+        return false;
+      }
+      if (module.id !== name) {
+        return false;
+      }
+      return true;
+    }
+    const dss = datasource.getDatasources();
+
+    const loadedDs = loadModules(__dirname, validateDatasource);
+    expect(Array.from(dss.keys())).toEqual(Object.keys(loadedDs));
+
+    for (const dsName of dss.keys()) {
+      const ds = await dss.get(dsName);
+      expect(validateDatasource(ds, dsName)).toBe(true);
+    }
+  });
+  it('returns if digests are supported', async () => {
     expect(
-      datasource.supportsDigests({ datasource: datasourceGithubTags.id })
+      await datasource.supportsDigests({ datasource: datasourceGithubTags.id })
     ).toBe(true);
   });
   it('returns null for no datasource', async () => {
diff --git a/lib/datasource/index.ts b/lib/datasource/index.ts
index 62f675fc219ab91d579628a16655e770b3dde259..82b80a3631c0fed8b2c98bea9f396a97d00609b1 100644
--- a/lib/datasource/index.ts
+++ b/lib/datasource/index.ts
@@ -11,28 +11,20 @@ import {
   DigestConfig,
 } from './common';
 import * as semverVersioning from '../versioning/semver';
-import { loadModules } from '../util/modules';
+import datasources from './api.generated';
 
 export * from './common';
 
-// istanbul ignore next
-function validateDatasource(module, name): boolean {
-  if (!module.getPkgReleases) {
-    return false;
-  }
-  if (module.id !== name) {
-    return false;
-  }
-  return true;
-}
-
-const datasources = loadModules<Datasource>(__dirname, validateDatasource);
-export const getDatasources = (): Record<string, Datasource> => datasources;
-const datasourceList = Object.keys(datasources);
-export const getDatasourceList = (): string[] => datasourceList;
+export const getDatasources = (): Map<string, Promise<Datasource>> =>
+  datasources;
+export const getDatasourceList = (): string[] => Array.from(datasources.keys());
 
 const cacheNamespace = 'datasource-releases';
 
+function load(datasource: string): Promise<Datasource> {
+  return datasources.get(datasource);
+}
+
 async function fetchReleases(
   config: PkgReleaseConfig
 ): Promise<ReleaseResult | null> {
@@ -41,11 +33,11 @@ async function fetchReleases(
     logger.warn('No datasource found');
     return null;
   }
-  if (!datasources[datasource]) {
+  if (!datasources.has(datasource)) {
     logger.warn('Unknown datasource: ' + datasource);
     return null;
   }
-  const dep = await datasources[datasource].getPkgReleases(config);
+  const dep = await (await load(datasource)).getPkgReleases(config);
   addMetaData(dep, datasource, config.lookupName);
   return dep;
 }
@@ -75,7 +67,7 @@ export async function getPkgReleases(
     logger.error({ config }, 'Datasource getPkgReleases without lookupName');
     return null;
   }
-  let res;
+  let res: ReleaseResult;
   try {
     res = await getRawReleases({
       ...config,
@@ -107,17 +99,17 @@ export async function getPkgReleases(
   return res;
 }
 
-export function supportsDigests(config: DigestConfig): boolean {
-  return 'getDigest' in datasources[config.datasource];
+export async function supportsDigests(config: DigestConfig): Promise<boolean> {
+  return 'getDigest' in (await load(config.datasource));
 }
 
-export function getDigest(
+export async function getDigest(
   config: DigestConfig,
   value?: string
 ): Promise<string | null> {
   const lookupName = config.lookupName || config.depName;
   const { registryUrls } = config;
-  return datasources[config.datasource].getDigest(
+  return (await load(config.datasource)).getDigest(
     { lookupName, registryUrls },
     value
   );
diff --git a/lib/workers/repository/process/lookup/index.ts b/lib/workers/repository/process/lookup/index.ts
index 5b24ec1233eaea70287c01067d0e9d1ee77e10f7..dd9a154fc0966215a7cda788b1d28c7d8498f9c6 100644
--- a/lib/workers/repository/process/lookup/index.ts
+++ b/lib/workers/repository/process/lookup/index.ts
@@ -357,7 +357,7 @@ export async function lookupUpdates(
     }
   }
   // Add digests if necessary
-  if (supportsDigests(config)) {
+  if (await supportsDigests(config)) {
     if (
       config.currentDigest &&
       config.datasource !== datasourceGitSubmodules.id
diff --git a/package.json b/package.json
index b70bb4f9b034b521f84b0a4fb42f0c3d74088ee7..d1ad57aab8098c6fac935578b1eba098e77d64c4 100644
--- a/package.json
+++ b/package.json
@@ -7,12 +7,14 @@
     "renovate-config-validator": "bin/config-validator.js"
   },
   "scripts": {
-    "build": "run-s clean compile:* copy-static-files",
-    "build:docker": "run-s compile:ts copy-static-files",
-    "clean": "rimraf dist",
+    "build": "run-s clean generate:* compile:* copy-static-files",
+    "build:docker": "run-s generate:* compile:ts copy-static-files",
+    "clean": "rimraf dist lib/**/*.generated.ts",
     "clean-cache": "node bin/clean-cache.js",
     "compile:ts": "tsc -p tsconfig.app.json",
     "compile:dts": "tsc -p tsconfig.dts.json",
+    "generate": "run-s generate:*",
+    "generate:imports": "node --experimental-modules tools/generate-imports.mjs",
     "copy-static-files": "copyfiles -u 1 -e **/__fixtures__/**  -e **/__mocks__/** lib/**/*.json lib/**/*.py dist/",
     "create-json-schema": "babel-node --extensions \".ts,.js\" -- bin/create-json-schema.js && prettier --write \"renovate-schema.json\"",
     "debug": "babel-node --inspect-brk  --extensions \".ts,.js\" -- lib/renovate.ts",
@@ -26,6 +28,9 @@
     "prepare": "run-s prepare:*",
     "prepare:jest": "node --experimental-modules tools/patch-jest.mjs",
     "prepare:re2": "node --experimental-modules tools/check-re2.mjs",
+    "prepare:generate": "run-s generate:*",
+    "prestart": "run-s generate:* ",
+    "pretest": "run-s generate:* ",
     "prettier": "prettier --list-different \"**/*.{ts,js,mjs,json,md}\"",
     "prettier-fix": "prettier --write \"**/*.{ts,js,mjs,json,md}\"",
     "release": "run-s \"release:* {@}\" --",
@@ -36,7 +41,8 @@
     "test-e2e": "npm pack && cd e2e && yarn install --no-lockfile --ignore-optional --prod && yarn test",
     "test-schema": "babel-node --extensions \".ts,.js\" -- test/json-schema.ts",
     "test": "run-s lint test-schema type-check jest",
-    "type-check": "tsc --noEmit"
+    "tsc": "tsc",
+    "type-check": "run-s generate:* \"tsc --noEmit\""
   },
   "repository": {
     "type": "git",
diff --git a/tools/generate-imports.mjs b/tools/generate-imports.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..df0b07fca70ad526d0af898f7b186cb040bbc8ff
--- /dev/null
+++ b/tools/generate-imports.mjs
@@ -0,0 +1,43 @@
+import shell from 'shelljs';
+import fs from 'fs-extra';
+
+shell.echo('generating imports');
+const newFiles = new Set();
+
+if (!fs.existsSync('lib')) {
+  shell.echo('> missing sources');
+  shell.exit(0);
+}
+
+function findModules(dirname) {
+  return fs
+    .readdirSync(dirname, { withFileTypes: true })
+    .filter(dirent => dirent.isDirectory())
+    .map(dirent => dirent.name)
+    .filter(name => !name.startsWith('__'))
+    .sort();
+}
+function updateFile(file, code) {
+  const oldCode = fs.existsSync(file) ? fs.readFileSync(file) : null;
+  if (code !== oldCode) {
+    fs.writeFileSync(file, code);
+  }
+  newFiles.add(file);
+}
+
+let code = `
+import { Datasource } from './common';
+const api = new Map<string, Promise<Datasource>>();
+export default api;
+`;
+for (const ds of findModules('lib/datasource')) {
+  code += `api.set('${ds}', import('./${ds}'));\n`;
+}
+
+updateFile('lib/datasource/api.generated.ts', code);
+
+for (const file of shell
+  .find('lib/**/*.generated.ts')
+  .filter(f => !newFiles.has(f))) {
+  fs.removeSync(file);
+}
diff --git a/tools/package.json b/tools/package.json
index 41853d2eb18a6755c5476fe6757f1ad89363da56..0b98cf51cfa9ec82ce36de1a287b416f7752971c 100644
--- a/tools/package.json
+++ b/tools/package.json
@@ -1,8 +1,9 @@
 {
-  "private":true,
+  "private": true,
   "type": "module",
   "dependencies": {
     "commander": "4.1.1",
+    "fs-extra": "8.1.0",
     "got": "9.6.0",
     "shelljs": "0.8.3"
   }
diff --git a/tsconfig.app.json b/tsconfig.app.json
index 25e6cee568675a91d5d7d635ad4129162b505074..9802bf51a91005edd903da718fac2254ce398159 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -5,8 +5,16 @@
     "isolatedModules": true,
     "sourceMap": true,
     "inlineSources": true,
+    "allowJs": false,
+    "checkJs": false,
     "types": ["node"]
   },
-  "include": ["lib/**/*"],
-  "exclude": ["lib/**/*.spec.ts", "**/__mocks__/**", "**/__fixtures__/**"]
+  "exclude": [
+    "./.cache",
+    "./dist",
+    "**/__mocks__/**",
+    "**/__fixtures__/**",
+    "**/*.spec.ts",
+    "./test"
+  ]
 }
diff --git a/tsconfig.json b/tsconfig.json
index 3517c16fd7dd78a7107d76993a0ca4baf4a39b4f..a7e3021387312e713072bc7657502f7a5b19d971 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -12,8 +12,17 @@
     "resolveJsonModule": false,
     "isolatedModules": true,
     "lib": ["es2018"],
-    "types": ["node", "jest"]
+    "types": ["node", "jest"],
+    "allowJs": true,
+    "checkJs": true
   },
-  "include": ["lib/**/*", "test/**/*", "tools/**/*.mjs"],
-  "exclude": ["node_modules", "./.cache", "./dist", "**/__mocks__/*"]
+  "exclude": [
+    "node_modules",
+    "./.cache",
+    "./dist",
+    "**/__mocks__/*",
+    "bin",
+    "coverage",
+    "config.js"
+  ]
 }
diff --git a/tsconfig.lint.json b/tsconfig.lint.json
new file mode 100644
index 0000000000000000000000000000000000000000..cb284e39d461741e4d89ab496609e26a125968e7
--- /dev/null
+++ b/tsconfig.lint.json
@@ -0,0 +1,4 @@
+{
+  "extends": "./tsconfig",
+  "include": ["**/*.ts", "**/*.js", "**/*.mjs"]
+}