2

I am working on a web app in which I need locally-defined React components to 'over-write' (i.e. take precedence over) default React components that are in a common code base. The common code base is required as a dependency by the local app, and it provides the server code and the default components.

The goal is that if you require the common code base and run the app without making any changes, you get a vanilla version of the app, but if you locally define a component (for example Logo.jsx), then the app should use that version of Logo.jsx rather than the default component in the underlying code base.

My first thought is to solve this by using a dynamic import statement in all of the components in the underlying code base, so that at build time webpack would simply look for local versions of the components and then either include the local version in the dependency graph or else use the fallback component. In pseudocode, my ideal version of this solution would look something like this:

define a custom import statement (importCustomOrDefaultComponent.js):

function importCustomOrDefaultComponent(name) {
  // check for a component with this name in the local project, and if so, return
  // if no local component exists, return the default component from this repo
}

module.exports = importCustomOrDefaultComponent;

use that custom import statement in all components in underlying code base (e.g. a NavBar component):

import React from 'react';
const importCustomOrDefaultComponent = require('../importCustomOrDefaultComponent.js');
const Logo = importCustomOrDefaultComponent('Logo.jsx')

function NavBar () {
  return (
    <div>
       ...
       <Logo />
       ...
    </div>
  );
};

export default NavBar;

I have been fiddling with this and it does not seem possible to do as I have outlined above, because webpack won't let you use a custom import statement like this to affect how it builds the dependencies. I tried writing importCustomOrDefaultComponent using 'fs' to look for the custom file, but that didn't work because webpack tried to bundle the function in with everything else and ultimately I got an error because 'fs' will not be available in the browser... It seems you can only use require('xxx') or import xxx from 'xxx'. Is the above possible to do, and if so, how would that importCustomOrDefaultComponent(name) operate?

If this can't be done via a custom import function in the source, could this be done via a webpack plugin? I was unable to find any that handle this case.

If this can't work, are there alternative patterns to accomplish my goal?

  • I have looked into code splitting, but while it splits up the code it does not seem to provide a solution for being able to dynamically choose whether to include component A or component B at build time.
  • I also considered pursuing a plugin architecture, where custom components are stored in redux state and are accessed dynamically client side instead of trying to make these decisions while building the client bundle. Is that a better approach?

1 Answer 1

2

I'll rewrite my response later this evening with examples, for now this would be my immediate suggestion:

This looks like a good use case for webpack aliases or preprocess-loader (and ifdef statements). For React Native only Babel plugins can be used, that's another story all together.

Personally, I like using babel-plugin-module-alias to support React Native with a react-native-web target.

Update: (as promised)

Code Splitting (Load Static Modules Dynamically)

Since you are using Webpack, you want to keep the vendor modules in consideration. The easiest way to do dynamic loading is with code splitting via Webpack's require(). Since this method is async, your surrounding code cannot load the module synchronously.

Example:

class SomeView extends Component {
    state = {
        asyncComponent: null
    };

    componentDidMount() {
        import(/* webpackChunkName: "asyncComponent" */ './asyncComponent.jsx')
            .then((asyncComponent) => this.setState({ asyncComponent }));
    }

    render() {
        const AsyncComponent = this.state.asyncComponent;

        return (
            <div>
                {AsyncModule !== null ? <AsyncModule /> : null}
            </div>
        )
    }
}

Note: In older versions of Webpack, require.ensure() was used.

Using Conditional Compilation

With preprocess-loader you can define different constants for at build time. This also allows for conditional login around top-level statements like import that cannot be used inside block scopes. Keep in mind preprocess does treat @ifdef as truthful regardless of value, some cases you may need to use @if.

Example:

// @if MOBILE=true
import Header from 'components/MobileHeader.js'
// @endif
// @if MOBILE=false
import Header from 'components/DesktopHeader.js'
// @endif

Using Webpack Aliases

Inside your Webpack config:

resolve: {  
    alias: {
        components: path.resolve(__dirname, 'src/components/'),
        'components/Header': path.resolve(__dirname, IS_MOBILE ? 'src/components/MobileHeader' : 'src/components/DesktopHeader')
    }
}

Inside your JS (from any file, any directory):

import Header from 'components/Header';
Sign up to request clarification or add additional context in comments.

2 Comments

Aliasing seems to work for creating the client bundle via webpack and is a nice clean solution. Unfortunately, I also need to do SSR and aliases causes an issue: When the server starts up it throws a Cannot find module error for components that have aliased imports in them, presumably because they are not webpacked. If SSR cannot be avoided, options seem to be (a) webpack the entire server code (seems not ideal) or (b) somehow pass the aliases to the server code (perhaps via babel polyfill with the babel-plugin-module-resolver plugin?) Any other suggestions?
I'm thinking module-alias might be an option for creating aliases for the server code

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.