#how do i convert my js disc bot to ts
59 messages · Page 1 of 1 (latest)
@mighty valve Depends on your existing process - but if you add a tsconfig with allowJs you can mix JS and TS files while migrating to TS.
I recommend strict: true, allowJs: true, checkJs: false for migration config.
heres mines
{
"compilerOptions": {
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. /
"module": "commonjs", / Specify what module code is generated. /
"outDir": "./dist", / Specify an output folder for all emitted files. /
"esModuleInterop": true, / Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. /
"forceConsistentCasingInFileNames": true, / Ensure that casing is correct in imports. /
"strict": true, / Enable all strict type-checking options. /
"skipLibCheck": true, / Skip type checking all .d.ts files. */
"allowJs": true
}
}
so do i rename my .js files to .ts?
You may want to make sure checkJs: false is on; maybe that's the default but otherwise TS will start complaining about existing JS files.
And yeah, then you change a file to .ts -> fix the errors that the compiler raises, mostly by adding type annotations -> repeat.
It helps to start from the "leaves", the files that don't depend on other files.
is there a faster way to convert the code to ts?
There are various automated approaches that end up with IMO kinda mediocre results
ok then ill do it myself
const path = require('path');
const getAllFiles = require('../utils/getAllFiles');
module.exports = (client: { on: (arg0: any, arg1: (arg: any) => Promise<void>) => void; }) => {
const eventFolders = getAllFiles(path.join(__dirname, '..', 'events'), true);
for (const eventFolder of eventFolders) {
let eventFiles = getAllFiles(eventFolder);
eventFiles.sort();
const eventName = eventFolder.replace(/\\/g, '/').split('/').pop();
client.on(eventName, async (arg) => {
for (const eventFile of eventFiles) {
const eventFunction = require(eventFile);
await eventFunction(client, arg);
}
});
}
};
js code converted to ts
is this what i should be doing?
You should switch require code to import
"module": "commonJs" in your tsconfig means it'll compile back to require code, but TS won't understand the types of things you require; they're just any.
like this?
import { path } from 'path';
I think it's just import path from 'path' or import { join } from 'path'.
ok
import path from "path";
import getAllFiles from "../utils/getAllFiles";
export default (client: { on: (arg0: any, arg1: (arg: any) => Promise<void>) => void; }) => {
Though honestly, what I'd actually do here is:
import fooEvent from "../events/FooEvent";
import barEvent from "../events/BarEvent";
const events = [fooEvent, barEvent];
!*:discordjs-dynamic-imports
The discord.js docs recommend using fs to dynamically load and register resources (e.g. commands) within a specific folder - but this is more complex than necessary and has some footguns associated with it.
A very common issue is whether the file will have a .ts or .js extension, depending on whether the code is compiled to JS or run directly (e.g. with ts-node or tsx).
And TS does not know the contents of dynamically imported files, so you have to cast the imports (or use any) and you can have errors where the code in the file doesn't match the type you cast it to.
Instead, I recommend a static list of imports:
import FooCommand from "./commands/foo.js";
import BooCommand from "./commands/bar.js";
export const commands = [FooCommand, BarCommand];
Yes, it's 'boilerplate' but it's very simple, usually much less code than dynamic fs stuff, and TS can properly type-check it.
Or for an alternative structure for organizing your bot, you can check out the TS Community Discord bot: https://github.com/typescript-community/community-bot which uses a 'modules' organization.
getallfiles.ts
import fs from "fs";
import path from "path";
export default (directory: any, foldersOnly = false) => {
let fileNames = [];
const files = fs.readdirSync(directory, { withFileTypes: true });
for (const file of files) {
const filePath = path.join(directory, file.name);
if (foldersOnly) {
if (file.isDirectory()) {
fileNames.push(filePath);
}
} else {
if (file.isFile()) {
fileNames.push(filePath);
}
}
}
return fileNames;
};
did i convert it right?
@mighty valve Looks reasonable, though you want to try to minimize any usage. directory: any probably can just be directory: string.
Actually, I gues it has to be a string. fs.readdirSync technically accepts non-strings as its first argument (e.g. buffers and urls) but path.join only accepts strings
gives me TypeError: getAllFiles is not a function when i run the bot
what happened here
You probably do have to find the places where you're doing require("./getAllFiles") and either change it to require("./getAllFiles").default or else change it to an import downstream.
I though esModuleInterop might handle that, but maybe not. It's been awhile since I did a CJS to ESM code migration.
oh ok
found out its happening from getLocalCommands.js
got an error
Argument of type 'any' is not assignable to parameter of type 'never'.
import path from "path";
import getAllFiles from "./getAllFiles";
export default (exceptions = []) => {
let localCommands = [];
const commandCategories = getAllFiles(
path.join(__dirname, '..', 'commands'),
true
);
for (const commandCategory of commandCategories) {
const commandFiles = getAllFiles(commandCategory);
for (const commandFile of commandFiles) {
const commandObject = require(commandFile);
if (exceptions.includes(commandObject.name)) {
continue;
}
localCommands.push(commandObject);
}
}
coming from line 20 where it says if (exceptions.includes(commandObject.name)) {
You have to tell TS what type exceptions is.
(exceptions: string[] = []) => {
//...
}
Generally you have to tell TS the type of empty arrays, but TS can infer based on push usage for locally declared variables:
let x = [];
x.push("foo");
x // inferred as string[] due to the push type
... but this doesn't work for function params.
Argument of type 'string | any[]' is not assignable to parameter of type 'any[]'.
Type 'string' is not assignable to type 'any[]'.
arecommandsdifferent.ts
export default (existingCommand: { description: any; options: string | any[]; }, localCommand: { description: any; options: string | any[]; }) => {
const areChoicesDifferent = (existingChoices: any[], localChoices: any) => {
for (const localChoice of localChoices) {
const existingChoice = existingChoices?.find(
(choice: { name: any; }) => choice.name === localChoice.name
);
if (!existingChoice) {
return true;
}
if (localChoice.value !== existingChoice.value) {
return true;
}
}
return false;
};
const areOptionsDifferent = (existingOptions: any[], localOptions: any) => {
for (const localOption of localOptions) {
const existingOption = existingOptions?.find(
(option: { name: any; }) => option.name === localOption.name
);
if (!existingOption) {
return true;
}
if (
localOption.description !== existingOption.description ||
localOption.type !== existingOption.type ||
(localOption.required || false) !== existingOption.required ||
(localOption.choices?.length || 0) !==
(existingOption.choices?.length || 0) ||
areChoicesDifferent(
localOption.choices || [],
existingOption.choices || []
)
) {
return true;
}
}
return false;
};
if (
existingCommand.description !== localCommand.description ||
existingCommand.options?.length !== (localCommand.options?.length || 0) ||
areOptionsDifferent(existingCommand.options, localCommand.options || [])
) {
return true;
}
return false;
};
Yeah, areOptionsDifferent says it only accepts arrays, you're potentially passing it a string.
Either you need to update areOptionsDifferent to accept strings, handle the case where existingCommand.options is a string, or change the types of existingCommand.options to not include string.
and also this
Expected 2 arguments, but got 1.
import areCommandsDifferent from "../../utils/areCommandsDifferent";
import getApplicationCommands from "../../utils/getApplicationCommands";
import getLocalCommands from "../../utils/getLocalCommands";
export default async (client: any) => {
try {
const localCommands = getLocalCommands();
const applicationCommands = await getApplicationCommands(
client,
// testServer
);
from const applicationCommands = await getApplicationCommands(
should i do await getApplicationCommands(
client, undefined)
because i dont need testserver
You can use ? in the definition of the function if the argumenet is supposed to be optional
function foo(x: string, y?: string) {}
fixed both already
and also
Cannot find module '../../../config.json'. Consider using '--resolveJsonModule' to import module with '.json' extension.
import { devs, testServer } from "../../../config.json"
wtf happened
Consider using '--resolveJsonModule' to import module with '.json' extension
i got a problem: TypeError: eventFunction is not a function