- Issue created by @balintbrews
- First commit to issue fork.
- 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺
@hooroomoo We're working on ✨ Store and calculate dependencies in `JavaScriptComponent` Active , and would like you to confirm that the client side can determine the
JavaScriptComponent
config entities' machine names based only on the JS source code of the code component.Is that right? 🤓
Crediting @tedbow and @effulgentsia for the discussion that led to that!
- 🇺🇸United States hooroomoo
@wim leers No it can't determine machine name from only the JS source code. What prompts this question?
Here I have a component I named "My Component" and the machine name the backend sets is "my_component" but the machine name is not in the source code.
- 🇺🇸United States hooroomoo
Unless are you talking about if the frontend can get the machine names of imported JS components from its source code? Then the answer is yes.
I believe there is an expectation that the author has to write imports in the JS code editor like this:
import Whatever from "@/components/button" import FooWhatever from "@/components/heading"
Where the import statement has to be "@/components/machineName"
- 🇳🇱Netherlands balintbrews Amsterdam, NL
While on a call with @hooroomoo and @tedbow, it became clear to me that one piece is missing in the plan. We need to make sure the code editor preview can also understand these imports. Here is an idea.
-
Extend the code editor preview's import map by adding every imported component's compiled JS code in a blob URL — something like this:
diff --git a/ui/src/features/code-editor/Preview.tsx b/ui/src/features/code-editor/Preview.tsx index 9f433faf..6a18e787 100644 --- a/ui/src/features/code-editor/Preview.tsx +++ b/ui/src/features/code-editor/Preview.tsx @@ -79,6 +79,22 @@ const importMap = { 'class-variance-authority': 'https://esm.sh/class-variance-authority', 'tailwind-merge': 'https://esm.sh/tailwind-merge', '@/lib/utils': `${XB_MODULE_UI_PATH}/lib/astro-hydration/dist/utils.js`, + '@/components/button': URL.createObjectURL( + new Blob( + [ + ` + import { jsx as _jsx } from "react/jsx-runtime"; + const Button = ({ children = "Button" }) => { + return /*#__PURE__*/ _jsx("button", { + children: children + }); + }; + export default Button; + `, + ], + { type: 'text/javascript' }, + ), + ), }, };
As part of this issue, we will already know what other components are imported, and we can fetch their compiled JS code. The trick is to grab the auto-saved version if it exists. Use code from
ui/src/features/code-editor/hooks/useCodeComponentData.ts
as example. - Also add the CSS from the imported code components to the preview iFrame, similar to how we add CSS now in
ui/src/features/code-editor/Preview.tsx
. This will result in a lot of duplicated CSS, but we'll rectify that in 📌 Compile Tailwind CSS globally for code components Active .
We could extract this to another issue, but this one can't work without it anyway, so I'd suggest to include it in the scope here.
-
- 🇺🇸United States effulgentsia
For #11.1, you should be able to just do this:
'@/components/': Drupal.url('xb/api/auto-saves/js/js_component/')
And thereby not need a separate import map entry for every component.
- 🇳🇱Netherlands balintbrews Amsterdam, NL
#12: That's amazing, thank you! 👏🏻
When there is no auto-saved version, that endpoint does a
307
redirect to the static JS file. I wonder if the browser will like that. But we'll find out soon! Or maybe you already know. 🙂 - 🇺🇸United States hooroomoo
@balintbrews I ended up with the same idea before I even saw your comments. That's validating to me haha
@effulgentsia Thanks!! Was running into a security error on my very WIP-y test commit, need to give that a try
- 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺
So then … the answer to my question at #8 is:
import Whatever from "@/components/button" import Whatever2 from "@/components/asdfsdf"
would result in the client sending
imported_js_components: ["button", "asdfsdf"]
to the server, and the client can compute it from only the
source_code_js
it received.Is that right?
A definitive answer to this is blocking ✨ Store and calculate dependencies in `JavaScriptComponent` Active .
- 🇳🇱Netherlands balintbrews Amsterdam, NL
#16: That's right, that's what's being implemented in this issue.
- 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺
Lee's screenshot is 🤩 — great work, @hooroomoo!
Didn't test it, really trust @larowlan's review and wanted to go ahead and merge, but … I reviewed this MR anyway because I reviewed the back-end counterpart (#3518185), and I ended up finding a few things where it's not clear how to enter the failure mode that the code is protecting against.
Either the protection is unnecessary, or the logic + error message can be tightened some, which would leave the codebase in a clearer state for future iteration 🙏
(Also: Lee also had one remark to address 😇)
-
hooroomoo →
committed 1ddd0aba on 0.x
Issue #3518198 by hooroomoo, balintbrews, wim leers, effulgentsia,...
-
hooroomoo →
committed 1ddd0aba on 0.x
- 🇺🇸United States hooroomoo
GIF in https://www.drupal.org/project/experience_builder/issues/3518198#comment... ✨ Set code component dependencies based on import statements Active
- 🇺🇸United States hooroomoo
Testing steps with example components
1. Create a new code component named 'My Button'
2. Add this code and also add a prop named 'name' of type 'text' in the component data windowconst MyButton = ({name}) => { return ( <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> {name} </button> ); }; export default MyButton;
3. Create a second code component named 'My Card'
4. Add this code:import MyButton from '@/components/my_button' const MyCard = () => { return ( <div className="max-w-sm rounded overflow-hidden shadow-lg bg-orange-500 p-4"> <div className="font-bold text-xl mb-2">Card Title</div> <p className="text-gray-700 text-base"> Here is a simple description of what this card is about. Add anything you like here. </p> <div className="pt-4"> <MyButton name="FOOOO"/> </div> </div> ); }; export default MyCard;
5. MyCard component imports the button component created in step 1 and you can pass whatever name into the name prop of the button component