Embroider Initiative: Progress Update

– By Chris Manson

This post was originally published on mainmatter.com

For anyone who has clicked the link to this blog post but doesn't already know, Embroider is a new build pipeline that compiles Ember apps into spec-compliant, modern JavaScript. Before Embroider, it was somewhat difficult to participate in modern build-tooling optimisations such as code-splitting and tree-shaking. Embroider allows you to opt-into these behaviours out of the box.

Embroider is of critical importance for the entire Ember ecosystem, yet has been in development for many years with no clear end in sight. The Embroider Initiative is spearheaded by Mainmatter with the support of a group of sponsors to help speed up development on Embroider so that we can make it the default build system for newly generated Ember apps as soon as possible. This will allow Ember developers to continue leveraging its advantages while benefiting from a modern build system. We hope to see Ember apps being built with Vite before the end of the year.

The Embroider Initiative aims to:

  1. Finish Embroider itself by assigning experienced Ember engineers full-time to the project. This covers development work on Embroider and its dependencies, as well as helping backers setting it up in their repos to uncover and fix edge cases.
  2. Make Embroider maintainable by decentralizing technical knowledge beyond the project's core developer Ed Faulkner and thus improving the bus factor. Mainmatter makes use of the apprenticeship model to onboard and train new developers on the intricate parts of the project.
  3. Shift the ecosystem and make Embroider mainstream by making it easier to generate Embroider-optimized Ember apps and supporting addon developers make their addons compatible with Embroider.

To reach these goals and keep up the momentum, the project needs your financial support. Please get in touch to learn how to directly benefit from this investment.

Mainmatter founder and director Marco Otte-Witte recently talked about the Embroider Initiative and open source funding in general at EmberFest in Madrid. He detailed how important the Initiative is to the future of Ember itself and to all companies that have built on Ember and need to secure the investments they have made in Ember. He also went into detail about the Embroider Initiative in his original blog post that announced the initiative. This blog post will dive into the things our team has been able to achieve up until now.

Getting Ember to work with Vite

At EmberConf this year Ed Faulkner announced that we were closing in on a Vite plugin for Embroider. While that was true at the time, we have learned a lot about the Vite build process since then and we know more about the steps that are still required to get the Vite integration working.

The Vite app that Ed demoed at EmberConf was a trivial app that is a package in the Embroider monorepo and if you wanted to test it yourself then you could either clone the Embroider monorepo, or you could clone this repo which is essentially just extracting the same test app into an independent repo. It works, and you can even see the incredible rebuild speeds in action.

The issue with this trivial demo is that it doesn't represent an average Ember application. There aren't any Ember applications out there that don't have a single addon installed. While it's not exactly true that the demo doesn't have any addons installed, it doesn't have any addons that are doing any real work. And, as it turns out, getting dependencies to work right is the challenge with the Vite build.

Ed and Mainmatter Senior Engineer Chris Manson have been pairing weekly, plugging away at the remaining things that are required to fix the Vite build. We are now optimistic we can get to a point where it's possible to build the first real-world Ember applications with Vite until the end of the year.

ember-auto-import allowAppImports

While the main focus of the Embroider Initiative was always going to be the Embroider code base, there are other parts of the ecosystem that will require some work to bring them more in line with how we want people to build their apps.

If you're already using Embroider, you will know that a lot of the work to package your app is done by Webpack. If you're still on a classic build, you may not be aware that ember-auto-import uses Webpack under the hood to allow you to seamlessly import from node_modules. This has been a very useful feature but since the acceptance of the v2 addon spec RFC we have noticed that we have a bit of a blindspot in classic builds. Since v2 addons can't influence the build in any way (effectively making them static packages) addon authors need to add extra installation instructions to detail how to add a Webpack plugin to their application build config if they still wanted to influence the build process in any way. This is perfectly legitimate in Embroider but it does not work for classic apps.

The issue is that ember-auto-import was originally designed to only work with npm packages, so that means that classic apps couldn't add a Webpack plugin that would influence the build process for any files controlled by their app, only for files controlled by npm packages or addons. This has been a blocker for some addon developers who want to upgrade their addons to the new v2 format and our solution to this problem has been to add a new config to ember-auto-import to allow you to specify parts of your app that should be under its control.

While this work has been done to facilitate v2 addons having the same install instructions for classic ember-cli builds and Embroider apps, this functionality could also be considered a way to allow you to opt-in to Embroider on a folder by folder basis and when your whole app is being controlled by ember-auto-import (and Webpack) the move to embroider should technically require no changes to the app.

Progress for Embroider Initiative backers

For the Embroider Initiative to be successful, it needs backing from companies that would benefit from a modern and thriving Ember ecosystem. But the benefits of sponsoring the project go beyond the end goal.

While all sponsorship tiers (starting at 3k€ for individual supporters) include a backer's logo in Mainmatter communication around the Initiative, companies committing 18k€ to the project get access to the weekly 1-hour call with the team and get opportunities to discuss their technical needs and challenges related to Embroider.

Premium tiers of 36k€ and above include a 2-hour weekly private session with Mainmatter engineers focused on improving the backer's Ember build. In practice, these backers have seen a great return on their investment as the Mainmatter Embroider team has been able to deliver dramatic speed improvements to their build, removing a bottleneck in their development process. Some issues specific to these backers also resulted in solutions that the larger Ember community can now benefit from.

Let's see some success stories in details:

Ticketsolve

Like many companies, Ticketsolve has an internal addon that they use throughout their other applications. Internal addons are a great place to start when you are thinking about upgrading to Embroider for two reasons:

  1. It's an isolated place to opt into Embroider's stricter requirements
  2. There is a clear specification of what your v2 addon can do.

Chris' work on improving the ergonomics of the v2 addon blueprint in the early weeks of the Embroider Initiative, proved very useful during the process of converting Ticketsolve's internal addon.

After the internal addon was fully converted to v2 and deployed to all of Ticketsolve's apps, our next step was to add support for GJS files in both the internal addon and their applications. The team also worked on the ecosystem PRs required to enable GJS support in v2 addons in both a @embroider/addon-dev PR and a PR to the @embroider/addon-blueprint.

Before the Embroider Initiative, Ticketsolve had one of their three apps already running Embroider. We worked to update the Embroider version of that app, then started to convert the remaining two apps. There were some challenges along the way, mostly related to addons that just didn't support Embroider. A great example of this is the ember-service-worker addon which uses the postprocessTree() hook that doesn't work with Embroider. We updated the addon locally using pnpm patch <pkg> and submitted a PR with the same changes to help the rest of the community.

This is only a fraction of the changes that came out of these pairing sessions, but it gives a flavour of the kinds of things that we have been able to achieve. While our Chris Manson has been instrumental in helping speed up this work, Ticketsolve's engineer Andreas Minnich deserves a shout-out for the colossal amount of work they have put into this effort between pairing sessions.

Intercom

Intercom also had an internal addon they wanted to convert to v2 before working on converting their main application. Mainmatter worked with them to convert that and, incidentally, we also helped them to convert the tests app that was documenting the v2 addon to use Embroider with the staticAddonTestSupportTrees and staticAddonTrees flags turned on.

While this work was going on, Intercom Engineer Aaron Chambers had setup a CI build step that would keep track if the main Intercom app could build with Embroider and if any tests were passing. Because of this CI job, Aaron identified a 10x slowdown in build times between Embroider v3.1 and v3.2. Chris used a number of pairing sessions to dive into the issue and produce a number of flamecharts for their Embroider builds to get an insight into what was causing their specific slowdown. Those flamecharts were pointing at a particular part of Embroider's package cache not working exactly as we intended it to. Ed Faulkner managed to pinpoint the problem (while we were discussing it in the "hallway track" of EmberFest) and opened a pull-request with the fix. This completely fixed the performance regression for Intercom.

The Mainmatter Embroider team also noticed that Intercom's build was behaving differently on CI compared to running the exact same build locally. We tracked it down to the fact that Intercom's CI was configured to create a symbolic link to an existing node_modules folder rather than running a new npm install for every job. As it turns out, this was the same reason that the CI was failing for Chris' pull request to make Embroider optimised the default when running the command ember new --embroider. Chris identified the cause of the issue inside Embroider and fixed it without the need for any assistance from Ed, which also shows the success in making Embroider more maintainable by increasing the team size and distributing technical knowledge (more about this in the next section).

The rest of the pairing sessions were spent working through issues that were causing tests to fail. Some of these issues were systemic and when they were fixed they would turn hundreds of tests green (Intercom has a lot of tests), while other problems were highly specific and required multiple pairing sessions to identify and fix the problem. An example of this would be Intercom's use of the charting library Highcharts. When a Highcharts user needs to include timezones in their Date axis, the library will check for the presence of window.moment and use it for any timezone-related calculations. This allows the user to have a chance to setup window.moment to use moment-timezone and correctly configure it with the right set of timezone data for their application. With the move to Embroider, window.moment is no longer accidentally set to the correct instance of moment-timzone as a side-effect of the build system, so Highcharts wasn't finding the right instance during its initialisation. It turns out that Highcharts provides a config option, time.moment, to cover this exact case and, as soon as the team set that correctly in the application's charts base class, the x-axis started behaving again.

Again, these are only a fraction of the changes that came out of these pairing sessions. Both Aaron Chambers and Peter Meehan were able to achieve a lot of big things over the course of the Embroider Initiative for Intercom and our team was more than happy to help speed the process along.

Improving the bus factor

The goals of Embroider may seem simple from the outside, i.e. "just use Webpack or Vite to build your Ember app", but when digging a bit deeper, it's easy to see how complex of a project it really is. This complexity arises from the project having challenging design constraints which pose a significant challenge to anyone who would like to contribute to the core of Embroider. The main design constraint that causes a lot of this complexity arises from the fact that the team wants to provide an easy on-ramp for existing Ember apps to convert to Embroider, and then slowly move those apps from full-compatibility mode to a "fully static" build that can automatically benefit from tree-shaking and code-splitting. This means that we need to provide systems that can automatically convert the still-supported conventions of an Ember app to fully standard compliant ESM code. This is a significant challenge since some of the patterns that are still officially supported today date back to the 1.x series of Ember which was released in 2015.

Because of the overall complexity of project and the rapid iteration of the internal architecture, it hasn't been practical to create any documentation that could communicate what is going on under the hood. This is illustrated by the fact that Aaron Chambers and Peter Meehan put together a document trying to explain the full architecture of the project, which was completely outdated only less than a month later when Ed Faulkner merged the first of many internal refactors.

To address this challenge, we have adopted an apprenticeship model where Ed Faulkner pairs with Chris every week, collaborating on solving complex problems deep within the heart of the Embroider codebase. This has made a significant improvement to the bus factor of the Embroider project and has also had a deep impact on its velocity.

With our second full-time engineer on the Embroider Initiative, Andrey Mikhaylov, we have extended the apprenticeship model by having Chris and Andrey pair-program for half a day each week. This has helped both Chris and Andrey ramp up their knowledge of the codebase while improving the overall project's bus factor.

General stability and ecosystem improvements

Providing watch-mode tests for Embroider

Recently, Godfrey Chan has been discovering some places in Embroider and our Webpack plugin that were causing crashes when certain files were added or deleted. He has already come up with a fix for some of the cases, but it showed a blind spot in the team's testing infrastructure that meant we weren't testing "watch mode" in Embroider. Preston Sego and Chris paired together to add some basic watch mode tests that would highlight the problem and prove the effectiveness of the fix, but they were hit by some strange quirks that prevented the watch-mode tests from ever exiting properly on the Windows CI job. Chris spent most of his weekly streaming session on Twitch trying to figure out the solution. The team finally got the PR merged and is now ready to start adding more expansive watch-mode tests.

ember-cli-update supporting v2 addons

ember-cli-update is a great asset to the Ember community, and it's a vital tool during the push for Embroider and encouraging the community to use the new v2 addon blueprint because a lot of the recent changes require updating both a dependency version and related configuration.

One issue the team worked through was to fix CI, which had been broken for a year. ember-cli-update interacts with the npm APIs to discover versions, but a change on the npm side prevented it from functioning properly. The solution to fixing CI was ultimately updating a dependency, boilerplate-update, to use pacote to interact with the npm API. Once the upstream dependency was updated, the team only needed to add a few tweaks to ember-cli-update to get CI to finally pass. This gave us time and confidence for the rest of the project.

The team then worked to get a new release of ember-cli-update so that it could update any Ember addon generated with the v2 addon blueprint.

ember-cli-update works by generating a diff between the version of the blueprint that the app or addon was generated with, and the version to update it to. Under the hood, this means that ember-cli-update will generate a pristine new version of the app/addon at its current version, then generates a new pristine copy of the target version. It generates a git diff between those versions then applies that diff to the current app.

Generating these pristine "from" and "to" versions of the blueprint involved a custom code-path in ember-cli-update that aimed at working around some bugs in Node 8. As those workarounds were causing it to fail, Chris' fix for custom blueprints involved removing these workarounds. This fix was released in ember-cli-update v2.0.0, then followed by another release that fixed a small bug. These two releases are a big deal for the wider Ember community, especially as its members are encouraged to migrate their existing v1 addons to v2 addons. They bring an important DX functionality that the Ember community has come to expect.

Embroider optimised with the --embroider flag

Creating a new Ember app with the embroider flag (ember new my-super-app --embroider) generates a "full compat" app were none of the Embroider optimisation flags are passed to the build for the developer.

While more existing apps will work with a "full compat" mode, Ember has reached a point where it makes sense for newly generated apps to start with a high water mark so that developers don't accidentally or unconsciously add functionality that won't work in a fully optimised Embroider application. Developers have the option to turn off any of the optimisation flags, but it would be a deliberate choice as they would need to add a specific dependency or functionality to their app.

The Mainmatter Embroider team opened a PR to switch the functionality so that passing the --embroider flag uses Embroider optimised by default. This involved working through issues with some of the slow test suites that rely on a custom package caching mechanism. The PR got merged and the functionality will be part of Ember v5.5.

scenario-tester ESM compatibility

Scenario tester is a testing tool that the team makes use of a lot when testing Embroider and ember-auto-import. It allows us to generate many scenarios with different combinations of dependencies. We tried using it outside of Embroider, or more specifically outside of a Typescript project, and saw it doesn't work in a CJS environment. Chris started the effort to move the Typescript build to output an ESM-compatible build so it can be consumed directly in ESM without a build step. The only remaining task is to test if it will work in Embroider before merging and releasing the new version.

Documenting the scenario-tester library.

scenario-tester is to Embroider what ember-try is to Ember CLI: it's a tool that lets us perform automated tests with various combinations of dependencies, configs and circumstances. The approach of scenario-tester is different: instead of reinstalling dependencies for every test case, it has all dependencies (including all versions of dependencies) set up once, saving a lot of time. It leverages fixturify-project to create and emit to filesystem Ember apps and addons with predefined dependencies and configuration, in order to run tests on them.

Working on the reverse-exports

During the build, Embroider needs to expose Ember internals to Vite and Webpack in a way they can understand and consume. Modern Ember apps can have multiple exports entry points in their package.json configs. This poses a peculiar challenge for Embroider: it needs to know how to reorganize files in an Ember project in such a way that they would resolve into paths defined as exports values. Essentially, this requires resolving exports in reverse, and this it what the reverse-exports package is for.

Call to action

We are hoping we can extend the initiative's budget and timeline to keep up the momentum in 2024 and finish the work we started. Please get in touch to learn how to support the Embroider Initiative and directly benefit from this investment!