Injecting JS into HTMLs platform wide

Hello @arbrandes
I am just installing OpenEdX. I have tutor 22.0.0 and tutor-mfe 22.0.0. I have used your code example:

but when I build the mfe image, I get:

 > [site-prod 2/2] RUN npm run build:
 108.1 ERROR in ./src/customApp.tsx:7:22
 108.1 TS2304: Cannot find name 'CookieYesScriptLoader'.
 108.1     5 |   appId: 'custom',
 108.1     6 | };
 108.1   > 7 | addScript(customApp, CookieYesScriptLoader);
 108.1       |                      ^^^^^^^^^^^^^^^^^^^^^
 108.1     8 |
 108.1     9 | export default customApp;
 108.1

Any idea what is going on ?

I believe this error is related to the new frontend-base schema for apps. The code provided in the issues works for “legacy” MFEs, but not for the new apps. If I don’t add our script to ‘all’, but only to the list of MFEs that exclude notifications and instructor-dashboard, then the MFE image builds fine.

Therefore, it fails building the notifications (and probably the instructor dashboard) which enable frontend-base by default.

Would there be a corrected code for frontend-base MFEs ?

Hum… I have now in principle added the script loader to all of the MFEs except notifications and instructor-dashboard (i.e. tutor-plugins/plugin_slots.py at v22.0.0-3 · calculquebec/tutor-plugins · GitHub) , but the cookie banner only shows on the registration page, i.e.

but it is not in, for example, the catalog MFE:

https://apps.edx.evolo-dev.calculquebec.cloud/catalog/

The script is present in none of the other MFE’s pages beside the authentification one.

What am I doing wrong ?

Just so we’re on the same page: did you follow the relevant instructions in the tutor-mfe README?

I don’t mean to imply you’re doing anything wrong, by the way. It’s entirely possible the documentation is incomplete, or that the code itself has a bug.

As I look at what the sample Google Analytics tutor plugin does, it seems the tutor-mfe README is indeed incomplete. It’s missing the frontend-base bit:

Can you check if it works for you if you add that?

Or rather, the information is there, but I figure the placement could be improved. The frontend-base bits are in the last paragraph, with no examples to shore it up:

To register a loader on the frontend-base site, use the mfe-site-custom-app-definitions and mfe-site-custom-app-imports patches in place of their mfe-env-config-buildtime-* equivalents, and target “site” (or “all”) in EXTERNAL_SCRIPTS. The loader’s constructor({ config }) receives the customApp runtime configuration, which you can populate via the site config (for example, through commonAppConfig) or the runtimeConfigJsonUrl endpoint.

If you care to review the docs improvement:

Hum… I think there is still a bug with the code sample. When building it, I now get:

90.28 ERROR in ./src/customApp.tsx:9:10
90.28 TS2339: Property 'config' does not exist on type 'CookieYesScriptLoader'.
90.28      7 | class CookieYesScriptLoader {
90.28      8 |   constructor({ config }) {
90.28   >  9 |     this.config = config;
90.28        |          ^^^^^^
90.28     10 |   }
90.28     11 |
90.28     12 |   loadScript() {
90.28
90.28 ERROR in ./src/customApp.tsx:13:15
90.28 TS2339: Property 'config' does not exist on type 'CookieYesScriptLoader'.
90.28     11 |
90.28     12 |   loadScript() {
90.28   > 13 |     if (!this.config.COOKIEYES_SCRIPT_URL) {
90.28        |               ^^^^^^
90.28     14 |       return;
90.28     15 |     }
90.28     16 |     const script = document.createElement('script');
90.28
90.28 ERROR in ./src/customApp.tsx:18:23
90.28 TS2339: Property 'config' does not exist on type 'CookieYesScriptLoader'.
90.28     16 |     const script = document.createElement('script');
90.28     17 |     script.id = 'custom-script';
90.28   > 18 |     script.src = this.config.COOKIEYES_SCRIPT_URL;
90.28        |                       ^^^^^^
90.28     19 |     document.head.appendChild(script);
90.28     20 |   }
90.28     21 | }

Are apps in tsx more strict on the code declaration than jsx MFEs were ?

Looks like I am hitting this:

https://stackoverflow.com/questions/45604043/define-class-property-types-in-typescript

I am not sure how to make it work for both JSX and TSX though, because if I add

config: object;

to the class declaration, then it’s the JSX that errors out with:

78.53 SyntaxError: /openedx/app/env.config.jsx: Unexpected token (26:8)
78.53
78.53   24 | }
78.53   25 | class CookieYesScriptLoader {
78.53 > 26 |   config: object;
78.53      |         ^
78.53   27 |   constructor({ config }) {
78.53   28 |     this.config = config;
78.53   29 |   }

I suspect the GoogleAnalytics plugin example will have the same issue ?

So, I have only managed to make this work by defining both a JSX and a TSX (thanks Antigravity for translating the code), and adding the JSX version to MFEs, and the TSX version to apps.

It looks like this:

I don’t know if there’s a better way to do this ?

Indeed, it did. Good find! Both GA plugin and sample code were developed before the full frontend-base implementation landed, so neither were properly tested with it.

The GA plugin was fixed here, and the tutor-mfe README, here.

The root cause, as you figured out, is that the Typescript compiler is configured to be stricter in *.ts files. This is generally a good thing, as it usually results in safer, cleaner code. It’s also perfectly possible to write plain javascript that passes these checks and also works in *.js files. Check the diff in the GA plugin fix, for instance.

Your solution is perfectly fine, but there’s probably no need to have two sets of snippets. You just have to write an equivalent piece of plain javascript that passes the stricter checks.

Thanks! With the example I was able to write a single JSX which works for both.

I wrote it so it can load multiple external scripts. And I also wrote one for external CSS stylesheets.

Dropping it here if this is useful to some people.