Patchwork: A Dart/Flutter Tool That Patches Third-Party Packages Without Forking
Flutter Patchwork: A Third-Party Tool for Modifying Dependency Package Source Code Without Forking
Actually, this is quite common in the JS ecosystem, similar to Node.js's patch-package. Patchwork is a dependency package patch management tool specifically designed for Dart/Flutter projects.
The functionality is actually very simple:
When you need to modify the source code of a third-party pub package (e.g., to fix a bug, temporarily add a feature), instead of forking the entire repository, you can simply use Patchwork to generate a
.patchfile and save it in your project.
That is, when you encounter a problem, you can directly fix it locally first, get it running, then sync it with your team, and finally, when you have time, turn the patch into a PR and submit it to the package maintainer.
| Command | Purpose |
|---|---|
dart run patchwork patch <package-name> |
Start an editable patch session, copy package source locally |
dart run patchwork patch --commit <package-name> |
Commit your edits as a .patch file |
dart run patchwork apply |
Apply all committed patches to the project |
dart run patchwork status |
View patch and override status |
dart run patchwork doctor |
Check if the local environment is ready (e.g., whether git is installed) |
So this patch file can be committed to git. Everyone can run patchwork apply to overlay and use it, while keeping pubspec.yaml unpolluted and the original third-party local dependencies unpolluted. You can also use the patch as a temporary fix before the official release.
Currently, the entire workflow is mainly divided into three stages. The first is creating a session via patchwork patch <package-name>:
dart run patchwork patch greeter
This command mainly does:
- Read
package_config.json(the pub resolution result) to find the actual path of the target package - Copy the package source code into two copies under
.dart_tool/patchwork/:- baseline (baseline copy):
baseline/pub/[email protected]/— never changes - edit (editable copy):
edit/pub/[email protected]/— we can modify the code here
- baseline (baseline copy):
Then sessions/pub/[email protected] writes session metadata (recording paths, versions, etc.):
.dart_tool/patchwork/
baseline/pub/[email protected]/ // original snapshot
edit/pub/[email protected]/ // user edits here
sessions/pub/[email protected]
Then comes the second stage: patchwork patch --commit <package-name> generates the patch file. It essentially generates diff information by calling the system git diff --no-index on the baseline/ and edit/ directories, producing standard unified diff format text:
final arguments = [
'diff', '--no-ext-diff', '--no-color',
'--src-prefix=a/', '--dst-prefix=b/',
'--no-index',
baselinePath,
editPath,
];
Then comes patch verification: in a temporary directory, copy the baseline again and use git apply --check to verify whether the generated patch can work correctly.
Finally, the third stage is patchwork apply. The general process is:
- Read
patchwork.lockto get all patch entries - Perform a hash check: compare with the
.patchwork-patch-hashmarker file in the store directory to determine if the already-applied copy is still up-to-date, avoiding duplicate application - If a rebuild is needed:
- Copy the original package source code into a temporary directory
tmp/ - Call
git apply --binaryto apply the patch file into the temporary copy - Write the hash marker file
- Atomically replace the
store/pub/[email protected]_patch_hash=xxxx/directory
- Copy the original package source code into a temporary directory
- Finally, write to
pubspec_overrides.yaml:
# Auto-generated, do not modify manually
dependency_overrides:
greeter:
path: .dart_tool/patchwork/store/pub/[email protected]_patch_hash=xxxx
After that, you just need to run
pub get, and pub will use the patched local copy via the path override. So the entire implementation is not particularly complex, but the idea is quite good.
At least it does not modify pubspec.yaml, only intrudes on pubspec_overrides.yaml. Only two things are committed to git: patchwork.lock and patches/pub/*.patch. Everything under .dart_tool/patchwork/ is reproducible generated modules. On failure, it can also automatically roll back (the commit stage has a file snapshot mechanism).
It's quite suitable for scenarios involving temporary fixes that require team collaboration and rapid packaging. After all, in the past, there really were people who directly modified the local package, fixed the bug, and then committed it — that kind of "genius". This Patchwork is actually somewhat useful.