Skip to content

Each JSX DOM element adds ~30 KB to the .cmt: omitted optional record fields become typed-tree expressions #9148

Closed

Description

@cknitt

Every JSX DOM element adds about 30 KB to its module's .cmt file. Its props record, JsxDOM.domProps, has 612 optional fields, and the typed tree holds a full expression node for each one the element doesn't set.

Where it comes from

When a record literal leaves out an optional field, the type checker fills that field with a synthesized None expression (compiler/ml/typecore.ml, the Overridden (…, option_none lbl.lbl_arg loc) case in the record-literal branch). Each synthesized expression has its own type instance, environment summary and location. They all reach the .cmt and are also translated to Lambda (compiler/ml/translcore.ml, Overridden (_, value) -> field (transl_exp value)), although the generated JavaScript contains only the props that were set.

A DOM element sets a few props and omits the remaining ~610, so each element adds about 610 expression nodes.

Measurements

These were taken on master 7249c11 with ordinary JSX (-bs-jsx 4), using a generated file of N elements like <div key="k1" className="c1"> {React.string("t1")} </div>. Times are user time, minimum of 3 runs; memory is peak RSS. The "without .cmt" columns use -bs-no-bin-annot.

Elements Source .cmt Typed-tree expressions Time with / without .cmt Memory with / without .cmt
50 3 KB 1.5 MB 31,201 0.05 s / 0.03 s 51 MB / 32 MB
200 13 KB 6.3 MB 124,801 0.20 s / 0.12 s 121 MB / 64 MB

The 50 record literals hold 30,600 field entries, 612 each. The "without .cmt" numbers still include typing the synthesized expressions and translating them to Lambda.

Existing tests show the same pattern:

  • tests/tests/src/async_jsx.res: 455 bytes of source, 139 KB .cmt (305×).
  • tests/tests/src/jsx_preserve_test.res: 5.4 KB of source, 912 KB .cmt (168×). 15,200 expressions in the typed tree; 49 record literals with 17,146 field entries.
  • For comparison, .cmt files of the runtime, Belt and the test suite average 11.5× their source size (median 12×).

Impact

  • Every React project pays this in every module with DOM elements: compile time, peak memory, and the disk and read time of the .cmt files, which the editor analysis, reanalyze and gentype load.
  • The same happens for any record literal that omits many optional fields, not only domProps; JSX is simply where large optional-field records are everywhere.

Repro

// Gen.res, compiled with -bs-jsx 4 against @rescript/react
let elements = [
  <div key="k1" className="c1"> {React.string("t1")} </div>,
  <div key="k2" className="c2"> {React.string("t2")} </div>,
  // … N elements
]

Compare the size of Gen.cmt with that of Gen.res, or count the expressions in the bsc -dtypedtree output.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions