- Issue created by @effulgentsia
- First commit to issue fork.
- πΊπΈUnited States effulgentsia
Monaco is a component of VSCode, but VSCode does a lot more than Monaco. https://code.visualstudio.com/docs/editor/vscode-web is an interesting option, but does it work in a cross-origin iframe?
- First commit to issue fork.
- First commit to issue fork.
- πΊπΈUnited States effulgentsia
the two leading options are CodeMirror and Monaco
https://npmtrends.com/@codemirror/view-vs-monaco-editor shows how basically tied they are in terms of npm downloads.
we'll need to choose which one to try out first (we can always switch to the other one later if we learn that our initial choice wasn't the best one)
Monaco has some appeal just because it's backed by Microsoft and it's the "just the editor" portion of VSCode and VSCode is great. However, there's a few downsides:
- Its version is still 0.x and has been for years. I'm not aware of any announcement as to whether a 1.0 release is even remotely close.
- https://microsoft.github.io/monaco-editor/ says it's not supported on mobile.
- https://blog.replit.com/code-editors is a blog post that's a few years old now, so not sure how much of it is still accurate, but it points out some other challenges with Monaco.
I think this makes CodeMirror the better option at least for now, but I'm curious what others think.
- Merge request !384Draft: #3483267: [exploratory] PoC of Preact+Tailwind components editable via CodeMirror or Monaco β (Open) created by hooroomoo
- π³π±Netherlands balintbrews Amsterdam, NL
This is still very much in progress, but here is a sneak peek into where we are currently.
- π¬π§United Kingdom f.mazeikis Brighton
balintbrews β credited f.mazeikis β .
- πΊπΈUnited States tedbow Ithaca, NY, USA
balintbrews β credited tedbow β .
- π³π±Netherlands balintbrews Amsterdam, NL
Here is an outline of our approach for handling Tailwind-generated CSS β developed based on several meetings with @effulgentsia, and another meeting with @effulgentsia, @hooroomoo, @tedbow, @longwave, and @f.mazeikis.
Experience Builder's Tailwind CSS support for JavaScript components
JS components in XB have two target groups:
- Marketing teams with sufficient level of design and frontend coding skills to author (and/or use LLMs to generate) basic components, with no or minimal interactivity, mostly consisting of markup.
- Developers, who can create advanced, potentially highly interactive components.
80-90% of the JS components will be authored by marketing teams. Therefore we will consider XB as the primary source of truth for JS components. This mostly means that the Tailwind CSS 4 config will be maintained by the Experience Builder module, rather then e.g. residing in an external code repository.
Basic components
Marketers can write Preact components using an in-browser editor. They can also maintain their Tailwind CSS 4 config using Experience Builder. Tailwind CSS 4 is still in alpha, but there are already examples of it being used in production. One of the great new features is CSS-first configuration.
We are already able to build CSS using Tailwind CSS 4 in the browser via a new package that has been authored and published on npm:
tailwindcss-in-browser
. Using this we will produce the following CSS files using the components' markup and the Tailwind CSS 4 config maintained in XB:- Tailwind Preflight & Tailwind theme CSS;
- Individual CSS files with Tailwind utility classes for each component.
Advanced components
Developers can develop components outside of Experience Builder using any workflow that fits them, e.g. keeping their code in a code repository. XB will provide a CLI tool to support the followings:
- Preview components by building CSS using the Tailwind CSS 4 config, retrieved from XB on-the-fly;
- Deploy components to XB with their built CSS β an individual CSS file for each.
CSS aggregation
Every JS component, basic or advanced, will end up with their own CSS file. While this will ensure great portability and reduces complexity for the initial implementation, it also results in a great deal of duplication in the CSS code. It was agreed upon that this is acceptable for π± Milestone 0.2.0: Experience Builder-rendered nodes Active , and can potentially be addressed later in Drupal core, implementing de-duplication as part of the CSS aggregation process.
- πΊπΈUnited States hooroomoo
Ok my update: passing in the SWC compiled code into an astro island looks like it is working. There is still a lot of cleaning up to do. The editor is not connected to the SDC wrapper/astro island yet, I just used a copy and paste of the compiled SWC code for a simple counter which lives in Counter_SWC.js in the gist linked below.
Currently all the files, including those relevant to the renderer-url attribute of the astro island live in my /sites/default/xb/astro and that is what the code is pointing to but that should change in the future. For now, I created a gist with those files if anyone wants to test it.
https://gist.github.com/harumijang/86ed1c6148690404d02ef322d0eddd37
One thing I am not sure about is in Counter_SWC .js, is where I am supposed to be calling the render function and to what part of the document I should inject it into.
- πΊπΈUnited States effulgentsia
One thing I am not sure about is in Counter_SWC .js, is where I am supposed to be calling the render function and to what part of the document I should inject it into.
I don't think you need to call
render()
. I think you only need toexport { Counter as default };
. - πΊπΈUnited States hooroomoo
Steps to test SimplePreactCounter to the preview canvas:
1. Add Astro bundled files located in /ui/components/editor/astro-bundles to /sites/default/files/xb/astro
2. In the XB UI, go to Library Tab and click Simple Preact Counter and open it in editor.
3. Click the blue Upload button in the top bar. This button will write the SWC-compiled code to your /sites/default/files/xb/astro with some manual changes with the imports. @see JSComponentUploadController.php for a comment
4. Refresh XB to close the editor since we don't have a a button to close it yet, and under the Library's component's tab you should see Simple Preact Counter that you can drag into the preview canvas. It should be hydrated but with the current implementation of the preview canvas you can't interact with it but you can go into your console and document.querySelector(xxxxx).click(); to test the counter
The code in JSComponentUploadController.php to handle the SWC-compiled file will need to be updated to handle different imports
since imports are currently hard-coded and based on a conversation with @effulgentsia ideally we could just rely on the Astro-bundled Preact packages instead of also bringing in Preact CDN through importmap.Another important note, the component currently requires an export default to work with the astro bundles which is why the SimplePreactCounter uses the inline export default function syntax.
- πΊπΈUnited States effulgentsia
We're also looking into whether we can still wrap the Preact components into Astro islands
Yay, #17 proves that we can! Because Astro doesn't insert any special sauce into the JS for the
component-url
: that's just vanilla Preact so@swc/wasm-web
is sufficient for compiling that. Meanwhile, all of Astro's special integration code, from the JS for therenderer-url
to its JS for the<astro-island>
custom element can be generated statically rather than in-browser. So that's all very promising!Later on we also want to change how either SWC or Astro bundles their JavaScript to be mutually compatible
I looked a bit into this, and I think all we need for this is to set SWC's jsc.transform.react.runtime configuration to
automatic
. That compiles to the jsx() function introduced in React 17, which is more efficient at creating vnodes at runtime than the older createElement()/h() function. So that's what Astro's Preact integration (and pretty much everything else in the React ecosystem) uses by default at this point, and SWC's default ofclassic
is pointless for new projects.Based on a conversation with @effulgentsia ideally later on we could just rely on the Astro-bundled Preact packages instead of also bringing in Preact CDN through importmap.
I think I figured out how to do this. The challenge here is how to make Astro bundle its JS in such a way that it exports the original, rather than the minified, names of the Preact functions that we want components to be able to import. Normally, Astro takes care of bundling the components, so it can minify the export names of library functions since it can compile the components to import those minified names. In our case, since we're compiling the component code separately from the Astro code, we need any Astro bundles that we want to import from to export un-minified names. Astro uses Vite which uses Rollup, but I couldn't find any Astro/Vite/Rollup configuration to accomplish this. However, I found the following indirect way to accomplish it.
Within the Astro project, we can add a
Stub
component like this:const { useState } = await import("preact/hooks") const { useSignal } = await import("@preact/signals") const { jsx, jsxs, Fragment } = await import("preact/jsx-runtime") export default function () { }
And then add a
<Stub client:only="preact"/>
usage of it in the index.astro file. Because this Stub component uses dynamic imports, it results in Rollup exporting theuseState
,useSignal
,jsx
,jsxs
, andFragment
functions, with those names, from the corresponding module bundles. If we want to expose additional functions/hooks for in-browser-editable components to be able to use, we can add those as well to the Stub component, but for now, let's just keep it to these.With this in place, we can then take the output of SWC's compilation and replace
from 'preact/hooks'
withfrom './hooks.module.js'
, and similarly forsignals
andjsx-runtime
. - πΊπΈUnited States effulgentsia
Just jotting some notes down from a conversation with @balintbrews and @hooroomoo...
When you run
astro build
, it generatespreact.module.js
,hooks.module.js
, and some other JS files that the in-browser-editable components depend on. So the question is how do we want to get those assets "into Drupal".I think the most straightforward way would be to just add an
astro build
step to XB's build step. In other words, XB's current build step isnpm run type-check && vite build
so conceptually we could expand that tonpm run type-check && vite build && astro build
.However, XB is a React project in the
ui
directory. It would probably be good for the Astro project to be a separate project (meaning, have its own package.json) from the main XB project. This separate project could be either a directory that's a sibling of theui
directory, or a child. For example,astro
orui/astro
(or we might want to come up with some other name for it than just calling itastro
). In which case, we'd want the build script inui/package.json
to do whatever it does plus then kick off annpm run build
command within the astro directory. I'm guessing there's idiomatic conventions for how to structure/implement this type of setup, but I don't know what that is. - πΊπΈUnited States effulgentsia
The MR here is currently using CodeMirror and no strong opinions have yet been raised making the case for why Monaco would be better, so retitling to reflect the current state. We're still open to switching to Monaco if a strong case is made for that, but in addition to comment #7, I'd like to point out that:
- CodeMirror is used as the code editor within the dev tools of Chrome, Safari, and Firefox.
- The experience described in https://sourcegraph.com/blog/migrating-monaco-codemirror also matches my (much more limited) initial positive impressions of CodeMirror and frustrations with Monaco. There's a lot to like about the user experience of Monaco, so I hope Microsoft evolves it into something that can also be made lighter weight and easier to write plugins for.
- π³π±Netherlands balintbrews Amsterdam, NL
#17:
It would probably be good for the Astro project to be a separate project (meaning, have its own package.json) from the main XB project. This separate project could be either a directory that's a sibling of the ui directory, or a child. For example, astro or ui/astro (or we might want to come up with some other name for it than just calling it astro). In which case, we'd want the build script in ui/package.json to do whatever it does plus then kick off an npm run build command within the astro directory. I'm guessing there's idiomatic conventions for how to structure/implement this type of setup, but I don't know what that is.
Tools we can evaluate that comes to my mind are Yarn workspaces, Lerna, or Nx (also uses Lerna, probably an overkill for us, but it's an awesome tool).
- πΊπΈUnited States hooroomoo
Olivero(?) styles bleeding through but yay Preact component using Tailwind css being rendered in the Preview canvas.
- π³π±Netherlands balintbrews Amsterdam, NL
I just learned about @brianperry's module, Islands β . We could explore how to leverage it for our hydration logic with Astro.
- πΊπΈUnited States effulgentsia
Oh that's neat: thanks, @brianperry, for creating that!!
Looks like that module is using 11ty instead of Astro. Not sure if that really matters for us. We started here with Astro because that's a very popular and well maintained framework, but it's quite possible that 11ty is sufficient for our needs here. @brianperry: what are your thoughts on pros/cons between 11ty islands vs. Astro islands?
- πΊπΈUnited States brianperry
Thanks for sharing here @balintbrews. Following along with this issue and other XB work has pretty directly led to experiments like this, so happy to share anything I can.
> @brianperry: what are your thoughts on pros/cons between 11ty islands vs. Astro islands?
@effulgentsia the main reason I started with 11ty's implementation is that Astro didn't seem to have an easy way to use the astro-island element outside of a full astro project (unless I'm missing something). 11ty's implementation is focused on exactly that. The readme calls it "a framework independent partial hydration islands architecture implementation" and it doesn't require 11ty to use.
It's a lot simpler than I expected once I dug into it. Most of the code is the the expected client loading directives, and then it adds some simple utilities for module imports, automatically initializing various frameworks, and replacing fallback content.
Being framework independent also means that pretty much everything the package does is on the client. So it won't help with any of the things the MR on this issue does around compiling. It can work with 11ty's server rendering features, but it could work SSR from other frameworks, or as this module is trying to prove - content server rendered by Drupal.
The other noteworthy difference is that @11ty/is-land doesn't seem to have built in support for React. I don't know if this is directly related to the challenges of using JSX without a compile step, or just that the maintainer isn't a big fan of React based frameworks.
So for what this issue is setting out to do, my gut would be that you're still better off with Astro. The Islands module currently isn't much beyond the provider of a re-named is-land element, along with a naming convention based approach to import maps that will become obsolete once Drupal has formal support. I have issues in the queue to go deeper into dealing with Drupal content, and also things that will require some kind of compilation step - if I come up with anything interesting I'll be sure to share it here and/or in Slack.
- πΊπΈUnited States hooroomoo
hooroomoo β changed the visibility of the branch blocks-spike to hidden.
- Assigned to balintbrews
- π§πͺBelgium wim leers Ghent π§πͺπͺπΊ
What are the next steps here? Is this ready for review? I asked @f.mazeikis to review the config bits of this MR already, to get this going again.
(Doing this because @effulgentsia told me this is the biggest unknown/highest risk for π± Milestone 0.2.0: Experience Builder-rendered nodes Active .)
- π³π±Netherlands balintbrews Amsterdam, NL
There is a lot of code in multiple MRs in this issue which were written to prove concepts and ideas.
A short summary of what we proved:
- We can provide a code editor using CodeMirror;
- Compile and bundle JavaScript code authored as Preact components using the WebAssembly build of SWC (Speedy Web Compiler);
- Use the bundled code with import maps to provide a live preview;
- Compiled CSS in the browser using Tailwind CSS v4 (currently at
beta.9
) that resulted in this library:tailwindcss-in-browser
; - Built UI for defining props for components where the default values were also presented in the preview;
- Architected a solution leveraging Astro's islands architecture to hydrate the components when rendering them server-side;
- Solved overriding the menu block template with a JS component;
- Implemented the first version of the config entity which will store the JS components.
I'm sure there is more. With all these challenges our focus was the build a POC, to see what is possible. Because of that, the code we've written is mostly not ready for prime time.
Here is what I would recommend. Now that we have a clear idea of what we would like to build, and we now also have designs (not sure if they're ready to be shared yet), I would propose that we close this issue, use it as a reference moving forward, and create a meta issue that maps out what needs to be done in a series of smaller child issues. That way the progress will be more visible, it's easier to prioritize features, and we can plan it better what we can work on in parallel. I'd be happy to work on the meta issue and build out the plan.
Thoughts, @wim leers, @effulgentsia?
- πΊπΈUnited States effulgentsia
I agree with all of #29. I'm conflicted about whether to mark this Fixed (as in, we learned what we needed to from the PoC) or Outdated, but choosing the latter to reduce potential confusion in someone thinking that we delivered any usable implementation as of yet.
- πΊπΈUnited States effulgentsia
@balintbrews: When you create the new meta, please make β¨ Create a ComponentSource plugin for JS components Active a child of it. I opened it just now realizing that's a piece that wasn't part of the PoC at all. There's probably some other pieces too that'll require entirely new work rather than just polishing what's in this MR.