Design flaws
1. Transform Hook
The hook judgment and execution logic in rollup
are all in the transform
hook, which means that the transform
hook of each hook will be executed once for each module. This is not advisable for native bundler
. If native bundler
also adopts this design, there will be performance issues. The native bundler
will frequently communicate with javascript
and call the transform
hook of javascript
, even if the processing logic of this module is not in the transform
hook of this plugin. The native bundler
calling single-threaded javascript
is a very expensive performance overhead, and the communication overhead will be enormous in scenarios with tens of thousands of module communications, especially under HMR
, which will be even more unacceptable.
Take esbuild
as an example, modules need to be filtered through filter
regex logic first, and the allowed regex syntax is the syntax supported by go
's regex engine. This is slightly different from javascript
, as go
's regex does not support look-ahead, look-behind, and backreferences. The design purpose of go
's regex engine is to avoid some performance issues that could lead to catastrophic exponential time worst-case scenarios in javascript
. If the filter
logic is hit, then go
will communicate with javascript
and callback the transform
hook of javascript
, achieving low performance overhead while avoiding a large amount of communication between the go
side and the javascript
side.
In the article Bundler's Design Trade-offs
, it is also mentioned that rspack
agrees with esbuild
's plugin and native communication design.
2. Incremental Build
rollup
's incremental update is similar to react
's incremental update of the fiber
tree, checking from top to bottom. For the entry module, resolveId
is called every time, and at the same time, every module will execute the load
hook. If a module hits the cache, it will reuse the transform product of the transform
hook of that module, and also reuse the resolveId
results of all dependent modules contained in that module.
With the above caching features, compared with the first build in watch
mode, it reduces the number of executions of resolveId
and transform
hooks for many unrelated modules, only executing the transform
hook for the entry module and changed modules, accelerating the hot update build speed in watch
mode. However, it is regrettable that every module needs to execute the load
hook, which is very performance-consuming in scenarios with a large number of modules.
From rollup issue 2182, rollup issue 3728, we can see that rollup
currently does not support persistent cache on disk space (Persistent Cache
), which means that rollup
currently only supports incremental updates in watch
mode, not incremental updates during cold start again. webpack
supports Persistent Cache
, which is also one of the reasons why webpack
outperforms rollup
in secondary cold start.
Vite Incremental Build
vite
has not yet implemented complete Persistent Cache
, only supporting Persistent Cache
for pre-build products. For details, see feat: Persistent cache-Jul 5, 2021. The reason may be related to changes in some configuration files causing cache invalidation, which needs to be considered more comprehensively. It also provides two caching ideas.
The first idea is to implement caching at the plugin level rather than at the entire dependency graph level. This approach allows for finer granularity and easier management of caching.
The second idea is to pre-transform all requests on the server side and implement a function similar to
import-analysis
. This function uses the hash value of the transform request as a query parameter and leverages the browser's strong caching mechanism. This method needs to recursively invalidate plugin cache when files change (through file watcher) and when the server restarts (as implemented in thisPR
). This is similar to thessrTransformation
mechanism invite
.