Summary
Enable developers to work on frontend apps (e.g., frontend-app-authn, frontend-app-learner-dashboard) and frontend-base itself locally, using npm workspaces. This applies both to site templates like frontend-template-site and to individual apps developing against a local frontend-base.
Note that this method is likely to fit nicely with tutor dev, since it also relies on bind mounts. I actually arrived at this proposal while researching a way to make app forks work with Tutor in production mode: it just so happens that it works for development (and without Tutor) just as well.
Problem
Currently, developing changes to frontend apps requires publishing to npm or using workarounds such as autoinstall, which currently only works for frontend-base, or yalc, which while generic, is unmaintained upstream. Both workarounds also make changes to package.json in target repos, which is painful since they should never be committed. Both are also comparatively slow, and are not particularly suited for Docker-based development.
Thus, there's no simple and efficient workflow for iterating on multiple packages simultaneously in the context of a running site, or for developing an app against a local copy of frontend-base.
Proposed Approach
Use npm workspaces with a packages/ directory (gitignored) to hold local checkouts of packages being developed. These are development-only overrides of the npm-published versions: no dependency-specific changes need to be made to package.json. If a package doesn't have a local checkout in packages/, the regular dependency is used.
This applies at two levels:
-
Site template — frontend-template-site can have any combination of frontend-base and frontend apps as workspace packages, with Turborepo orchestrating builds in dependency order.
-
Individual apps — Apps like frontend-app-learner-dashboard can have frontend-base as a workspace package for independent development, also using Turborepo for orchestration.
Key pieces:
-
Workspace configuration — "workspaces": ["packages/*"] in package.json, with packages/ in .gitignore.
-
Mounting local checkouts — Since symlinks cause module resolution failures (Node.js/TypeScript resolve to the real path, breaking hoisted dependency resolution), local checkouts should be made available via bind mounts (sudo mount --bind or FUSE's bindfs) or Docker volume mounts, both of which present the directory as a real path within the project tree.
-
Build orchestration — Turborepo handles building workspace packages in dependency order (e.g., frontend-base before apps that depend on it) via dependsOn: ["^build"] in turbo.json. The openedx CLI could eventually provide this natively.
-
Watch mode without race conditions — The current make build target starts with rm -rf dist, which causes race conditions when watch scripts and the dev server run concurrently. The clean target must be decoupled from build, so that watch rebuilds overwrite in place without wiping dist/. The prepack script should explicitly run clean && build to preserve clean builds for publishing.
Changes Required
In frontend-base:
- Decouple
clean from build in the Makefile
- Update
prepack to explicitly run clean && build
- Add a
watch:build npm script and a generic nodemon.json
In frontend apps (frontend-app-authn, frontend-app-learner-dashboard, etc.):
- Same Makefile decoupling of
clean from build
- Add
workspaces field, packages/ to .gitignore
- Add
turbo, nodemon as dev dependencies
- Add
turbo.json for build orchestration
- Add workspace-aware scripts (
build:packages, clean:packages, dev:site, dev:packages, watch:build)
- Add
nodemon.json for watch configuration
In frontend-template-site:
- Add
workspaces field, packages/ to .gitignore
- Add
turbo.json for build orchestration
- Add workspace-aware scripts (
build:packages, clean:packages, dev:site, dev:packages)
PRs
Trying it out
If you have all of the above checked out cleanly as siblings (like many of us often do):
cd frontend-template-site
mkdir -p packages/{frontend-base,frontend-app-authn,frontend-app-learner-dashboard}
cd packages
for i in *; do sudo mount --bind ../../${i} ${i}; done
cd ..
npm install
npm run dev:packages
This should get you a tutor dev-compatible site deployment, in such a way that if you make changes to any of the dependencies, they'll show up automatically on the page.
For the future
If we for some reason start disliking Turborepo as a devDependency, we can consider adding the specific features we want out of it to the openedx CLI.
I don't recommend doing this now - or maybe ever - because more modern package managers (pnpm, etc) may handle our workspace needs without custom code or dependencies. In other words: let's wait and see.
Summary
Enable developers to work on frontend apps (e.g.,
frontend-app-authn,frontend-app-learner-dashboard) andfrontend-baseitself locally, using npm workspaces. This applies both to site templates likefrontend-template-siteand to individual apps developing against a localfrontend-base.Note that this method is likely to fit nicely with
tutor dev, since it also relies on bind mounts. I actually arrived at this proposal while researching a way to make app forks work with Tutor in production mode: it just so happens that it works for development (and without Tutor) just as well.Problem
Currently, developing changes to frontend apps requires publishing to npm or using workarounds such as autoinstall, which currently only works for frontend-base, or yalc, which while generic, is unmaintained upstream. Both workarounds also make changes to package.json in target repos, which is painful since they should never be committed. Both are also comparatively slow, and are not particularly suited for Docker-based development.
Thus, there's no simple and efficient workflow for iterating on multiple packages simultaneously in the context of a running site, or for developing an app against a local copy of
frontend-base.Proposed Approach
Use npm workspaces with a
packages/directory (gitignored) to hold local checkouts of packages being developed. These are development-only overrides of the npm-published versions: no dependency-specific changes need to be made to package.json. If a package doesn't have a local checkout inpackages/, the regular dependency is used.This applies at two levels:
Site template —
frontend-template-sitecan have any combination offrontend-baseand frontend apps as workspace packages, with Turborepo orchestrating builds in dependency order.Individual apps — Apps like
frontend-app-learner-dashboardcan havefrontend-baseas a workspace package for independent development, also using Turborepo for orchestration.Key pieces:
Workspace configuration —
"workspaces": ["packages/*"]inpackage.json, withpackages/in.gitignore.Mounting local checkouts — Since symlinks cause module resolution failures (Node.js/TypeScript resolve to the real path, breaking hoisted dependency resolution), local checkouts should be made available via bind mounts (
sudo mount --bindor FUSE'sbindfs) or Docker volume mounts, both of which present the directory as a real path within the project tree.Build orchestration — Turborepo handles building workspace packages in dependency order (e.g.,
frontend-basebefore apps that depend on it) viadependsOn: ["^build"]inturbo.json. TheopenedxCLI could eventually provide this natively.Watch mode without race conditions — The current
make buildtarget starts withrm -rf dist, which causes race conditions when watch scripts and the dev server run concurrently. Thecleantarget must be decoupled frombuild, so that watch rebuilds overwrite in place without wipingdist/. Theprepackscript should explicitly runclean && buildto preserve clean builds for publishing.Changes Required
In
frontend-base:cleanfrombuildin the Makefileprepackto explicitly runclean && buildwatch:buildnpm script and a genericnodemon.jsonIn frontend apps (
frontend-app-authn,frontend-app-learner-dashboard, etc.):cleanfrombuildworkspacesfield,packages/to.gitignoreturbo,nodemonas dev dependenciesturbo.jsonfor build orchestrationbuild:packages,clean:packages,dev:site,dev:packages,watch:build)nodemon.jsonfor watch configurationIn
frontend-template-site:workspacesfield,packages/to.gitignoreturbo.jsonfor build orchestrationbuild:packages,clean:packages,dev:site,dev:packages)PRs
Trying it out
If you have all of the above checked out cleanly as siblings (like many of us often do):
This should get you a
tutor dev-compatible site deployment, in such a way that if you make changes to any of the dependencies, they'll show up automatically on the page.For the future
If we for some reason start disliking Turborepo as a devDependency, we can consider adding the specific features we want out of it to the
openedxCLI.I don't recommend doing this now - or maybe ever - because more modern package managers (
pnpm, etc) may handle our workspace needs without custom code or dependencies. In other words: let's wait and see.