Web/20240118 monorepo2
Details
What this is, is a progress stake-in-the-ground about converting our web-ui to be broken up into independent components. And what you’re reading is a progress report with all the warts exposed.
Build Orchestration Tool
I’ve chose the build tool Lage, from Microsoft. Unlike every other build tool I reviewed, Lage is only a build tool. It doesn’t do anything special, it doesn’t need its own custom edition of Storybook, it doesn’t try to handle publishing your packages. It’s only a build tool. It understands your JavaScript workspaces and monitors them for changes.
It’s not always perfect. I have caught it failing to rebuild after a change, and for that there’s the no-cache option:
$ npm run build -- --no-cache
But overall, I’m pleased with the way it works. If @goauthentik/elements is a dependency of @goauthentik/admin, you add "@goauthentik/elements": "*" to Admin’s package.json file, and if you change an element Lage will rebuild admin after rebuilding elements, but if you only change an admin thing, Lage will only rebuild admin.
Build System
Both Lage and NPM-Run-All enable running things in parallel, exploiting the multiple CPUs on your laptop to do the job as quickly as possible. You’ll see in this code examples of this:
"build": "npm-run-all --sequential locales:build --parallel build:styles build:compile build:types",
This is from @goauthentik/common, and it says “Build the locale files, and then in parallel build the style sheets, convert the typescript into javascript, and extract the type definitions as well.
Anything that’s a dependency should have its type files constructed. Admin, User, Loading and API-Browser aren’t dependencies of anything else. Flow, sadly, is a dependency of Admin and User, and that slows down its build by a few tenths of a second.
Compilers
The following TypeScript projects are compiled and bundled using esbuild:
- common
- elements
- components
- flow
- user
- admin
- loading
- api-browser
… which seems like everything, but it isn’t.
Changes made
Aside from swapping out the compiler & bundler, I had to make a few significant changes to the code base.
Independent Packages Create Conflicts
The way ESBuild does tree-shaking and dynamic importing, each dynamic import includes its own copy of the elements and components it needs, namespaced to the module’s internal lexicographic scope. That would be fine but for one thing: we’re using Web Components, which have to register with the global customElements registry. Our single-page apps are very much at risk of attempting to register the same component name twice, and that will crash the registry.
To work around this, I had to create a custom function for registering a component that will check and see if the component is already registered before trying. This should be fine, as we control our component IDs and this conflict doesn’t arise because we’re bad at naming, but because JavaScript is bad an namespacing.
CSS Imports work differently
Jens has a custom rollup plug-in that converts all imported CSS to an instance of CSSResult before handing it off to a component that needs it. ESBuild has no equivalent, but it turned out that Lit has a work-around: state that the CSS Import is plain text and use Lit’s unsafeCSS() function to convert it to a CSSResult at import time.
ESM Modules require suffixes
This is the big one: every TypeScript or JavaScript import must be suffixed with .js now. TypeScript has its own magic for, if it sees .js, trying, in order, .ts, .d.ts, .tsx, .js, .jsx. This is a tolerable, if annoying, change, but it’s a better hack than any other, and assuming anything without a suffix was a compilable import was a convenience anyway.
This Project is Incomplete
There are a lot of thing unfinished:
- Locales are broken. Only a partial copy of the locales are being preserved. It’s the nature of the system, and Lit’s Localization code has a bug that prevents us from having a straightforward extract-and-compile pass. I have some ideas for workarounds, but nothing solid yet.
- I haven’t done Enterprise yet.
- Assets are not being copied into
distcorrectly yet. - Storybook is, to put it mildly, effed.
- Polyfill is still questionable.
- The compatibility of esbuild’s “bundled-with-dynamic-imports” output with older browsers is very (very!) much an open question at this point.
- Markdown Display is broken. I anticipate it will be a text-to-markdown pass like CSS above, but I haven’t experimented with it yet.
- Those registry conflicts I mentioned? There are probably more of them.
Nonetheless, This Is Amazing
A real hats-off moment to Jens for making the system generally very composable (and hence, decomposable). Currently on my little MacBook, it takes 1 minute and 3 seconds to build our product; with this structure, that build takes only 13.4 seconds. And it runs. Not perfectly, not yet; the missing assets and broken markdown are apparent, and it looks clunky and incomplete. But we are getting there.
And hoo, boy, do I have things to blog about. :-)
REPLACE ME
Checklist
- [ ] Local tests pass (
ak test authentik/) - [ ] The code has been formatted (
make lint-fix)
If an API change has been made
- [ ] The API schema has been updated (
make gen-build)
If changes to the frontend have been made
- [ ] The code has been formatted (
make web) - [ ] The translation files have been updated (
make i18n-extract)
If applicable
- [ ] The documentation has been updated
- [ ] The documentation has been formatted (
make website)
Deploy Preview for authentik-storybook failed.
| Name | Link |
|---|---|
| Latest commit | f5121d1689220df9ae20567a6da74fca8f562175 |
| Latest deploy log | https://app.netlify.com/sites/authentik-storybook/deploys/65b3e7444a83f300088d124e |
Deploy Preview for authentik ready!
| Name | Link |
|---|---|
| Latest commit | e119c51eee34c9cd46e16ea3e6ae773ab5e5877f |
| Latest deploy log | https://app.netlify.com/sites/authentik/deploys/65ab11c45c36d90008c5f53c |
| Deploy Preview | https://deploy-preview-8242--authentik.netlify.app |
| Preview on mobile | Toggle QR Code...Use your smartphone camera to open QR code link. |
To edit notification comments on pull requests, go to your Netlify site configuration.
The "Use only esbuild, with the script stolen from Lage" branch turned out to be fast enough (less than two seconds); the monorepo would be nice, but I no longer believe it is necessary.