Skip to content

Commit

Permalink
Merge pull request #5 from davidjoy/main
Browse files Browse the repository at this point in the history
Build Milestone: frontend-build migrated into the library
  • Loading branch information
davidjoy authored Jul 24, 2024
2 parents 7f5fd23 + b685edc commit e9e4285
Show file tree
Hide file tree
Showing 68 changed files with 14,890 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
coverage/*
dist/
node_modules/
__mocks__/
__snapshots__/
18 changes: 18 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const path = require('path');

const { createConfig } = require('.');

module.exports = createConfig('eslint', {
ignorePatterns: [
'cli/test-app',
],
parserOptions: {
project: path.resolve(__dirname, './tsconfig.json'),
},
rules: {
'no-console': 'off',
'import/no-dynamic-require': 'off',
'global-require': 'off',
'no-template-curly-in-string': 'off',
},
});
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Default CI
on:
push:
branches:
- main
pull_request:
branches:
- '**'
jobs:
tests:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- name: Setup Nodejs
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VER }}
- name: Install dependencies
run: |
npm ci
cd cli/test-app
npm ci
- name: Lint
run: npm run lint
- name: Test
run: npm run test
13 changes: 13 additions & 0 deletions .github/workflows/lockfileversion-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# check package-lock file version

name: Lockfile Version check

on:
push:
branches:
- main
pull_request:

jobs:
version-check:
uses: openedx/.github/.github/workflows/lockfile-check.yml@master
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
.eslintcache
.idea
.vscode
coverage
dist
node_modules
npm-debug.log
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
18
119 changes: 119 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,122 @@ This project uses the [#module-federation](https://openedx.slack.com/archives/C0
You can follow ongoing progress on the project's [Github project board](https://github.com/orgs/openedx/projects/65/views/1).

Feel free to reach out to David Joy ([Github](https://github.com/davidjoy), [Slack](https://openedx.slack.com/team/UFM4FEN0J)) with any questions.

## Development

This library is not yet published to npm.

In the meantime, it can be used as a replacement for `openedx/frontend-build` in an Open edX micro-frontend in a few steps.

### 1. Clone this repository

Clone this repository as a peer of your micro-frontend folder(s).

### 2. Edit package.json

- Replace the `@openedx/frontend-build` dependency with:

```
- "@openedx/frontend-build": "13.1.4",
+ "@openedx/frontend-base": "file:../frontend-base",
```

This will let your MFE use the checked out version of `frontend-base`.

### 3. `npm install`

Run `npm install` again to update your `node_modules` and `package-lock.json`.

### 4. Migrate your MFE

Follow the steps below to migrate an MFE to use frontend-base.

## Migrating to frontend-base

### 1. Edit package.json `scripts`

Replace all instances of `fedx-scripts` with `openedx` in your package.json file.

> [!TIP]
> **Why change `fedx-scripts` to `openedx`?**
> A few reasons. One, the Open edX project shouldn't be using the name of an internal community of practice at edX for its frontend tooling. Two, some dependencies of your MFE invariably still use frontend-build for their own build needs. This means that they already installed `fedx-scripts` into your `node_modules/.bin` folder. Only one version can be in there, so we need a new name. Seemed like a great time for a naming refresh. |
### 2. Add a tsconfig.json file

Create a tsconfig.json file and add the following contents to it:

```
{
"extends": "@openedx/frontend-base/config/tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"include": [
".eslintrc.js",
"jest.config.js",
"env.config.js",
"src",
"app.d.ts",
]
}
```

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

### 3. Add a Type Declaration file (app.d.ts)

Add a file named `app.d.ts` to the root of your MFE. It should contain:

```
/// <reference types="@openedx/frontend-base" />
```

### 4. Edit `jest.config.js`

Replace the import from 'frontend-build' with 'frontend-base'.

### 5. Edit `.eslintrc.js`

Replace the import from 'frontend-build' with 'frontend-base'.

### 6. Search for any other usages of `frontend-build`

Find any other imports/usages of `frontend-build` in your repository and replace them with `frontend-base` so they don't break.

### 7. i18n Descriptions

Description fields are now required on all i18n messages in the repository. This is because of a change to the ESLint config.

### 8. Jest Mocks

Jest test suites that test React components that import SVG and files must add mocks for those filetypes. This can be accomplished by adding the following module name mappers to jest.config.js:

```
moduleNameMapper: {
'\\.svg$': '<rootDir>/src/__mocks__/svg.js',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/src/__mocks__/file.js',
},
```

Then, create a `src/__mocks__` folder and add the necessary mocks.

**svg.js:**

```
module.exports = 'SvgURL';
```

**file.js:**

```
module.exports = 'FileMock';
```

You can change the values of "SvgURL", and "FileMock" if you want to reduce changes necessary to your snapshot tests; the old values from frontend-build assume svg is only being used for icons, so the values referenced an "icon" which felt unnecessarily narrow.

This is necessary because we cannot write a tsconfig.json in MFEs that includes transpilation of the "config/jest" folder in frontend-base, it can't meaningfully find those files and transpile them, and we wouldn't want all MFEs to have to include such idiosyncratic configuration anyway. The SVG mock, however, requires ESModules syntax to export its default and ReactComponent exports at the same time. This means without moving the mocks into the MFE code, the SVG one breaks transpilation and doesn't understand the `export` syntax used. By moving them into the MFE, they can be easily transpiled along with all the other code when jest tries to run.

### 9. SVGR "ReactComponent" imports have been removed.

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 `cli/test-app/src/App.jsx`.
83 changes: 83 additions & 0 deletions bin/openedx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env node

const chalk = require('chalk');

const presets = require('../lib/presets');

/**
* TLDR:
* - Find the command to be run in process.argv
* - Remove 'openedx' in process.argv
* - Add a --config option to process.argv if one is missing
* - Execute the command's bin script by pulling it directly in with require()
*
* This file forwards cli commands by manipulating process.argv values and then
* directly requiring bin scripts from the specified packages (as opposed to
* attempting to run them from the aliases npm copies to the .bin folder upon
* install). This seems like a relatively safe thing to do since these file
* names are identical to their cli name and this method of requiring/executing
* them should behave the same as if run from the command line as usual.
*/

function optionExists(keys) {
return process.argv.some((arg) => {
// eslint-disable-next-line no-plusplus
for (let i = 0; i < keys.length; i++) {
if (arg.startsWith(keys[i])) {
return true;
}
}
return false;
});
}

// Ensures that a config option already exists and if it does not adds a default
function ensureConfigOption(preset, keys = ['--config', '-c']) {
if (!optionExists(keys)) {
console.log(`Running with resolved config:\n${preset.resolvedFilepath}\n`);
process.argv.push(keys[0]);
process.argv.push(preset.resolvedFilepath);
}
}

// commandName is the third argument after node and 'openedx'
const commandName = process.argv[2];

// remove 'openedx' from process.argv to allow subcommands to read options properly
process.argv.splice(1, 1);

switch (commandName) {
case 'eslint':
ensureConfigOption(presets.eslint);
// eslint-disable-next-line import/extensions, import/no-extraneous-dependencies
require('.bin/eslint');
break;
case 'jest':
ensureConfigOption(presets.jest);
require('jest/bin/jest');
break;
case 'webpack':
ensureConfigOption(presets.webpack);
require('webpack/bin/webpack');
break;
case 'webpack-dev-server':
ensureConfigOption(presets.webpackDevServer);
require('webpack-dev-server/bin/webpack-dev-server');
break;
case 'formatjs': {
const commonArgs = [
'--format', 'node_modules/@openedx/frontend-base/lib/formatter.js',
'--ignore', 'src/**/*.json',
'--out-file', './temp/formatjs/Default.messages.json',
'--', 'src/**/*.js*',
];
process.argv = process.argv.concat(commonArgs);
require('@formatjs/cli/bin/formatjs');
break;
}
case 'serve':
require('../lib/scripts/serve');
break;
default:
console.log(chalk.red(`[ERROR] openedx: The command ${chalk.bold.red(commandName)} is unsupported.`));
}
Empty file added cli/test-app/.env
Empty file.
3 changes: 3 additions & 0 deletions cli/test-app/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PORT=3000
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
TEST_VARIABLE='foo'
2 changes: 2 additions & 0 deletions cli/test-app/.env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
TEST_VARIABLE='foo'
5 changes: 5 additions & 0 deletions cli/test-app/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
coverage/*
dist/
node_modules/
__mocks__/
__snapshots__/
7 changes: 7 additions & 0 deletions cli/test-app/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { createConfig } = require('@openedx/frontend-base');

module.exports = createConfig('eslint', {
parserOptions: {
project: './tsconfig.json',
},
});
6 changes: 6 additions & 0 deletions cli/test-app/env.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
FALSE_VALUE: false,
CORRECT_BOOL_VALUE: 'Good, false meant false. We did not cast a boolean to a string.',
INCORRECT_BOOL_VALUE: 'Why was a false boolean true?',
INTEGER_VALUE: 123,
};
8 changes: 8 additions & 0 deletions cli/test-app/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { createConfig } = require('@openedx/frontend-base');

module.exports = createConfig('jest', {
moduleNameMapper: {
'\\.svg$': '<rootDir>/src/__mocks__/svg.js',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/src/__mocks__/file.js',
},
});
Loading

0 comments on commit e9e4285

Please sign in to comment.