-
Notifications
You must be signed in to change notification settings - Fork 61
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
exports
field and worker bundles
#435
Conversation
🦋 Changeset detectedLatest commit: e73af13 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
Update: I have used this version of preconstruct and made the appropriate changes to the Emotion monorepo and then built a super simple react + emotion site and deployed to Cloudflare workers and it works! |
packages/cli/src/build/config.ts
Outdated
{ | ||
format: "cjs" as const, | ||
entryFileNames: "[name].worker.cjs.js", | ||
chunkFileNames: "dist/[name]-[hash].worker.cjs.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.
q: is a CJS file even needed for a worker target? Can't we assume that a consumer is able to understand 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.
The same argument could be made for browser target so I added this to be consistent. IMO, this is more about bundler preference than the target's preference.
I'm happy to take it out though if you feel like it is not 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.
I agree with your sentiment - it's just that the browser field has been created in the past, where the tooling's state was different. I assume that for the newly introduced exports
we can start from a clean slate - I don't feel super strong about it though, my bundlers should just never pick up CJS files for those and that's it. I just wonder if we can skip generating those for perf reasons and to spare some disk space etc.
Let's wait for @mitchellhamilton's opinion
This is awesome - thank you very much for working on this. I've left some open-ended questions - if you are any opinions of your own regarding those topics, please share them. We'll also have to wait for @mitchellhamilton's opinion about these. The topic is very delicate and we might not be able to merge this overnight - but this PR is a great start for making this a reality. |
I added support for "production" and "development" package exports conditionals. I also added the I ran this on the {
"name": "@emotion/react",
"version": "11.7.0",
"main": "dist/emotion-react.cjs.js",
"module": "dist/emotion-react.esm.js",
"browser": {
"./dist/emotion-react.cjs.js": "./dist/emotion-react.browser.cjs.js",
"./dist/emotion-react.esm.js": "./dist/emotion-react.browser.esm.js"
},
"exports": {
"./package.json": "./package.json",
".": {
"worker": {
"production": {
"module": "./dist/emotion-react.worker.esm.prod.js",
"default": "./dist/emotion-react.worker.cjs.prod.js"
},
"development": {
"module": "./dist/emotion-react.worker.esm.dev.js",
"default": "./dist/emotion-react.worker.cjs.dev.js"
},
"module": "./dist/emotion-react.worker.esm.js",
"default": "./dist/emotion-react.worker.cjs.js"
},
"browser": {
"production": {
"module": "./dist/emotion-react.browser.esm.prod.js",
"default": "./dist/emotion-react.browser.cjs.prod.js"
},
"development": {
"module": "./dist/emotion-react.browser.esm.dev.js",
"default": "./dist/emotion-react.browser.cjs.dev.js"
},
"module": "./dist/emotion-react.browser.esm.js",
"default": "./dist/emotion-react.browser.cjs.js"
},
"production": {
"module": "./dist/emotion-react.esm.prod.js",
"default": "./dist/emotion-react.cjs.prod.js"
},
"development": {
"module": "./dist/emotion-react.esm.dev.js",
"default": "./dist/emotion-react.cjs.dev.js"
},
"module": "./dist/emotion-react.esm.js",
"default": "./dist/emotion-react.cjs.js"
},
"./jsx-runtime": {
"worker": {
"production": {
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.worker.esm.prod.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.worker.cjs.prod.js"
},
"development": {
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.worker.esm.dev.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.worker.cjs.dev.js"
},
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.worker.esm.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.worker.cjs.js"
},
"browser": {
"production": {
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.browser.esm.prod.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.browser.cjs.prod.js"
},
"development": {
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.browser.esm.dev.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.browser.cjs.dev.js"
},
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.browser.esm.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.browser.cjs.js"
},
"production": {
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.esm.prod.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.cjs.prod.js"
},
"development": {
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.esm.dev.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.cjs.dev.js"
},
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.esm.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.cjs.js"
},
"./jsx-dev-runtime": {
"worker": {
"production": {
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.worker.esm.prod.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.worker.cjs.prod.js"
},
"development": {
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.worker.esm.dev.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.worker.cjs.dev.js"
},
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.worker.esm.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.worker.cjs.js"
},
"browser": {
"production": {
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.browser.esm.prod.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.browser.cjs.prod.js"
},
"development": {
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.browser.esm.dev.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.browser.cjs.dev.js"
},
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.browser.esm.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.browser.cjs.js"
},
"production": {
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.esm.prod.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.cjs.prod.js"
},
"development": {
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.esm.dev.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.cjs.dev.js"
},
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.esm.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.cjs.js"
},
"./_isolated-hnrs": {
"worker": {
"production": {
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.worker.esm.prod.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.worker.cjs.prod.js"
},
"development": {
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.worker.esm.dev.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.worker.cjs.dev.js"
},
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.worker.esm.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.worker.cjs.js"
},
"browser": {
"production": {
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.browser.esm.prod.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.browser.cjs.prod.js"
},
"development": {
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.browser.esm.dev.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.browser.cjs.dev.js"
},
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.browser.esm.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.browser.cjs.js"
},
"production": {
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.esm.prod.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.cjs.prod.js"
},
"development": {
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.esm.dev.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.cjs.dev.js"
},
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.esm.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.cjs.js"
},
"./": "./"
},
"types": "types/index.d.ts",
"files": [
"src",
"dist",
"jsx-runtime",
"jsx-dev-runtime",
"_isolated-hnrs",
"types/*.d.ts",
"macro.js",
"macro.d.ts",
"macro.js.flow"
],
"sideEffects": true,
"author": "mitchellhamilton <[email protected]>",
"license": "MIT",
"scripts": {
"test:typescript": "dtslint types"
},
"dependencies": {
"@babel/runtime": "^7.13.10",
"@emotion/cache": "^11.6.0",
"@emotion/serialize": "^1.0.2",
"@emotion/sheet": "^1.1.0",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"hoist-non-react-statics": "^3.3.1"
},
"peerDependencies": {
"@babel/core": "^7.0.0",
"react": ">=16.8.0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"@types/react": {
"optional": true
}
},
"devDependencies": {
"@babel/core": "^7.13.10",
"@emotion/css": "11.5.0",
"@emotion/css-prettifier": "1.0.0",
"@emotion/server": "11.4.0",
"@emotion/styled": "11.6.0",
"@types/react": "^16.9.11",
"dtslint": "^0.3.0",
"html-tag-names": "^1.1.2",
"react": "^17.0.2",
"svg-tag-names": "^1.1.1"
},
"repository": "https://github.com/emotion-js/emotion/tree/main/packages/react",
"publishConfig": {
"access": "public"
},
"umd:main": "dist/emotion-react.umd.min.js",
"preconstruct": {
"entrypoints": [
"./index.js",
"./jsx-runtime.js",
"./jsx-dev-runtime.js",
"./_isolated-hnrs.js"
],
"umdName": "emotionReact"
}
}
Draft PR to emotion repo can be view here: emotion-js/emotion#2589 |
I think we could minimize the output a little bit, like illustrated on this example {
"exports": {
".": {
"worker": {
- "production": {
- "module": "./dist/emotion-react.worker.esm.prod.js",
- "default": "./dist/emotion-react.worker.cjs.prod.js"
- },
"development": {
"module": "./dist/emotion-react.worker.esm.dev.js",
"default": "./dist/emotion-react.worker.cjs.dev.js"
},
- "module": "./dist/emotion-react.worker.esm.js",
+ "module": "./dist/emotion-react.worker.esm.prod.js",
- "default": "./dist/emotion-react.worker.cjs.js"
+ "default": "./dist/emotion-react.worker.cjs.prod.js"
}
}
}
} This output makes production bundles to be chosen by default (as sort of a "safe" option) while making development bundles opt-in. And there is that open question if we even need CJS bundles for browser/worker conditions. One thing that absolutely has to be checked before publishing this is to check if this behaves correctly with the current TS version and with the upcoming TS versions that will support As we, most likely, would like to use Emotion as a testbed for this - we might need the whole entrypoints extensions for the MVP, this stuff is mentioned here. Otherwise, imports like
This actually shouldn't be available publicly - but such "private" entrypoints sound like a super niche thing. So this is probably not worth solving anytime soon anyway. |
@Andarist I have made package exports support strictly "opt-in" using the API you describe in #432. This includes support for the "extra" option to add arbitrary items to the package exports field when generating them. Here is what {
"name": "@emotion/react",
"version": "11.7.0",
"main": "dist/emotion-react.cjs.js",
"module": "dist/emotion-react.esm.js",
"browser": {
"./dist/emotion-react.cjs.js": "./dist/emotion-react.browser.cjs.js",
"./dist/emotion-react.esm.js": "./dist/emotion-react.browser.esm.js"
},
"exports": {
"./package.json": "./package.json",
".": {
"worker": {
"production": {
"module": "./dist/emotion-react.worker.esm.prod.js",
"default": "./dist/emotion-react.worker.cjs.prod.js"
},
"development": {
"module": "./dist/emotion-react.worker.esm.dev.js",
"default": "./dist/emotion-react.worker.cjs.dev.js"
},
"module": "./dist/emotion-react.worker.esm.js",
"default": "./dist/emotion-react.worker.cjs.js"
},
"browser": {
"production": {
"module": "./dist/emotion-react.browser.esm.prod.js",
"default": "./dist/emotion-react.browser.cjs.prod.js"
},
"development": {
"module": "./dist/emotion-react.browser.esm.dev.js",
"default": "./dist/emotion-react.browser.cjs.dev.js"
},
"module": "./dist/emotion-react.browser.esm.js",
"default": "./dist/emotion-react.browser.cjs.js"
},
"production": {
"module": "./dist/emotion-react.esm.prod.js",
"default": "./dist/emotion-react.cjs.prod.js"
},
"development": {
"module": "./dist/emotion-react.esm.dev.js",
"default": "./dist/emotion-react.cjs.dev.js"
},
"module": "./dist/emotion-react.esm.js",
"default": "./dist/emotion-react.cjs.js"
},
"./jsx-runtime": {
"worker": {
"production": {
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.worker.esm.prod.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.worker.cjs.prod.js"
},
"development": {
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.worker.esm.dev.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.worker.cjs.dev.js"
},
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.worker.esm.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.worker.cjs.js"
},
"browser": {
"production": {
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.browser.esm.prod.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.browser.cjs.prod.js"
},
"development": {
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.browser.esm.dev.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.browser.cjs.dev.js"
},
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.browser.esm.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.browser.cjs.js"
},
"production": {
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.esm.prod.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.cjs.prod.js"
},
"development": {
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.esm.dev.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.cjs.dev.js"
},
"module": "./jsx-runtime/dist/emotion-react-jsx-runtime.esm.js",
"default": "./jsx-runtime/dist/emotion-react-jsx-runtime.cjs.js"
},
"./jsx-dev-runtime": {
"worker": {
"production": {
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.worker.esm.prod.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.worker.cjs.prod.js"
},
"development": {
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.worker.esm.dev.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.worker.cjs.dev.js"
},
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.worker.esm.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.worker.cjs.js"
},
"browser": {
"production": {
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.browser.esm.prod.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.browser.cjs.prod.js"
},
"development": {
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.browser.esm.dev.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.browser.cjs.dev.js"
},
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.browser.esm.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.browser.cjs.js"
},
"production": {
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.esm.prod.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.cjs.prod.js"
},
"development": {
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.esm.dev.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.cjs.dev.js"
},
"module": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.esm.js",
"default": "./jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.cjs.js"
},
"./_isolated-hnrs": {
"worker": {
"production": {
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.worker.esm.prod.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.worker.cjs.prod.js"
},
"development": {
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.worker.esm.dev.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.worker.cjs.dev.js"
},
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.worker.esm.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.worker.cjs.js"
},
"browser": {
"production": {
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.browser.esm.prod.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.browser.cjs.prod.js"
},
"development": {
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.browser.esm.dev.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.browser.cjs.dev.js"
},
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.browser.esm.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.browser.cjs.js"
},
"production": {
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.esm.prod.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.cjs.prod.js"
},
"development": {
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.esm.dev.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.cjs.dev.js"
},
"module": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.esm.js",
"default": "./_isolated-hnrs/dist/emotion-react-_isolated-hnrs.cjs.js"
},
"./macro": {
"types": "./macro.d.ts",
"default": "./macro.js"
}
},
"types": "types/index.d.ts",
"files": [
"src",
"dist",
"jsx-runtime",
"jsx-dev-runtime",
"_isolated-hnrs",
"types/*.d.ts",
"macro.js",
"macro.d.ts",
"macro.js.flow"
],
"sideEffects": true,
"author": "mitchellhamilton <[email protected]>",
"license": "MIT",
"scripts": {
"test:typescript": "dtslint types"
},
"dependencies": {
"@babel/runtime": "^7.13.10",
"@emotion/cache": "^11.6.0",
"@emotion/serialize": "^1.0.2",
"@emotion/sheet": "^1.1.0",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"hoist-non-react-statics": "^3.3.1"
},
"peerDependencies": {
"@babel/core": "^7.0.0",
"react": ">=16.8.0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"@types/react": {
"optional": true
}
},
"devDependencies": {
"@babel/core": "^7.13.10",
"@emotion/css": "11.5.0",
"@emotion/css-prettifier": "1.0.0",
"@emotion/server": "11.4.0",
"@emotion/styled": "11.6.0",
"@types/react": "^16.9.11",
"dtslint": "^0.3.0",
"html-tag-names": "^1.1.2",
"react": "^17.0.2",
"svg-tag-names": "^1.1.1"
},
"repository": "https://github.com/emotion-js/emotion/tree/main/packages/react",
"publishConfig": {
"access": "public"
},
"umd:main": "dist/emotion-react.umd.min.js",
"preconstruct": {
"exports": {
"extra": {
"./macro": {
"types": "./macro.d.ts",
"default": "./macro.js"
}
}
},
"entrypoints": [
"./index.js",
"./jsx-runtime.js",
"./jsx-dev-runtime.js",
"./_isolated-hnrs.js"
],
"umdName": "emotionReact"
}
} |
@Andarist @mitchellhamilton I think we're really close but I could use some help testing these changes on the latest typescript. Do you have any ideas on how I could do that? |
Is there value from making this change besides making the package.json file smaller? |
I would install
A file imported like this |
I'm not so sure about defaulting to the production bundle. Also note that the dev bundle doesn't have |
Codecov Report
@@ Coverage Diff @@
## main #435 +/- ##
==========================================
+ Coverage 90.21% 92.11% +1.89%
==========================================
Files 32 32
Lines 1319 1433 +114
Branches 350 403 +53
==========================================
+ Hits 1190 1320 +130
+ Misses 124 108 -16
Partials 5 5
Continue to review full report at Codecov.
|
Also, this definitely needs to be behind an experimental flag, we won't get this right immediately and we need to be able to iterate on it without doing majors constantly |
(i'm not very concerned about the coverage thing, that just showed up because I allowed CI to run. tests for running validate, fix and build with an exports field would be good though) |
I'm happy to do this but I'm but sure how this is typically done. Is noting that this feature is experimental in the docs enough? Do I need to rename the opt-in config name to |
You can add another flag to the |
@@ -305,3 +305,50 @@ test("typescript with typeScriptProxyFileWithImportEqualsRequireAndExportEquals" | |||
" | |||
`); | |||
}); | |||
|
|||
test("exports field with worker condition", async () => { | |||
let tmpPath = realFs.realpathSync.native( |
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.
q: why do we use realpathSync.native
here? some tests in this file do this and some dont
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.
The tests that read symlinks use realpathSync.native
because (at least from my vague reading of things) realpath and realpath.native do subtly different things on Windows(like realpath
seems to just not resolve some links and realpath.native does?) and that matters when getting the path that a symlink resolves to.
".": { | ||
"module": { | ||
"worker": "./dist/package-exports.worker.esm.js", | ||
"browser": "./dist/package-exports.browser.esm.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.
Hm, I don't think it's ideal to rely on the "caller"/bundler being configured with a module condition. Rollup always includes the module condition but I'm pretty sure that webpack doesn't if you override resolve.conditionNames
(I didn't test it though). Usually used condition names probably should be configured indirectly through target
but it is not unlikely that somebody configures it explicitly. In such a case it is likely that with conditionNames: ['browser']
they wouldn't pick up our exports.module.browser
.
It also seems that esbuild doesn't support module
condition by default - I've looked into its code and I don't see it there. Its docs also doesn't mention it: https://esbuild.github.io/api/#how-conditions-work
Throwing new tools, like bun, into the mix - we can't assume that module
is always supported.
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.
Are you saying we should change something here or just "this sucks"? Because I agree it sucks but I don't see a way to make it better except for going Node ESM only (I think Node ESM only is the only practical way to go in the end, even though it of course sucks for other reasons. I'm imagining that Preconstruct will have a mode for what it does now and a Node ESM only mode)
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.
Are you saying we should change something here or just "this sucks"? Because I agree it sucks but I don't see a way to make it better except for going Node ESM only
Hm, I was only commenting on the module
condition used with env conditions here. I agree that for now, we shouldn't allow node loading our ESM files.
Are you afraid here that even when using a bundler we could risk introducing a dual-package hazard when the module condition is not present because they might bundle using "node strict rules" when the exports are present? And that's why you have chosen to only allow those env conditions in combination with the module
condition?
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.
That sort of problem, yes, basically "the semantics of what all this stuff does is not well defined so do the thing that seems the least likely to break"
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.
A slight problem here is that it will start loading CJS for browser targets without the module condition. But I guess the only offender here is esbuild right now. Perhaps we should open an issue there about this.
// shortest entrypoints first since shorter entrypoints | ||
// are generally more commonly used | ||
const comparison = a.length - b.length; | ||
if (comparison !== 0) return comparison; |
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.
neat
site/src/pages/configuration.mdx
Outdated
} | ||
``` | ||
|
||
Additionally, you'll also need to enable the feature on each individual pacakge. |
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.
Additionally, you'll also need to enable the feature on each individual pacakge. | |
Additionally, you'll also need to enable the feature on each individual package. |
Kinda a bummer - but I guess it's better for bigger monorepos that may not want to add this field everywhere just yet.
It would be cool to have a way to enforce that every package specifies this config, without it I need to be on my toes when adding new packages (arguably I usually copy-paste this stuff but the risk is still there).
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.
Yeah, I was always planning to have project-level config for it since that's what you'll probably want to do but didn't add it to this PR, might just add that now
|
||
`Array<"browser" | "worker">` | ||
|
||
Specifying the `envConditions` option adds additional environments that Preconstruct will generate bundles for. This option is currently aimed at generating bundles with `typeof SOME_ENV_SPECIFIC_GLOBAL` replaced with what it would be in that environment. It may be expanded to provide the ability to have Preconstruct resolve a different file or etc. depending on the environment in the future. |
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.
It may be expanded to provide the ability to have Preconstruct resolve a different file or etc. depending on the environment in the future.
We could mention that this already should, sort of, be supported thanks to package.json#imports
.
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.
It isn't currently - we don't pass the conditions to the resolve plugin.
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.
Any particular reason why we don't do this? 🤔 It seems like it would be a good addition
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.
just didn't want to figure out the exact semantics as part of this PR
@@ -102,11 +103,23 @@ function createEntrypoints( | |||
); | |||
} | |||
|
|||
export type ExportsConditions = { | |||
module: string | { worker?: string; browser?: string; default: string }; | |||
default: string; |
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.
Naïve question: Where do module
and default
come from?
My understanding, based on Node.js conditional exports, is that import
(not module
) is the condition that points to the ES module and require
(not default
) points to the CommonJS module. default
is the fallback condition. module
isn't a standard condition, so the fallback would always be used, therefore no ESM support at all.
Like I said, naïve question. I must be missing something.
https://nodejs.org/docs/latest-v16.x/api/packages.html#packages_conditional_exports
Glad to see this is being worked on, though. I've been adding these manually in my packages just to get Next.js + Styled Components + any third-party ESM-only package(s) working together.
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.
Some limitations of the approach in this PR are mentioned here. Perhaps this requires some additional information to give more context behind this decision.
module
condition has been "invented" by webpack to avoid dual-package hazard for packages defining it. Rollup also supports this condition. It allows requester to load the same file (usually in the ESM format) regardless of it being loaded through import
or require
. It assumes that a consuming tool knows how to handle those scenarios - that's why node doesn't define it anyhow. After all it can't load ESM file using require
.
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 that Preconstruct's support for the
exports
field does not currently include generating ESM compatible with Node.js. While ESM builds are generated, they are targeting bundlers, not Node.js or browsers directly so they use themodule
condition, not theimport
condition.
Are the ESM bundles themselves not compatible with Node.js? And that's why you're not including the import
condition? Or is this only saying that the import
condition is out of scope for this PR?
What happens to the import
and require
conditions that I'm manually adding? Will they be clobbered? So many questions!
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.
Are the ESM bundles themselves not compatible with Node.js?
Essentially yes because Node.js ESM has different semantics to the semantics commonly adopted by bundlers
expect(stdout.toString("utf8")).toMatchInlineSnapshot(` | ||
"blah.ts(3,29): error TS2307: Cannot find module 'pkg-a/not-exported' or its corresponding type declarations. | ||
blah.ts(11,22): error TS2345: Argument of type '\\"index\\"' is not assignable to parameter of type '\\"other\\"'. | ||
blah.ts(12,22): error TS2345: Argument of type '\\"something\\"' is not assignable to parameter of type '\\"other\\"'. | ||
" | ||
`); |
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.
So we don't need to do anything special to support types? 🤔 They will still be resolved without the types condition just fine?
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.
Yeah, it's a natural consequence of how our d.ts files exist, TypeScript just goes to the default
condition, finds the js file and then finds the d.ts file next to it.
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.
i've suspected smth like that - thanks for confirming 👍
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.
- we do not support right now any form of glob-based entrypoints, right? I don't see any tests using smth like this and the
getRollupConfig
doesn't seem to support this. I just want to make sure we don't need to add any support/tests for subpath patterns - It would be lovely to discuss production/development support after we land this
They're supported as they already are in Preconstruct but subpath patterns aren't used, there will be an entry for each entrypoint |
Ah, got it - so entrypoints are always expanded up front (at least right now). |
Co-authored-by: Mateusz Burzyński <[email protected]>
See #431 for discussion.
I know the discussion hasn't finished on that issue but I figured while I was motivated, I would get the ball rolling with an implementation.
With these changes, users can now define "worker" bundles in the exact same way that they define "browser" bundles. The only difference between the two is that the worker bundle assumestypeof document
,typeof window
, andtypeof process
are all"undefined"
This PR now adds support to package exports field including "browser" and "worker" custom conditions. These changes should be mostly backwards compatible with previous versions. I used the "vanilla-extract" repo as a template and I have confirmed that these changes are at least compatible with their current config.
Remaining items