* @param {Compiler | MultiCompiler} compiler compiler * @returns {void}
(compiler)
| 3742 | * @returns {void} |
| 3743 | */ |
| 3744 | apply(compiler) { |
| 3745 | this.compiler = compiler; |
| 3746 | this.isPlugin = true; |
| 3747 | this.logger = this.compiler.getInfrastructureLogger(pluginName); |
| 3748 | |
| 3749 | /** @type {Promise<void> | undefined} */ |
| 3750 | let setupPromise; |
| 3751 | let inWatchMode = false; |
| 3752 | let listening = false; |
| 3753 | let stopped = false; |
| 3754 | |
| 3755 | // `setup()` boots webpack-dev-middleware, which replaces the compiler's |
| 3756 | // `outputFileSystem` with an in-memory one. That swap has to happen before |
| 3757 | // the first compilation writes its assets, otherwise the first build lands |
| 3758 | // on the real disk — so it runs on `watchRun`, at the start of a watch run, |
| 3759 | // before anything is emitted. Guarded so the async `setup()` runs at most |
| 3760 | // once across rebuilds. |
| 3761 | /** |
| 3762 | * @returns {Promise<void>} promise |
| 3763 | */ |
| 3764 | const ensureSetup = () => { |
| 3765 | if (!setupPromise) { |
| 3766 | setupPromise = this.setup(); |
| 3767 | } |
| 3768 | return setupPromise; |
| 3769 | }; |
| 3770 | |
| 3771 | // `watchRun` and `done` are tapped on the compiler directly — no iteration |
| 3772 | // over `MultiCompiler.compilers`: |
| 3773 | // - `watchRun` is a `MultiHook` on a `MultiCompiler`, so the tap is forwarded |
| 3774 | // to every child and awaited. It stays `tapPromise` so a failing `setup()` |
| 3775 | // rejects the user's `watch()` callback. It only fires in watch mode, which |
| 3776 | // is how we know a one-shot `compiler.run()` build is not in play. |
| 3777 | // - `done` on a `MultiCompiler` is the aggregate `SyncHook` that fires once |
| 3778 | // after every child finishes, so the server starts exactly once. Being a |
| 3779 | // `SyncHook`, it can only be `tap`ped; `listen()` runs detached. |
| 3780 | const { hooks } = /** @type {Compiler} */ (compiler); |
| 3781 | |
| 3782 | hooks.watchRun.tapPromise(pluginName, () => { |
| 3783 | inWatchMode = true; |
| 3784 | return ensureSetup(); |
| 3785 | }); |
| 3786 | |
| 3787 | hooks.done.tap(pluginName, () => { |
| 3788 | // `done` also fires for a one-shot `compiler.run()` build, where no |
| 3789 | // `watchRun` ran; staying passive lets that build finish and exit. |
| 3790 | if (listening || !inWatchMode) return; |
| 3791 | listening = true; |
| 3792 | ensureSetup() |
| 3793 | .then(() => this.listen()) |
| 3794 | .catch((error) => { |
| 3795 | this.logger.error(error); |
| 3796 | }); |
| 3797 | }); |
| 3798 | |
| 3799 | /** |
| 3800 | * @returns {Promise<void>} promise |
| 3801 | */ |
no test coverage detected