Skip to content

Commit 475e884

Browse files
arbrandesclaude
andcommitted
feat!: remove module.config.js support, add ADR for compilation and local dev workflow
Remove the getLocalAliases() functionality and all module.config.js references from the codebase. This mechanism is replaced by the tgz-based local development workflow introduced earlier. Add ADR 0010 documenting the decisions to compile TypeScript before publishing, remove module.config.js, and adopt tarball-based local development as the official workflow. BREAKING CHANGE: module.config.js is no longer supported for local development of dependencies. Use the tgz-based workflow instead. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 63d57cb commit 475e884

7 files changed

Lines changed: 62 additions & 76 deletions

File tree

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
node_modules
22
npm-debug.log
33
coverage
4-
module.config.js
54
/.tsbuildinfo.*
65
dist/
76
scss
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Compile TypeScript before publishing and adopt tgz-based local development
2+
3+
## Summary
4+
5+
We compile `@openedx/frontend-base` and `@openedx/frontend-app-*` packages to JavaScript (with `.d.ts` declarations) before publishing to npm, and adopt a tarball-based (`npm pack`) workflow as the official mechanism for local development of said packages. As part of this, we remove support for `module.config.js`-based local aliases.
6+
7+
## Context
8+
9+
### The need for TypeScript compilation
10+
11+
Previously, `@openedx/frontend-base` shipped raw TypeScript source and relied on consumers' bundlers (webpack via `ts-loader`) to transpile it at build time. This worked, but prevented the use of TypeScript path aliases (e.g., `@src/*`) in consuming applications. Path aliases are resolved by `tsc` during compilation, and when a dependency ships raw TypeScript, its own aliases leak into the consumer's build, where they cannot be resolved. By compiling to JavaScript before publishing and using `tsc-alias` to resolve path aliases at that stage, consumers are free to define and use their own aliases independently.
12+
13+
### The problem with module.config.js
14+
15+
`module.config.js` was the previous mechanism for local development of frontend dependencies. It worked by reading a configuration file at webpack build time and creating `resolve.alias` entries to redirect imports to local directories. While functional in simple cases, it had several problems:
16+
17+
1. **Webpack-only**: It only affected webpack's module resolution, so features that depend on Node.js resolution (such as TypeScript path aliases or `tsc`-based compilation) were not supported.
18+
2. **Brittle dependency resolution**: It attempted to second-guess npm's dependency resolution by manually resolving peer dependencies to the consumer's `node_modules`. This not only side-stepped the deduplication that happens in production, but often broke things entirely with modern `exports` maps.
19+
3. **Divergence from production**: The aliased resolution paths differed fundamentally from how dependencies resolve in a published package, violating the principle that development should mirror production as closely as possible.
20+
21+
### Alternatives considered
22+
23+
- **`npm link`**: Does not work with the project's TypeScript configuration (`moduleResolution: "bundler"` is incompatible with symlinked packages).
24+
- **`yarn`, `pnpm`, `bun`**: Switching package managers would entail too many changes across the Open edX ecosystem for the benefit gained.
25+
- **`yalc`**: A promising tool that wraps `npm pack` with push/watch semantics. It does what we need, but has not been actively maintained (last merge in 2023) and is missing features we would want (e.g., `--peer` flag). It remains a potential future option if it becomes actively maintained again or if we fork it.
26+
27+
### Design principles
28+
29+
The following principles guided this decision:
30+
31+
- **Closeness to production**: The development workflow should mirror production as closely as possible to minimize environment-specific bugs.
32+
- **Resource optimization**: The workflow should not add unnecessary hoops. Developer time, system resources, and cognitive load should be minimized.
33+
- **Consistency**: The same development workflow should work uniformly across all packages: `frontend-base` itself, consuming applications like `frontend-app-authn`, third-party packages, and site configuration repositories.
34+
35+
## Decision
36+
37+
1. **Compile TypeScript before publishing**: `@openedx/frontend-base` and all frontend-app-* repositories ship compiled JavaScript with `.d.ts` declaration files, using export maps to define its public API. This enables consuming applications to also pre-compile their TypeScript and use path aliases.
38+
39+
2. **Remove `module.config.js` support**: The `getLocalAliases()` function and all references to `module.config.js` are removed from the webpack configurations and the codebase.
40+
41+
3. **Adopt tarball-based local development**: The official mechanism for local development of Open edX frontend dependencies is the `npm pack` / `.tgz`-based workflow. The developer builds the dependency, packs it into a tarball, and the consuming project installs from that tarball. Tooling to automate this (watching for changes, re-packing, and re-installing) is provided in the `frontend-dev-utils` repository and can be adapted by consuming projects.
42+
43+
## Implementation
44+
45+
### TypeScript compilation
46+
47+
The package is compiled with `tsc` using `tsconfig.build.json`, producing JavaScript output and declaration files under `/dist`. Path aliases are resolved post-compilation with `tsc-alias`. The npm `exports` map in `package.json` maps public entry points to their compiled locations.
48+
49+
At the bundler level, we add `tsconfig-paths-webpack-plugin` to the default webpack configurations so that TypeScript path aliases are also respected by webpack builds (including during `npm run dev`) without duplicating their definitions.
50+
51+
As part of this change, we unify the TypeScript build outputs under `/dist` and use npm export maps to decouple the internal file structure from the package's public API.
52+
53+
### Local development workflow
54+
55+
To develop a local dependency (e.g., `@openedx/frontend-base`) against a consuming project:
56+
57+
1. In the dependency: `npm run build` (or use a watcher like `nodemon` with `npm run pack:local`)
58+
2. In the consumer: install from the tarball and run the dev server
59+
60+
This approach is consistent across all package types and faithfully reproduces production resolution semantics, since `npm pack` produces the same artifact that `npm publish` would.
61+
62+
The `test-site` project contains reference tooling that automates detecting new tarballs and reinstalling them during development.

docs/how_tos/migrate-frontend-app.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,6 @@ This is the current standard `.gitignore`:
242242
node_modules
243243
npm-debug.log
244244
coverage
245-
module.config.js
246245
dist/
247246
/*.tgz
248247

tools/webpack/utils/getLocalAliases.ts

Lines changed: 0 additions & 65 deletions
This file was deleted.

tools/webpack/webpack.config.build.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@ import {
1414
getStylesheetRule
1515
} from './common-config';
1616

17-
import getLocalAliases from './utils/getLocalAliases';
1817
import getPublicPath from './utils/getPublicPath';
1918
import getResolvedSiteConfigPath from './utils/getResolvedSiteConfigPath';
2019

21-
const aliases = getLocalAliases();
2220
const resolvedSiteConfigPath = getResolvedSiteConfigPath('site.config.build.tsx');
2321

2422
const config: Configuration = {
@@ -35,7 +33,6 @@ const config: Configuration = {
3533
},
3634
resolve: {
3735
alias: {
38-
...aliases,
3936
'site.config': resolvedSiteConfigPath,
4037
},
4138
plugins: [

tools/webpack/webpack.config.dev.shell.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@ import {
1515
} from './common-config';
1616

1717
import HtmlWebpackPlugin from 'html-webpack-plugin';
18-
import getLocalAliases from './utils/getLocalAliases';
1918
import getPublicPath from './utils/getPublicPath';
2019
import getResolvedSiteConfigPath from './utils/getResolvedSiteConfigPath';
2120

22-
const aliases = getLocalAliases();
2321
const resolvedSiteConfigPath = getResolvedSiteConfigPath('shell/site.config.dev.tsx');
2422

2523
const config: Configuration = {
@@ -32,7 +30,6 @@ const config: Configuration = {
3230
},
3331
resolve: {
3432
alias: {
35-
...aliases,
3633
'site.config': resolvedSiteConfigPath,
3734
},
3835
extensions: ['.js', '.jsx', '.ts', '.tsx'],

tools/webpack/webpack.config.dev.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@ import {
1515
getStylesheetRule
1616
} from './common-config';
1717

18-
import getLocalAliases from './utils/getLocalAliases';
1918
import getPublicPath from './utils/getPublicPath';
2019
import getResolvedSiteConfigPath from './utils/getResolvedSiteConfigPath';
2120

22-
const aliases = getLocalAliases();
2321
const resolvedSiteConfigPath = getResolvedSiteConfigPath('site.config.dev.tsx');
2422

2523
const config: Configuration = {
@@ -32,7 +30,6 @@ const config: Configuration = {
3230
},
3331
resolve: {
3432
alias: {
35-
...aliases,
3633
'site.config': resolvedSiteConfigPath,
3734
},
3835
plugins: [

0 commit comments

Comments
 (0)