-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Refactor extensions to move framework modules to peerDependencies #5959
Conversation
packages/boot/package.json
Outdated
"peerDependencies": { | ||
"@loopback/core": "^2.9.1", | ||
"@loopback/repository": "^2.9.0", | ||
"@loopback/service-proxy": "^2.3.4" |
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 is potentially tricky.
Before my change, it was possible to use @loopback/boot
in applications that don't have @loopback/service-proxy
in their dependencies. @loopback/boot
would install it's own copy that would be pretty much ignored at runtime.
With the change in place, apps must install both @loopback/repository
and @loopback/service-proxy
to be able to use @loopback/boot
. I wish there was something like optionalPeerDependencies
!
I see this as a sign that @loopback/boot
is providing too many features and should be split into smaller packages.
- It defines contract for Booter implementations and few base classes to help with implementation.
- It implements the bootstrapping algorithm (
BootMixin
). - It provides booters for common artifacts (controllers, repositories, services, etc.).
I think the following structure would be better:
- A new module to define Booter contract and possibly base classes. Prior art: https://github.com/strongloop/loopback-next/tree/master/packages/model-api-builder
@loopback/boot
should provideBootMixin
and artifact mixins that can be loaded using@loopback/core
only.- Artifact booters requiring optional extensions (repository booter, service booter, etc.) should be moved either to those extensions (
@loopback/repository
,@loopback/service-proxy
) or to a new package. A question to consider: where to put datasource booter that's shared by repositories and service proxies?
Such change is out of scope of this pull request though. The question is how to address the issue above inside this pull request:
- Are we ok to require apps to install both
repository
andservice-proxy
if they want to use@loopback/boot
? By default,lb4 app
includes both, so it may be ok. But then it feels a kind of lame to force users to install packages they don't need. - Should I revert the repository & service-proxy part of this pull request and keep those two packages as regular
dependencies
? - We can also investigate how to rework booters to not require direct dependency on repository & service-proxy packages. They mostly call application methods mixed in by the user (e.g.
app.repository
) and import only type definitions for internal consumption from repository/service-proxy, so maybe it's ok to declare them indevDependencies
only? The only exception I found isModelMetadataHelper.getModelMetadata
, it's called byModelBooter
to check if a class is decorated with@model()
.
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's my intention for #5618.
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.
To move this pull request forward, I am going to pick the option 2:
revert the repository & service-proxy part of this pull request and keep those two packages as regular dependencies
It's not ideal, but it will give us more time to consider different options and implement a proper long-term fix.
Added two more commits to reorganize order of package.json fields and move |
Signed-off-by: Miroslav Bajtoš <[email protected]>
BREAKING CHANGE: Extensions no longer install framework packages as their own dependencies, they use the framework packages provided by the target application instead. If you are getting `npm install` errors after upgrade, then make sure your project lists all dependencies required by the extensions you are using. Signed-off-by: Miroslav Bajtoš <[email protected]>
82ded8e
to
2d9cfec
Compare
Signed-off-by: Miroslav Bajtoš <[email protected]>
2d9cfec
to
3392a1e
Compare
@raymondfeng updated per #5959 (comment), LGTY now? |
|
||
We recommend to use `peerDependencies` to specify what packages is your | ||
extension expecting in the target application and `devDependencies` to make | ||
these packages available for extension tests. |
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 should mention that npm 6
does not install peerDependencies
. As a workaround, we need to add duplicate entries in devDependencies
so that they can be installed and used for testing.
```json | ||
{ | ||
"peerDependencies": { | ||
"@loopback/core": "^1.9.0 || ^2.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.
Can lerna or our build update such version ranges for new releases?
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.
See the discussions in #4937 and your pull request #4972.
Quoting from #4972 (comment):
Let's be conservative to use the
^<pkgVersion>
for peer dependencies as a release is cut.
The current implementation:
"@loopback/authentication": "^4.2.9", | ||
"@loopback/core": "^2.9.1", | ||
"@loopback/rest": "^5.2.0", | ||
"@loopback/service-proxy": "^2.3.4" |
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.
Should @loopback/core
stay as a regular dependency?
"@loopback/core": "^2.9.1", | ||
"@loopback/rest": "^5.2.0", | ||
"@loopback/rest": "^5.2.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.
Should @loopback/core
stay as a regular dependency? We need to clarify the criteria between dependencies
and peerDependencies
. I'm thinking about the following:
- If the dependency needs to be consistent with the application level, it's a peer dependency (always use the one from the application level)
- Otherwise, it should be a regular dependency (and possibly with a different version than the application level)
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.
Another criteria:
If the dependency is the target extension point for this package to contribute extensions, it's a peer dependency.
Using this rule, @loopback/core
should be a regular dependency while @loopback/rest
should be a peer dependency.
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.
- If the dependency needs to be consistent with the application level, it's a peer dependency (always use the one from the application level)
- If the dependency is the target extension point for this package to contribute extensions, it's a peer dependency.
- Otherwise, it should be a regular dependency (and possibly with a different version than the application level)
Your criteria make sense to me, however I have a different view on how to interpret them.
In my opinion, there should be only one version of @loopback/core
installed in any application. Is see the core packages as a cornerstone, a "LoopBack runtime". Among other things, it provides foundation for decorators and manipulation with metadata (MetadataInspector
, ParameterDecoratorFactory
, etc.) I am concerned that allowing multiple versions of @loopback/core
can introduce subtle bugs that will be difficult to troubleshoot, for example when a decorator uses different code for setting metadata than is used by the application to look up that metadata.
Also, if the application developer decides to use their forked version of @loopback/core
(e.g. to fix a bug), then I want extensions to use the fixed version of core, not the vanilla version from npm.
I would also like to add one more criteria:
- If the dependency is typically installed in target applications, and the extension can support multiple major versions of that dependency at the same time, then the dependency should be a peer dependency.
The use case to consider: where a single version of an extension (e.g. @loopback/extension-health
) wants to support multiple version of the framework, so that the maintainers can have a single branch and don't have to maintain different branches (major-version release lines) for each framework version. Let's say the extension depends on @loopback/core@2
and the application depends on @loopback/core@1
. This is very reasonable, since the only breaking change listed in @loopback/[email protected]
changelog is dropped support for Node.js 8.x. When such extension is installed into the target app, we end up with two copies of @loopback/core
, where the extension uses a different "LoopBack runtime" than the target application it's installed to.
Let's discuss!
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's a valid use case that one extension package can support multiple versions of the LoopBack runtime. But it's hard to ensure that with our CI and release process though.
In addition to the containing application, we probably should also take into consideration that certain LoopBack packages can be used standalone outside a scaffolded LB4 application.
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's a valid use case that one extension package can support multiple versions of the LoopBack runtime. But it's hard to ensure that with our CI and release process though.
I now. Personally, I am fine to keep the support for older major versions on a "best effort" basis with no CI check for now. We can setup a more comprehensive CI solution once there are complaints from users about incompatibilities.
In addition to the containing application, we probably should also take into consideration that certain LoopBack packages can be used standalone outside a scaffolded LB4 application.
That's a good argument. IIRC, packages that can be used standalone outside of LB4 apps are usually more low level (@loopback/metadata
, @loopback/context
etc.) and don't participate in peer dependencies, because they are accessed via higher-level wrappers (@loopback/core
).
If a LB package is used by a LB4 application that's not scaffolded via lb4 app
, then I believe the app is still going to depend on @loopback/core
, therefore my arguments above still apply.
Are you envisioning a project where the developer wants to use an extension depending on @loobpack/core
but the application does not depend on @loopback/core
? Would that work at all?
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 envisioning a project where the developer wants to use an extension depending on @loobpack/core but the application does not depend on @loopback/core? Would that work at all?
No. At minimum, we'll use @loopback/core
.
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. At minimum, we'll use @loopback/core.
In that case, I think it's reasonable to for extensions to declare @loopback/core
as a peer dependency, right?
@raymondfeng Do you agree to keep the currently proposed direction? May I proceed to rebase this PR on top of latest master (there are going to be many merge conflicts to resolve, so I don't want to do it too many times) and get this PR landed soon?
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.
I'll open a series of smaller PRs to land these changes in a way that will minimize merge conflicts. The first one: #6243 |
As discussed several times in the past (most recently in #5927 (comment)), extensions should use framework modules from the target application via
peerDependencies
. This pull request makes that change happen.I split the changes into three commits:
/cc @strongloop/loopback-maintainers
Checklist
👉 Read and sign the CLA (Contributor License Agreement) 👈
npm test
passes on your machinepackages/cli
were updatedexamples/*
were updated👉 Check out how to submit a PR 👈