Skip to content

Don't require rebuilds for downstream modules if module interface is unchanged #3724

@hdgarrood

Description

@hdgarrood

Currently, any change to a module will force all downstream modules to be recompiled. For example:

-- src/Upstream.purs
module Upstream where
info = 0

-- src/Downstream.purs
module Downstream where
import Upstream (info)
result = { info }

Suppose we compile this, and then update Upstream as follows:

--- a/src/Upstream.purs
+++ b/src/Upstream.purs
@@ -1,2 +1,2 @@
 module Upstream where
-info = 0
+info = 1

Then, the next time we compile, both Upstream and Downstream will be recompiled: Upstream because the source file changed, and Downstream because an upstream module changed. However, the changes to Upstream do not necessitate any changes to Downstream. I think skipping recompiling downstream modules has the potential to speed builds up quite dramatically in certain cases.

This will need a bit of careful thought and some changes to codegen and externs file formats, as I wrote in #3145 (comment):

Currently we implicitly depend on the current behaviour that if a module is rebuilt then all of its downstream modules must be rebuilt too in a few places, in the sense that information which is more than one hop away in the module dependency graph can leak through into the output artifacts. If we want to try to make things more granular we will need to address this first. The first example which comes to mind is re-exports, as re-exports are currently always generated as coming from the module they were defined in (as opposed to the module they were locally imported from). If module M1 exports foo, module M2 re-exports foo, and M3 imports foo from M2, then in the generated code for M3 we will have an import of M1. This means that if foo later moves to a different module (but continues to be re-exported from M2), and if M2 is determined not to need rebuilding, we could end up in a situation where the generated code for M3 still refers to M1.foo.

At the moment, when a module gets rebuilt, it has access to the externs files for every module which it depends on (directly or transitively). If/when we have addressed the above, and to ensure we've addressed this properly once and for all, I think we should try to have our build system only provide externs for the direct dependencies of any given module when that module is being built; that way, we make it easier not to end up accidentally leaking information through the module graph transitively, which then makes it easier to provide fast and safe incremental builds.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions