MFE header override

So we’ve been trying to do this, but running into issues. Wondering if there are any gotchas, version incompatibilities, etc.?

For reference, this is what we did:

  1. forked GitHub - edx/frontend-component-header

  2. created a new branch off v2.2.2 (the current version we were using), and made a minor tweak

  3. pushed it to a public gitlab repository

  4. installed in the mfe as:

npm install  '@edx/frontend-component-header@git+https://gitlab.com/path/to/frontend-component-header-test.git#v2.2.2'

But then, building the MFE fails to resolve @edx/frontend-component-header:

ERROR in ./src/index.scss (./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./node_modules/resolve-url-loader!./node_modules/sass-loader/dist/cjs.js??ref--5-4!./src/index.scss)
Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
SassError: Can't find stylesheet to import.
  ╷
8 │ @import '~@edx/frontend-component-header/dist/index';
  │         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ╵
  src/index.scss 8:9  root stylesheet
 @ ./src/index.scss 2:26-248 22:4-35:5 25:25-247
 @ ./src/index.jsx

ERROR in ./src/index.jsx
Module not found: Error: Can't resolve '@edx/frontend-component-header' in './src'

This resolving fails for any form of @edx/frontend-component-header from css and js.

Is there some extra configuration we need, or an updated peer dependency, or …? It looks like something in the build chain isn’t following the alias, but we can’t figure out where.

@djoy or @kmccormick Can you help with this issue? Since edX is using the edx-frontend-component-header-edx fork, we figured that following the docs would Just Work™, but it didn’t :frowning:

I think the issue here is that when you install the package from npm, it is the compiled version of the package, and has a ‘dist’ folder with the compiled assets. The git repo doesn’t have this folder so it won’t work the same way.

Here is one thing you can do:

  1. Clone the git repo to a directory
  2. Run npm install && npm run build in this directory
  3. Install this as an alias using npm install '@edx/frontend-component-header@/path/to/git/repo'

Running npm run build will create the dist directory in the folder and the override should now work :slight_smile:

Thanks @xitij2000.

The git repo doesn’t have this folder so it won’t work the same way.

I see two problems with the assumption of this restriction:

  1. the official docs mention installing from git, npm, and local directory, so this use case should be supported.
  2. what’s special about the build process such that npm can’t build it properly when installing from git source? This is the first package where I’ve experienced it failing to run when installed from git.

I tried installing from the local directory according to your instructions though, and get this:

Module not found: Error: Can't resolve 'react-responsive' in '/path/to/custom-header/dist'

I did previously try publishing to npm and that didn’t work (same errors as from git). However, just now I tried npm install && npm run build && npm publish, and the resulting package does work. So I guess that’s a kind of work-around for now.

But how can we do local development and testing on this? We don’t want to publish a new npm package every time we want to test changes locally, or run a test deployment. :confused:

I’m afraid the official docs might be wrong in this case. The mechanism they outline is correct, but the way these components are imported means that it won’t work without running he build step first.

When you install a package from the npm registry it downloads a pre-built package which contains only the compiled assets. You can see this here from the package for the header component: https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-2.2.7.tgz

For local development here is what you can do:

  1. You can use local module overrides to override the location is looks for the header component: GitHub - edx/frontend-build
    OR
    You can use npm aliases to get the same effect. Clone the frontend app you want to work with and frontend-component-header in your projects folder. Go to the frontend app folder, and install your local copy of the header component by running npm install '@edx/frontend-component-header@~/projects/frontend-component-header'
  2. Run npm install in the frontend-component-header directory.
  3. Run npm run build in the frontend-component-header directory.
  4. Run npm start in the frontend app directory.

If you change the frontend component, you will need to run npm run build in that directory again. One way around this is to temporarily modify the SCSS and JavaScript imports so that the component is loaded from the src directory instead of dist.

Late to the conversation, but I think @xitij2000 is right. Without the build step, the dist directory won’t exist for most libraries and so it won’t be able to find the assets. If you’re pointing at a release tag on Github, it’s possible it works since Github has the built assets associated with the tag? I’m not sure exactly. It seems as if we should update that doc to be clearer and more correct.

Maybe it would be a good idea to add by default a “prepare” script in the package.json file with something like:

"prepare": "npm run build", 

The prepare script is run when the package is installed through git. https://docs.npmjs.com/cli/v7/using-npm/scripts#life-cycle-scripts

With that change, I think that the installation from github should work.

1 Like

Oh yes, that looks like it would be perfect! I knew there must be something like that, otherwise it wouldn’t be possible to install most packages from git or local filesystem. :+1:

That is a pretty convoluted method there; hopefully we can develop support for a more streamlined method. :confused: Maybe adding a “prepare” script as @morenol suggests could improve things? As @djoy points out, without a build step, most libraries aren’t going to work, so other libraries must have some solution to allow installing from source/git/filesystem. I’m not sure though, and don’t have any more spare time to experiment further right now unfortunately.

@swalladge Adding a prepare script might solve the issue when installing from git, but that will not solve the following problem:

With the prepare script in place the build step can run when you’re installing from git. However, if you make a change locally and want to test it, it won’t show up, since the package will be loaded from the dist directory, which will contain outdated builds. You will still need to run npm run build again after each change you want to test.

Which is why I recommend adding the local module override for development. The prepare script will be useful during deployment since it will make sure that the dist directory exists when the package is being installed on the server.

1 Like

Hi Folks! I am trying to install the header component for the Account MFE. I am working with a lilac.2 devstack, the account MFE is also checked out for lilac.2.
I have cloned the header component repository, checked out v2.2.4, ran npm install and npm run build in that repo.
I installed the component in the MFE with
npm install --save @edx/frontend-component-header@file../frontend-component-header
When I start the MFE, it compiles successfully but getting an error in the browser:

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.

The brand-openedx and the frontend-component-footer override work just fine, I am only facing issues with the header.

I haven’t made any changes in the header repository yet, just trying to install it as it is for a start.

I am new to react and would really appreciate some help! Thanks!

Hi @mrtmm , did you run the extra npm steps in this post? MFE header override - #5 by xitij2000

It doesn’t show the same error you’re seeing, but it does talk about how to get around issues when trying to build frontend header overrides in local dev environments.

Hi @Jill, yes, these are the steps I’ve been following.

Hi Maari,

Just to make sure I understand - are you using npm install --save @edx/frontend-component-header@file../frontend-component-header so that you can do local development on a header to modify it for your MFE? If not, then it’s not necessary to ‘install’ the header with an alias like this, as it’s already been downloaded and is in node_modules.

So that was a sanity check - now, assuming you’re trying to edit the header locally…

So what you’re running into is a weird limitation in how npm/webpack works.

If you have these directories:

frontend-app-account
    node_modules
        react
        react-dom
frontend-component-header
    node_modules
        react
        react-dom

You’ve told npm to find the header in frontend-component-header via package.json. Any reference to import 'react' or 'react-dom' from those files in frontend-component-header is going to look first in the node_modules directory in that package. That means it’ll find react and react-dom in frontend-component-header/node_modules. Meanwhile, the account MFE is going to import react and react-dom from frontend-app-account/node_modules.

So the thing that’s breaking is option #3 in that error message you’re seeing - there are literally two instances of React being loaded, so it blows up.

That’s the underlying problem, anyway. It’s one we’ve run into in a number of libraries that MFEs depend on, and we created a solution for it that has yet to be applied to frontend-component-header, unfortunately. At least I just tried it, and couldn’t quite get it to work. I can dig in a bit more, but wanted to post here sooner rather than later.

See documentation here on local module overrides in webpack: https://github.com/edx/frontend-build#local-module-configuration-for-webpack

The idea is you put a module.config.js file in your MFE that tells webpack where to find the code for a given import using webpack resolve aliases. You can see the guts of what it’s doing under the hood here: https://github.com/edx/frontend-build/blob/master/config/getLocalAliases.js

I’m going to mess around with it a bit more to see if I can get it to work for frontend-component-header, but it’s possible that library needs to be tweaked a bit to make its distribution compatible with the mechanism; I’m not yet sure exactly where the problem lies.

Let me know if I can explain any of that more clearly!

1 Like

Alright! I got it to work!

module.config.js contents:

module.exports = {
  localModules: [
    // This line intercepts imports for the header's stylesheet and redirects them to the `src` directory of your local checkout of the header, which is where index.scss lives.
    { moduleName: '@edx/frontend-component-header/dist', dir: '../frontend-component-header', dist: 'src' },
    // This line catches JavaScript imports and points them at that same src directory.
    { moduleName: '@edx/frontend-component-header', dir: '../frontend-component-header', dist: 'src' },
  ],
};

I’ve noticed that JavaScript changes will be hot-reloaded, whereas SCSS changes may require a manual refresh. But this should get you working! Note that the dir attribute in each of those needs to point at your checkout of the header, wherever that is.

3 Likes

@djoy Thank you so much for helping @mrtmm here! I was out of ideas.

Thank you so much for the help and the detailed explanation @djoy! I just tried this out and got it working! :slightly_smiling_face:

1 Like