open-draft / dotalias

A single configuration for path aliases to reuse across all your tools (TypeScript, Jest, webpack, Rollup, etc.).
91 stars 2 forks source link

Programmatic Interface #9

Open moltar opened 2 years ago

moltar commented 2 years ago

Would be great to have a programmatic interface to integrate into other projects.

I'd like to use it with projen.

kettanaito commented 2 years ago

Hey, @moltar. That's an interesting idea. Do you have some imaginary API in mind that we could expose to help you implement this?

Internally, this library just maps a single declaration file to multiple sources (jest, webpack, etc.). All mapping functions can be described as:

function map(config: AliasConfig): any

But as I understand, you want to use some sort of API so that dotailas would know about your custom mapping and then expose you the result?

I've looked at projen, and it looks like higher-level tooling. You should be able to use dotalias with any frameworks/project generators without issues, as dotalias exposes you configs to insert into the individual tools right away.

moltar commented 2 years ago

Do you have some imaginary API in mind that we could expose to help you implement this?

The original API is almost fine, I think.

The only issue is that it will throw if it can't find the source paths.

https://github.com/open-draft/dotalias/blob/45d6f438cf908d8ab058e1b2fcc969f16db93061/src/index.ts#L10-L12

So, maybe alias can be a fn that accepts paths, or defaults to discovering them.


alias({ foo: '../foo' }).jest
alias({ foo: '../foo' }).WebpackPlugin
// ...

I've looked at projen, and it looks like higher-level tooling. You should be able to use dotalias with any frameworks/project generators without issues, as dotalias exposes you configs to insert into the individual tools right away.

Right, almost there.

The only thing is that projen does not commit the config files to the filesystem, until all configs are synth'ed in memory.

We can access the config in memory (tsconfig object) during synth, and I could pass it to alias. But currently alias assumes that it will read the files from the filesystem.

I am proposing that alias should be able to accept existing path config as a parameter without reading anything from disk.

kettanaito commented 2 years ago

Thanks for the examples! It's much more clear now.

I think we can create a new config API which would act as you've described:

  1. Accept an explicit configuration of paths;
  2. Produce the alias-compatible API, exposing the generated configurations for Jest, webpack, etc.
import { config } from 'dotalias'

const alias = config({
  server: '../packages/server/'
})

alias.jest
alias.WebpackPlugin

It's important we keep the actual usage identical to the one you get with using alias that reads the paths from the configuration files in the file system.

One question I have is how should the config behave if there is a .alias configuration file in your project? Should it merge two configs, giving the one you pass to config() the priority? Should it replace the config?

moltar commented 2 years ago

IMO in programmatic use case everything should be explicit without any side effects, e.g. no reading from disk.

kettanaito commented 2 years ago

Yeap, in favor with you on that 👍

Nice, I think we have the API worked out. Would you be interested in opening a pull request with the draft implementation? I could use your help with this.

moltar commented 2 years ago

Certainly can try.

One thing I am not sure of, is how to handle this part:

https://github.com/open-draft/dotalias/blob/45d6f438cf908d8ab058e1b2fcc969f16db93061/src/index.ts#L10-L12

Since this is in the global scope, this will always throw in the programmatic use case, and it's kind of impossible to hide it now, since index exports alias that is already resolved.

kettanaito commented 2 years ago

We can start by moving what's currently in index.ts to alias.ts. The index module will then point to both alias and new configure:

src/
  index.ts
  alias.ts
  configure.ts

This won't solve the global-scope configuration check just yet. We have two options:

Make alias a function, so that reading configuration happens when it's invoked.

Pros:

Cons:

Use a Proxy around the alias object.

This only retain the current API but still requires some sort of memorization of the configuration so we don't read it multiple times.

Expose alias as an explicit import.

As in:

import { alias } from 'dotalias/alias'

This can also be dotalias/runtime or similar.

This way we scope the config reading to that explicit import, instead of exporting alias from the root-level index.ts.

Actually, I think this may be a good solution for config:

import { config } from 'dotalias/config'

config({ ... })

I'd recommend this option. The config module can be built separately and required to be imported directly, circumventing all the logic from alias.

What do you think?

moltar commented 2 years ago

Sure, I was considering all these options too, but wasn't sure if breaking change was an option.

Not a huge fan of:

import { config } from 'dotalias/config'

But I think that might be the best option in this case.

Similar patterns exist already, e.g. see:dotenv/config.

moltar commented 2 years ago

Another thought...

Might be beneficial to expose programmatic API for each converter independently.

The downside is to that approach is that it exposes (right now) internal API, and then makes it harder to move away from it later.

But the benefit is that if you only need one converter, then you don't need to call all other converters to get the full alias object back.

Thoughts?

kettanaito commented 2 years ago

So basically make toJest and other converters public?

moltar commented 2 years ago

Yeah.

Because, for example, for our use case, we have no use for Webpack, so it's just extra CPU cycles to create an instance.

kettanaito commented 2 years ago

Then I suppose it's a replacement for config(), is that correct?

I can see both APIs being useful but I'd start with the one that immediately covers your use case.

I'm all hands for the following thing:

import { toRollup, toJest } from 'alias/config'

const config = { foo: '../bar/foo' }

const aliasOptions = toRollup(config)
const jestMapperOptions = toJest(config)

Would that be the functionality you're looking for?

By the way, I don't think there's particular trouble exporting the internal converters. I'd pretty much like to do so if it helps people achieve their goals. Converters are not complex and implement the same call signature.

moltar commented 2 years ago
import { toRollup, toJest } from 'alias/config'

Yup, that's what we are looking for!