docs/react-v9/contributing/rfcs/react-components/convergence/fixed-versions-for-prerelease.md
@fluentui dependencies in prerelease@ling1726
This RFC proposes using fixed version dependencies within the Fluent monorepo for all v9 prerelease packages.
The term 'prerelease' refers to any packages with matching the version pattern 9.y.z-<prerelease tag>.<prerelease version>.
Prerelease tags do not communicate any kind of breaking changes, we will show this in the problem statement.
This versioning strategy will be temporary while we are in pre-release. It will reduce the dependency hell currently caused by using carets with prerelease tags.
Once the core of Fluent v9 is fully released without any prerelease tags, this approach should be abandoned. Without prerelease tags, we can fully rely on caret dependencies to provide consumers with a consistent dependency tree and communicate breaking changes with major version bumps.
Assume a constraint ^9.0.0-alpha.15 for @fluentui/react-shared-contexts. The following versions all satisfy this
constraint:
9.0.0-alpha.169.0.0-alpha.17 -> breaking change from alpha.169.0.0-alpha.30Any version greater than the current is matched, there is no way of communicating breaking changes with prerelease versions. You can test this for yourself on the official npm semver calculator.
All Fluent v9 packages are currently released with a prerelease tag. There is no possible way of knowing
the compatibility of packages without examining the lockfile, or node_modules if
no lockfile is being used, and then tracing this version to code.
Each reinstall could result in accidentally consuming breaking changes.
Unfortunately there is no 'correct' way to handle this problem. According to the NPM semver docs we are already trying to handle this problem incorrectly.
a user who has opted into using a prerelease version has clearly indicated the intent to use that specific set of alpha/beta/rc versions. By including a prerelease tag in the range, the user is indicating that they are aware of the risk.
We have to work around the problem in a non-standard way because we have a hard requirement to not utilize semver fully while introducing new components for partners to use in production
While Fluent v9 is in a prerelease stage and the core of packages for v9 use a prerelease tag (alpha, beta...), use fixed versions for all dependencies within the Fluent monorepo.
Example of proposed changes to @fluentui/react-components package.json file.
{
"dependencies": {
- "@fluentui/react-accordion": "^9.0.0-alpha.70",
- "@fluentui/react-avatar": "^9.0.0-alpha.75",
- "@fluentui/react-badge": "^9.0.0-alpha.75",
- "@fluentui/react-button": "^9.0.0-alpha.80",
- "@fluentui/react-divider": "^9.0.0-alpha.62",
- "@fluentui/react-image": "^9.0.0-alpha.73",
- "@fluentui/react-label": "^9.0.0-alpha.34",
- "@fluentui/react-link": "^9.0.0-alpha.76",
- "@fluentui/react-make-styles": "^9.0.0-alpha.61",
- "@fluentui/react-menu": "^9.0.0-alpha.71",
- "@fluentui/react-popover": "^9.0.0-alpha.36",
- "@fluentui/react-portal": "^9.0.0-alpha.43",
- "@fluentui/react-provider": "^9.0.0-alpha.72",
- "@fluentui/react-theme": "^9.0.0-alpha.22",
- "@fluentui/react-tooltip": "^9.0.0-alpha.76",
- "@fluentui/react-utilities": "^9.0.0-alpha.43",
+ "@fluentui/react-accordion": "9.0.0-alpha.70",
+ "@fluentui/react-avatar": "9.0.0-alpha.75",
+ "@fluentui/react-badge": "9.0.0-alpha.75",
+ "@fluentui/react-button": "9.0.0-alpha.80",
+ "@fluentui/react-divider": "9.0.0-alpha.62",
+ "@fluentui/react-image": "9.0.0-alpha.73",
+ "@fluentui/react-label": "9.0.0-alpha.34",
+ "@fluentui/react-link": "9.0.0-alpha.76",
+ "@fluentui/react-make-styles": "9.0.0-alpha.61",
+ "@fluentui/react-menu": "9.0.0-alpha.71",
+ "@fluentui/react-popover": "9.0.0-alpha.36",
+ "@fluentui/react-portal": "9.0.0-alpha.43",
+ "@fluentui/react-provider": "9.0.0-alpha.72",
+ "@fluentui/react-theme": "9.0.0-alpha.22",
+ "@fluentui/react-tooltip": "9.0.0-alpha.76",
+ "@fluentui/react-utilities": "9.0.0-alpha.43",
"tslib": "^2.1.0"
},
}
Example of proposed changes to @fluentui/react-button package.json file.
"dependencies": {
- "@fluentui/keyboard-keys": "^9.0.0-alpha.1",
- "@fluentui/react-icons": "^1.1.136", // not a monorepo dependency
- "@fluentui/react-make-styles": "^9.0.0-alpha.61",
- "@fluentui/react-tabster": "^9.0.0-alpha.55",
- "@fluentui/react-utilities": "^9.0.0-alpha.43",
+ "@fluentui/keyboard-keys": "9.0.0-alpha.1",
+ "@fluentui/react-icons": "^1.1.136", // not a monorepo dependency
+ "@fluentui/react-make-styles": "9.0.0-alpha.61",
+ "@fluentui/react-tabster": "9.0.0-alpha.55",
+ "@fluentui/react-utilities": "9.0.0-alpha.43",
"tslib": "2.1.0"
},
alpha component problemIt is important to remember that this section applies mainly to the prerelease stage of Fluent. Once the core of Fluent v9 uses caret dependencies and full semver, some edge cases might still occur. For the most part as long as the Fluent uses semver accurately with caret dependencies, resolving dependencies becomes a problem (rightly) for the consumer. For example
yarnis known to require deduplication in its lockfile even with caret resolution, this is a problem that Fluent cannot solve.
The consequence of using fixed versions for the entire suite of @fluentui/ scoped packages is that consuming
multiple individual packages becomes problematic.
Assuming Fluent uses fixed internal dependency versions, a consumer installs the
latest version of react-components and react-dropdown.
@fluentui/react-components -> 9.0.0.beta.15
@fluentui/react-shared-contexts -> 9.0.0.beta.4@fluentui/react-make-styles -> 9.0.0.beta.3@fluentui/react-dropdown -> 9.0.0.alpha.1
@fluentui/react-shared-contexts -> 9.0.0.beta.4@fluentui/react-make-styles -> 9.0.0.beta.3This scenario will work, both packages are on the latest version and pin the same shared dependencies. We release all changes in one pipeline run, so after a single release phase the dependencies should be in sync.
When react-components is an older version a customer is already using, adding the new package will
result in duplicate versions. This can happen if a consumer is already using an older react-components
and installs a newly released react-dropdown.
@fluentui/react-components -> 9.0.0.beta.13
@fluentui/react-shared-contexts -> 9.0.0.beta.3@fluentui/react-make-styles -> 9.0.0.beta.3@fluentui/react-dropdown -> 9.0.0.alpha.1
@fluentui/react-shared-contexts -> 9.0.0.beta.4@fluentui/react-make-styles -> 9.0.0.beta.3In the above example react-shared-contexts is duplicated.
When upgrading Fluent v9 packages, the customer needs to make sure that the dependencies of react-components and
react-dropdown are pinned to the same versions, the only way to easily guarantee this without doing an exhaustive
investigation is to make sure to use the latest version of both, which is still not easy.
To mitigate this problem during the prerelease phase we can do the following:
For sane versioning, new packages should be released straight into the beta prerelease tag. A new way of consuming
unstable packages is proposed in the next section. alpha and beta are liable to breaking API changes both internally
and externally. Having both prerelease tags existing side by side could cause dependency hell too easily.
As Fluent v9 moves to the RC stage, we want to clearly communicate to partners what they can expect from the stable
release of Fluent. It would not make sense to elevate beta components to rc release if we do not expect to release
those components in the first stable release of Fluent v9.
Since RC will solidify our internal APIs and concepts in our core utilities, we do not plan to make breaking changes.
While the previous problems with prerelease tags still exist, the RC status will be more stable than any alpha or beta
release. Therefore, releasing rc and beta packages side by side should be safer in terms of breaking changes.
Partners that use separate packages will still have the same problem of figuring out compatible versions to avoid
duplication with pinned versions. Meanwhile partners that use the single react-components package can still benefit
from the unstable import path to avoid any extra effort in dependency management.
unstable packagesDemo here: https://github.com/ling1726/multiple-entrypoints-test
This solution reintroduces deep imports with the path @fluentui/react-components/lib/unstable. The only workaround that
should be necessary is for jest, which uses commonjs.
Since lib is used the esm imports only, it's possible to generate a new package.json file that points to the
correct commonjs code so that tests still work in the same way for alpha components. This modification needs to happen
for react-components only. Here is the sample snippet for package.json, you can find a fully
working demo repo in the link under the heading.
// react-components/unstable/package.json
{
"main": "../lib-commonjs/unstable/index.js", // commonjs entrypoint for jest
"module": "../lib/unstable/index.js" // esm entrypoint for webpack
}
Even though all new package will have the beta prerelease tag, our recommendation to users should still be to use
the @fluentui/react-components package uniquely. Then we should clearly document/explain for our partners that
unstable components can be used through these deep imports. If new components are released to the same
package, there is no obvious reason to consume components as separate packages and the solution to
consuming a new component is to bump the suite package.
The only scalable solution is to get out of the prerelease phase and leverage all the benefits of semver.
Prerelease tags matching every latest version is not a bug, it's a feature.
unstableunstable concept to partners.