-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add support for native ESM imports, make repository ESM-first #2409
Conversation
🦋 Changeset detectedLatest commit: cb13b1c The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
a92f704
to
f078517
Compare
packages/renderer/package.json
Outdated
"main": "./lib/react-pdf.cjs.js", | ||
"module": "./lib/react-pdf.es.js", | ||
"exports": { | ||
".": { | ||
"import": "./lib/react-pdf.es.js", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: we also need to add types
there, as types are not siblings of the outputted JS files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would this affect backwards compatibility? I know some module resolvers do not work with exports yet right? @wojtekmaj
@wojtekmaj would be great to add this. Can you rebase? 🙏🏻 |
110d4e5
to
989a64b
Compare
@diegomura There you go! |
This comment was marked as outdated.
This comment was marked as outdated.
@diegomura Resolved Jest issue, but I'm not entirely happy with it (of course, since Jest can't handle ESM properly). However, I'll have Vitest migration ready to go for you as soon as this one gets merged :D |
Thanks @wojtekmaj ! Can't check in full detail now but I'll soon. In the meantime, do you mind explaining the benefits of moving in this direction? I get the benefits of ESM but my worry is this will cause issues for lots of consumers using old bundlers or node versions. |
Absolutely! My tree of thoughts on why we should move towards ESM-first.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK
All look good! Left some small questions to answer before I merge but I'll merge immediately after. Thanks for the work @wojtekmaj
package.json
Outdated
@@ -61,12 +62,14 @@ | |||
"jest-image-snapshot": "^6.1.0", | |||
"lerna": "^8.0.2", | |||
"lint-staged": "^10.5.4", | |||
"node-gyp": "^10.0.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, works without it directly declared, but please mind, it's gonna stay in our deps anyway. I can see it was added in #2478.
|
||
import setAlignSelf from '../../src/node/setAlignSelf'; | ||
|
||
// yoga-layout sets default export using non-standard __esModule property, so we need to | ||
// make an additional check in case it's used in a bundler that does not support it. | ||
const Yoga = 'default' in yogaModule ? yogaModule.default : yogaModule; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we extract this in a layout/yoga.js
file? We can capture it and I can do it separately. It might not be necessary either as I'd like to experiment with async yoga which I think it's more performant and uses wasm build
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, defining that 35 times was idiotic :D Fixed that.
I don't think move to wasm will change anything here though.
import { basename, extname } from 'path'; | ||
import { parse } from '../afm'; | ||
// This file is ran directly with Node - needs to have .js extension |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we always need .js extension with ESM?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes and no! The key word in this comment is "directly".
In most cases, react-pdf is pre-bundled. When bundled, module resolution is already done by the bundler and these rules no longer apply.
I WOULD do this if I were you sooner or later, see "Adding file extensions in select places" in original post for rationale.
@@ -3,26 +3,33 @@ | |||
"version": "3.1.17", | |||
"license": "MIT", | |||
"description": "Create PDF files on the browser and server", | |||
"types": "index.d.ts", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not only no longer needed, but actively harmful for type resolution in TypeScript. Instead, each bundled .js file gets its own .d.ts sibling file. This makes the bundle larger, unfortunately, but this is the reason we'll be seeing a vast improvement on arethetypeswrong after this PR.
Read more: https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseESM.md
This is good to hear. I was thinking if would be convenient to otherwise do a major bump. Recent yoga-layout bump included library using |
I genuinely believe we can do this without the major bump. I successfully migrated more than 30 OSS repositories of my own to ESM-first in a minor bump and received 0 complaints. Granted, they were much less complex than react-pdf, but I now know a thing or two, and worry not - if anything goes south, I'll have your back while bugfixing :) |
…es for each outputted file separately
Thanks for addressing all my questions and the work done here! |
…iegomura#2409) * Set "type": "module" and add "exports" * Convert Babel config to ESM * Add our own node-gyp so that canvas can be built * Fix parse:afm command, remove useless babel-node * Convert Jest config and CJS Jest tests to ESM * Convert ESLint config to JSON * Allow extraneous dependencies in test files * Change .size-limit config extension to .cjs * Fix build * Add changeset * Add types * Fix faux ESM yoga-layout import * Make ESLint happy * Remove unnecessary eslint-disable * Fix broken imports in browserify-zlib See browserify/browserify-zlib#45 * Fix Jest command? * Use .cjs and .js instead of .cjs.js and .es.js respectively, copy types for each outputted file separately * Remove direct node-gyp dependency * Move Yoga hack to a separate file
Overview
Fixes #2068
Converts the repository to be ESM first. Ensures that every package published has
"type": "module" and set "exports"
. As a natural consequence, tools like ESLint, Jest are running in ESM mode too.With these changes, I'm able to import and use react-pdf in ESM Node.js projects, and build it with esbuild too.
Details
Setting
"type": "module"
and"exports"
Before this PR,
package.json
s containedmain
andmodule
entires. The former is universal, the latter - kinda-supported-by-most-bundlers-maybe way to point them to ESM modules. But this does not work with Node.js. To make Node.js aware of the ESM version,"type": "module"
and"exports"
must be set.Note: We may want to push this further by introducing
.cjs
extensions, or use/dist/cjs
and/dist/esm
directories, each with their own package.json with"type": "module"
/"commonjs"
.Adding file extensions in select places
There were 4 files that appear to be running in Node without bundling or any other help that would handle extensionless module resolution. For the whole repository to work with Node.js in ESM mode, I had to add
.js
to 6 imports.Note: We may want to push this further by adding all required extensions everywhere. This PR includes the bare minimum to make react-pdf work with ESM imports. In modern Node.js, you are actually required to include extensions in your imports. However, this would mean I would need to change hundreds of files at once, which would make it unlikely for this PR to ever get merged. It's quite large anyway...
Configuration
Due to Jest changes (see below),
jest/globals
were removed from ESLintenv
config. Because of@jest/globals
import, an exemption fromimport/no-extraneous-dependencies
was also added for unit tests.Tooling
Babel
By setting
"type": "module"
,module.exports
no longer worked inbabel.config.js
. This had to be changed toexport default
.ESLint
Similarly to Babel,
module.exports
was no longer an option. However, ESLint in your repository didn't want to play nicely with ESM either, so I opted for pure JSON instead.Jest
By far most significant change in this PR.
jest
toyarn node --experimental-vm-modules \"$(yarn bin jest)\"
.require()
syntax, which didn't work anymore.jest
global is not available, tojest
had to be explicitly imported from@jest/globals
instead.__dirname
is not available, sopath.dirname(url.fileURLToPath(import.meta.url))
"polyfill" was used instead.Additional notes
Note: I was forced to add updated node-gyp to our devDependencies for the project to even install correctly, in 4c0fbcd. I may split it into a separate PR if you want.
Note: While I'm quite confident this setup works very well in ESM Node.js, CJS was not verified in any way. It would be a good idea to test it out in legacy Node.js environment before merging.
Note: The PR is currently failing due to an issue in external dependency, browserify-zlib. I have raised a PR to fix it: browserify/browserify-zlib#45