Skip to content

Commit 22049d5

Browse files
Support dynamically loaded custom components (#804)
1 parent a7336fc commit 22049d5

File tree

15 files changed

+311
-30
lines changed

15 files changed

+311
-30
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ __pycache__/
33
# From yarn install
44
admin-js/yarn.lock
55
admin-js/node_modules/
6+
examples/demo/admin-js/yarn.lock
7+
examples/demo/admin-js/node_modules/
68
# Generated by yarn build
79
aiohttp_admin/static/admin.js
810
aiohttp_admin/static/*.js.map
11+
examples/demo/static/admin.js
12+
examples/demo/static/*.js.map
913
# coverage (when running pytest)
1014
.coverage

admin-js/src/App.js

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,66 @@
11
import {
2-
// App
3-
Admin, AppBar, InspectorButton, Layout, Resource, TitlePortal,
4-
// Create/Edit
5-
Create, DeleteButton, Edit, SaveButton, SimpleForm, Toolbar,
6-
// List
7-
Datagrid, DatagridConfigurable, List,
8-
// Show
9-
SimpleShowLayout, Show,
10-
// Actions
11-
BulkDeleteButton, BulkExportButton, BulkUpdateButton, CloneButton, CreateButton,
12-
ExportButton, FilterButton, ListButton, SelectColumnsButton, ShowButton, TopToolbar,
13-
// Fields
14-
BooleanField, DateField, NumberField, ReferenceField, ReferenceManyField,
15-
ReferenceOneField, SelectField, TextField,
16-
// Inputs
17-
BooleanInput, DateInput, DateTimeInput, NullableBooleanInput, NumberInput,
18-
SelectInput, TextInput,
19-
TimeInput as _TimeInput, ReferenceInput as _ReferenceInput,
20-
// Filters
2+
Admin, AppBar, AutocompleteInput,
3+
BooleanField, BooleanInput, BulkDeleteButton, Button, BulkExportButton, BulkUpdateButton,
4+
CloneButton, Create, CreateButton,
5+
Datagrid, DatagridConfigurable, DateField, DateInput, DateTimeInput, DeleteButton,
6+
Edit, EditButton, ExportButton,
7+
FilterButton, HttpError, InspectorButton,
8+
Layout, List, ListButton,
9+
NullableBooleanInput, NumberInput, NumberField,
10+
ReferenceField, ReferenceInput, ReferenceManyField, ReferenceOneField, Resource,
11+
SaveButton, SelectColumnsButton, SelectField, SelectInput, Show, ShowButton,
12+
SimpleForm, SimpleShowLayout,
13+
TextField, TextInput, TimeInput, TitlePortal, Toolbar, TopToolbar,
14+
WithRecord,
2115
email, maxLength, maxValue, minLength, minValue, regex, required,
22-
// Misc
23-
AutocompleteInput, EditButton, HttpError, WithRecord
16+
useCreate, useCreatePath, useDelete, useDeleteMany, useGetList, useGetMany,
17+
useGetOne, useInfiniteGetList, useGetRecordId, useInput, useNotify,
18+
useRecordContext, useRedirect, useRefresh, useResourceContext, useUnselect,
19+
useUnselectAll, useUpdate, useUpdateMany,
2420
} from "react-admin";
2521
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
2622

23+
window.ReactAdmin = {
24+
Admin, AppBar, AutocompleteInput,
25+
BooleanField, BooleanInput, BulkDeleteButton, Button, BulkExportButton, BulkUpdateButton,
26+
CloneButton, Create, CreateButton,
27+
Datagrid, DatagridConfigurable, DateField, DateInput, DateTimeInput, DeleteButton,
28+
Edit, EditButton, ExportButton,
29+
FilterButton, HttpError, InspectorButton,
30+
Layout, List, ListButton,
31+
NullableBooleanInput, NumberInput, NumberField,
32+
ReferenceField, ReferenceInput, ReferenceManyField, ReferenceOneField, Resource,
33+
SaveButton, SelectColumnsButton, SelectField, SelectInput, Show, ShowButton,
34+
SimpleForm, SimpleShowLayout,
35+
TextField, TextInput, TimeInput, TitlePortal, Toolbar, TopToolbar,
36+
WithRecord,
37+
email, maxLength, maxValue, minLength, minValue, regex, required,
38+
useCreate, useCreatePath, useDelete, useDeleteMany, useGetList, useGetMany,
39+
useGetOne, useInfiniteGetList, useGetRecordId, useInput, useNotify,
40+
useRecordContext, useRedirect, useRefresh, useResourceContext, useUnselect,
41+
useUnselectAll, useUpdate, useUpdateMany,
42+
};
43+
2744
// Hacked TimeField/TimeInput to actually work with times.
2845
// TODO: Replace once new components are introduced using Temporal API.
2946

30-
const TimeField = (props) => (
47+
const _TimeField = (props) => (
3148
<WithRecord {...props} render={
3249
(record) => <DateField {...props} showDate={false} showTime={true}
3350
record={{...record, [props["source"]]: record[props["source"]] === null ? null : "2020-01-01T" + record[props["source"]]}} />
3451
} />
3552
);
3653

37-
const TimeInput = (props) => (<_TimeInput format={(v) => v} parse={(v) => v} {...props} />);
54+
const _TimeInput = (props) => (<TimeInput format={(v) => v} parse={(v) => v} {...props} />);
3855

3956
/** Reconfigure ReferenceInput to filter by the displayed repr field. */
40-
const ReferenceInput = (props) => {
57+
const _ReferenceInput = (props) => {
4158
const ref = props["reference"];
4259
const repr = STATE["resources"][ref]["repr"];
4360
return (
44-
<_ReferenceInput sort={{"field": repr, "order": "ASC"}} {...props}>
61+
<ReferenceInput sort={{"field": repr, "order": "ASC"}} {...props}>
4562
<AutocompleteInput filterToQuery={s => ({[repr]: s})} />
46-
</_ReferenceInput>
63+
</ReferenceInput>
4764
);
4865
};
4966

@@ -64,10 +81,10 @@ const COMPONENTS = {
6481
ExportButton, FilterButton, ListButton, ShowButton,
6582

6683
BooleanField, DateField, NumberField, ReferenceField, ReferenceManyField,
67-
ReferenceOneField, SelectField, TextField, TimeField,
84+
ReferenceOneField, SelectField, TextField, TimeField: _TimeField,
6885

6986
BooleanInput, DateInput, DateTimeInput, NullableBooleanInput, NumberInput,
70-
ReferenceInput, SelectInput, TextInput, TimeInput
87+
ReferenceInput: _ReferenceInput, SelectInput, TextInput, TimeInput: _TimeInput
7188
};
7289
const FUNCTIONS = {email, maxLength, maxValue, minLength, minValue, regex, required};
7390
const _body = document.querySelector("body");

admin-js/src/index.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
import React from "react";
2-
import ReactDOM from "react-dom/client";
2+
import ReactJSXRuntime from "react/jsx-runtime";
3+
import ReactDOM from "react-dom";
4+
import ReactDOMClient from "react-dom/client";
5+
import {Link, Route, useLocation, useNavigate, useParams} from 'react-router-dom';
6+
import QueryString from 'query-string';
37
import {App, MODULE_LOADER} from "./App";
48

5-
const root = ReactDOM.createRoot(document.getElementById("root"));
9+
// Copy libraries to global location for shim.
10+
window.React = React;
11+
window.ReactJSXRuntime = ReactJSXRuntime;
12+
window.ReactDOM = ReactDOM;
13+
window.ReactDOMClient = ReactDOMClient;
14+
window.ReactRouterDOM = {Link, Route, useLocation, useNavigate, useParams};
15+
window.QueryString = QueryString;
16+
17+
const root = ReactDOMClient.createRoot(document.getElementById("root"));
618
MODULE_LOADER.then(() => {
719
root.render(
820
<React.StrictMode>

examples/demo/README

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
To build a custom component:
2+
3+
First we need to replace some of our dependencies with a shim (ensure shim/ is copied
4+
to your project directory).
5+
In package.json, update the dependencies which are available in the shim/ directory:
6+
7+
"react": "file:./shim/react",
8+
"react-admin": "file:./shim/react-admin",
9+
"react-dom": "file:./shim/react-dom",
10+
11+
Also repeat these in a 'resolutions' config:
12+
13+
"resolutions": {
14+
"react": "file:./shim/react",
15+
"react-admin": "file:./shim/react-admin",
16+
"react-dom": "file:./shim/react-dom",
17+
"react-router-dom": "file:./shim/react-router-dom",
18+
"query-string": "file:./shim/query-string"
19+
},
20+
21+
Using the shim for atleast react-admin is required, otherwise the components will
22+
end up using different contexts to the application and will fail to function.
23+
Using the shim for other libraries is recommended as it will significantly reduce
24+
the size of your compiled module.
25+
26+
27+
28+
Second, we need to ensure that it is built as an ES6 module. To achieve this, add
29+
craco to the dependencies:
30+
31+
"@craco/craco": "^7.1.0",
32+
33+
Then create a craco.config.js file:
34+
35+
module.exports = {
36+
webpack: {
37+
configure: {
38+
output: {
39+
library: {
40+
type: "module"
41+
}
42+
},
43+
experiments: {outputModule: true}
44+
}
45+
}
46+
}
47+
48+
And replace `react-scripts` with `craco` in the 'scripts' config:
49+
50+
"scripts": {
51+
"start": "craco start",
52+
"build": "craco build",
53+
"test": "craco test",
54+
"eject": "craco eject"
55+
},
56+
57+
58+
Then the components can be built as normal:
59+
60+
yarn install
61+
yarn build
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module.exports = {
2+
webpack: {
3+
configure: {
4+
output: {
5+
library: {
6+
type: 'module'
7+
}
8+
},
9+
experiments: {outputModule: true}
10+
}
11+
}
12+
}

examples/demo/admin-js/package.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"name": "admin-js",
3+
"version": "0.1.0",
4+
"private": true,
5+
"dependencies": {
6+
"@craco/craco": "^7.1.0",
7+
"@emotion/react": "^11.11.1",
8+
"@emotion/styled": "^11.11.0",
9+
"@mui/icons-material": "^5.14.14",
10+
"@mui/material": "^5.14.14",
11+
"@testing-library/jest-dom": "^5.14.1",
12+
"@testing-library/react": "^13.0.0",
13+
"@testing-library/user-event": "^13.2.1",
14+
"query-string": "file:./shim/query-string",
15+
"react": "file:./shim/react",
16+
"react-admin": "file:./shim/react-admin",
17+
"react-dom": "file:./shim/react-dom",
18+
"react-router-dom": "file:./shim/react-router-dom",
19+
"react-scripts": "5.0.1",
20+
"web-vitals": "^2.1.0"
21+
},
22+
"resolutions": {
23+
"react": "file:./shim/react",
24+
"react-admin": "file:./shim/react-admin",
25+
"react-dom": "file:./shim/react-dom",
26+
"react-router-dom": "file:./shim/react-router-dom",
27+
"query-string": "file:./shim/query-string"
28+
},
29+
"scripts": {
30+
"start": "craco start",
31+
"build": "craco build && (rm ../static/*.js.map || true) && mv build/static/js/main.*.js ../static/admin.js && mv build/static/js/main.*.js.map ../static/ && rm -rf build/",
32+
"test": "craco test",
33+
"eject": "craco eject"
34+
},
35+
"browserslist": {
36+
"production": [
37+
">0.2%",
38+
"not dead",
39+
"not op_mini all"
40+
],
41+
"development": [
42+
"last 1 chrome version",
43+
"last 1 firefox version",
44+
"last 1 safari version"
45+
]
46+
}
47+
}

examples/demo/admin-js/public/index.html

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = window.QueryString;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = window.ReactAdmin;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = window.ReactDOM;

0 commit comments

Comments
 (0)