You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: support npm workspaces for local development
Decouple clean from build in the Makefile so that watch mode can
rebuild without wiping dist/. Add nodemon.json and watch:build,
watch:docs, watch:pack scripts to standardize file watching.
Also, ensure bin entry points are executable after build, and add
bin-linking workaround to migration guide.
Part of #184
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The `0.0.0-dev` alternative is the placeholder version used in the source checkout — semantic-release replaces it with the real version at publish time, but in npm workspaces the package still needs to satisfy peer dependency checks. The `||` lets both scenarios work.
133
+
124
134
Edit package.json scripts
125
135
-------------------------
126
136
@@ -134,7 +144,7 @@ With the exception of any custom scripts, replace the `scripts` section of your
134
144
"i18n_extract": "openedx formatjs extract",
135
145
"lint": "openedx lint .",
136
146
"lint:fix": "openedx lint --fix .",
137
-
"prepack": "npm run build",
147
+
"prepack": "npm run clean && npm run build",
138
148
"snapshot": "openedx test --updateSnapshot",
139
149
"test": "openedx test --coverage --passWithNoTests"
140
150
},
@@ -158,11 +168,13 @@ Also:
158
168
159
169
Last but not least, add `clean:` and `build:` targets to your `Makefile`. The build target compiles TypeScript to JavaScript, copies all SCSS and asset files from `src/` into `dist/` preserving directory structure, and finally uses `tsc-alias` to rewrite `@src` path aliases to relative paths:
160
170
171
+
Note that `build` intentionally does *not* depend on `clean`. This allows incremental rebuilds during development (especially in workspace mode, where a watcher triggers `build` on every change). The `prepack` script in `package.json` runs `clean && build` explicitly, so published packages always start fresh.
172
+
161
173
```makefile
162
174
clean:
163
175
rm -rf dist
164
176
165
-
build: clean
177
+
build:
166
178
tsc --project tsconfig.build.json
167
179
find src -type f \( -name '*.scss' -o -path '*/assets/*'\) -exec sh -c '\
168
180
forfin"$$@";do \
@@ -250,6 +262,9 @@ node_modules
250
262
npm-debug.log
251
263
coverage
252
264
dist/
265
+
packages/
266
+
/.turbo
267
+
/turbo.json
253
268
/*.tgz
254
269
255
270
### i18n ###
@@ -958,3 +973,145 @@ Refactor slots
958
973
First, rename `src/plugin-slots`, if it exists, to `src/slots`. Modify imports and documentation across the codebase accordingly.
959
974
960
975
Next, the frontend-base equivalent to `<PluginSlot />` is `<Slot />`, and has a different API. This includes a change in the slot ID, according to the [new slot naming ADR](../decisions/0009-slot-naming-and-lifecycle.rst) in this repository. Rename them accordingly. You can refer to the `src/shell/dev` in this repository for examples.
976
+
977
+
978
+
Set up npm workspaces for local development
979
+
===========================================
980
+
981
+
Frontend apps support `npm workspaces <https://docs.npmjs.com/cli/using-npm/workspaces>`_ so that developers can work on the app and its dependencies (such as ``frontend-base``) simultaneously, with changes reflected automatically.
982
+
983
+
Add the workspaces field to package.json
984
+
-----------------------------------------
985
+
986
+
```diff
987
+
+ "workspaces": [
988
+
+ "packages/*"
989
+
+ ],
990
+
```
991
+
992
+
This tells npm to look in ``packages/`` for local overrides of published packages. The ``packages/`` directory is gitignored (see the `.gitignore` step above), since it contains development-only bind-mounted checkouts.
993
+
994
+
Add a turbo.site.json file
995
+
--------------------------
996
+
997
+
Create a ``turbo.site.json`` at the repository root. This configures `Turborepo <https://turbo.build/>`_ to build workspace packages in dependency order and run persistent tasks (watch and dev server) concurrently:
998
+
999
+
```json
1000
+
{
1001
+
"$schema": "https://turbo.build/schema.json",
1002
+
"tasks": {
1003
+
"build": {
1004
+
"dependsOn": ["^build"],
1005
+
"outputs": ["dist/**"],
1006
+
"cache": false
1007
+
},
1008
+
"clean": {
1009
+
"cache": false
1010
+
},
1011
+
"watch:build": {
1012
+
"dependsOn": ["^build"],
1013
+
"persistent": true,
1014
+
"cache": false
1015
+
},
1016
+
"//#dev:site": {
1017
+
"dependsOn": ["^build"],
1018
+
"persistent": true,
1019
+
"cache": false
1020
+
}
1021
+
}
1022
+
}
1023
+
```
1024
+
1025
+
The file is named ``turbo.site.json`` rather than ``turbo.json`` to avoid conflicts with turbo v2's workspace validation. When a site repository includes your app as an npm workspace, turbo scans for ``turbo.json`` in each package directory and rejects root task syntax (``//#``) and configs without ``"extends"``. By using a different filename, the config is invisible to turbo during workspace runs, and only activated via the Makefile when running standalone (see below).
1026
+
1027
+
Add a nodemon.json file
1028
+
------------------------
1029
+
1030
+
Create a ``nodemon.json`` at the repository root. This configures the ``watch:build`` script to rebuild automatically when source files change:
1031
+
1032
+
```json
1033
+
{
1034
+
"watch": [
1035
+
"src"
1036
+
],
1037
+
"ext": "js,jsx,ts,tsx,scss"
1038
+
}
1039
+
```
1040
+
1041
+
Add workspace-aware scripts
1042
+
----------------------------
1043
+
1044
+
Install ``turbo`` and ``nodemon`` as dev dependencies:
1045
+
1046
+
```sh
1047
+
npm install --save-dev turbo nodemon
1048
+
```
1049
+
1050
+
Then add the following scripts to ``package.json``:
$(TURBO) run watch:build dev:site; rm -f turbo.json
1086
+
1087
+
dev-site: bin-link
1088
+
npm run dev
1089
+
```
1090
+
1091
+
-``watch:build`` uses ``nodemon`` to watch for source changes (as configured in ``nodemon.json``) and re-runs ``npm run build`` on each change. Turbo runs this in each workspace package that defines it.
1092
+
-``build:packages`` builds all workspace packages in dependency order (e.g., ``frontend-base`` before the app), then runs ``make bin-link`` to create missing bin links. This is necessary because npm skips bin-linking for workspace packages during install, so without this step the ``openedx`` CLI won't be available in ``node_modules/.bin``.
1093
+
-``clean:packages`` runs the ``clean`` script in each workspace package.
1094
+
-``dev:site`` is an alias for ``npm run dev`` that also bin-links the frontend-base bin files; turbo uses it as a root-only task (``//#dev:site``).
1095
+
-``dev:packages`` depends on ``build-packages`` so the CLI is available before starting the watch, then concurrently watches workspace packages for changes and starts the dev server.
1096
+
1097
+
The Makefile targets copy ``turbo.site.json`` to ``turbo.json`` before invoking turbo, then remove the copy afterward. This ensures turbo finds its expected config when running standalone, without leaving a ``turbo.json`` that would conflict in a workspace context. The ``--dangerously-disable-package-manager-check`` flag and ``TURBO_TELEMETRY_DISABLED=1`` are also set here, keeping turbo invocation details in one place.
1098
+
1099
+
Using workspaces
1100
+
-----------------
1101
+
1102
+
Todevelopagainstalocal``frontend-base``:
1103
+
1104
+
```sh
1105
+
mkdir -p packages/frontend-base
1106
+
sudo mount --bind /path/to/frontend-base packages/frontend-base
1107
+
npm install
1108
+
npmrundev:packages
1109
+
```
1110
+
1111
+
Bind mounts are used instead of symlinks because Node.js resolves symlinks to real paths, which breaks hoisted dependency resolution. Docker volume mounts work equally well (and are what ``tutor dev`` uses).
0 commit comments