Configure package exports for browser build
See original GitHub issueI’ve been trying out the browser build @antfu added recently.
There is a slight issue in that we only specify the following in package.json
:
"main": "dist/index.js",
"module": "dist/index.esm.js",
which isn’t necessarily true. index.esm.js
is valid ESM within nodejs. there is a separate index.browser.mjs
which is valid ESM for browsers.
however, rollup and most other tools will resolve to the node one because of the module
field in the package manifest.
im not entirely sure how we resolve this as thats the correct behaviour for node, but not for browsers.
its true we could just tell browser consumers to directly import index.browser.mjs
but we then need to alter the typescript declarations somehow to strongly type imports from that path…
import {Highlighter} from 'shiki'; // works
import {Highlighter} from 'shiki/dist/index.browser.mjs'; // doesn't work, TS has no clue what this file is as there's no index.browser.d.ts
Issue Analytics
- State:
- Created 3 years ago
- Comments:6 (4 by maintainers)
Top Results From Across the Web
Package exports - webpack
The exports field in the package.json of a package allows to declare which module should be used when using module requests like import...
Read more >Node.JS (New) Package.json Exports Field | by Thomas Juster
The exports field (or “export map”) provides a way to expose your package modules for different environments and JavaScript flavors WHILE ...
Read more >Modules: Packages | Node.js v19.3.0 Documentation
For new packages targeting the currently supported versions of Node.js, the "exports" field is recommended. For packages supporting Node.js 10 and below, the...
Read more >How to universally export an NPM module (for the Browser)?
create a simplified module returnExport that has no dependency (function ... like Node. module.exports = factory(); } else { // Browser ...
Read more >how to build modular applications with browserify - GitHub
Here is a handy configuration for using watchify and browserify with the package.json "scripts" field: { "build": "browserify browser.js -o ...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
This is a two part.
First is exports resolution which I couldn’t find docs for it in the browsers realm, but here are docs for node side of things. You can employ conditioning with
exports
which I will give you an example of at the end.Second is typescript resolution - typescript doesn’t care about how files in your package are looking like in the file system. Generally it needs to find declaration files (
d.ts
files) for every single thing that it imports. You can stub that behavior with wildcard shims ie:but that’s not going to work on the library end, because it would basically allow end user to import stuff that’s not here.
What you want to do on the library end is to do a clever hacking technique with usage of
typesVersion
that you can see here: https://github.com/microsoft/TypeScript/issues/33079#issuecomment-702617758 I can propose better package.json config so your typescript (and potentially browser) is going to be happier and better at resolution:This will allow you to do import in browser and if nested condition work in the ESM environment in browser it will grab correct file, and in other browser env it will default to iife. Alternatively you can do
shiki/browser
now that will for ensure you are importingbrowser.mjs
file and correctly identify typescript typings because oftypesVersions
. You can add this locally and test for yourself in the monorepo, it actually correctly resolves typescript definitions. Hopefully that was helpful, but feel free to correct me on minor details.I can also create PR for that change so people can control what kind of import they want and have better defaulting.
EDIT: spelling mostly
@octref the difference is interop. Node’s implementation of ESM comes with a boat load of interop & backwards compatibility with commonjs.
If we ever have a dependency anywhere in the dependency tree which is not ESM, it means the entire package (shiki) becomes incompatible with browsers. Meanwhile, Node will happily import it because of the interop layer their module system contains which understands how to import commonjs modules.
For example:
https://github.com/shikijs/shiki/blob/3c596f1c97f8a6af0ad672a9f83bea64f55959c5/packages/shiki/src/loader.ts#L1
This line in an ES module may correctly resolve via import maps (needed for bare specifiers), but the file it resolves to is this:
https://github.com/NeekSandhu/onigasm/blob/82c56c383334b0c502e805d8940e4b745b67eda2/package.json#L5
Which specifies only
main
, at this file:https://unpkg.com/onigasm@2.2.5/lib/index.js
As you can see, it is a commonjs module and it doesn’t appear there is an es module equivalent.
At this point, the browser would fall over and complain that
exports
isn’t a thing (tried to set a property onundefined
).To get around this, there are basically two options:
Obviously option 1 is the perfect place to be, but very difficult because the entire dependency tree has to be ESM. You would have to get onigasm, vscode-textmate and any of their dependencies to publish ESM before shiki can work in the browser “as is”.
Btw technically i think the browser bundle would also work in node.