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

[pub]: Support patch-package like npm #4509

Open
Ahmadre opened this issue Feb 2, 2025 · 5 comments
Open

[pub]: Support patch-package like npm #4509

Ahmadre opened this issue Feb 2, 2025 · 5 comments
Labels
type-enhancement A request for a change that isn't a bug

Comments

@Ahmadre
Copy link

Ahmadre commented Feb 2, 2025

Feature Request

Today I was helping a friend of mine which was developing for the first time with Flutter & Dart and he was seeing an issue with a nullable bug in a package he was using in his flutter project.

He asked me how he could instantly fix this issue locally, without forking the repository and all this overhead and without cloning the repo locally and referencing to it with pubspec.yaml?

And he was asking about if there's a patch-package like npm (see: https://github.com/ds300/patch-package)?

I was kind of surprised as a long term flutter developer since 2019, that we don't have patch-package like in nodejs projects 🤔 .

As a reference how exactly that works: https://dev.to/zhnedyalkow/the-easiest-way-to-patch-your-npm-package-4ece

Can we please also have patch-package functionality for dart pub? It would be nice if these quick patches could be applied as a post-hook after dart pub get automatically.

Environment details

Dart info:

#### General info

- Dart 3.7.0-267.0.dev (dev) (Wed Dec 18 12:05:21 2024 -0800) on "windows_x64"
- on windows / "Windows 11 Pro" 10.0 (Build 22631)
- locale is de-DE

#### Project info

- sdk constraint: '>=3.3.4 <4.0.0'
- dependencies: args, collection, cupertino_icons, dcli, equatable, flutter, font_awesome_flutter, path, provider, scrollview_observer
- dev_dependencies: flutter_lints, icon_font_generator

#### Process info

| Memory | CPU | Elapsed time | Command line |
| -----: | --: | -----------: | ------------ |
|   0 MB |  -- |              | dart.exe     |
|   0 MB |  -- |              | dart.exe     |
|   0 MB |  -- |              | dart.exe     |
|   0 MB |  -- |              | dart.exe     |
|   0 MB |  -- |              | dart.exe     |

Using following platforms:

  • Windows
  • macOS
  • Linux

Using following browsers:

  • Chrome
  • Firefox
@Ahmadre Ahmadre changed the title [pub]: Support patch-package like node/npm [pub]: Support patch-package like npm Feb 2, 2025
@FibreFoX
Copy link

FibreFoX commented Feb 3, 2025

Hi everyone, I was the one asking @Ahmadre about this on my adventure to learn Flutter. I have a strong Java-background and came to the NodeJS/TypeScript-world, where patches are kinda needed a lot.

In case not everyone knows about the working of patch-packages, it uses the way how NodeJS works with dependencies. These are stored in a directory called node_modules, but can be modified "by hand" and thats what patch-package does. It uses .patch-files (which are stored in a project-specific directory), that only contain the changes for a single dependency for that exact version.

To create that .patch-file, you download packages normally, then modify the package as needed, and then you run patch-package PACKAGENAME. Simplified process is to download the same dependency into a tmp-project, compare that downloaded version with the current-project version, and then writing the DIFF into a patch-file. To detect version differences, the filename of the .patch-file does contain the version it was made for.

This way you only have to store the actual patches inside a project-specific folder, and not to modify the system pub cache (like the current existing https://pub.dev/packages/patch_package does).

It would need some kind of execution-hook to run after dart pub get, which then modifies the package in a project-local copy. Maybe this would get rid of a lot of duplicated code or forked git-projects (because often only a few lines needs to be changed).

@sigurdm
Copy link
Contributor

sigurdm commented Feb 3, 2025

I somewhat like the idea of having a set of local patches to apply on top of a package.

It seems to me that patch-package is an external package, and not something maintained by npm itself.

I think something similar could be build for pub.

Pub stores all hosted packages in the PUB_CACHE which is considered immutable and shared between therefore it should not be modified directly.

But for example https://pub.dev/packages/vendor allows you to take a hosted package and inline it into a third_party folder.
It doesn't currently have functionality for applying patches - but that could be built I believe.

@jonasfj might have more ideas.

(It is of course also possible to just use a dependency override and a git dependency to do this - that would probably be the preferred workflow in most cases.)

@sigurdm sigurdm added the type-enhancement A request for a change that isn't a bug label Feb 3, 2025
@FibreFoX
Copy link

FibreFoX commented Feb 3, 2025

Yes, thats an external created "addon" for a lot of NodeJS projects, but NodeJS allows to have "postinstall"-hooks, where packages can register themselves to execute custom commands (of course this can be an attack vector on its own).

Having the functionality of having patches only, reduces code-duplication (be it by forking or having copying a local copy of the 3rd party lib, and using dependency_overrides) and makes it so much cleaner in general.
And it might help to avoid "noise" when so many forks are created "just because it's the normal way".

@sigurdm
Copy link
Contributor

sigurdm commented Feb 3, 2025

Having the functionality of having patches only, reduces code-duplication (be it by forking or having copying a local copy of the 3rd party lib, and using dependency_overrides)

Each to his own - I would much prefer a git clone with layered commits that support merges.

but NodeJS allows to have "postinstall"-hooks

Yeah - we don't have any plans of adding post-install hooks. You would have to run a separate command (you could though make one command wrapping pub get and applying the patches)

@jonasfj
Copy link
Member

jonasfj commented Feb 14, 2025

@sigurdm I'm not sure we should completely dismiss the idea of post-install hooks in the root-package.

Example:

  • if myapp depends on foo, then
    • pub get will:
      • run the post-install hook from myapp
      • but not run the post-install hook from foo.

Ofcourse this means that patch-packages like this:

  • only works on the root-package, and,
  • requires that the root-package author manually registers a post-install that calls patch-package.

There could be some overlap with build hooks here, so maybe we shouldn't jump ahead and do it just yet.


On topic, I haven't seen the patch-package concept before. I've done a bunch of node.js stuff a few years ago, but I think I just monkey patched things 🤣

I did steal the idea for package:vendor from how golang used to do dependency vendoring (which was a bit crazy).
But the idea of using package:vendor to download and extract a couple of dependencies into your repository is not crazy.

I think it would be cool to extend package:vendor with an upgrade feature, where it would:

  • Remove import-rewrites,
  • Download old version of the vendored package,
  • Create a patch of changes made to the vendored package,
  • Download new version of the vendored package,
  • Apply patch from old version,
  • Apply import-rewrites,
  • Ask user to fix any conflicts.

What package:vendor does is currently just to:

  • Download a package into lib/src/third_party/<package>/
  • Write import-paths such that the new location works
    (this won't work for native plugins and things like that)
  • Track state of vendored packages in vendor-state.yaml.

Having vendor-state.yaml means that running dart run vendor won't delete any patches you've made to a vendored package, so long as you don't change the entry for the vendored package in vendor.yaml.

But if you change the version of a vendored package in vendor.yaml, then dart run vendor will detect the change and plan to:

  • delete the vendored package,
  • download/extract, and,
  • rewrite import-paths.

It will always ask for confirmation before executing the plan. But if dart run vendor could be made to extract a patch and then re-apply the patch after upgrading that would make patching packages with package:vendor a lot easier.


Yes, using package:vendor is not as elegant as having a .patch file that is automagically applied by a post-install hook.
It does have some down sides:

  • It won't work well for Flutter plugins (and probably not flutter packages with assets).
  • It rewrites your import paths.

But it does have some upsides:

  • You can use it inside a package published to pub.dev (you should be careful when doing this though).
  • It's simple, team-mates don't really need to know about it unless they play around inside lib/src/third_party/...
  • If you don't want to use package:vendor anymore, you just leave the source it generated in place, and things are fine.

Personally, I'm not sure I'd love to manage patch files with git. It works for small things. And while it will scale surprisingly far, it can become very painful.


Honestly, if you're not publishing you package to pub.dev, suppose you're developing a Flutter application, I'd consider:

  • (A) Put a git submodule of the forked package in third_party/<package>/ and use a path-dependency in dependency_overrides.
  • (B) Use dart pub unpack to download the package into third_party/<package>/ and use a path-dependency in dependency_overrides.

Both of these options are painful in their own ways:

  • (A) means you have to use submodules, but from what I've seen this git feature is pretty decent these days.
  • (B) means that upgrading your patched dependency becomes a bit of a pain, and you'll effective have a copy of the dependency in your repository. On the other hand, it's simple!

I haven't seen the patch-package concept yet, but it's somewhat interesting. It could certainly work for pub, we'd just need to copy the package from PUB_CACHE to .dart_tool/patched-packages/<package>/ and rewrite package_config.json.

Though, if it were to happen in a third-party tool rewriting package_config.json is a bad idea, as pub get would just overwrite it again. And pub get would have bad logic for detecting if dependencies are up-to-date. So it might be better to add a dependency_override.

I guess that's another problem with a post-install hook, if such a post-install hook modifies package_config.json, then pub get might not be able to detect that no changes are necessary, and so when you save your pubspec.yaml, even for trivial changes, the editor will run pub get, which will then create a new resolution and trigger the post-install hook again. With all of this happening, it might also cause the analysis server to throw away all state, so you'll wait for a new complete analysis before you get auto-completion again.

Just saying: post-install hook can have negative implication for the developer user experience, it would certainly make hot-reload harder / slower, unless it just ignored it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

4 participants