Closed Marcel-G closed 1 year ago
Part of my goal with this is to avoid having to write conditionals in exports
as much as possible, in favor of conventions that capture the differences and make it easier for me to know what I'm editing when I'm looking at a filename without having to constantly check with package.json
at dev time.
Since you're already using *.browser.js
as the filename here, what about just using that as the switch?
Ie, if you have foo.browser.ts
or foo.browser.mts
, and also a corresponding foo.ts
or foo.mts
, and src/foo.ts
or src/foo.mts
is exported, it could just note that there's a browser version, and use that instead?
Then you'd do this:
# files
src/esm-only.mts
src/esm-only.browser.mts
src/index.ts
src/index.browser.ts
{
"tshy": {
"exports": {
"./foo": "./src/esm-only.mts",
".": "./src/index.ts"
},
"exports": {
"./foo": {
"browser": {
"types": "./dist/esm/esm-only.browser.d.mts",
"default": "./dist/esm/esm-only.browser.mjs"
},
"import": {
"types": "./dist/esm/esm-only.d.mts",
"default": "./dist/esm/esm-only.mjs"
},
".": {
"browser": {
"types": "./dist/esm/index.browser.d.ts",
"default": "./dist/esm/index.browser.js"
},
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/commonjs/index.d.ts",
"default": "./dist/commonjs/index.js"
}
}
}
}
}
Another alternative would be to treat browser
as a dialect, like esm
and commonjs
, which builds as ESM into ./dist/browser/...
, and allows browser-specific overrides named blah-browser.mts
just like how blah-cjs.cts
overrides blah.ts
in the commonjs build. But I think most of the time, the browser and esm builds are pretty much identical, so it's probably better to not have a third entire copy of the lib?
If a fourth "thing" shows up, it's probably better to make it configurable in some way, but I think having browser
as a blessed option makes sense. There aren't that many different conditions that people actually use in exports.
Ie, if you have
foo.browser.ts
orfoo.browser.mts
, and also a correspondingfoo.ts
orfoo.mts
, andsrc/foo.ts
orsrc/foo.mts
is exported, it could just note that there's a browser version, and use that instead?
Thanks for your help, I'll try this out and get back to you.
If a fourth "thing" shows up, it's probably better to make it configurable in some way, but I think having
browser
as a blessed option makes sense. There aren't that many different conditions that people actually use in exports.
As I understand from the docs the nested conditions allow an extra branching level to be introduced for environment specific entrypoints browser
, node
and default
. It would feel a bit odd to mix module resolution (import/require) with environment branching. However my interpretation could be wrong here
Hmmmmm.... deno
.
Probably it's only a matter of time before there's a bun
conditional export, if there isn't already. So maybe this should be a conditional thing? Plus webpack defines a whole bunch of these things
What about something like this?
# files
./src/index.ts
./src/index.deno.ts
./src/index.browser.ts
{
"tshy": {
"main": false,
"exports": { ".": "./src/index.ts" },
"esmDialects": ["browser", "deno"]
},
"exports": {
".": {
"browser": {
"default": "./dist/esm/index.browser.js",
"types": "./dist/esm/index.browser.d.ts"
},
"deno": {
"default": "./dist/esm/index.deno.js",
"types": "./dist/esm/index.deno.d.ts"
},
"import": {
"default": "./dist/esm/index.js",
"types": "./dist/esm/index.d.ts"
},
"require": {
"default": "./dist/commonjs/index.js",
"types": "./dist/commonjs/index.d.ts"
}
}
}
}
So the logic is:
{file}.{ext}
in exports
x
in tshy.config.esmDialects
file.${x}.${ext}
exists in files
, set as exports[subpath][x]
file.${ext}
as exports.import[x]
If these were nested under the import
and require
top-level conditions, it'd be a little cleaner, and also support having a commonjsDialects
list as well. Eg:
# files
./src/index.ts
./src/index.browser.ts
./src/index.deno.ts
{
"tshy": {
"main": false,
"exports": { ".": "./src/index.ts" },
"esmDialects": ["browser", "deno"],
"commonjsDialects": ["browser"]
},
"exports": {
".": {
"import": {
"browser": {
"default": "./dist/esm/index.browser.js",
"types": "./dist/esm/index.browser.d.ts"
},
"deno": {
"default": "./dist/esm/index.deno.js",
"types": "./dist/esm/index.deno.d.ts"
},
"default": "./dist/esm/index.js",
"types": "./dist/esm/index.d.ts"
},
"require": {
"browser": {
"types": "./dist/commonjs/index.browser.d.ts",
"default": "./dist/commonjs/index.browser.js"
},
"default": "./dist/commonjs/index.js",
"types": "./dist/commonjs/index.d.ts"
}
}
}
}
Ok, not going to get clever with nesting, it makes things too complicated. Just going to make the dialect/override behavior generalized.
2 new optional fields added, commonjsDialects
and esmDialects
. These will be any arbitrary string, other than 'esm'
, 'commonjs'
, 'cjs'
, 'require'
, 'import'
, 'node'
or 'default'
, all for obvious reasons, and must not have any strings in common if both set.
{
"tshy": {
"esmDialects": ["deno", "browser"],
"commonjsDialects": ["whatever"],
}
}
This will build dist/deno
and dist/browser
(along with dist/esm
) as ESM, and dist/whatever
(along with dist/commonjs
) as CommonJS.
A file like src/foo-deno.mts
will override src/foo.ts
in the deno build, and be excluded from all the other builds (same with all the others).
Conditional exports can be defined using nested-conditions to specify different entrypoints for
browser
,node
anddefault
for example. It would be nice if these could be expressed usingtshy
- as far as I can tell this is not yet supported.Example Config
Expected Output
Current Behaviour