Skip to content

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 this PR). This is similar to the ssrTransformation mechanism in vite.

Contributors

Changelog

Discuss

Released under the CC BY-SA 4.0 License. (dbcbf17)