#Export/import types and values using default vs namespace while still being able to extend exports

44 messages · Page 1 of 1 (latest)

vestal cypress
#

Hello everyone, I was running into a problem recently that I somehow can't figure out how to solve:

I created a module called "lib" which exports a function and a type:

// lib/index.ts

export type Foo = {
  bar: string;
};

export const foo = { bar: "baz" };

I then import this module in another file and try to use the type and the function:

// src/index.ts
import { Foo, foo } from "../lib";

So far, this works fine. However the import is pretty verbose and I would like to make my life easier and import the type and the function in one go and access it with a single name:

// (1) import everything using a default export
import Lib from `../lib`;

type alias = Lib.Foo;
const moreFoo = Lib.foo;


// (2) import a namespace
import { Lib } from `../lib`;

type alias = Lib.Foo;
const moreFoo = Lib.foo;


// (3) import everything using a named export
import * as Lib from `../lib`;

type alias = Lib.Foo;
const moreFoo = Lib.foo;

v3 works, but I don't like the * as syntax; this would be my last resort if i can't find a way to make v1 or v2 work.
To make v1 or v2 work obviously I have to change my module to export a default (v1) or a namespace (v2) instead of exporting the type and the function directly:

// lib/index.ts (v1: use default export)
type Foo = {
  bar: string;
};

const foo = { bar: "baz" };

export default {
  Foo, // this will not work: Foo is a type and can't be used a value
  foo
};
// lib/index.ts (v2: use namespace)

export namespace Lib {
    export type Foo = {
        bar: string;
    };

    export const foo = { bar: "baz" };
}

In v1 I can't figure out how to default export a type as well as an object. Is that possible?
In v2 I have a bit more complicated problem: while my module is meant for the frontend there is also an extension for the backend (some database stuff). The backend extension is in a separate file and I would like to export the namespace in the main file and extend it in the backend file. However, I can't figure out how to extend the namespace in the backend file. Is that possible?

I am looking for something like this:

// lib/server.ts

import { Lib } from "./index";

export namespace Lib {
    // "inherit" the namespace Tag from './index'
    export const additionalFoo = "bar"
}

So here the problem is that I can't figure out how to extend the namespace Lib with the extension only being present when importing import { Lib } from "./server" and not when importing import { Lib } from "./index".

To get to my question -- is there a way to be able to import my whole module (type and function) without using import * as Lib... and still be able to access it like so:

import { Lib } from `../lib/index`; // or whatever way I should use to import the module
type alias = Lib.Foo;
const moreFoo = Lib.foo;
const evenMoreFoo = Lib.additionalFoo; // this should error here since it's only available when importing from server file

// server file
import { Lib } from `../lib/server`; // or whatever way I should use to import the module
type alias = Lib.Foo;
const moreFoo = Lib.foo;
const evenMoreFoo = Lib.additionalFoo; // this should work here

Is my way to use namespaces? but then how can I "extend" the namespace for the server file?
Or should I go with default exports which I can easily extend in the server file but for which I can't figure out how to export a type and a value at the same time?
Or is there a completely different way to do this?

low current
#

v1 is just directly possible.

#

v3 is the built-in way though, so i'd recommend that

#

you won't have any sync issues since you only need a single source of truth

#

and other devs including your future self will be able to understand it without any prior knowledge about the repo

vestal cypress
#

I somehow can't figure out how to export a type and a value as default :/

#

that somehow stops me from using v1

#

How would I do this:

export default {
  foo, // this is a value
  Bar  // this is a type
}
low current
#

oh you can't do types since they don't exist

#

you would need a ts namespace or esm namespace for that

vestal cypress
#

I would go for the namespace solution, but then I can't figure out how to extend it. I need something like this:

export namespace Lib {
    export const DB = allDBStuff
    export * from './index'
}
low current
#

im kinda confused what you mean by "extend" to begin with

#

in the original case, without the default, would you have been doing a barrel export from the frontend version?

vestal cypress
#

I wanted to basically keep my interface the same no matter if you import lib/index or lib/server. However if you import lib/server then it a new property would show up (DB in this example)

#

I was hoping i can simply import the old interface from index in my server.ts and then "spread" it onto a fresh interface where I also add my new stuff.

#

if it was an object, I would do something like:

export const Lib = { ...LibFromIndex, newProp }
low current
#

im not sure how to do that with ts namespaces, sorry

#

but it Just Works™️ with esm namespaces

#

with a barrel export like i mentioned before

vestal cypress
#

that's the import * as Foo from Bar?

low current
#

that's a namespace import

#

a barrel export is export * from "module" which re-exports all the exports from module

#

(i don't remember if it's all exports or all the named exports though, sorry)

vestal cypress
#

Yep, I mean, it works. Maybe I should not be so picky. After all i jump through all this hassle just to make the import look -- imo -- nicer 😄

#

maybe it's just not worth the time.

#

I keep this open for now, mayeb someone else has an idea. But thanks so far.

low current
vestal cypress
#

true. But most of my imports are in the form import X from Y and then are used like X.doSomething; so my OCD triggers when all of the sudden I have a import * as X from Y. I really don't have another reason I guess. 😄

low current
#

you could have another file that does a namespace export

vestal cypress
#

how do you mean?

low current
#

it'd just be hiding the problem away, but i mean...

low current
vestal cypress
#

so I'd have a namespace definition in a file named index-def.ts and then reexport that namespace in 'index.ts'?

low current
#

you wouldn't have a namespace definition anywhere

#

namespace X is a ts namespace, * as X is an esm namespace

vestal cypress
#

Oh my god, just tried it! you are right, this works

#

I feel like jumping through hoops up and down and all, but it does what I want 🙂

low current
#

module.ts:

export const x = 0;
export type X = number;
````module.barrel.ts`:
```ts
export * as XNs from "./module.js"
```usage:
```ts
import { x, X } from "./module.js"
import * as XNs from "./module.js"
import { XNs } from "./module.barrel.js"
vestal cypress
#

jup, works like a charm

#

I take it!

#

Thanks a bunch!

#

obviously I can also make it the default now 😄

export * as Lib from 'exports.ts'
export * as default from 'exports.ts'
#

2 birds, 1 stone.

#

Thanks again.