|
| 1 | +# Component System |
| 2 | + |
| 3 | +The component system bridges React components to Python with auto-generated wrappers. |
| 4 | + |
| 5 | +## Generation Pipeline |
| 6 | + |
| 7 | +``` |
| 8 | +React/TypeScript Source |
| 9 | + ↓ |
| 10 | + extract-meta.js (Node.js) |
| 11 | + ├── react-docgen (for .js/.jsx - parses PropTypes) |
| 12 | + └── TypeScript Compiler API (for .tsx - parses type definitions) |
| 13 | + ↓ |
| 14 | + metadata.json |
| 15 | + ↓ |
| 16 | + dash-generate-components (Python CLI) |
| 17 | + ↓ |
| 18 | + Python component classes (+ R/Julia if requested) |
| 19 | +``` |
| 20 | + |
| 21 | +### Key Files |
| 22 | + |
| 23 | +- **`dash/extract-meta.js`** - Node.js script that extracts component metadata. For JavaScript components, uses `react-docgen` to parse PropTypes. For TypeScript components, uses the TypeScript Compiler API to parse type definitions and convert them to metadata format. |
| 24 | + |
| 25 | +- **`dash/development/component_generator.py`** - CLI entry point (`dash-generate-components`). Orchestrates metadata extraction and code generation. |
| 26 | + |
| 27 | +- **`dash/development/_py_components_generation.py`** - Generates Python class files from metadata. Creates typed `__init__` methods, docstrings, and prop validation. |
| 28 | + |
| 29 | +- **`dash/development/_py_prop_typing.py`** - Maps JavaScript/TypeScript types to Python types (e.g., `arrayOf` → `typing.Sequence`, `shape` → `TypedDict`). |
| 30 | + |
| 31 | +- **`dash/development/_generate_prop_types.py`** - For TypeScript components, generates a `proptypes.js` file since TSX doesn't have runtime PropTypes. |
| 32 | + |
| 33 | +## Component JSON Structure |
| 34 | + |
| 35 | +Components serialize to `{type, namespace, props}` via `to_plotly_json()` in `base_component.py`: |
| 36 | + |
| 37 | +```python |
| 38 | +# Python component |
| 39 | +html.Div(id='my-div', children='Hello') |
| 40 | + |
| 41 | +# Serializes to JSON |
| 42 | +{ |
| 43 | + "type": "Div", |
| 44 | + "namespace": "dash_html_components", |
| 45 | + "props": { |
| 46 | + "id": "my-div", |
| 47 | + "children": "Hello" |
| 48 | + } |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +This JSON is sent to the frontend via `/_dash-layout` (initial load) and `/_dash-update-component` (callback responses). |
| 53 | + |
| 54 | +## Frontend Component Resolution |
| 55 | + |
| 56 | +Components must be available on the frontend at `window[namespace][type]`: |
| 57 | + |
| 58 | +```javascript |
| 59 | +// Component packages register themselves |
| 60 | +window.dash_html_components = { |
| 61 | + Div: DivComponent, |
| 62 | + Span: SpanComponent, |
| 63 | + // ... |
| 64 | +}; |
| 65 | + |
| 66 | +window.dash_core_components = { |
| 67 | + Dropdown: DropdownComponent, |
| 68 | + Graph: GraphComponent, |
| 69 | + // ... |
| 70 | +}; |
| 71 | +``` |
| 72 | + |
| 73 | +The renderer resolves components via `registry.js`: |
| 74 | + |
| 75 | +```javascript |
| 76 | +resolve: (component) => { |
| 77 | + const {type, namespace} = component; |
| 78 | + return window[namespace][type]; // Returns React component class |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +## Package Structure |
| 83 | + |
| 84 | +### `_imports_.py` |
| 85 | + |
| 86 | +Auto-generated by `generate_imports()` in `_py_components_generation.py`: |
| 87 | + |
| 88 | +```python |
| 89 | +from .Dropdown import Dropdown |
| 90 | +from .Graph import Graph |
| 91 | +from .Input import Input |
| 92 | +# ... one import per component |
| 93 | + |
| 94 | +__all__ = [ |
| 95 | + "Dropdown", |
| 96 | + "Graph", |
| 97 | + "Input", |
| 98 | + # ... |
| 99 | +] |
| 100 | +``` |
| 101 | + |
| 102 | +### `__init__.py` |
| 103 | + |
| 104 | +Manually maintained, imports from `_imports_.py`: |
| 105 | + |
| 106 | +```python |
| 107 | +from ._imports_ import * # Re-exports all components |
| 108 | +from ._imports_ import __all__ as _components |
| 109 | + |
| 110 | +# Read version from package-info.json |
| 111 | +with open(os.path.join(_basepath, "package-info.json")) as f: |
| 112 | + package = json.load(f) |
| 113 | +__version__ = package["version"] |
| 114 | + |
| 115 | +# Define JavaScript assets to serve |
| 116 | +_js_dist = [ |
| 117 | + { |
| 118 | + "relative_package_path": "dash_core_components.js", |
| 119 | + "namespace": "dash", |
| 120 | + }, |
| 121 | + # async chunks, source maps, proptypes.js for dev, etc. |
| 122 | +] |
| 123 | + |
| 124 | +# Attach _js_dist to each component class |
| 125 | +for _component in _components: |
| 126 | + setattr(locals()[_component], "_js_dist", _js_dist) |
| 127 | +``` |
| 128 | + |
| 129 | +## Resource System (`_js_dist` / `_css_dist`) |
| 130 | + |
| 131 | +**`dash/resources.py`** manages JavaScript and CSS asset loading for components. |
| 132 | + |
| 133 | +### Resource Entry Structure |
| 134 | + |
| 135 | +```python |
| 136 | +{ |
| 137 | + "relative_package_path": "dcc/dash_core_components.js", # Path within package |
| 138 | + "external_url": "https://unpkg.com/...", # CDN fallback |
| 139 | + "namespace": "dash", # JS namespace |
| 140 | + "async": True | "eager" | "lazy", # Async loading mode |
| 141 | + "dynamic": True, # Loaded on demand (source maps) |
| 142 | + "dev_package_path": "dcc/proptypes.js", # Dev-only path |
| 143 | + "dev_only": True, # Only in dev mode |
| 144 | +} |
| 145 | +``` |
| 146 | + |
| 147 | +### Resource Loading Flow |
| 148 | + |
| 149 | +1. Each component class has `_js_dist` (and optionally `_css_dist`) attribute set in `__init__.py` |
| 150 | +2. When component is imported, `ComponentMeta` registers module in `ComponentRegistry.registry` |
| 151 | +3. `ComponentRegistry.get_resources("_js_dist")` iterates registered modules, collects all `_js_dist` lists |
| 152 | +4. `Scripts` / `Css` classes in `resources.py` filter resources based on config: |
| 153 | + - `serve_locally=True`: Use `relative_package_path`, serve via `/_dash-component-suites/` |
| 154 | + - `serve_locally=False`: Use `external_url` (CDN) |
| 155 | + - `eager_loading=True`: Load async resources immediately |
| 156 | + - `dev_bundles=True`: Include `dev_package_path` resources |
| 157 | + |
| 158 | +### Async Loading Modes |
| 159 | + |
| 160 | +- `async: True` - Dynamic unless `eager_loading` is enabled |
| 161 | +- `async: "lazy"` - Always loaded dynamically (on-demand) |
| 162 | +- `async: "eager"` - Loaded dynamically only if server isn't in eager mode |
| 163 | + |
| 164 | +## Creating New Components |
| 165 | + |
| 166 | +1. Write React component with PropTypes (JS) or TypeScript props interface (TSX) |
| 167 | +2. Run `dash-generate-components src/lib/components -p package_name` |
| 168 | +3. Generated Python wrapper goes to `package_name/ComponentName.py` |
| 169 | +4. `_imports_.py` is auto-generated with imports for all components |
| 170 | +5. For TSX, `proptypes.js` is also generated for runtime prop validation |
| 171 | +6. Bundle with webpack, register on `window[namespace]` |
| 172 | +7. Update `__init__.py` to set `_js_dist` on components |
| 173 | + |
| 174 | +## Built-in Component Packages |
| 175 | + |
| 176 | +Managed as a Lerna monorepo in `components/`: |
| 177 | + |
| 178 | +- **`components/dash-core-components/`** - Interactive components (Dropdown, Slider, Graph, Input, etc.) |
| 179 | +- **`components/dash-html-components/`** - HTML element wrappers (Div, Span, H1, etc.) |
| 180 | +- **`components/dash-table/`** - DataTable component (deprecated in favor of dash-ag-grid) |
| 181 | + |
| 182 | +Use `dash-update-components "component-name"` to rebuild after changes. |
0 commit comments