Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Webpack theme loader

I'd like to accomplish the following structure:

  • button.core.jsx
  • button.theme-a.jsx
  • button.theme-b.jsx

To take React as an example, I'd like to do the following in button.core.jsx:

import React from 'react';
import Themed from './button.[theme]';

export default class Button extends React.Component {
    render() {
        if (Themed) {
            return <Themed />;
        }
        return <button>default button</button>;
    }
}

In other words, I want to define a theme in my webpack.config.js and load that file if it exists. If it does't, render the default behaviour. I think this would be a very powerful setup!

I've been searching around for making a custom loader, but no success yet. Can anyone point me in the right direction?

like image 632
Jpunt Avatar asked Sep 26 '22 13:09

Jpunt


1 Answers

I've got this working with writing a custom "resolver":

const ThemeResolver = {
    apply: function(resolver) {
        resolver.plugin('file', function(req, cb) {
            if (req.request.indexOf('[theme]') == -1) {
                return cb();
            }

            const defaultFile = req.request.replace('[theme]', 'Default');
            const themedFile = req.request.replace('[theme]', process.env.THEME);
            req.request = themedFile;

            this.doResolve(['file'], req, (err) => {
                if (!err) {
                    return cb();
                }
                req.request = defaultFile;
                this.doResolve(['file'], req, cb);
            })
        });
    }
};

module.exports = {
    // ...
    plugins: [
        new webpack.ResolverPlugin([ThemeResolver]),
    ]
    // ...
};

It tries to resolve a file with [theme] in its path into a path with the theme defined as a environment variable. If it fails, it'll fallback to a default file instead. This way I can require a themed file like so:

import Presentation from './button-[theme]'

The main component turned out to be a bit different than in my question, but I'm actually pretty content with it:

import React from 'react';
import Presentation from './button-[theme]';

export default class Button extends React.Component {
    onClick = (e) => console.log('some logic');

    render() {
        return <Presentation onClick={ this.onClick } />;
    }
}

The logic of this button-component can live inside of button.core.jsx, while the presentation will be handled by one of these components:

THEME=theme-a npm start // button-[theme] resolves to button-theme-a.jsx
THEME=theme-c npm start // button-[theme] resolves to button-default.jsx

Disclaimer: I didn't use this in a large scale or production environment yet, but it seems to work in a small POC. Please let me know if I'm doing something unwise!

like image 154
Jpunt Avatar answered Dec 04 '22 03:12

Jpunt