-
-
Notifications
You must be signed in to change notification settings - Fork 131
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
Switch to Preconstruct to enable dev bundles #148
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
@@ -1,4 +1,5 @@ | |||
{ | |||
"name": "react-resizable-panels-repo", |
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 was a Preconstruct requirement, we actually should lift this off - I don't see a strong reason why this would be hardly required for the monorepo root
"prettier": "prettier --write \"**/*.{css,html,js,json,jsx,ts,tsx}\"", | ||
"prettier:ci": "prettier --check \"**/*.{css,html,js,json,jsx,ts,tsx}\"", | ||
"typescript": "tsc --noEmit", | ||
"typescript:watch": "tsc --noEmit --watch" | ||
}, | ||
"devDependencies": { | ||
"@babel/preset-typescript": "^7.21.5", |
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.
Preconstruct is Babel-based so u get a good control over the output (plugins support and all). I don't think that u need this anyhow but well, I had to opt into TypeScript syntax somehow. We plan to explore supporting alternative transpilers but, quite frankly, we run Babel in a separate thread and stuff and it's really not that bad perf-wise - especially for just a few files. I use this in a couple of projects and never noticed any noticeable slowdowns
"scripts": { | ||
"build": "parcel 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.
it's already executed by the root so there is no strong need to have it here - atm Preconstruct doesn't exactly allow per-directory builds, it's running through the whole monorepo cause we need to sort builds in a topological order to generate TS declarations. I have an idea how to lift this requirement though and plan to work on this.
If needed this script could just proxy to the top-level build script
9e0b476
to
7093e20
Compare
"imports": { | ||
"#is-development": { | ||
"development": "./src/env-conditions/development.ts", | ||
"default": "./src/env-conditions/production.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.
You can freely reorganize this, only keys (aka conditions) are relevant (and the fact that they are pointing to files in src
). I'm just experimenting with how to best organize those files - their implementation can also be just anything but we use simple named exports with boolean values in them here. The recommendation is for all files used for a certain imports
to have the same module interface (same types) - otherwise it could lead to weird problems, remember that they should be "swappable".
Those are only used in development (when using Preconstruct, since those are valid things in node etc and u could leave imports to them as-is). preconstruct build
inlines/bundles those files so all of that should still work with older runtime/bundlers/etc
"exports": { | ||
"importConditionDefaultExport": "default" | ||
}, | ||
"___experimentalFlags_WILL_CHANGE_IN_PATCH": { |
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 name is scary since we just shipped this as a new feature but we don't intend to break this, if we promote this to a stable feature, rename this or anything then preconstruct fix
will just become capable of migrating this to the new format
"types": "dist/react-resizable-panels.d.ts", | ||
"main": "dist/react-resizable-panels.cjs.js", | ||
"module": "dist/react-resizable-panels.esm.js", | ||
"exports": { |
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.
All of this is a little bit crazy... but well, it's what we found to cover all of the "compatibility boxes". This should work in old nodes, new nodes, with require, with import, with different bundlers, with different versions of TS and its moduleResolution
setting etc
For example, previously your package was CJS-only and thus in pure-ESM environment it was consumable through a default import (as well as through named ones):
TS playground.
This is now fixed - no default export is provided and one can't use a default import to consume this module (that matches how you defined its interface)
"default": "./dist/react-resizable-panels.development.cjs.js" | ||
}, | ||
"module": "./dist/react-resizable-panels.esm.js", | ||
"import": "./dist/react-resizable-panels.cjs.mjs", |
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.
.cjs.mjs
might be a little bit confusing here - why do we point to something with .cjs
in the filename from within the import
condition?
we avoid the dual package hazard by using the es module wrapper technique. This way only one copy of your library (given the matching versions etc) ends up being loaded and it's always a CJS version (either directly when loaded using require
or through the ESM wrapper if loaded using import
).
Isn't this bad for bundlers? well, not quite - because we also use the module
condition that points to the ESM bundles. This is also understood by all modern bundlers and it's equivalent of the old package.json#module
. Both require
and import
are capable of resolving using this condition in bundlers and thanks to that you will also only ever load one instance of this library.
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.
Hey @Andarist! Looking at this tonight, to add some dev-only warnings, and wondering how I'm supposed to use this new condition. Referencing isDevelopment
in TypeScript doesn't seem possible without directly importing one of the two files, which would seem to defeat the purpose. I'm probably overlooking something silly.
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.
This should work:
import { isDevelopment } from "#is-development"
if it doesnt - could u push out ur attempt to use it so i could take a look at the exact situation?
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 confirmed that with this patch:
diff --git a/packages/react-resizable-panels/src/PanelGroup.ts b/packages/react-resizable-panels/src/PanelGroup.ts
index 6beadfc..0c73f4f 100644
--- a/packages/react-resizable-panels/src/PanelGroup.ts
+++ b/packages/react-resizable-panels/src/PanelGroup.ts
@@ -46,6 +46,7 @@ import {
} from "./utils/group";
import { loadPanelLayout, savePanelGroupLayout } from "./utils/serialization";
import { isServerRendering } from "./utils/ssr";
+import { isDevelopment } from "#is-development";
const debounceMap: {
[key: string]: (
@@ -196,7 +197,9 @@ function PanelGroupWithForwardedRef({
0
);
- assert(total === 100, "Panel sizes must add up to 100%");
+ if (isDevelopment) {
+ assert(total === 100, "Panel sizes must add up to 100%");
+ }
setSizes(sizes);
},
I can only found the wrapped code in .development.
builds:
> grep -R 'must add up to' packages/react-resizable-panels/dist
packages/react-resizable-panels/dist/react-resizable-panels.development.esm.js: assert(total === 100, "Panel sizes must add up to 100%");
packages/react-resizable-panels/dist/react-resizable-panels.development.cjs.js: assert(total === 100, "Panel sizes must add up to 100%");
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.
Ooo, neat! Thank you
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.
Could you push what you have to a branch?
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.
Away from the desk for a bit but the same build error on the suspense package PR we’re talking on so that’s probably an easier starting place :)
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, Parcel definitely has support for every puzzle piece here - I'm not yet sure what's wrong and since Parcel's internals are written partially in Rust I can't quite debug this in depth :S
I'll continue digging into this - there is definitely at least one Parcel bug here to be found because it crashes on existing but empty diagnostics
internally.
If I don't come up with a fix soon-ish I will propose some alternative solution to the problem here. I think that preconstruct dev
might be a substitute for package.json#source
but I have to test it out with Parcel.
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 add this to your root package.json
:
"@parcel/resolver-default": {
"packageExports": true
}
It seems that Parcel introduced exports
support as opt-in (weird choice if you ask me) and that impacts imports
as well. The DX here could definitely be improved on the Parcel's side of things - at the very least they could inform the user what went wrong as the situation itself is pretty clear here. Importing a module specifier with a leading hash isn't really ambiguous as far as I know.
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.
Oh! That's...interesting.
Thanks for the help, @Andarist! 🙂
@@ -0,0 +1 @@ | |||
export const isDevelopment = true; |
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.
There is a small caveat right now that you won't get auto-imports for this #is-development
thing. There is a tracking issue about this here. Emma (Preconstruct's author) is already working on adding support for that - that will benefit everybody since package.json#imports
are by no means Preconstruct's feature.
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.
Am I understanding you correctly to mean that e.g. VS Code auto-imports will stop working for the monorepo?
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 think you're just saying that attempts to import isDevelopment
will need to be manually written?
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 think you're just saying that attempts to import isDevelopment will need to be manually written?
This. Once you add the import to the file then isDevelopment
will appear in the suggestions with it - it's just about the auto-import thing (and it's a temporary situation, hopefully, since the support in the TS lang server for this will come in the future).
7093e20
to
b02c307
Compare
"types": { | ||
"import": "./dist/react-resizable-panels.cjs.mjs", | ||
"default": "./dist/react-resizable-panels.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.
I don't think I understand why this "types"
key exists or what it means (vs the higher level "types"
key)
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.
package.json#exports
define everything about the package (well, almost, but let's simplify here) that is. It's like a higher-priority manifest describing the content of the package.
Consider this:
{
"name": "my-lib",
"main": "./lib/main.js",
"exports": "./lib/public.js"
}
In here, package.json#main
is completely ignored. package.json#exports
win and that's all. This should be the behavior of every tool that understands exports
. As soon as a package adds that it opts into that new behavior - other keys might only be used as "fallback" for tools from the pre-exports era.
However, TS has chosen a different route - likely because following this strictly would be quite disruptive for the ecosystem. So despite the fact that modern TS versions know what to do with exports
... they ignore it by default. Most projects are configured with moduleResolution: node
and that ignores package.json#exports
completely.
But as soon as you opt in to the new semantics with moduleResolution: node16
, they start matching the "runtime semantics" - that is: if a package has exports
then other fields are ignored. And yes, that includes the top-level package.json#types
.
Note that types
in exports are not mandatory (just like package.json#types
isn't). You can alternatively rely on the "sibling lookup" (TS always will try to load file.d.ts
when importing~ file.js
, file.d.mts
when importing file.mjs
, etc). Personally, I find the explicit types
field (within exports or the top-level one) to be more explicit and easier to reason about.
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 want to add on to @Andarist's explanation, the types
condition is here specifically so that the types correctly resolve regardless of the conditions specified in https://www.typescriptlang.org/tsconfig#customConditions (currently it would technically work without the types
condition anyway because of a TypeScript bug but that shouldn't be relied on)
I also get a failure when I try to build the website locally (Node v18.12.1 if relvant):
|
@bvaughn fixed that, Parcel has seen the added |
|
||
export { | ||
// TypeScript types | ||
ImperativePanelGroupHandle, | ||
ImperativePanelHandle, | ||
Panel, | ||
PanelOnCollapse, |
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.
Those added things were not exported but yet they were consumable... I have no idea how that worked. If we inspect the content od the current declaration files we can clearly see that the types that I had to export from here were not exported:
https://unpkg.com/browse/react-resizable-panels@0.0.45/dist/react-resizable-panels.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.
I found out why this worked before (with @emmatown's help) and opened a PR in Parcel with a change that would help to avoid accidental leakage like this: parcel-bundler/parcel#9016
@@ -1,12 +1,36 @@ | |||
import { expect, Page, test } from "@playwright/test"; | |||
import { createElement } from "react"; | |||
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; | |||
import { getCursorStyle } from "react-resizable-panels/src/utils/cursor"; |
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.
This was reaching for a private~ util (and with exports
we can't reach into undeclared files, although we could add src/*
"forwarding", I just wanted to avoid this). I decided to inline it in this test file - if you have any other preferred about to solve this, let me know.
package.json
Outdated
"globals": { | ||
"react": "React" | ||
}, |
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.
Did you mean to add this @Andarist? I can't see any UMD builds (and I'd personally recommend not adding them)
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, I wasn't sure if UMDs were opt-in with Preconstruct so I included this and forgot to revisit it. Thanks for catching this! I removed it now.
Looking at this again this morning.
I think overall this looks like a change worth merging. I just want to give it a little more testing this weekend. |
This one could be addressed by running Preconstruct in watch mode so Parcel would see the dist files. This is a little bit less convenient than just loading |
Yeah, that's fair. I think it's nice to test as close to the thing that I'm releasing as possible, to reduce the chance that I overlook something critical (and reduce the burden of fixing a bug and publishing a release for a package I don't use often– context switching is hard and this is all extra curricular work) |
Agreed that it's nice - so if that's crucial for you then I'd say that the only way to solve this here is to run 2 watchers in parallel: 1 for the lib, 1 for the app/docs. |
Right. That's what I was doing before :) I do often iterate on the component library using the docs site as a testing ground, so it is pretty important to my work flow that I run both in watch flow. Whether it's important that the library is built by Preconstruct vs Parcel when iterating is a less distinction important but would be nice. |
As discussed on Twitter - I think that it's likely that Preconstruct could output source maps for you. From what I can tell, it's just a matter of passing a boolean to a couple of functions (I have this change setting locally in a draft on my machine). The only question is - if you'd even want to enable them with the pretty readable Preconstruct's output. It seems that some tools just don't load source maps from
I think though that if a tool trips somehow on a source map that is available in the module then it's on that tool and that it should fix its own issue. So if the package author's desire is to ship source maps, I think that Preconstruct should allow them. |
I'd probably prefer them, but I don't view them as necessary– given how readable Preconstruct code is.
Agreed with this, generally. I don't think they're a blocker for me though. |
Thank you! 🙇🏼 |
Published in 0.0.48 |
No description provided.