As opposed to most contemporary web-oriented JavaScript ecosystems, the game development frameworkPhaser does not come with a bundling solution out of the box, which would allow the use of cutting-edge syntax, or writing code across multiple source files but still referencing only one in the HTML page hosting the project. Fortunately, adding one in the mix is fairly easy and works just as well as one would expect.
There is, however, one tiny caveat; loading an asset (e.g. an image) implies providing its relative path as a string, delegating the underlying logic to Phaser itself:
In the context of a built project, the above just might fail, since our PNG file will be out of reach from its scope. In the case of Parcel, the way to go is pretty straightforward, and consists of bundling these assets with the code and using its internal mechanism to get a proper reference to their actual location:
import Phaser from "phaser";
import tiles from "../../assets/map/spritesheet.png";
Working on a legacy JavaScript app powered by Backbone and Marionette, and willing to use React to develop new features? While that is most certainly feasible, integrating the former in the latter has its quirks, especially if there is routing involved on both sides.
Let's say your Marionette app looks something like the following:
import Marionette from "backbone.marionette";
import LayoutView from "./views/LayoutView";
And LayoutView implicitly renders in a div#app element:
import { View } from "backbone.marionette";
export default View.extend({
el: "#app",
// ...
});
Chances are you will be willing to keep using this same element as the root of your brand new React app:
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Switch, Route, Link } from "react-router-dom";
import Wrapper from "./Wrapper";
Implementing onDestroy as such ensures everything is cleaned up in order to start fresh when the user browses / again and your Marionette app is remounted; it will not work otherwise.
For this setup to be complete, you will also need to watch route changes on the Marionette side, in order to notify React Router accordingly. Your mileage may vary regarding how to do it; hopefully, you already are overriding Backbone.Router or Marionette.AppRouter and emitting an event you can subscribe to. Regardless, here is what you want to do in the Wrapper component:
Upon integrating the Beamer widget in your application, you might have noticed a tooltip appearing over the HTML element you bound to it whenever a new post is published. This feature isn't mentioned in the documentation as far as I can tell, which also implies it cannot be disabled. However, there is a way to do so in JavaScript, and even use the last post title any other way you see fit, even though this information isn't provided to you initially!
The window.Beamer object exposes the ajax method, used by Beamer to request data from its backend: this method is used, among other things, to retrieve the last post title and display it in the aforementioned tooltip. One can use that to their own advantage, and override the method as follows:
let lastPostTitle;
const ajax = window.Beamer.ajax; // keep a reference to the original method...
window.Beamer.ajax = (url, callback) => {
ajax(url, response => { // ...and call it ourselves
try {
// Grab the info for later use if it is available
lastPostTitle = JSON.parse(response).lastPostTitle || "";
} catch (e) {} // fail silently otherwise
callback(response);
});
};
Finally, we can make sure the tooltip is never shown by replacing the method displaying it with a no-op function:
The latest major version of React introduced what has become one of its major features, namely hooks, which enable one to benefit, among other things, from state and lifecycle mechanisms inside a functional component, that is to say one defined as a function rather than a class. The component's lifecycle is thus handled by the useEffect hook, which binds together the behaviours of componentDidMount, componentDidUpdate, and componentWillUnmount. It is used as such:
useEffect(() => {
// This will run upon mounting or unmounting the component,
// as well as everytime it rerenders
});
Of course, this is very rarely what you want: most of the time, you want the effect to run when given props or state values change, which is done as follows:
const [bar, setBar] = useState("");
useEffect(() => {
// This will run when the component rerenders
// because one of the values listed below changed
}, [props.foo, bar]);
You can also pass [] as the second argument to limit the effect's scope to mounting and unmounting of the component.
But you might also want to be nitpicking even more than that: in some situations, it could be ideal to have the effect run based on a computed value. Imagine a component A reading a value from a remote API, and supplying it to its child component B through props; B can then trigger a behaviour in A that would cause that value to change. It is a perfectly valid use case to want to process the first "change" (actually receiving the initial value with a delay) differently than the subsequent ones. Well, the following works just fine:
useEffect(() => {
// This will only run the first time props.foo receives a defined value
}, [props.foo !== undefined]);
This is a pretty helpful workaround to the fact we have no access to effect dependencies' previous values.
In my experience, if you find yourself writing a "complex" effect dependency such as this, half the time there is a simpler solution to your problem; but the other half, knowing this kind of thing is possible can come in handy (and it just makes sense: under the hood, React just matches the values against the previous ones to know whether it should run the effect again or not).
Last but not least, toying around with that dependency list is also useful if a value you depend on is an Object or an Array, since there is a fair chance those are not mutated when their values change, and these therefore cannot be matched against the previous ones:
useEffect(() => {
// This will only run if someArray's contents are actually different,
// even if the parent component overwrote it [thisWay, ...orSomething]
}, [JSON.stringify(someArray)]);
I would be interested to know if other people are using expressions in their effects' dependency lists, so you know what to do!
If you maintain your own set of JavaScript libraries and publish them on NPM, you might find yourself in a situation where a package A depends on a package B, which in turn depends on A itself. Although you should generally strive to avoid such an ordeal, one can always imagine some edge case where this is a valid way of implementing things. The main problem is that it means each package will require a version of itself installed in node_modules which, on top of being semantically ugly, can make for really painful upgrades, as bumping the version of B in A and then releasing A should be followed by updating to this new version in B, and so forth... so what can we do to avoid this?
The quickest way to break the cycle is to remove A from B's dependencies (or the other way around, depending on what makes the most sense) and add it to its peerDependencies instead. It basically tells your package consumers that the latest compatible version of A should be installed alongside B for everything to work properly, without installing it directly: this transfers responsibility of maintaining the installed version of A onto them. You might also want to add A to B's devDependencies if its presence is necessary for tests to run properly, or anything else related to its maintenance. This should ease the pain within your dependency tree!
Finally, when developing A (which still explicitly depends on B), you might need to set things up so import calls targetting A resolve to your local code, the twin dependency not being around anymore. Doing so depends on your specific build setup, but it is pretty easy to do with Webpack thanks to the resolve part of its configuration:
{
resolve: {
// tell Webpack to look for third-party code in A's root directory before node_modules
modules: [__dirname, "node_modules"],
alias: {
"A": __dirname // tell it to resolve A to this same directory
}
},
// ...
}
This example assumes your entry point (typically index.js) lives in A's root directory; if it rather is is src/, for example, you will want to use path.resolve(__dirname, "src") instead.
Feel free to let me know of a better way to overcome such pitfalls when they cannot be avoided, if you know any!