Webpack theme loader - reactjs

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?

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!

Related

How to integrate Phaser into React

I've got a React application created with create-react-app and I'm trying to integrate Phaser 3 as well. I followed this guide to get started. I've got the canvas rendering the text but loading images in the preload does not seem to be working. I get the default failed to load texture image displayed.
import ExampleScene from "./scenes/ExampleScene";
import * as React from "react";
export default class Game extends React.Component {
componentDidMount() {
const config = {
type: Phaser.AUTO,
parent: "phaser-example",
width: 800,
height: 600,
scene: [ExampleScene]
};
new Phaser.Game(config);
}
shouldComponentUpdate() {
return false;
}
render() {
return <div id="phaser-game" />;
}
}
ExampleScene:
import Phaser from "phaser";
export default class ExampleScene extends Phaser.Scene {
preload() {
this.load.image("logo", "assets/logo.png");
}
create() {
const text = this.add.text(250, 250, "Phaser", {
backgroundColor: "white",
color: "blue",
fontSize: 48
});
text.setInteractive({ useHandCursor: true });
this.add.image(400, 300, "logo");
text.on("pointerup", () => {
console.log("Hello!");
//store.dispatch({ type: ACTION_TYPE });
});
}
}
The idea is to create a visualization with flowers growing based on a simple gene engine. So Phaser would get instructions from the Store about the current state.
I'm guess this has something to do with the way Phaser loads and there's a conflict with how React updates. I'm preventing the component from updating as I only need the game to receive instructions by listening to the store
I've already looked at this SO answer and the accompanying wrapper, but it is outdated.
How can I get Phaser to load images when in a Create-React-App?
CodeSandbox: https://codesandbox.io/s/github/nodes777/react-punnett/tree/phaser-game
Repo: https://github.com/nodes777/react-punnett/tree/phaser-game
Other option is using WebComponents to be able to integrate Phaser with any other framework (React, Angular, VueJS, etc), check this npm package: https://www.npmjs.com/package/#ion-phaser/core
Also, you can use the React wrapper of that library to use Phaser with React components easily, so you don't need to manipulate WebComponents directly, example:
import React from 'react'
import Phaser from 'phaser'
import { IonPhaser } from '#ion-phaser/react'
const game = {
width: "100%",
height: "100%",
type: Phaser.AUTO,
scene: {
init: function() {
this.cameras.main.setBackgroundColor('#24252A')
},
create: function() {
this.helloWorld = this.add.text(
this.cameras.main.centerX,
this.cameras.main.centerY,
"Hello World", {
font: "40px Arial",
fill: "#ffffff"
}
);
this.helloWorld.setOrigin(0.5);
},
update: function() {
this.helloWorld.angle += 1;
}
}
}
const App = () => {
return (
<IonPhaser game={game} />
)
}
export default App;
Fore more details check the repo: https://github.com/proyecto26/ion-phaser/tree/master/react
A year ago I was here looking for the answer myself. Here's pattern which should work.
import Phaser from "phaser"
import React, { useEffect, useState } from "react"
/** #tutorial I made this! This answers how you get your image. */
import logoImage from "./path-to-logo.png"
/** #tutorial I made this! Use a functional React component and `useEffect` hook.*/
export const Phaser3GameComponent = ({ someState }) => {
// Optional: useful to delay appearance and avoid canvas flicker.
const [isReady, setReady] = useState(false)
// Just an example... do what you do here.
const dataService = (changedState) => {
// I'm not sure how to use stores, but you'll know better what to do here.
store.dispatch(
{
...someState,
...changedState,
},
{ type: ACTION_TYPE }
)
}
// This is where the fun starts.
useEffect(() => {
const config = {
callbacks: {
preBoot: game => {
// A good way to get data state into the game.
game.registry.merge(someState)
// This is a good way to catch when that data changes.
game.registry.events.on("changedata", (par, key, val, prevVal) => {
// Simply call whatever functions you want outside.
dataService({ [key]: val })
})
},
},
type: Phaser.AUTO,
parent: "phaser-example",
width: 800,
height: 600,
scene: [ExampleScene],
}
let game = new Phaser.Game(config)
// Triggered when game is fully READY.
game.events.on("READY", setReady)
// If you don't do this, you get duplicates of the canvas piling up.
return () => {
setReady(false)
game.destroy(true)
}
}, []) // Keep the empty array otherwise the game will restart on every render.
return (
<div id="phaser-example" className={isReady ? "visible" : "invisible"} />
)
}
export default class ExampleScene extends Phaser.Scene {
preload() {
this.load.image("logo", logoImage)
}
create() {
// You made this!
const text = this.add.text(250, 250, "Phaser")
text.setInteractive({ useHandCursor: true })
this.add.image(400, 300, "logo")
/** #tutorial I made this! */
// Get all that lovely dataState into your scene,
let { clickCount } = this.registry.getAll()
text.on("pointerup", () => {
// This will trigger the "changedata" event handled by the component.
this.registry.merge({ clickCount: clickCount++ })
})
// This will trigger the scene as now being ready.
this.game.events.emit("READY", true)
}
}
I started from scratch and created my own boilerplate from the phaser 3 template. I wrote about the specific steps to add React to the Phaser 3 template here.
It seems like you could eject from Create-React-App and add in Phaser 3 from there, but the warnings not to eject turned me away from that solution.
In my case I use the following component and it works fine:
import Phaser from 'phaser';
import * as React from 'react';
import { HTML_DIV_ID, gameConfig } from './gameConfig';
export const GameWrapper = () => {
const [game, setGame] = React.useState<Phaser.Game>();
React.useEffect(() => {
const _game = new Phaser.Game(gameConfig());
setGame(_game);
return (): void => {
_game.destroy(true);
setGame(undefined);
};
}, []);
return (
<>
<div id={HTML_DIV_ID} />
</>
);
};
With create-react-app and React.StrictMode:
Also I deleted React.StrictMode (default option with create-react-app) because it mounts
and unmounts all components so I had unexpected behavior with phaser
sometimes
You can use react hook for the code above as:
// usePhaser.js
export function userPhaser(config) {
const [game, setGame] = React.useState();
React.useEffect(() => {
const _game = new Phaser.Game(config);
setGame(_game);
return (): void => {
_game.destroy(true);
setGame(undefined);
};
}, []);
return game;
}
You need to put images inside the folder public!
For me, I see the best practice to use both of them properly is to create phaser project separately and host it separately using firebase or whatever hosting service you prefer, and then take the link and put it in an iframe tag inside react.
in this way you can manage them efficiently and you can manipulate react website in more comfortable way especially the mobile width compatibility.

Is there react/redux like mapStateToProps way in Vue/Vuex

Usual way to map state and actions in React/Redux looks something like this, so mapping functions are placed separately from component code:
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import myAction from 'actions/request';
class MyComponent extends Component {
/* BODY */
}
function mapStateToProps(state) {
return {
myComponentProp: state.myReducer.myReducerProp
};
}
function mapDispatchToProps(dispatch) {
return {
myComponentPropAction: bindActionCreators(myAction, dispatch),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
The only described way to map state and actions I have found in Vue looks like this
import { mapState, mapActions } from 'vuex';
export default {
computed: {
...mapState('myReducer', {
myComponentProp: (state) => state.myReducerProp,
}),
...{
/* aditional manipulations with this.myComponentProp */
}
},
methods: {
...mapActions('myReducer', [
'myReducerAction'
]),
...{
myEventHandler: function() {
/* checke conditions before action call */
this.myReducerAction();
},
}
}
}
Because of the number of spreading, the code looks fuzzy, so the question is:
Is there a way to move mapState and mapActions outside component like in react/redux usual approach.
Thanks for help!
Okay so along with typescript support they also added in a vue-class-component decorator which could be used to achieve what your after. The link to the repository for this can be found here but I would suggest instead creating a new project via the CLI and going from there as it was added in v3 Vue Class Component Github Repository.
<script>
function Getter (getterType) {
return createDecorator((options, key) => {
if (!options.computed) options.computed = {}
options.computed[key] = function () {
return this.$store.getters[getterType]
}
})
}
import Vue from 'vue'
import Component from 'vue-class-component'
#Component({
props: {
propMessage: String
}
})
export default class App extends Vue {
#Getter('foo') bar
#Setter('psu') psi
// computed
get computedMsg () {
return 'computed ' + this.msg
}
// method
greet () {
alert('greeting: ' + this.msg)
}
}
</script>
As you can see were calling in our getters and setters using a function here which is less than optimal but is closer to a succulent answer. Now in comes the vuex-class-binding package which abstracts all of those murky functions: vuex class
import Vue from 'vue'
import Component from 'vue-class-component'
import {
State,
Getter,
Action,
Mutation,
namespace
} from 'vuex-class'
const someModule = namespace('path/to/module')
#Component
export class MyComp extends Vue {
#State('foo') stateFoo
#State(state => state.bar) stateBar
#Getter('foo') getterFoo
#Action('foo') actionFoo
#Mutation('foo') mutationFoo
#someModule.Getter('foo') moduleGetterFoo
// If the argument is omitted, use the property name
// for each state/getter/action/mutation type
#State foo
#Getter bar
#Action baz
#Mutation qux
created () {
this.stateFoo // -> store.state.foo
this.stateBar // -> store.state.bar
this.getterFoo // -> store.getters.foo
this.actionFoo({ value: true }) // -> store.dispatch('foo', { value: true })
this.mutationFoo({ value: true }) // -> store.commit('foo', { value: true })
this.moduleGetterFoo // -> store.getters['path/to/module/foo']
}
}
This is there example and it's really nice because were able to take a namespaced module and call all of it's getters and setters without any nasty custom functions and we can import all of that ready to use into a const like above. Now you'd have access to all of your modules functionality using just decorators. This is as close as it really gets to being able to assign your functionality into the component sadly, but it looks pretty nice once you've got it all setup. You can do this with or without TS I think but I've always done it in TS as it has the first class support for the vue class components which are still relatively new.

Is there a way to require in multiple modules into a file, which is then exported again as a single module?

Currently working with Redux and was wondering if there was a way to require in multiple modules into a single file, which is then exported again as a single module?
For example, in my actions/bookmark.js I group all actions related to bookmarks accordingly:
module.exports = {
fetchBookmarkList: () => {
return {
type: 'FETCH_LIST'
}
},
fetchBookmark: () => {
return {
type: 'FETCH_BOOKMARK'
}
}
}
Then in my actions/index.js file, I require in all groups of actions (which will include bookmark actions as well as others). Then I would like to export the entire file as a single module.
Schematically I had something like this in mind (obviously this code does not work):
actions/index.js:
module.exports = {
require('./bookmark');
require('./tags');
}
The reason that I want to do this is so that I only have to import a single action file that contains all my actions (i.e. the actions/index.js file):
Example component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
class BookmarkList extends Component {
constructor(props) {
super(props);
this.props.fetchBookmarkList();
}
render() {
return (
<div></div>
);
}
}
export default connect(null, actions)(BookmarkList);
I see that you es6 modules syntax in components then, why not in your redux files?
export const fetchBookmarkList = () => {
return {
type: 'FETCH_LIST'
}
};
export const fetchBookmark = () => {
return {
type: 'FETCH_BOOKMARK'
}
}
Based on this reference I think it could be possible to re-export everything like this:
export * from './bookmark'
export * from './tags;
But haven't tried it and I could be wrong.
I would NOT recommend you to pass all the actions to mapDispatchToProps, only the actions needed by your component
But you could just use in actions/index.js:
Using the spread operator:
module.exports = {
...require('./bookmark');
...require('./tags');
}
And in ES6 syntax:
actions/index.js:
export * from './bookmark'
export * from './tags'

Testing React-router component throws navigator is not defined

I am writing a test spec for the component that uses browserHistory. It throws an error
ReferenceError: navigator is not defined
i tried solution from Mocha-Chai throws "navigator not defined" due to React-router component but it is still not working. May be I am not being able to use the solution in right way.
Here is my spec file.
import React from 'react';
import { expect } from 'chai';
import jsdom from 'jsdom';
import sinon from 'sinon';
import shallow from 'enzyme';
import { MyComponent } from 'component.jsx';
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.document = doc;
global.window = doc.defaultView;
global.navigator = {
userAgent: 'node.js',
};
describe('<Component />', () => {
let wrapper;
before(() => {
sinon.stub(Component.prototype, 'componentWillMount');
wrapper = shallow(<Component />);
});
context('Component', () => {
it('should render component', () => {
expect(wrapper.type()).to.equal('div');
});
});
after(() => {
Component.prototype.componentWillMount.restore();
});
});
Any help will be appreciated. Thank you.
Here is component.jsx file
import React, { Component, PropTypes } from 'react';
import R from 'ramda';
import { browserHistory } from 'react-router';
export class MyComponent extends Component {
componentWillMount() {
const query = this.props.location.query;
// looping through the query object of url
R.mapObjIndexed((value, key) => this.prepareStateData(value, key), query);
}
componentDidUpdate(prevProps) {
// this push the url every time the component is updated
if (this.props.urlHistory && this.props.urlHistory !== prevProps.urlHistory) {
browserHistory.push(this.props.urlHistory);
}
}
prepareStateData(value, key) {
// this changes the state according to the url
switch (key) {
case 'query1': {
// do something
break;
}
case 'query2': {
// do something
break;
}
default:
// do something
break;
}
}
render() {
return (
<div>
{ /* render part */ }
</div>
);
}
}
MyComponent.propTypes = {
location: PropTypes.object,
urlHistory: PropTypes.string,
};
Your test runner doesn't know anything about the environment that your app is supposed to run in, so window.navigator/window.location etc are not available.
browserHistory that you're using requires browser environment to work correctly and I assume that this is the issue you're facing. Try replacing imported browserHistory with createMemoryHistory and see if the test passes.
Excerpts from the docs which shows the difference:
browserHistory uses the HTML5 History API when available, and falls back to full refreshes otherwise. browserHistory requires additional configuration on the server side to serve up URLs, but is the generally preferred solution for modern web pages.
createMemoryHistory creates an in-memory history object that does not interact with the browser URL. This is useful for when you need to customize the history object used for server-side rendering, for automated testing, or for when you do not want to manipulate the browser URL, such as when your application is embedded in an .

Add an external child in React

I need to add another react component that's located in an external CDN as a child in my react component. Something like this:
var RemoteComponent = require('//cdn.com/some-remote-component');
var MyComponent = React.createClass({
render: function() {
return (
<div>
<RemoteComponent />
</div>
);
}
});
module.exports = MyComponent;
Any ideas on how could I do that?
Assuming the component registers to a global you could use a script loader such as script.js. Note that it is an asynchronous process so that will lead to some complexity. You may need to run the component through React.createFactory before plugging it in dynamically.
I expect you'll need to do something like this (not tested):
export default MyComponent {
constructor(props) {
super(props);
this.state = {
component: null
};
$script('cdn url goes here', () => {
// expects to find a global named `RemoteComponent` now
this.setState({
component: React.createFactory(RemoteComponent);
})
});
}
render() {
const component = this.state.component;
return (
<div>
{component}
</div>
);
}
}
You may need to tweak the code to your liking but I hope it illustrates the basic idea.
You can also build your own function to load remote react components without using requirejs or scriptjs. It can be very straightforward as follows (I was inspired by the solution here)
import React from 'react';
export function loadRemoteComponent(url) {
return fetch(url)
.then(response => response.text())
.then(text => {
// Define exports and require method for eval(text)
const exports = {};
function require(name) {
if (name === 'react') {
return React;
}
throw new Error('Does not support modules other than "react"');
}
eval(text);
return exports.__esModule ? exports.default : exports;
});
}

Resources