Skip to content

Commit a008b3b

Browse files
arbrandesclaude
andcommitted
feat!: compile to JS before publishing
Ship compiled JavaScript + .d.ts declarations instead of raw TypeScript source. This allows consuming apps to also pre-compile their TypeScript and use tsc-alias to resolve arbitrary paths (such as @src). At the bundler level, we add tsconfig-paths-webpack-plugin to Webpack configuration so that the Typescript aliases can also be used by Webpack builds without duplicating their definition. As part of this, unify the tsc builds so that everything ends up in /dist, and then use more modern export maps to decouple the internal file structure from the package's API. Of note, the default tsconfig (not the one used by tools) now uses `moduleResolution: bundler` to allow for more modern entrypoint definition. BREAKING CHANGE: consuming apps need to modify their imports and configuration accordingly. See the changes in the migration howto for details. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2badcaf commit a008b3b

19 files changed

Lines changed: 518 additions & 3928 deletions

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ node_modules
22
npm-debug.log
33
coverage
44
module.config.js
5+
/.tsbuildinfo.*
56
dist/
6-
/config
77
scss
88
docs/api
99

.npmignore

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1 @@
1-
__mocks__
21
node_modules
3-
*.test.js
4-
*.test.jsx
5-
*.test.ts
6-
*.test.tsx

Makefile

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,13 @@ transifex_temp = ./temp/babel-plugin-formatjs
1010
doc_command = ./node_modules/.bin/documentation build src -g -c ./docs/documentation.config.yml -f md -o ./docs/_API-body.md --sort-order alpha
1111
cat_docs_command = cat ./docs/_API-header.md ./docs/_API-body.md > ./docs/API.md
1212

13-
build:
14-
rm -rf ./config ./tools/dist
13+
clean:
14+
rm -rf dist .tsbuildinfo.*
15+
16+
build: clean
1517
tsc --project ./tsconfig.json
16-
mkdir -p ./config
17-
cp tools/typescript/tsconfig.json config/tsconfig.json
18-
tsc --project ./tools/tsconfig.json
19-
cp -prf ./tools/dist/config-helpers ./config/config-helpers
20-
cp -prf ./tools/dist/defaultConfigPaths.js ./config/defaultConfigPaths.js
21-
cp -prf ./tools/dist/types.js ./config/types.js
22-
cp -prf ./tools/dist/eslint ./config/eslint
23-
cp -prf ./tools/dist/jest ./config/jest
24-
cp -prf ./tools/dist/webpack ./config/webpack
25-
cp -prf ./tools/dist/babel ./config/babel
26-
cp -prf ./tools/dist/index.js ./config/index.js
18+
tsc --build ./tsconfig.build.json
19+
cp ./shell/app.scss ./dist/shell/app.scss
2720

2821
docs-build:
2922
${doc_command}

docs/how_tos/migrate-frontend-app.md

Lines changed: 100 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ With the exception of any custom scripts, replace the `scripts` section of your
128128

129129
```json
130130
"scripts": {
131+
"build": "make build",
131132
"dev": "PORT=YOUR_PORT PUBLIC_PATH=/YOUR_APP_NAME openedx dev",
132133
"i18n_extract": "openedx formatjs extract",
133134
"lint": "openedx lint .",
@@ -137,6 +138,28 @@ With the exception of any custom scripts, replace the `scripts` section of your
137138
},
138139
```
139140

141+
The `build` script invokes a Makefile target. You'll need to install `tsc-alias` as a dev dependency:
142+
143+
```sh
144+
npm install --save-dev tsc-alias
145+
```
146+
147+
Then add a `build:` target to your `Makefile`:
148+
149+
```makefile
150+
build:
151+
tsc --project tsconfig.build.json
152+
tsc-alias -p tsconfig.build.json
153+
find src -type f -name '*.scss' -exec sh -c '\
154+
for f in "$$@"; do \
155+
d="dist/$${f#src/}"; \
156+
mkdir -p "$$(dirname "$$d")"; \
157+
cp "$$f" "$$d"; \
158+
done' sh {} +
159+
```
160+
161+
This target compiles TypeScript to JavaScript, uses `tsc-alias` to rewrite `@src` path aliases to relative paths, and copies all SCSS files from `src/` into `dist/` preserving directory structure.
162+
140163
- Replace `YOUR_PORT` with the desired port, of course.
141164
- Replace `YOUR_APP_NAME` with the basename used on your site.config, not doing this will result in only the root route working.
142165
- Note that `fedx-scripts` no longer exists, and has been replaced with `openedx`.
@@ -150,21 +173,32 @@ Other package.json edits
150173

151174
- Change the author to "Open edX"
152175

153-
main
154-
----
176+
exports
177+
-------
178+
179+
Define the public API for your package:
155180

156181
```json
157-
"main": "src/index.ts",
182+
"exports": {
183+
".": {
184+
"types": "./dist/index.d.ts",
185+
"import": "./dist/index.js",
186+
"default": "./dist/index.js"
187+
},
188+
"./app.scss": "./dist/app.scss"
189+
},
158190
```
159191

192+
The `exports` map decouples your public API from the internal `dist/` directory structure. Consumers import from clean paths (e.g., `@openedx/frontend-app-yourapp/app.scss`) and the map resolves them to the actual files in `dist/`. If your app has SCSS files that downstream site projects need to `@use`, add them as exports as shown above.
193+
160194
files
161195
-----
162196

163-
This is a buildless library, so we package files in `src`:
197+
Package the compiled output in `dist`:
164198

165199
```json
166200
"files": [
167-
"/src"
201+
"/dist"
168202
],
169203
```
170204

@@ -175,8 +209,8 @@ This means that the code from the library can be safely tree-shaken by webpack.
175209

176210
```json
177211
"sideEffects": [
178-
"*.css",
179-
"*.scss"
212+
"dist/**/*.css",
213+
"dist/**/*.scss"
180214
],
181215
```
182216

@@ -196,15 +230,10 @@ Finally, make sure the following fields are set properly:
196230
Clean up .npmignore
197231
===================
198232

199-
This is what should be in the repo's `.npmignore`. No more, no less:
233+
Since we use the `files` field in `package.json` to whitelist only `/dist`, the `.npmignore` file is largely unnecessary. You can keep a minimal version:
200234

201235
```
202-
__mocks__
203236
node_modules
204-
*.test.js
205-
*.test.jsx
206-
*.test.ts
207-
*.test.tsx
208237
```
209238

210239
Clean up .gitignore
@@ -257,10 +286,14 @@ Create a `tsconfig.json` file and add the following contents to it:
257286

258287
```json
259288
{
260-
"extends": "@openedx/frontend-base/config/tsconfig.json",
289+
"extends": "@openedx/frontend-base/tools/tsconfig.json",
261290
"compilerOptions": {
291+
"baseUrl": ".",
262292
"rootDir": ".",
263293
"outDir": "dist",
294+
"paths": {
295+
"@src/*": ["./src/*"]
296+
}
264297
},
265298
"include": [
266299
"src/**/*",
@@ -275,6 +308,53 @@ Create a `tsconfig.json` file and add the following contents to it:
275308

276309
This assumes you have a `src` folder and your build goes in `dist`, which is the best practice.
277310

311+
The `@src` path alias
312+
---------------------
313+
314+
The `paths` configuration above sets up the `@src` alias, which allows you to import from your app's `src` directory using `@src/...` instead of relative paths. For example:
315+
316+
```typescript
317+
// Instead of:
318+
import { MyComponent } from '../../../components/MyComponent';
319+
320+
// You can use:
321+
import { MyComponent } from '@src/components/MyComponent';
322+
```
323+
324+
For this to work, the app must define its own `@src` path mapping in `tsconfig.json`. **tsc-alias** will then rewrite `@src` imports to relative paths during the build step, so the compiled JavaScript has proper paths.
325+
326+
Add a tsconfig.build.json file
327+
------------------------------
328+
329+
Create a `tsconfig.build.json` file for compiling your app before publishing:
330+
331+
```json
332+
{
333+
"extends": "./tsconfig.json",
334+
"compilerOptions": {
335+
"rootDir": "src",
336+
"outDir": "dist",
337+
"noEmit": false
338+
},
339+
"include": [
340+
"src/**/*"
341+
],
342+
"exclude": [
343+
"src/**/*.test.ts",
344+
"src/**/*.test.tsx",
345+
"src/**/*.spec.ts",
346+
"src/**/*.spec.tsx",
347+
"src/__mocks__/**/*",
348+
"src/setupTest.js"
349+
]
350+
}
351+
```
352+
353+
This config:
354+
- Extends your main `tsconfig.json`
355+
- Outputs compiled JavaScript and type declarations to `dist/`
356+
- Excludes test files and mocks from the published package
357+
278358

279359
Edit jest.config.js
280360
===================
@@ -283,7 +363,7 @@ Replace the import from 'frontend-build' with 'frontend-base'.
283363

284364
```diff
285365
- const { createConfig } = require('@openedx/frontend-build');
286-
+ const { createConfig } = require('@openedx/frontend-base/config');
366+
+ const { createConfig } = require('@openedx/frontend-base/tools');
287367
```
288368

289369
Use 'test' instead of 'jest' as the config type for createConfig()
@@ -327,7 +407,7 @@ Resulting jest.config.js file
327407
An uncustomized jest.config.js looks like:
328408

329409
```js
330-
const { createConfig } = require('@openedx/frontend-base/config');
410+
const { createConfig } = require('@openedx/frontend-base/tools');
331411

332412
module.exports = createConfig('test', {
333413
// setupFilesAfterEnv is used after the jest environment has been loaded. In general this is what you want.
@@ -354,7 +434,7 @@ Add a babel.config.js file for Jest
354434
Jest needs a babel.config.js file to be present in the repository. It should look like:
355435

356436
```js
357-
const { createConfig } = require('@openedx/frontend-base/config');
437+
const { createConfig } = require('@openedx/frontend-base/tools');
358438

359439
module.exports = createConfig('babel');
360440
```
@@ -381,7 +461,7 @@ ESLint has been upgraded to v9, which has a new 'flat' file format. Replace the
381461
```js
382462
// @ts-check
383463

384-
const { createLintConfig } = require('@openedx/frontend-base/config');
464+
const { createLintConfig } = require('@openedx/frontend-base/tools');
385465

386466
module.exports = createLintConfig(
387467
{
@@ -494,14 +574,14 @@ SVGR "ReactComponent" imports have been removed
494574
We have removed the `@svgr/webpack` loader because it was incompatible with more modern tooling (it requires Babel). As a result, the ability to import SVG files into JS as the `ReactComponent` export no longer works. We know of a total of 5 places where this is happening today in Open edX MFEs - frontend-app-learning and frontend-app-profile use it. Please replace that export with the default URL export and set the URL as the source of an `<img>` tag, rather than using `ReactComponent`. You can see an example of normal SVG imports in `test-site/src/ExamplePage.tsx`.
495575

496576

497-
Import createConfig and getBaseConfig from @openedx/frontend-base/config
577+
Import createConfig and getBaseConfig from @openedx/frontend-base/tools
498578
========================================================================
499579

500580
In frontend-build, `createConfig` and `getBaseConfig` could be imported from the root package (`@openedx/frontend-build`). They have been moved to a sub-directory to make room for runtime exports from the root package (`@openedx/frontend-base`).
501581

502582
```diff
503583
- const { createConfig, getBaseConfig } = require('@openedx/frontend-build');
504-
+ const { createConfig, getBaseConfig } = require('@openedx/frontend-base/config');
584+
+ const { createConfig, getBaseConfig } = require('@openedx/frontend-base/tools');
505585
```
506586

507587
You may have handled this in steps 4 and 5 above (jest.config.js and .eslintrc.js)
@@ -874,19 +954,3 @@ Refactor slots
874954
First, rename `src/plugin-slots`, if it exists, to `src/slots`. Modify imports and documentation across the codebase accordingly.
875955

876956
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.
877-
878-
879-
Remove build step from CI
880-
=========================
881-
882-
In `.github/workflow/ci.yml`, remove the build step.
883-
884-
```diff
885-
- name: Test
886-
run: npm run test
887-
- - name: Build
888-
- run: npm run build
889-
- name: i18n_extract
890-
run: npm run i18n_extract
891-
```
892-
```

nodemon.pack.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"watch": ["runtime", "shell", "tools", "index.ts", "types.ts"],
33
"ext": "ts,tsx,js,jsx,json,scss,css",
4-
"ignore": ["node_modules/**", ".git/**", "pack/**", "config/**", "tools/dist/**"],
4+
"ignore": ["node_modules/**", ".git/**", "pack/**"],
55
"delay": "250ms",
66
"exec": "npm run build:pack"
77
}

package-lock.json

Lines changed: 43 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)