Open hyrious opened 6 months ago
Note: for the first problem -- multiple instances. A solution in TypeScript is to only export types that having no JS entities. For example, do not export a class, but export the interface and a function isInstanceOfType
to support checking types.
export interface Dog { bark(): void }
class DogImpl implements Dog { bark() {} }
export function createDog(): Dog { return new DocImpl }
export function isDog(a: any): a is Dog {
return a && typeof a.bark == 'function'
}
Interfaces can be compatible with each other even in different versions of one package, while classes can not because they may refer to different classes that having the same name. That is to say, classes are bound to its file path while interfaces are not.
Dual CommonJS/ES module packages are a way to publish both ESM and CJS library in one package. This is a very common practice for many modern packages but I found it sometimes sucks in many ways. For which in the end I may just prefer Pure ESM.
Instance hazards. As described in the Node.js docs, your global variables will become 2 copies. If in some cases the user is importing both CJS and ESM versions of the package, they conflict with each other. For example
mobx
uses a globalSymbol()
to mark objects as observable. For example react hooks store all states in a global array which directly causes you cannot use 2 copies of react in the same project. (However it's funny that you CAN use multiple reacts in one project, only has to make sure not using any hooks, i.e. only use class-style components). This is not a big issue since years later we are more writing pure packages.TypeScript Definitions. TypeScript simply resolves type definitions by changing the file extensions (if not using
typesVersions
to hack this process). What that means is you need 2 dts files that have almost the same contents to make this work:This is what tsup and codemirror finally choose. I don't like it.
In fact as I mentioned above, you can hack into the resolving process and get a cleaner dist folder with such config (from
value-enhancer
):ESM behaviors differ between Node.js and Babel. Modern bundlers choose different behaviors by looking at file extensions (does it end with '.mjs'?) and
{type: module}
in package.json. This is not a big problem since all these issues happen on the default export. Just use named exports and you're done.If the author wants the most compatibility with older bundlers and CJS only libraries. This is definitely a pain. In fact because those bundlers may have weird behavior on .mjs extensions (you see see you, webpack), all the author could do is maybe not using .mjs extension at all (the same with
{type: module}
). Your ESM entry files needs to use .es.js file extension or something like that.In summary, it is maybe only painless in the below cases to use dual packages:
index.d.mts
in your dist folder, or having a hack in package.json.