Skip to content
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

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions docs/site/Creating-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,59 @@ export class MyComponent implements Component {
}
```

## Working with dependencies

Extensions should preferably use LoopBack modules installed by the target
application, to allow application developers to choose the version of framework
modules they want to use. For example, they may want to hold to an older version
until a regression is fixed in the latest version, or even use their own fork to
have a bug fix available before it's officially published.

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.
Copy link
Contributor

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.


For example:

```json
{
"name": "my-lb4-extension",
"version": "1.0.0",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@loopback/core": "^2.9.1",
"@loopback/rest": "^5.2.0"
},
"devDependencies": {
"@loopback/build": "^6.1.0",
"@loopback/core": "^2.9.1",
"@loopback/eslint-config": "^8.0.3",
"@loopback/rest": "^5.2.0",
"@loopback/testlab": "^3.2.0"
}
}
```

It is also possible for a single extension version to support multiple versions
of a framework dependency:

```json
{
"peerDependencies": {
"@loopback/core": "^1.9.0 || ^2.0.0"
Copy link
Contributor

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?

Copy link
Member Author

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:

https://github.com/strongloop/loopback-next/blob/6e916494f1bcbdf5ba5ecd20ac8cbadcbae4fc9f/bin/update-peer-deps.js#L38-L41

}
}
```

{% include important.html content=' Shrinking the supported version range of a
peer dependency is a breaking change that should trigger a semver-major release.

For example, changing the range from `"^1.5.3"` to `"^1.5.3 || ^2.0.0"` is
backwards compatible, while changing the range from `"^1.5.3"` to `"^2.0.0"` is
a breaking change. ' %}

## Injecting the target application instance

You can inject anything from the context and access them from a component. In
Expand Down
8 changes: 6 additions & 2 deletions extensions/apiconnect/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,18 @@
"url": "https://github.com/strongloop/loopback-next.git",
"directory": "extensions/apiconnect"
},
"dependencies": {
"peerDependencies": {
"@loopback/core": "^2.9.1",
"@loopback/rest": "^5.2.0",
"@loopback/rest": "^5.2.0"
Copy link
Contributor

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:

  1. If the dependency needs to be consistent with the application level, it's a peer dependency (always use the one from the application level)
  2. Otherwise, it should be a regular dependency (and possibly with a different version than the application level)

Copy link
Contributor

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.

Copy link
Member Author

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!

Copy link
Contributor

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.

Copy link
Member Author

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?

Copy link
Contributor

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.

Copy link
Member Author

@bajtos bajtos Aug 4, 2020

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

},
"dependencies": {
"tslib": "^2.0.0"
},
"devDependencies": {
"@loopback/build": "^6.1.0",
"@loopback/core": "^2.9.1",
"@loopback/eslint-config": "^8.0.3",
"@loopback/rest": "^5.2.0",
"@loopback/testlab": "^3.2.0",
"@types/node": "^10.17.27"
}
Expand Down
10 changes: 8 additions & 2 deletions extensions/authentication-jwt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,28 @@
"publishConfig": {
"access": "public"
},
"dependencies": {
"peerDependencies": {
"@loopback/authentication": "^4.2.9",
"@loopback/core": "^2.9.1",
"@loopback/rest": "^5.2.0",
"@loopback/service-proxy": "^2.3.4"
Copy link
Contributor

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?

},
"dependencies": {
"@loopback/rest-explorer": "^2.2.6",
bajtos marked this conversation as resolved.
Show resolved Hide resolved
"@loopback/security": "^0.2.14",
"@loopback/service-proxy": "^2.3.4",
"@types/bcryptjs": "2.4.2",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^8.5.1"
},
"devDependencies": {
"@loopback/authentication": "^4.2.9",
"@loopback/boot": "^2.3.5",
"@loopback/build": "^6.1.0",
"@loopback/core": "^2.9.1",
"@loopback/eslint-config": "^8.0.3",
"@loopback/repository": "^2.9.0",
"@loopback/rest": "^5.2.0",
"@loopback/service-proxy": "^2.3.4",
"@loopback/testlab": "^2.0.2",
"@types/lodash": "^4.14.157",
"@types/node": "^10.17.27",
Expand Down
9 changes: 7 additions & 2 deletions extensions/authentication-passport/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,25 @@
"url": "https://github.com/strongloop/loopback-next.git",
"directory": "extensions/authentication-passport"
},
"dependencies": {
"peerDependencies": {
"@loopback/authentication": "^4.2.9",
"@loopback/core": "^2.9.1",
"@loopback/rest": "^5.2.0",
"@loopback/rest": "^5.2.0"
},
"dependencies": {
"@loopback/security": "^0.2.14",
"passport": "^0.4.1",
"tslib": "^2.0.0",
"util-promisifyall": "^1.0.6"
},
"devDependencies": {
"@loopback/authentication": "^4.2.9",
"@loopback/build": "^6.1.0",
"@loopback/core": "^2.9.1",
"@loopback/eslint-config": "^8.0.3",
"@loopback/mock-oauth2-provider": "^0.1.3",
"@loopback/openapi-spec-builder": "^2.1.9",
"@loopback/rest": "^5.2.0",
"@loopback/testlab": "^3.2.0",
"@types/jsonwebtoken": "^8.5.0",
"@types/lodash": "^4.14.157",
Expand Down
8 changes: 6 additions & 2 deletions extensions/context-explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@
"publishConfig": {
"access": "public"
},
"dependencies": {
"peerDependencies": {
"@loopback/core": "^2.9.1",
"@loopback/rest": "^5.2.0",
"@loopback/rest": "^5.2.0"
},
"dependencies": {
"ts-graphviz": "^0.13.1",
"viz.js": "^2.1.2"
},
"devDependencies": {
"@loopback/build": "^6.1.0",
"@loopback/core": "^2.9.1",
"@loopback/eslint-config": "^8.0.3",
"@loopback/rest": "^5.2.0",
"@loopback/testlab": "^3.2.0",
"@types/node": "^10.17.27"
},
Expand Down
5 changes: 4 additions & 1 deletion extensions/cron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"@loopback/core": "^2.9.1"
},
"dependencies": {
"@loopback/core": "^2.9.1",
"@types/cron": "^1.7.2",
"@types/debug": "^4.1.5",
"cron": "^1.8.2",
Expand All @@ -30,6 +32,7 @@
},
"devDependencies": {
"@loopback/build": "^6.1.0",
"@loopback/core": "^2.9.1",
"@loopback/eslint-config": "^8.0.3",
"@loopback/testlab": "^3.2.0",
"@types/node": "^10.17.27"
Expand Down
8 changes: 6 additions & 2 deletions extensions/health/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,19 @@
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"@loopback/core": "^2.9.1",
"@loopback/rest": "^5.2.0"
},
"dependencies": {
"@cloudnative/health": "^2.1.2",
"@loopback/core": "^2.9.1",
"@loopback/rest": "^5.2.0",
"tslib": "^2.0.0"
},
"devDependencies": {
"@loopback/build": "^6.1.0",
"@loopback/core": "^2.9.1",
"@loopback/eslint-config": "^8.0.3",
"@loopback/rest": "^5.2.0",
"@loopback/testlab": "^3.2.0",
"@types/node": "^10.17.27"
},
Expand Down
8 changes: 6 additions & 2 deletions extensions/logging/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
"publishConfig": {
"access": "public"
},
"dependencies": {
"peerDependencies": {
"@loopback/core": "^2.9.1",
"@loopback/rest": "^5.2.0",
"@loopback/rest": "^5.2.0"
},
"dependencies": {
"fluent-logger": "^3.4.1",
"morgan": "^1.10.0",
"tslib": "^2.0.0",
Expand All @@ -31,7 +33,9 @@
},
"devDependencies": {
"@loopback/build": "^6.1.0",
"@loopback/core": "^2.9.1",
"@loopback/eslint-config": "^8.0.3",
"@loopback/rest": "^5.2.0",
"@loopback/testlab": "^3.2.0",
"@types/morgan": "^1.9.1",
"@types/node": "^10.17.27"
Expand Down
8 changes: 6 additions & 2 deletions extensions/metrics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@
"publishConfig": {
"access": "public"
},
"dependencies": {
"peerDependencies": {
"@loopback/core": "^2.9.1",
"@loopback/rest": "^5.2.0",
"@loopback/rest": "^5.2.0"
},
"dependencies": {
"prom-client": "^12.0.0",
"tslib": "^2.0.0"
},
"devDependencies": {
"@loopback/build": "^6.1.0",
"@loopback/core": "^2.9.1",
"@loopback/eslint-config": "^8.0.3",
"@loopback/rest": "^5.2.0",
"@loopback/testlab": "^3.2.0",
"@types/express": "^4.17.7",
"@types/node": "^10.17.27",
Expand Down
5 changes: 4 additions & 1 deletion extensions/pooling/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,17 @@
"src",
"!*/__tests__"
],
"peerDependencies": {
"@loopback/core": "^2.9.1"
},
"dependencies": {
"@loopback/core": "^2.9.1",
"@types/generic-pool": "^3.1.9",
"generic-pool": "^3.7.1",
"tslib": "^2.0.0"
},
"devDependencies": {
"@loopback/build": "^6.1.0",
"@loopback/core": "^2.9.1",
"@loopback/testlab": "^3.2.0",
"@types/node": "^10.17.27",
"typescript": "~3.9.7"
Expand Down
11 changes: 8 additions & 3 deletions extensions/typeorm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,24 @@
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"@loopback/boot": "^2.3.3",
"@loopback/core": "^2.8.0",
"@loopback/rest": "^5.1.1"
},
"devDependencies": {
"@loopback/boot": "^2.3.3",
"@loopback/build": "^5.4.3",
"@loopback/core": "^2.8.0",
"@loopback/eslint-config": "^8.0.1",
"@loopback/repository": "^2.7.0",
"@loopback/rest": "^5.1.1",
"@loopback/testlab": "^3.1.7",
"@types/json-schema": "^7.0.5",
"@types/node": "^10.17.27",
"sqlite3": "^5.0.0"
},
"dependencies": {
"@loopback/boot": "^2.3.3",
"@loopback/core": "^2.8.0",
"@loopback/rest": "^5.1.1",
"tslib": "^2.0.0",
"typeorm": "^0.2.25"
},
Expand Down
8 changes: 6 additions & 2 deletions packages/authentication/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
"publishConfig": {
"access": "public"
},
"dependencies": {
"peerDependencies": {
"@loopback/core": "^2.9.1",
"@loopback/rest": "^5.2.0",
"@loopback/rest": "^5.2.0"
},
"dependencies": {
"@loopback/security": "^0.2.14",
"@types/express": "^4.17.7",
"@types/lodash": "^4.14.157",
Expand All @@ -34,8 +36,10 @@
},
"devDependencies": {
"@loopback/build": "^6.1.0",
"@loopback/core": "^2.9.1",
"@loopback/eslint-config": "^8.0.3",
"@loopback/openapi-spec-builder": "^2.1.9",
"@loopback/rest": "^5.2.0",
"@loopback/testlab": "^3.2.0",
"@types/node": "^10.17.27",
"jsonwebtoken": "^8.5.1"
Expand Down
5 changes: 4 additions & 1 deletion packages/authorization/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"@loopback/core": "^2.9.1"
},
"dependencies": {
"@loopback/core": "^2.9.1",
"@loopback/security": "^0.2.14",
"debug": "^4.1.1",
"tslib": "^2.0.0"
},
"devDependencies": {
"@loopback/build": "^6.1.0",
"@loopback/core": "^2.9.1",
"@loopback/testlab": "^3.2.0",
"@types/debug": "^4.1.5",
"@types/node": "10.17.27",
Expand Down
5 changes: 4 additions & 1 deletion packages/boot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"@loopback/core": "^2.9.1"
},
"dependencies": {
"@loopback/core": "^2.9.1",
"@loopback/model-api-builder": "^2.1.9",
"@loopback/repository": "^2.9.0",
"@loopback/service-proxy": "^2.3.4",
Expand All @@ -36,6 +38,7 @@
},
"devDependencies": {
"@loopback/build": "^6.1.0",
"@loopback/core": "^2.9.1",
"@loopback/eslint-config": "^8.0.3",
"@loopback/rest": "^5.2.0",
"@loopback/rest-crud": "^0.8.9",
Expand Down
10 changes: 5 additions & 5 deletions packages/booter-lb3app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"@loopback/boot": "^2.3.5",
"@loopback/core": "^2.9.1",
"@loopback/rest": "^5.2.0"
},
"dependencies": {
"@types/express": "^4.17.7",
"debug": "^4.1.1",
Expand All @@ -28,11 +33,6 @@
"swagger2openapi": "^6.2.1",
"tslib": "^2.0.0"
},
"peerDependencies": {
"@loopback/boot": "^2.3.5",
"@loopback/core": "^2.9.1",
"@loopback/rest": "^5.2.0"
},
"devDependencies": {
"@loopback/boot": "^2.3.5",
"@loopback/build": "^6.1.0",
Expand Down
Loading