Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript - Execute code in files without importing them

Is there any way I can run the code in files without importing them in TypeScript (Angular project)? Maybe by specifying a file pattern through some config in tsconfig?

What I'd like to do is register some classes into a global object, but I don't want to maintain the list of classes in a single file. I'd rather have a simple line of code under each class definition like so:

export class MyClassToRegister {...}

GlobalRegistry.register(MyClassToRegister);

I understand this code will get executed when the file gets imported, but sometimes that's just too late. Any on how this can be achieved?

like image 458
Simon Corcos Avatar asked Feb 23 '26 07:02

Simon Corcos


2 Answers

Yes, by using Webpack's require.context(...) you can import a directory of files into your bundle.

Quick note: you're still importing the files into the bundle, but you just don't have to statically define every import path or manually have to keep them up-to-date if you add/remove files.

File structure:

Let's work with this example file structure:

src/
    items/
        item1.ts
        item2.ts
        item3.ts
    registry.ts
    index.ts

Here are the items we're going to require from a directory:

//item1.ts, item2.ts, item3.ts
import GlobalRegistry from "../registry";

export class Item1 {
    //...
}
GlobalRegistry.register(Item1);

The loaded items will register themselves with this service (or whatever your business logic is) - this proves that the items are being loaded:

//registry.ts
export default class GlobalRegistry {

    static _items = [];

    static register(cls){
        console.log('Register class: ', cls.name);
        this._items.push(cls.name);
    }

    static getItems(){
        return this._items;
    }

}

require.context(...)

Use require.context(...) to require all files under the 'items' directory:

//index.ts
import GlobalRegistry from './registry';

// Import all files under './items/*.ts'
var context = require.context('./items', true, /\.ts$/);
context.keys().forEach((key) => {
    context(key);
});

console.log('Loaded classes:', GlobalRegistry.getItems());

Finally, to keep TypeScript happy we declare the require.context() interface provided by Webpack:

//references.d.ts

// Extend @types/node NodeRequire type to define Webpack's context function
declare interface NodeRequire {
    context: (dir: string, includeSubdirs: boolean, filter: RegExp) => any;
}

// Tell TypeScript that there is a global `require` variable available to us
declare var require: NodeRequire;

Result:

When the application runs you should see this logged out:

Register class:  Item1
Register class:  Item2
Register class:  Item3
Loaded classes: (3) ["Item1", "Item2", "Item3"]

Notes:

1. Order of includes The order of includes is not guaranteed if you reference individual classes first.

For example, if you explicitly import a type and use it as a value then that type will be loaded before others that are only included via require.context(...).

Example - using Item2 as a value:

//index.ts

/* require.context(...) stuff is here */

import {Item2} from './items/Item2';
let myItem = new Item2();   // use Item2 as a value

Changes the load order:

Register class:  Item2
Register class:  Item1
Register class:  Item3
Loaded classes: (3) ["Item2", "Item1", "Item3"]

But note that just referencing by type (and not by value) will not change the load order

let myItem: Item2;  // just referencing by type won't affect load order

2. Require function dependencies warning

You might get a warning during build like: require function is used in a way in which dependencies cannot be statically extracted.

There isn't necessarily anything wrong with that, it's just Webpack letting you know that you're doing something funky with require - which is true :)

Doing this dynamic require might affect tree-shaking or other static analysis of your bundle (i.e. those types can't be excluded from the bundle if they're not used). But it might be a fair trade-off by not having to manually manage your file imports - you'll have to evaluate against your own requirements/project goals.

like image 95
Sly_cardinal Avatar answered Feb 24 '26 20:02

Sly_cardinal


One solution would be to use multi-file namespaces which allow splitting code into multiple files without having to import modules.

Here is a simplified example taken from the TypeScript documentation on namespaces.

Validation.ts file

namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
}

LettersOnlyValidator.ts file

namespace Validation {
    const lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
}
like image 37
Jesse Johnson Avatar answered Feb 24 '26 21:02

Jesse Johnson



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!