Excalidraw Onboarding LabChapter III — From index.tsx to <App>: how the editor gets mounted and configured0/6Contents
Chapter III

From index.tsx to <App>: how the editor gets mounted and configured

Before App.handleCanvasPointerDown can ever run, three layers have to boot in order: excalidraw-app/index.tsx (a Vite entry that just mounts the excalidraw.com website), packages/excalidraw/index.tsx (the public <Excalidraw> component every npm consumer imports), and the providers <Excalidraw> mounts above App (an isolated jotai store plus i18n loading). This module traces that boot sequence and the props/UIOptions normalization that happens along the way, and draws the line between 'the library' (packages/excalidraw) and 'the app built on it' (excalidraw-app) that recurs throughout the codebase.


walkthrough

excalidraw-app/index.tsx: the whole website, in 17 lines

excalidraw-app/index.tsx · lines 117
lines 1–7

ExcalidrawApp here is imported from ./App — this repo's own excalidraw-app/App.tsx, not the App class you traced in the drawing module (that one lives at packages/excalidraw/components/App.tsx). Same word, two different components at two different layers of the stack; keeping them straight is the whole point of this module.

lines 9–12

registerSW() registers the PWA service worker for offline support — a hosting concern of excalidraw.com specifically. Nothing under packages/excalidraw needs or knows about it.

lines 13–17

Standard React 18 bootstrapping: createRoot + render instantiate the excalidraw-app component tree. This is the entire 'app' in the sense of a Vite project — everything else, including the editor, is pulled in as a dependency starting here.

import { StrictMode } from "react";import { createRoot } from "react-dom/client";import { registerSW } from "virtual:pwa-register";import "../excalidraw-app/sentry";import ExcalidrawApp from "./App";window.__EXCALIDRAW_SHA__ = import.meta.env.VITE_APP_GIT_SHA;const rootElement = document.getElementById("root")!;const root = createRoot(rootElement);registerSW();root.render(  <StrictMode>    <ExcalidrawApp />  </StrictMode>,);
step 1 of 3
walkthrough

ExcalidrawBase's return: providers, then App

packages/excalidraw/index.tsx · lines 185236
lines 185–186

EditorJotaiProvider wraps everything below in editorJotaiStore — a jotai store built through jotai-scope's createIsolation(), so this editor instance's UI-state atoms are isolated from any jotai usage on the host page and from any other <Excalidraw> instance mounted alongside it.

line 187

InitializeApp only needs langCode and theme, not the full props object — it has one job (load the right language pack, show a loading screen meanwhile) and stays decoupled from everything else App is configured with.

lines 188–230

The <App> element receives the already-normalized UIOptions and normalizedImageOptions, plus handleExcalidrawAPI (a stable callback that fans a single API instance out to both the internal ExcalidrawAPISetContext setter and the caller's onExcalidrawAPI) and defaulted primitives like aiEnabled={aiEnabled !== false}. App's constructor never has to apply a single one of these defaults itself.

lines 231–236

{children} is the slot a caller's own JSX (passed as <Excalidraw>...</Excalidraw> children) lands in, still nested inside App's own context providers, before the three wrapper tags unwind and ExcalidrawBase's render finishes.

  return (    <EditorJotaiProvider store={editorJotaiStore}>      <InitializeApp langCode={langCode} theme={theme}>        <App          onExport={onExport}          onChange={onChange}          onThemeChange={onThemeChange}          onIncrement={onIncrement}          initialData={initialData}          initialState={initialState}          onExcalidrawAPI={handleExcalidrawAPI}          onMount={onMount}          onUnmount={onUnmount}          onInitialize={onInitialize}          isCollaborating={isCollaborating}          onPointerUpdate={onPointerUpdate}          renderTopLeftUI={renderTopLeftUI}          renderTopRightUI={renderTopRightUI}          langCode={langCode}          viewModeEnabled={viewModeEnabled}          zenModeEnabled={zenModeEnabled}          gridModeEnabled={gridModeEnabled}          libraryReturnUrl={libraryReturnUrl}          theme={theme}          name={name}          renderCustomStats={renderCustomStats}          UIOptions={UIOptions}          onPaste={onPaste}          detectScroll={detectScroll}          handleKeyboardGlobally={handleKeyboardGlobally}          onLibraryChange={onLibraryChange}          autoFocus={autoFocus}          generateIdForFile={generateIdForFile}          onLinkOpen={onLinkOpen}          generateLinkForSelection={generateLinkForSelection}          onPointerDown={onPointerDown}          onPointerUp={onPointerUp}          onScrollChange={onScrollChange}          onDuplicate={onDuplicate}          validateEmbeddable={validateEmbeddable}          renderEmbeddable={renderEmbeddable}          aiEnabled={aiEnabled !== false}          showDeprecatedFonts={showDeprecatedFonts}          renderScrollbars={renderScrollbars}          imageOptions={normalizedImageOptions}        >          {children}        </App>      </InitializeApp>    </EditorJotaiProvider>  );};
step 1 of 4
checkpoint

Check your understanding

Answered in place — nothing is graded, everything is explained. 0 / 3 passed

Which component is directly responsible for delaying render (showing a loading screen) until the requested language pack has loaded, before the <App> element beneath it appears?

excalidraw-app's <Excalidraw> call only sets UIOptions.canvasActions.toggleTheme and .export. Why does App still see values for changeViewBackgroundColor, clearCanvas, loadScene, etc.?

Where is the real-time collaboration (Collab) code defined?