diff --git a/.eslintrc.js b/.eslintrc.js
index 811c741ab6b..a582e1e2f48 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -10,10 +10,11 @@ module.exports = {
     extends: [
         'eslint:recommended',
         'plugin:@typescript-eslint/eslint-recommended',
-        'plugin:@typescript-eslint/recommended'
+        'plugin:@typescript-eslint/recommended',
+        'plugin:import/typescript'
     ],
     parserOptions: {
-        ecmaVersion: 2018
+        ecmaVersion: 2020
     },
     rules: {
         camelcase: 'off',
diff --git a/package.json b/package.json
index 49e4aa5937f..70d1a5d6ef4 100644
--- a/package.json
+++ b/package.json
@@ -2,12 +2,13 @@
   "name": "noderssbot",
   "version": "0.10.1",
   "description": "Another Telegram RSSBot in Node.js",
-  "main": "dist/source",
+  "exports": "./dist/source/index.js",
   "engines": {
-    "node": ">=12.13"
+    "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
   },
+  "type": "module",
   "scripts": {
-    "build": "del-cli dist && tsc && cpy source/template dist/source/template && cpy i18n dist/i18n",
+    "build": "del-cli dist && tsc && cpy source/template dist/source/template && cpy i18n dist/i18n && cpy package.json dist",
     "start": "cross-env NODE_PRODUTION=true node dist/source/index.js",
     "start-withsnapshot": "cross-env NODE_PRODUTION=true node --heapsnapshot-signal=SIGUSR2 dist/source/index.js",
     "dev": "node $NODE_DEBUG_OPTIONS dist/source/index",
diff --git a/source/config.ts b/source/config.ts
index 4b71d4c437b..c34d14a9722 100644
--- a/source/config.ts
+++ b/source/config.ts
@@ -1,12 +1,14 @@
 import * as path from 'path';
 import { Config } from './types/config';
-import { version } from '../package.json';
 import env from 'env-var';
+import { createRequire } from 'module';
+import { fileURLToPath } from 'url';
 
-const PKGROOT = path.join(
-    __dirname,
-    __dirname.includes('dist') ? '../..' : '..'
-);
+const cjsRequire = createRequire(import.meta.url);
+const { version } = cjsRequire('../package.json');
+
+const PKGROOT = fileURLToPath(new URL('../..', import.meta.url));
+console.log(PKGROOT);
 export const config: Config = {
     token: env.get('RSSBOT_TOKEN').required().asString(),
     proxy: {
diff --git a/source/controlers/import-reply.ts b/source/controlers/import-reply.ts
index 7cc13420aaf..3747bf46432 100644
--- a/source/controlers/import-reply.ts
+++ b/source/controlers/import-reply.ts
@@ -1,8 +1,8 @@
-import { getUserById } from '../proxies/users';
-import i18n from '../i18n';
+import { getUserById } from '../proxies/users.js';
+import i18n from '../i18n.js';
 import { MContext, Next } from '../types/ctx';
-import { config } from '../config';
-import { isSome } from '../types/option';
+import { config } from '../config.js';
+import { isSome } from '../types/option.js';
 
 export default async (ctx: MContext, next?: Next): Promise<void> => {
     const user = await getUserById(ctx.message.chat.id);
diff --git a/source/controlers/language.ts b/source/controlers/language.ts
index d3822d62809..b13a2ca5a10 100644
--- a/source/controlers/language.ts
+++ b/source/controlers/language.ts
@@ -1,5 +1,5 @@
-import { setLangById } from '../proxies/users';
-import i18n from '../i18n';
+import { setLangById } from '../proxies/users.js';
+import i18n from '../i18n.js';
 import { MContext, Next } from '../types/ctx';
 
 const chunk = (input: any[], size: number) => {
diff --git a/source/controlers/rss.ts b/source/controlers/rss.ts
index 8a8380efcc8..7d28861cbc1 100644
--- a/source/controlers/rss.ts
+++ b/source/controlers/rss.ts
@@ -1,11 +1,11 @@
-import * as RSS from '../proxies/rss-feed';
-import i18n from '../i18n';
-import twoKeyReply from '../utils/two-key-reply';
-import errors from '../utils/errors';
+import * as RSS from '../proxies/rss-feed.js';
+import i18n from '../i18n.js';
+import twoKeyReply from '../utils/two-key-reply.js';
+import errors from '../utils/errors.js';
 import { MContext, Next } from '../types/ctx';
-import { isNone } from '../types/option';
-import { decodeUrl, encodeUrl } from '../utils/urlencode';
-import sanitize from '../utils/sanitize';
+import { isNone } from '../types/option.js';
+import { decodeUrl, encodeUrl } from '../utils/urlencode.js';
+import sanitize from '../utils/sanitize.js';
 
 export async function sub(ctx: MContext, next: Next): Promise<void> {
     const { feedUrl, chat, lang } = ctx.state;
diff --git a/source/database/index.ts b/source/database/index.ts
index 90b543a4f93..943eee2c539 100644
--- a/source/database/index.ts
+++ b/source/database/index.ts
@@ -1,7 +1,7 @@
 import knex from 'knex';
-import { config } from '../config';
-import knexConfig from '../knexfile';
-import logger from '../utils/logger';
+import { config } from '../config.js';
+import knexConfig from '../knexfile.js';
+import logger from '../utils/logger.js';
 export const db = knex({
     ...knexConfig,
     debug: process.env.NODE_ENV === 'development'
diff --git a/source/i18n.ts b/source/i18n.ts
index 3e0e1ac9129..f81bb6542de 100644
--- a/source/i18n.ts
+++ b/source/i18n.ts
@@ -2,9 +2,12 @@ import * as yaml from 'js-yaml';
 import * as fs from 'fs';
 import * as path from 'path';
 import { I18n, I18nLang } from './types/i18n';
+import { fileURLToPath } from 'url';
 
+const __dirname = fileURLToPath(new URL('../i18n', import.meta.url));
 const result: I18n = {};
 const localeDir = path.join(__dirname, '../i18n'); // /dist/source/i18n -> /dist/[i18n]
+console.log(__dirname, localeDir);
 const baseStr = fs.readFileSync(path.join(localeDir, 'en.yaml'), {
     encoding: 'utf8'
 });
diff --git a/source/index.ts b/source/index.ts
index db2ff4da294..346239d9d68 100644
--- a/source/index.ts
+++ b/source/index.ts
@@ -1,14 +1,13 @@
 import Telegraf from 'telegraf';
 import { fork } from 'child_process';
 import { join } from 'path';
-import * as pkg from '../package.json';
-import send from './utils/send';
-import logger from './utils/logger';
-import errors from './utils/errors';
-import i18n from './i18n';
-import { initDB } from './database';
+import send from './utils/send.js';
+import logger from './utils/logger.js';
+import errors from './utils/errors.js';
+import i18n from './i18n.js';
+import { initDB } from './database/index.js';
 import cleanStack from 'clean-stack';
-import { replyKeyboard, changeLangCallback } from './controlers/language';
+import { replyKeyboard, changeLangCallback } from './controlers/language.js';
 import {
     getUrlById,
     rss,
@@ -17,33 +16,36 @@ import {
     unsubAll,
     viewAll,
     getActiveFeedWithErrorCount
-} from './controlers/rss';
-import importReply from './controlers/import-reply';
-import { config } from './config';
-import agent from './utils/agent';
+} from './controlers/rss.js';
+import importReply from './controlers/import-reply.js';
+import { config } from './config.js';
+import agent from './utils/agent.js';
 const { token, view_all, lang, item_num, db_path, not_send } = config;
 
-import getUrl from './middlewares/get-url';
-import getUrlByTitle from './middlewares/get-url-by-title';
-import getFileLink from './middlewares/get-file-link';
-import sendError from './middlewares/send-error';
-import testUrl from './middlewares/test-url';
-import isAdmin from './middlewares/is-admin';
-import onlyPrivateChat from './middlewares/only-private-chat';
-import subMultiUrl from './middlewares/sub-multi-url';
-import exportToOpml from './middlewares/export-to-opml';
-import importFromOpml from './middlewares/import-from-opml';
-import userAllowList from './middlewares/user_allow_list';
+import getUrl from './middlewares/get-url.js';
+import getUrlByTitle from './middlewares/get-url-by-title.js';
+import getFileLink from './middlewares/get-file-link.js';
+import sendError from './middlewares/send-error.js';
+import testUrl from './middlewares/test-url.js';
+import isAdmin from './middlewares/is-admin.js';
+import onlyPrivateChat from './middlewares/only-private-chat.js';
+import subMultiUrl from './middlewares/sub-multi-url.js';
+import exportToOpml from './middlewares/export-to-opml.js';
+import importFromOpml from './middlewares/import-from-opml.js';
+import userAllowList from './middlewares/user_allow_list.js';
 import { MContext, Next } from './types/ctx';
-import twoKeyReply from './utils/two-key-reply';
+import twoKeyReply from './utils/two-key-reply.js';
 import {
     isChangeFeedUrl,
     isErrorMaxTime,
     isSuccess,
     Message
-} from './types/message';
-import { migrateUser } from './proxies/users';
-import { encodeUrl } from './utils/urlencode';
+} from './types/message.js';
+import { migrateUser } from './proxies/users.js';
+import { encodeUrl } from './utils/urlencode.js';
+import { createRequire } from 'module';
+const cjsRequire = createRequire(import.meta.url);
+const pkg = cjsRequire('../package.json');
 
 const bot = new Telegraf(token, {
     telegram: {
diff --git a/source/knexfile.ts b/source/knexfile.ts
index 7e66c33cac7..c214983ad7b 100644
--- a/source/knexfile.ts
+++ b/source/knexfile.ts
@@ -1,9 +1,8 @@
 /* eslint @typescript-eslint/explicit-module-boundary-types: 0 */
-import { config } from './config';
-import { parse } from 'url';
+import { config } from './config.js';
+import { parse, fileURLToPath } from 'url';
 import { Knex } from 'knex';
-import { join } from 'path';
-import logger from './utils/logger';
+import logger from './utils/logger.js';
 
 const parsed = parse(config.db_path);
 const isWindows = process && process.platform && process.platform === 'win32';
@@ -21,9 +20,11 @@ const knexConfig: Knex.Config = {
     client,
     connection: config.db_path,
     migrations: {
+        // @ts-expect-error esm
+        esm: true,
         tableName: 'knex_migrations',
         extension: 'ts',
-        directory: join(__dirname, 'migrations')
+        directory: fileURLToPath(new URL('migrations', import.meta.url))
     },
     pool: {
         min: 1,
diff --git a/source/middlewares/export-to-opml.ts b/source/middlewares/export-to-opml.ts
index 6fcf3cd8163..cc8152c27b0 100644
--- a/source/middlewares/export-to-opml.ts
+++ b/source/middlewares/export-to-opml.ts
@@ -1,12 +1,13 @@
-import { getSubscribedFeedsByUserId } from '../proxies/rss-feed';
-import errors from '../utils/errors';
 import * as path from 'path';
-import * as ejs from 'ejs';
 import * as fs from 'fs';
+import * as ejs from 'ejs';
+import { htmlEscape } from 'escape-goat';
+
+import { getSubscribedFeedsByUserId } from '../proxies/rss-feed.js';
+import errors from '../utils/errors.js';
+import { config } from '../config.js';
 import { MContext, Next } from '../types/ctx';
 import { Feed } from '../types/feed';
-import { config } from '../config';
-import { htmlEscape } from 'escape-goat';
 
 function readFilePromise(path: string): Promise<string> {
     return new Promise((resolve, reject) => {
diff --git a/source/middlewares/get-file-link.ts b/source/middlewares/get-file-link.ts
index 4610368f94e..6e08564d066 100644
--- a/source/middlewares/get-file-link.ts
+++ b/source/middlewares/get-file-link.ts
@@ -1,4 +1,4 @@
-import errors from '../utils/errors';
+import errors from '../utils/errors.js';
 import { MContext, Next } from '../types/ctx';
 
 export default async (ctx: MContext, next: Next): Promise<void> => {
diff --git a/source/middlewares/get-url-by-title.ts b/source/middlewares/get-url-by-title.ts
index 4db048d02b6..577ef23e315 100644
--- a/source/middlewares/get-url-by-title.ts
+++ b/source/middlewares/get-url-by-title.ts
@@ -1,8 +1,8 @@
 import { MContext, Next } from '../types/ctx';
 
-import { getFeedsByTitle } from '../proxies/rss-feed';
-import errors from '../utils/errors';
-import { decodeUrl } from '../utils/urlencode';
+import { getFeedsByTitle } from '../proxies/rss-feed.js';
+import errors from '../utils/errors.js';
+import { decodeUrl } from '../utils/urlencode.js';
 export default async (ctx: MContext, next: Next): Promise<void> => {
     const me = await ctx.telegram.getMe();
     const myId = me.id;
diff --git a/source/middlewares/get-url.ts b/source/middlewares/get-url.ts
index 0db42903bdb..258bb7acfb9 100644
--- a/source/middlewares/get-url.ts
+++ b/source/middlewares/get-url.ts
@@ -1,8 +1,8 @@
-import errors from '../utils/errors';
-import { getSubscribedFeedsByUserId } from '../proxies/rss-feed';
-import i18n from '../i18n';
+import errors from '../utils/errors.js';
+import { getSubscribedFeedsByUserId } from '../proxies/rss-feed.js';
+import i18n from '../i18n.js';
 import { MContext, Next } from '../types/ctx';
-import { decodeUrl } from '../utils/urlencode';
+import { decodeUrl } from '../utils/urlencode.js';
 
 export default async (ctx: MContext, next: Next): Promise<void> => {
     const { lang } = ctx.state;
diff --git a/source/middlewares/import-from-opml.ts b/source/middlewares/import-from-opml.ts
index 885562cb21c..5676974187d 100644
--- a/source/middlewares/import-from-opml.ts
+++ b/source/middlewares/import-from-opml.ts
@@ -1,9 +1,9 @@
 import { Outline } from '../types/outline';
-import got from '../utils/got';
+import got from '../utils/got.js';
 import { transform } from 'camaro';
-import errors from '../utils/errors';
-import { sub } from '../proxies/rss-feed';
-import i18n from '../i18n';
+import errors from '../utils/errors.js';
+import { sub } from '../proxies/rss-feed.js';
+import i18n from '../i18n.js';
 import { MContext, Next } from '../types/ctx';
 
 const getOutlines = async function (data: string): Promise<Outline[]> {
diff --git a/source/middlewares/is-admin.ts b/source/middlewares/is-admin.ts
index 34f3db419d6..bd182b1e1f3 100644
--- a/source/middlewares/is-admin.ts
+++ b/source/middlewares/is-admin.ts
@@ -1,9 +1,9 @@
-import { config } from '../config';
-import errors from '../utils/errors';
-import { getUserById, newUser } from '../proxies/users';
+import { config } from '../config.js';
+import errors from '../utils/errors.js';
+import { getUserById, newUser } from '../proxies/users.js';
 import { MContext, Next } from '../types/ctx';
 import { User } from 'telegraf/typings/telegram-types';
-import { isNone, Option } from '../types/option';
+import { isNone, Option } from '../types/option.js';
 import { User as DBUser } from '../types/user';
 
 /**
diff --git a/source/middlewares/only-private-chat.ts b/source/middlewares/only-private-chat.ts
index 48218b1935f..350b8adbd3d 100644
--- a/source/middlewares/only-private-chat.ts
+++ b/source/middlewares/only-private-chat.ts
@@ -1,4 +1,4 @@
-import errors from '../utils/errors';
+import errors from '../utils/errors.js';
 import { MContext, Next } from '../types/ctx';
 export default async (ctx: MContext, next: Next): Promise<void> => {
     ctx.state.chat = await ctx.getChat();
diff --git a/source/middlewares/send-error.ts b/source/middlewares/send-error.ts
index a8217e43b6c..d47d8ebf615 100644
--- a/source/middlewares/send-error.ts
+++ b/source/middlewares/send-error.ts
@@ -1,6 +1,6 @@
-import i18n from '../i18n';
-import logger from '../utils/logger';
-import errors from '../utils/errors';
+import i18n from '../i18n.js';
+import logger from '../utils/logger.js';
+import errors from '../utils/errors.js';
 import { MContext, Next } from '../types/ctx';
 
 export default async (ctx: MContext, next: Next): Promise<void> => {
diff --git a/source/middlewares/sub-multi-url.ts b/source/middlewares/sub-multi-url.ts
index 3d9300e11fe..0167038bc34 100644
--- a/source/middlewares/sub-multi-url.ts
+++ b/source/middlewares/sub-multi-url.ts
@@ -1,12 +1,12 @@
-import errors from '../utils/errors';
-import got from '../utils/got';
-import { getFeedByUrl, sub } from '../proxies/rss-feed';
-import i18n from '../i18n';
+import errors from '../utils/errors.js';
+import got from '../utils/got.js';
+import { getFeedByUrl, sub } from '../proxies/rss-feed.js';
+import i18n from '../i18n.js';
 import { MContext, Next } from '../types/ctx';
-import { isSome } from '../types/option';
+import { isSome } from '../types/option.js';
 import { Feed } from '../types/feed';
-import { parseString } from '../parser/parse';
-import { decodeUrl, encodeUrl } from '../utils/urlencode';
+import { parseString } from '../parser/parse.js';
+import { decodeUrl, encodeUrl } from '../utils/urlencode.js';
 
 export default async (ctx: MContext, next: Next): Promise<void> => {
     const urls = ctx.message.text.match(
diff --git a/source/middlewares/test-url.ts b/source/middlewares/test-url.ts
index f84942e10e5..acb55803ac6 100644
--- a/source/middlewares/test-url.ts
+++ b/source/middlewares/test-url.ts
@@ -1,12 +1,12 @@
-import got from '../utils/got';
-import { findFeed, isFeedValid } from '../utils/feed';
-import errors from '../utils/errors';
-import i18n from '../i18n';
-import { getFeedByUrl } from '../proxies/rss-feed';
+import got from '../utils/got.js';
+import { findFeed, isFeedValid } from '../utils/feed.js';
+import errors from '../utils/errors.js';
+import i18n from '../i18n.js';
+import { getFeedByUrl } from '../proxies/rss-feed.js';
 import { MContext, Next } from '../types/ctx';
-import { isNone, isSome } from '../types/option';
-import { parseString } from '../parser/parse';
-import { decodeUrl, encodeUrl } from '../utils/urlencode';
+import { isNone, isSome } from '../types/option.js';
+import { parseString } from '../parser/parse.js';
+import { decodeUrl, encodeUrl } from '../utils/urlencode.js';
 
 export default async (ctx: MContext, next: Next): Promise<void> => {
     const url = encodeUrl(ctx.state.feedUrl);
diff --git a/source/middlewares/user_allow_list.ts b/source/middlewares/user_allow_list.ts
index 13c90479391..b9a5b97a036 100644
--- a/source/middlewares/user_allow_list.ts
+++ b/source/middlewares/user_allow_list.ts
@@ -1,8 +1,8 @@
-import { config } from '../config';
-import i18n from '../i18n';
+import { config } from '../config.js';
+import i18n from '../i18n.js';
 import { MContext, Next } from '../types/ctx';
-import { getUserById } from '../proxies/users';
-import { isSome } from '../types/option';
+import { getUserById } from '../proxies/users.js';
+import { isSome } from '../types/option.js';
 
 export default async (ctx: MContext, next: Next): Promise<void> => {
     let id: number;
diff --git a/source/parser/parse.ts b/source/parser/parse.ts
index fed732fd99c..7701ae002ff 100644
--- a/source/parser/parse.ts
+++ b/source/parser/parse.ts
@@ -1,6 +1,6 @@
-import { detectFeed } from './detect-feed';
+import { detectFeed } from './detect-feed.js';
 import { transform } from 'camaro';
-import { atomTpl, rdfTpl, rss2Tpl } from './templates';
+import { atomTpl, rdfTpl, rss2Tpl } from './templates.js';
 export type TRSS = {
     version: string;
     title: string;
diff --git a/source/proxies/rss-feed.ts b/source/proxies/rss-feed.ts
index e30c0d5e098..413698d56bc 100644
--- a/source/proxies/rss-feed.ts
+++ b/source/proxies/rss-feed.ts
@@ -1,9 +1,9 @@
-import { db } from '../database';
-import errors from '../utils/errors';
+import { db } from '../database/index.js';
+import errors from '../utils/errors.js';
 import { Feed } from '../types/feed';
 import { Subscribe } from '../types/subscribe';
-import { isSome, Option, Optional, Some } from '../types/option';
-import { decodeUrl } from '../utils/urlencode';
+import { isSome, Option, Optional, Some } from '../types/option.js';
+import { decodeUrl } from '../utils/urlencode.js';
 
 export async function sub(
     userId: number,
diff --git a/source/proxies/subscribes.ts b/source/proxies/subscribes.ts
index 6e94450f30f..44565ee783f 100644
--- a/source/proxies/subscribes.ts
+++ b/source/proxies/subscribes.ts
@@ -1,6 +1,6 @@
-import errors from '../utils/errors';
+import errors from '../utils/errors.js';
 import { Subscribe } from '../types/subscribe';
-import { db } from '../database';
+import { db } from '../database/index.js';
 
 export async function getSubscribersByFeedId(
     feedId: number
diff --git a/source/proxies/users.ts b/source/proxies/users.ts
index f755587f670..611fcb3ca7c 100644
--- a/source/proxies/users.ts
+++ b/source/proxies/users.ts
@@ -1,8 +1,8 @@
-import errors from '../utils/errors';
+import errors from '../utils/errors.js';
 import { User } from '../types/user';
-import { Option, Optional } from '../types/option';
-import logger from '../utils/logger';
-import { db } from '../database';
+import { Option, Optional } from '../types/option.js';
+import logger from '../utils/logger.js';
+import { db } from '../database/index.js';
 
 export async function getUserById(id: number): Promise<Option<User>> {
     try {
diff --git a/source/utils/agent.ts b/source/utils/agent.ts
index 296c6585a2e..be3560e1452 100644
--- a/source/utils/agent.ts
+++ b/source/utils/agent.ts
@@ -1,4 +1,4 @@
-import { config } from '../config';
+import { config } from '../config.js';
 const { proxy } = config;
 import {
     httpOverHttp,
@@ -6,9 +6,10 @@ import {
     httpsOverHttp,
     httpsOverHttps
 } from 'tunnel';
-import { SocksProxyAgent } from 'socks-proxy-agent';
+import * as SocksProxyAgentNs from 'socks-proxy-agent';
 import { Agent as HttpAgent, AgentOptions } from 'http';
 import { Agent as HttpsAgent } from 'https';
+const { SocksProxyAgent } = SocksProxyAgentNs;
 type Agent = {
     http: HttpAgent;
     https: HttpsAgent;
diff --git a/source/utils/errors.ts b/source/utils/errors.ts
index 372d12805ab..f93e17fee58 100644
--- a/source/utils/errors.ts
+++ b/source/utils/errors.ts
@@ -1,6 +1,6 @@
-import i18n from '../i18n';
-import logger, { logDBError } from './logger';
-import { config } from '../config';
+import i18n from '../i18n.js';
+import logger, { logDBError } from './logger.js';
+import { config } from '../config.js';
 
 export class ControllableError extends Error {
     code: string;
diff --git a/source/utils/feed.ts b/source/utils/feed.ts
index 7bb61cf4167..655afbca742 100644
--- a/source/utils/feed.ts
+++ b/source/utils/feed.ts
@@ -1,7 +1,7 @@
-import { none, Option, Optional } from '../types/option';
-import { parseString, TRSS } from '../parser/parse';
+import { none, Option, Optional } from '../types/option.js';
+import { parseString, TRSS } from '../parser/parse.js';
 import { FeedItem } from '../types/feed';
-import hashFeed from './hash-feed';
+import hashFeed from './hash-feed.js';
 
 export async function isFeedValid(feedStr: string): Promise<Option<TRSS>> {
     try {
diff --git a/source/utils/fetch.ts b/source/utils/fetch.ts
index cf1936b8283..38c22b2c1db 100644
--- a/source/utils/fetch.ts
+++ b/source/utils/fetch.ts
@@ -1,14 +1,14 @@
 import * as fs from 'fs';
 import * as path from 'path';
-import got from '../utils/got';
+import got from '../utils/got.js';
 import fastQueue from 'fastq';
 import { RecurrenceRule, scheduleJob } from 'node-schedule';
 import logger, { logHttpError } from './logger';
-import { findFeed, getNewItems } from './feed';
-import { config } from '../config';
+import { findFeed, getNewItems } from './feed.js';
+import { config } from '../config.js';
 import { Feed, FeedItem } from '../types/feed';
-import { Optional, Option, isNone, none, isSome } from '../types/option';
-import { parseString } from '../parser/parse';
+import { Optional, Option, isNone, none, isSome } from '../types/option.js';
+import { parseString } from '../parser/parse.js';
 import {
     getAllFeeds,
     updateHashList,
diff --git a/source/utils/got.ts b/source/utils/got.ts
index 1c6d7c15ae2..423db8af2d2 100644
--- a/source/utils/got.ts
+++ b/source/utils/got.ts
@@ -1,7 +1,6 @@
-import got from 'got-iconv';
-import { Got } from 'got-iconv';
-import { config } from '../config';
-import agent from './agent';
+import got, { Got } from 'got-iconv';
+import { config } from '../config.js';
+import agent from './agent.js';
 import QuickLru from 'quick-lru';
 const AcceptHeader =
     'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 ';
diff --git a/source/utils/send.ts b/source/utils/send.ts
index 5ae96e1da10..44e1dda9f4b 100644
--- a/source/utils/send.ts
+++ b/source/utils/send.ts
@@ -1,16 +1,16 @@
 import {
     getSubscribersByFeedId,
     deleteSubscribersByUserId
-} from '../proxies/subscribes';
-import logger from './logger';
-import sanitize from './sanitize';
-import { config } from '../config';
+} from '../proxies/subscribes.js';
+import logger from './logger.js';
+import sanitize from './sanitize.js';
+import { config } from '../config.js';
 import Telegraf, { Context } from 'telegraf';
 import { Feed, FeedItem } from '../types/feed';
-import { getUserById, migrateUser } from '../proxies/users';
-import { isNone, isSome } from '../types/option';
+import { getUserById, migrateUser } from '../proxies/users.js';
+import { isNone, isSome } from '../types/option.js';
 import * as ejs from 'ejs';
-import i18n from '../i18n';
+import i18n from '../i18n.js';
 
 /**
  * handle send error log or delete user or migrate user
diff --git a/tsconfig.json b/tsconfig.json
index fe720e2b322..33e77d4606a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,8 +1,8 @@
 {
     "compilerOptions": {
-        "outDir": "dist",
-        "target": "es2019", // Node.js 12
-        "module": "commonjs",
+        "outDir": "dist/source",
+        "target": "es2020", // Node.js 12
+        "module": "esnext",
         "moduleResolution": "node",
         "resolveJsonModule": true,
         "declaration": false,
@@ -18,7 +18,8 @@
         "useDefineForClassFields": true,
         "forceConsistentCasingInFileNames": true,
         "skipLibCheck": true,
-        "esModuleInterop": true
+        "esModuleInterop": true,
+        "allowSyntheticDefaultImports": true
     },
     "include": ["source"]
 }