- 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 Kingdom longwave UK
balintbrews β credited longwave β .
- πΊπΈ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.