Writing a Custom Module
A module is a build-time extension defined using defineGwenModule() from @gwenjs/kit. While plugins handle runtime behavior, modules configure your GWEN project during the build process (gwen dev, gwen build, gwen prepare).
The Basics
Minimal Module
The simplest module just provides metadata:
import { defineGwenModule } from '@gwenjs/kit/module'
export default defineGwenModule({
meta: {
name: '@my-scope/my-module',
configKey: 'myModule',
version: '1.0.0',
},
setup(options, gwen) {
// Module setup runs during build
console.log('Building with options:', options)
},
})Module with Plugin Registration
Most modules register one or more runtime plugins:
import { defineGwenModule } from '@gwenjs/kit/module'
import { definePlugin } from '@gwenjs/kit/plugin'
const MyPlugin = definePlugin(() => ({
name: 'my-plugin',
setup(engine) {
engine.provide('myService', {
greet: () => 'Hello from plugin!',
})
},
}))
export default defineGwenModule({
meta: { name: '@my-scope/my-module', configKey: 'myModule' },
setup(options, gwen) {
// Export the plugin to be registered at runtime
gwen.addPlugin(MyPlugin())
},
})Module with Options
Modules can accept typed options through the config:
interface MyModuleOptions {
debug?: boolean
apiUrl?: string
}
export default defineGwenModule<MyModuleOptions>({
meta: {
name: '@my-scope/my-module',
configKey: 'myModule',
},
defaults: {
debug: false,
apiUrl: 'https://api.example.com',
},
setup(options, gwen) {
console.log(`Debug mode: ${options.debug}`)
console.log(`API URL: ${options.apiUrl}`)
},
})Register in gwen.config.ts:
import { defineConfig } from '@gwenjs/app'
export default defineConfig({
modules: [['@my-scope/my-module', {
debug: true,
apiUrl: 'https://dev.api.example.com',
}]],
})Build-Time API (GwenKit)
The gwen parameter passed to setup() is the build-time API. Use it to configure plugins, auto-imports, Vite extensions, and type templates.
Adding Plugins
Register runtime plugins to be loaded:
gwen.addPlugin(MyPlugin())Auto-Imports
Register composables and utilities that auto-import into game code without explicit import statements:
gwen.addAutoImports([
{ name: 'useMyService', from: '@my-scope/my-module' },
{ name: 'MyHelper', from: '@my-scope/my-module', as: 'Helper' },
])In game code, useMyService is available without an import:
// No import needed!
import { defineSystem, onUpdate } from '@gwenjs/core/system'
export const MySystem = defineSystem(function MySystem() {
const service = useMyService()
onUpdate(() => { /* ... */ })
})Vite Extensions
Extend the Vite build configuration:
gwen.extendViteConfig((config) => ({
resolve: {
alias: {
'~assets': '/src/assets',
},
},
}))Add a Vite plugin:
gwen.addVitePlugin({
name: 'my-vite-plugin',
transform(code) {
return code.replace(/MY_CONSTANT/g, '"replaced"')
},
})Type Templates
Generate TypeScript declaration files for IDE auto-complete and type checking:
gwen.addTypeTemplate({
filename: 'types/my-service.d.ts',
getContents() {
return `declare module '@gwenjs/core' {
interface GwenProvides {
myService: MyServiceAPI
}
}`
},
})Module Augmentation
Add TypeScript declarations inline without creating a separate file:
gwen.addModuleAugment(`
declare module '@gwenjs/core' {
interface GwenProvides {
myService: { greet(): string }
}
}
`)Real-World Example: Score Module
Here's a complete module that provides a score tracking system:
import { defineGwenModule } from '@gwenjs/kit/module'
import { definePlugin } from '@gwenjs/kit/plugin'
interface ScoreModuleOptions {
initialScore?: number
maxScore?: number
}
// Runtime plugin
const ScorePlugin = definePlugin<ScoreModuleOptions>((opts = {}) => ({
name: 'score',
setup(engine) {
let score = opts.initialScore ?? 0
const maxScore = opts.maxScore ?? 999999
engine.provide('score', {
get: () => score,
add: (amount: number) => {
score = Math.min(score + amount, maxScore)
},
set: (value: number) => {
score = Math.max(0, Math.min(value, maxScore))
},
reset: () => {
score = opts.initialScore ?? 0
},
})
},
}))
// Build-time module
export default defineGwenModule<ScoreModuleOptions>({
meta: {
name: '@my-scope/score',
configKey: 'score',
},
defaults: {
initialScore: 0,
maxScore: 999999,
},
setup(options, gwen) {
gwen.addPlugin(ScorePlugin(options))
gwen.addAutoImports([
{ name: 'useScore', from: '@my-scope/score' },
])
gwen.addModuleAugment(`
declare module '@gwenjs/core' {
interface GwenProvides {
score: {
get(): number
add(amount: number): void
set(value: number): void
reset(): void
}
}
}
`)
},
})Use the score system in a game system:
import { defineSystem, useService, onUpdate } from '@gwenjs/core/system'
export const ScoreDisplaySystem = defineSystem(function ScoreDisplaySystem() {
const scoreService = useService('score')
onUpdate(() => {
const currentScore = scoreService.get()
// Render score on screen
})
})
// Or use auto-import
export const RewardSystem = defineSystem(function RewardSystem() {
const score = useScore()
onUpdate(() => {
if (playerCollectedCoin) {
score.add(10)
}
})
})Build Hooks
Modules can subscribe to build-time events:
gwen.hook('build:before', () => {
console.log('Build starting...')
})
gwen.hook('module:before', (mod) => {
console.log(`Setting up module: ${mod.meta.name}`)
})
gwen.hook('module:done', (mod) => {
console.log(`Finished module: ${mod.meta.name}`)
})
gwen.hook('build:done', () => {
console.log('Build complete!')
})
gwen.hook('vite:extendConfig', (config) => {
console.log('Vite config was extended')
})Module in Project
Register the module in gwen.config.ts:
import { defineConfig } from '@gwenjs/app'
export default defineConfig({
modules: [['@my-scope/score', {
initialScore: 0,
maxScore: 9999,
}]],
})API Summary
defineGwenModule
Create a build-time module:
export default defineGwenModule<Options>({
meta: {
name: string
configKey?: string
version?: string
}
defaults?: DeepPartial<Options>
setup(options: Options, gwen: GwenKit): void | Promise<void>
})GwenKit Methods
| Method | Purpose |
|---|---|
addPlugin(plugin) | Register a runtime plugin |
addAutoImports(imports) | Declare auto-imported utilities |
addVitePlugin(plugin) | Add a Vite plugin to the build |
extendViteConfig(extender) | Extend Vite configuration |
addTypeTemplate(template) | Generate .d.ts files |
addModuleAugment(snippet) | Add TypeScript declarations inline |
hook(event, fn) | Subscribe to build-time events |
options (property) | Access resolved config options |
AutoImport
interface AutoImport {
name: string // Export name from the module
from: string // NPM package or path
as?: string // Override name in auto-import
}GwenTypeTemplate
interface GwenTypeTemplate {
filename: string // Path inside `.gwen/`, e.g. 'types/my-service.d.ts'
getContents(): string // Content factory called during `gwen prepare`
}