5 of 6 standards met
Repository: facebook/relay. Description: Relay is a JavaScript framework for building data-driven React applications. Stars: 18936, Forks: 1879. Primary language: Rust. Languages: Rust (53%), JavaScript (36.4%), MDX (8.8%), HTML (1%), CSS (0.4%). License: MIT. Homepage: https://relay.dev Latest release: v20.1.1 (6mo ago). Open PRs: 100, open issues: 711. Last activity: 1h ago. Community health: 75%. Top contributors: kassens, alunyov, captbaritone, tyao1, josephsavona, rbalicki2, yungsters, wincent, leebyron, SamChou19815 and others.
Last 12 weeks ยท 202 commits
Summary In our monorepo at Microsoft, when using the (artifact directory) config to avoid cross-package artifact conflicts in a monorepo, type definitions are no longer co-located with their source fragments/queries. This breaks the developer experience for shared component packages. This proposal is adding an option to split each generated artifact into two files: a runtime artifact (placed either next to the source -same as current- or in the configured directory) and a types-only artifact (always placed next to the source file in the default folder with a in the name e.g. ). Problem In a monorepo where multiple apps share component packages but have different client resolvers, the config is necessary โ without it, each app's compiler writes different runtime artifacts to the same location in the shared package, and whichever runs last wins. However, using redirects all artifacts โ including type definitions โ away from the source files. This means: 1. Component packages lose TypeScript types โ no type information for fragments unless the compiler is manually run from within the component package. 2. Import ergonomics degrade โ types require long relative paths to the centralized directory (e.g., ) instead of co-located . 3. Storybook breaks โ to get types back into component packages, one could run a second compiler instance (without ) that writes artifacts cross-package. But those artifacts contain resolver references, which breaks Storybook. The root cause: the compiler generates a single artifact file containing both runtime code and type definitions, with no way to control where each part is written. Why is output needed in the first place? By default, artifacts go in next to the source file. In a monorepo this creates two problems: Artifact overwrite conflicts โ when and both compile against a shared component, they write to the same file. If the apps have different client resolvers, the runtime artifacts differ โ whichever compiler runs last wins. On CI where builds run in parallel, this produces incorrect production builds. Storybook resolver bleed โ when a host app's compiler writes artifacts into a component package, those artifacts reference client resolvers that the component package doesn't depend on. Storybook breaks trying to bundle resolver imports outside its dependency graph. Setting per app isolates the runtime artifacts, but at the cost of losing co-located types. Current workarounds (all have drawbacks) Proposed Solution: Artifact Splitting Add a config option that splits each artifact into two files: Runtime artifact โ directory (or default ). Contains the object (, , etc.) used at runtime. Types artifact โ always co-located in next to the source. Contains only TypeScript type exports (, , , etc.). Example: what the split files look like Runtime artifact (): Types artifact (): Why this works No overwrite conflicts โ each app's runtime artifacts live in their own directory; resolver references never bleed into component packages. Types always co-located โ type artifacts are identical regardless of which compiler produces them (types depend on fragment shape, not resolver implementations), so concurrent writes are safe. Storybook works โ component packages generate their own local runtime artifacts (no resolver imports); app compilers write to separate directories. Single compiler during dev โ running from the app package updates both app runtime artifacts and component type artifacts in one pass. We have implemented this in an internal fork of the Relay compiler and have been running it in our large production monorepo. Would be happy to contribute an upstream implementation. Detailed example with artifact splitting Package structure Compilation flow No conflicts โ Storybook never sees resolver imports from host apps. Fragment example Config examples App package (): Component package () โ no , co-locates everything: Compiler internals Architecture already supports splitting Artifact generation in builds an ordered list of objects: 1. Docblock + lint directives 2. Type imports and definitions (, , , ) 3. Runtime node () + hash + export Types (2) and runtime (3) are already constructed independently โ splitting means emitting them to different files instead of concatenating. Existing mechanism The compiler can already produce runtime-only artifacts. The complementary types-only artifact is the other half of the split. Path resolution already has both paths: path โ runtime artifacts Co-located โ type artifacts Naming convention โ runtime (backwards compatible) โ types only