setup
Method
The The setup
method provides a reliable way to import local files and share reusable functions across multiple commands.
This method works differently depending on the environment:
In development mode, it is run each time the target
execute
function is executed, making sure that the dynamically imported files are always updated.In production mode, the method is evaluated once on startup and injected into the
execute
function, which saves resources and ensures your bot runs with minimal overhead.
How it works
The function itself isn't special, all it does is get called and Chooksie then passes the return value as the this
context of the target execute
function.
The reason why this particular function exists is because of Module Caching by Node. If you have imports in the top-level, those imports will only get updated when you reload the entire file, which means that when you update a local file, you'll also have to reupdate all other files that imports that file.
In development mode, the framework detects when a file is saved and refreshes Node's module cache. This ensures that each time the relevant commands are executed and the setup
function called, it is able to provide the latest modules, allowing you to rerun the command in your Discord client without having to touch the command again.
Working smartly this way instead of making setup
just trash the module cache each time makes it so that calling the function results in minimal overhead (since it just reuses the cache and doesn't have to evaluate the module) and ensures that there are no discrepancies between development and production mode.
Usage
Before we start dynamically importing files, we first need to create the file to import.
In this example, we'll be making a utilities file that manipulates strings. We begin by exporting a function that changes a text's case to uppercase.
TIP
You can place these files inside any directory as long as the directory is not in use by the framework and does not start with a dot.
// utils/string.ts
export function upper(text: string) {
return text.toUpperCase()
}
// utils/string.js
export function upper(text) {
return text.toUpperCase()
}
// utils/string.js
exports.upper = text => {
return text.toUpperCase()
}
Now in our command where we want to use these functions, we can define a function that returns our imported module.
TIP
You can inline the function if you don't need to reuse it, use async
functions if you wish to use await
, or even return something else from the module you import!
Just keep in mind that whatever you return, be it a function, a value, or even a Promise
, it will be the value of this
in your execute
function, so you can be as creative as you want!
// subcommands/setup.ts
import { defineOption, defineSlashSubcommand, defineSubcommand } from 'chooksie'
function strings() {
return import('../utils/string')
}
const stringOption = defineOption({
name: 'text',
description: 'The text to transform.',
type: 'STRING',
required: true,
})
export default defineSlashSubcommand({
name: 'string',
description: 'Change a string\'s case.',
options: [
defineSubcommand({
name: 'upper',
description: 'Transform a text to be all uppercase.',
type: 'SUB_COMMAND',
setup: strings,
async execute(ctx) {
const targetText = ctx.interaction.options.getString('text', true)
await ctx.interaction.reply(this.upper(targetText))
},
options: [stringOption],
}),
],
})
// subcommands/setup.js
import { defineOption, defineSlashSubcommand, defineSubcommand } from 'chooksie'
function strings() {
return import('../utils/string')
}
const stringOption = defineOption({
name: 'text',
description: 'The text to transform.',
type: 'STRING',
required: true,
})
export default defineSlashSubcommand({
name: 'string',
description: 'Change a string\'s case.',
options: [
defineSubcommand({
name: 'upper',
description: 'Transform a text to be all uppercase.',
type: 'SUB_COMMAND',
setup: strings,
async execute(ctx) {
const targetText = ctx.interaction.options.getString('text', true)
await ctx.interaction.reply(this.upper(targetText))
},
options: [stringOption],
}),
],
})
// subcommands/setup.js
const { defineOption, defineSlashSubcommand, defineSubcommand } = require('chooksie')
function strings() {
return require('../utils/string')
}
const stringOption = defineOption({
name: 'text',
description: 'The text to transform.',
type: 'STRING',
required: true,
})
module.exports = defineSlashSubcommand({
name: 'string',
description: 'Change a string\'s case.',
options: [
defineSubcommand({
name: 'upper',
description: 'Transform a text to be all uppercase.',
type: 'SUB_COMMAND',
setup: strings,
async execute(ctx) {
const targetText = ctx.interaction.options.getString('text', true)
await ctx.interaction.reply(this.upper(targetText))
},
options: [stringOption],
}),
],
})
Running the command /string upper
should now reply with the text in all lowercase!
Going back in our utilities file, we can export another function that transforms a text to be all uppercase:
// utils/string.ts
export function upper(text: string) {
return text.toUpperCase()
}
export function lower(text: string) {
return text.toLowerCase()
}
// utils/string.js
export function upper(text) {
return text.toUpperCase()
}
export function lower(text) {
return text.toLowerCase()
}
// utils/string.js
exports.upper = text => {
return text.toUpperCase()
}
exports.lower = text => {
return text.toLowerCase()
}
Saving that file and heading to our command file, define a new subcommand that uses the new function.
// subcommands/setup.ts
import { defineOption, defineSlashSubcommand, defineSubcommand } from 'chooksie'
function strings() {
return import('../utils/string')
}
const stringOption = defineOption({
name: 'text',
description: 'The text to transform.',
type: 'STRING',
required: true,
})
export default defineSlashSubcommand({
name: 'string',
description: 'Change a string\'s case.',
options: [
defineSubcommand({
name: 'upper',
description: 'Transform a text to be all uppercase.',
type: 'SUB_COMMAND',
setup: strings,
async execute(ctx) {
const targetText = ctx.interaction.options.getString('text', true)
await ctx.interaction.reply(this.upper(targetText))
},
options: [stringOption],
}),
defineSubcommand({
name: 'lower',
description: 'Transform a text to be all lowercase.',
type: 'SUB_COMMAND',
setup: strings,
async execute(ctx) {
const targetText = ctx.interaction.options.getString('text', true)
await ctx.interaction.reply(this.lower(targetText))
},
options: [stringOption],
}),
],
})
// subcommands/setup.js
import { defineOption, defineSlashSubcommand, defineSubcommand } from 'chooksie'
function strings() {
return import('../utils/string')
}
const stringOption = defineOption({
name: 'text',
description: 'The text to transform.',
type: 'STRING',
required: true,
})
export default defineSlashSubcommand({
name: 'string',
description: 'Change a string\'s case.',
options: [
defineSubcommand({
name: 'upper',
description: 'Transform a text to be all uppercase.',
type: 'SUB_COMMAND',
setup: strings,
async execute(ctx) {
const targetText = ctx.interaction.options.getString('text', true)
await ctx.interaction.reply(this.upper(targetText))
},
options: [stringOption],
}),
defineSubcommand({
name: 'lower',
description: 'Transform a text to be all lowercase.',
type: 'SUB_COMMAND',
setup: strings,
async execute(ctx) {
const targetText = ctx.interaction.options.getString('text', true)
await ctx.interaction.reply(this.lower(targetText))
},
options: [stringOption],
}),
],
})
// subcommands/setup.js
const { defineOption, defineSlashSubcommand, defineSubcommand } = require('chooksie')
function strings() {
return require('../utils/string')
}
const stringOption = defineOption({
name: 'text',
description: 'The text to transform.',
type: 'STRING',
required: true,
})
module.exports = defineSlashSubcommand({
name: 'string',
description: 'Change a string\'s case.',
options: [
defineSubcommand({
name: 'upper',
description: 'Transform a text to be all uppercase.',
type: 'SUB_COMMAND',
setup: strings,
async execute(ctx) {
const targetText = ctx.interaction.options.getString('text', true)
await ctx.interaction.reply(this.upper(targetText))
},
options: [stringOption],
}),
defineSubcommand({
name: 'lower',
description: 'Transform a text to be all lowercase.',
type: 'SUB_COMMAND',
setup: strings,
async execute(ctx) {
const targetText = ctx.interaction.options.getString('text', true)
await ctx.interaction.reply(this.lower(targetText))
},
options: [stringOption],
}),
],
})
While writing the command, you may have noticed that typing this
in the execute
function already gives suggestions (assuming you use an editor that supports it) that both upper
and lower
exists.
Now saving the file, a new command called /string lower
should be available in your Discord client, and using it should reply back with a text that's in uppercase!
You've just created a new utility file, made a new command and used the new file, and run the command in your Discord client, all without having to reload or restart your bot!