ES6 : optimizing the Command pattern - javascript

I'm building a kind of in-browser CLI for doing tests on a Game Engine.
The app looks like a console, with an input test and a submit button below a results sequence container DIV.
Behind the scene, there is an Invoker and numerous Commands objects :
// imports
import Invoker from '../cli/Invoker.js'
import Command1 from '../commands/Command1.js'
import Command2 from '../commands/Command2.js'
// ...
import Command20 from '../commands/Command20.js'
// instanciatiing
const invoker = new Invoker(context)
const commands = [
new Command1(),
new Command2(),
// ...
new Command20(),
]
// attach in invoker
commands.forEach(command => invoker.install(command))
the usage is pretty simple :
// later...
invoker.run('command20', { ...withArgs })
QUESTION 1/2:
Since this creates a bunch of imports, and initialization code, which could over time pollute the namespace of the client app code. How to fix it ? Builder pattern ? Facade ? Flyweight ? or something other ?
QUESTION 2/2
Rendering results : couldn't figure exactly where to put the code which take the effects of running a command, render (either with Mustache or JSX) and append it to the resulting DIV. Some comments ?
Help is welcome. Regards.

OK! the good answer is the Module Pattern. Not really one of the 23 GoF patterns, mostly a ES 2015 pattern. But no matters ! I still develop this way. Thanks !
For question 2/2 I intend to attach the render method to the resulting object -or- a render method per command bound to context after each command invocation...
[Considering this as SOLVED]

You can use a webpack's feature called context to do a bunch of imports
const files = require.context('.', false, /\.js$/);
const commands = {};
files.keys().forEach((key) => {
if (key === './index.js') return;
commands[key.replace(/(\.\/|\.js)/g, '')] = files(key).default;
});
export default commands;
and you can use namespaced as vuex
command1.js
export { actions: { get: {} }, namespaced: true }
app.js
context.dispatch('command1/get', param);

Concerning 2/2 you should go ahead and use Observer or PubSub.
Simply follow the modern days way and add a store to your project using redux/mobx (I prefer mobx for small/medium projects) or any other technology of your choice and push the changes to the store after the command execution. Handle the store changes as a separate process. If you're using React in can be done in no time.

Related

Is it possible to do import js differently depending on the build target?

I developed games using unity3d and unrealengine, and this time I developed the web for the first time. I am going to use react, javascript, material-ui, and styled-component with visual studio code and sourcetree for git.
The site I need to develop has a similar basic structure, but I need to create several sites with slightly different detailed functions.
For example, when running for domain aaa.com, component comp is from the file 'src/aaa/compPerTarget/comp', and when running for domain bbb.com, 'src/bbb/compPerTarget/comp' be used. And most important thing is the source where comp is used is not be changed for "import 'compPerTarget/comp'"
Pages using component comp
import Comp from 'compPerTarget/Comp'
...
When build or run source for target aaa.com use 'src/aaa/compPerTarget/comp'
function Comp() {
const logicForAAA = () => {...};
return (
<div>using div..<div>
);
}
When build or run source for target bbb.com use 'src/bbb/compPerTarget/comp'
function Comp() {
const logicForBBB = () => {...};
return (
<li>using li..<li>
);
}
I'm trying to solve it using jsonconfig.json and webpack.config.js, but I'm wondering if this is the right way to go, and if there is another way to solve it more easily, please advise me.

Write Vue plugin with custom options

I'm trying to write a vue plugin with custom options. I followed the official vue guidelines (https://v2.vuejs.org/v2/guide/plugins.html) on doing so but can't find a way to define custom options. These options should be read by normal javascript which then exports an object that is used by a vue component.
My folder structure is like this:
/src
factory.js
CustomComponent.vue
factory.js
import Vue from "vue";
import ImgixClient from "imgix-core-js";
var imgixClient = new ImgixClient({
domain: CUSTOM_OPTION_URL <-- important bit
domain: Vue.prototype.$imgixBaseUrl //tried it like this
});
export { imgixClient };
I already tried to set this custom bit by utilizing Vue.prototype in the install method like this but can't seem to get it working
export function install(Vue, options) {
if (install.installed) return;
install.installed = true;
Vue.prototype.$imgixBaseUrl = options.baseUrl;
Vue.component("CustomComponent", component);
}
I'm afraid this isn't going to be the simple answer you might have been hoping for... there's a lot to unpick here.
Let's start with factory.js. That is not a factory. It's a singleton. Singletons have problems around dependencies, configuration and the timing of instantiation and that's precisely the problem you're hitting. More on that later.
Now let's take a look at the plugin. First up, these two lines:
if (install.installed) return;
install.installed = true;
That shouldn't be necessary. Vue already does this automatically and should ensure your plugin is only installed once. Perhaps this came from an old tutorial? Take a look at the source code for Vue.use, there's not a lot to it:
https://github.com/vuejs/vue/blob/4821149b8bbd4650b1d9c9c3cfbb539ac1e24589/src/core/global-api/use.js
Digging into the Vue source code is a really good habit to get into. Sometimes it will melt your mind but there are some bits, like this, that aren't particularly difficult to follow. Once you get used to it even the more opaque sections start to become a little clearer.
Back to the the plugin.
Vue.prototype.$imgixBaseUrl = options.baseUrl;
It is not clear why you are adding this to the prototype.
I'm going to assume you are already familiar with how JavaScript function prototypes work.
Component instances are actually instances of Vue. So any properties added to Vue.prototype will be inherited by your components with almost no overhead. Consider the following simple component:
<template>
<div #click="onClick">
{{ $imgixBaseUrl }}
</div>
</template>
<script>
export default {
methods: {
onClick () {
const url = this.$imgixBaseUrl
// ...
}
}
}
</script>
As $imgixBaseUrl is an inherited property it is available within onClick via this.$imgixBaseUrl. Further, templates resolve identifiers as properties of the current Vue instance, so {{ $imgixBaseUrl }} will also access this.$imgixBaseUrl.
However, if you don't need to access $imgixBaseUrl within a component then there is no need to put it on the Vue prototype. You might as well just dump it directly on Vue:
Vue.imgixBaseUrl = options.baseUrl;
In the code above I've ditched the $ as there's no longer a risk of colliding with component instance properties, which is what motivates the $ when using the prototype.
So, back to the core problem.
As I've already mentioned, singletons have major problems around creation timing and configuration. Vue has its own built-in solution for these 'do it once at the start' scenarios. That's what plugins are. However, the key feature is that plugins don't do anything until you call install, allowing you to control the timing.
The problem with your original code is that the contents of factory.js will run as soon as the file is imported. That will be before your plugin is installed, so Vue.prototype.$imgixBaseUrl won't have been set yet. The ImgixClient instance will be created immediately. It won't wait until something tries to use it. When Vue.prototype.$imgixBaseUrl is subsequently set to the correct value that won't have any effect, it's too late.
One way (though not necessarily the best way) to fix this would be to lazily instantiate ImgixClient. That might look something like this:
import Vue from "vue";
import ImgixClient from "imgix-core-js";
var imgixClient = null;
export function getClient () {
if (!imgixClient) {
imgixClient = new ImgixClient({
domain: Vue.prototype.$imgixBaseUrl
});
}
return imgixClient;
}
So long as nothing calls getClient() before the plugin is installed this should work. However, that's a big condition. It'd be easy to make the mistake of calling it too soon. Besides the temporal coupling that this creates there's also the much more direct coupling created by sharing the configuration via Vue. While the idea of having the ImgixClient instantiation code in its own little file makes perfect sense it only really stands up to scrutiny if it is independent of Vue.
Instead I'd probably just move the instantiation to within the plugin, something like this:
import ImgixClient from "imgix-core-js";
export default {
install (Vue, options) {
Vue.imgixClient = Vue.prototype.$imgixClient = new ImgixClient({
domain: options.baseUrl
});
Vue.component("CustomComponent", component);
}
}
I've made a few superficial changes, using a default export and wrapping the function in an object, but you can ignore those if you prefer the way you had it in the original code.
If the client is needed within a component it can be accessed via the property $imgixClient, inherited from the prototype. For any other code that needs access to the client it can either be passed from the component or (more likely) grabbed directly from Vue.imgixClient. If either of these use cases doesn't apply then you can remove the relevant section of the plugin.

Require from the actual file using NormalModuleReplacementPlugin

I have a Storybook setup for which I need for my React component's children components to stop doing API calls. The setup is quite complex, and it is also irrelevant to the question, so I'll just say that I need the components to stop doing API calls.
My ultimate goal is to have the children component stay in a "loading state" forever, so mocking the server response not a solution here.
The approach that I came up with was to replace my Thunk action creators with a stubbed one. Similar to what we do on Jest unit tests
// note that I'm using redux ducks: https://github.com/erikras/ducks-modular-redux
jest.mock('./ducks/students');
Of course the above doesn't work since Storybook doesn't run on Jest. So my current approach is to use the NormalModuleReplacementPlugin to replace the real module ducks/students.js with a stubbed one ducks/stubs/students.js which contains the functions, but with an empty body:
// ./ducks/students.js
export const loadResources() = fetch('/resources');
export default (state, actions => {
// reducer's body
}
// ./ducks/stubs/students.js
export const loadResources() = Promise.resolve(); // STUBBED
export default (state, actions => {
// reducer's body
}
The problem is that I need only the thunk action creators to be stubbed, everything else in the file (other actions, and reducer) needs to be the same.
This are the approaches I have considered so far to fix this:
Copy/paste the rest of the actual file into the stubbed one. This wouldn't scale.
Attempting to use require.requireActual(). It turns out this is a Jest custom function so I can't use it on Storybook.
Ideally I could find a way to import everything from the actual module into the stubbed one, and export the stubbed functions and the rest of the real functions that I need.
Any ideas how can I access the actual module from the stubbed one when I'm using NormalModuleReplacementPlugin?
Update 1: 2019-07-08
Tarun suggestion about just mocking the fetch function and returning a new Promise() worked for the particular case of "indefinitely loading".
However, looking at the big picture, I still would rather just stubbing out all of the API calls, so that I can setup the stories by just modifying the redux state.
"But why can't you just mock the JSON response?" I hear you ask. The JSON response is not necessarily 1-to-1 mapping with the app domain model. We have mapper functions that takes care of the transformation.
I'd be better if the programmers could work and setup the test cases with just the domain model knowledge, and don't need to know the server response JSON structure. Needless to say, the app redux store structure is the domain model.
So I still need an answer on how to require from the actual file, when using NormalModuleReplacementPlugin.
I haven't tested this, but you might be able to achieve what you're after with the Aggregating/re-exporting modules syntax and overwriting your loadResources() function.
To do this, import your actual module into ./ducks/stubs/students.js, export all from that module, then define/overwrite loadResources() and export it as well. You can then use the NormalModuleReplacementPlugin as normal and pass in your stub file as the newResource that will contain all of your actual module reducers/actions that you wanted to keep with the thunk overwritten and stubbed out:
//ducks.stubs.students.js
export * from './ducks/students.js';
//override students.loadResources() with our stub
//order matters as the override must come after
//the export statement above
export const loadResources() = //some stubbed behavior;
//webpack.config.js
module.exports = {
plugins: [
new webpack.NormalModuleReplacementPlugin(
/ducks\.students\.js/,
'./ducks.stubs.students.js'
)
]
}
A couple of notes/caveats/gotchas with this solution:
You might have to update your exports to use let vs. const (not a big deal)
Per this issue, the export * from expression isn't supposed to handle default exports. As such, you might have to add export { default } from './ducks/students.js';. Of course, keep in mind that you won't be able to export a default function native to your stubs file (unless you're overriding the original default function with a stub of course).

Isomorphic hyperHTML components without passing in wires

I have the following two components:
// component.js
// imports ...
function ListItem(item) {
const html = wire(item)
function render() {
return html`<li>${item.foo}</li>`
}
return render()
}
function List(items) {
const html = wire(items)
function render() {
return html`<ul>${items.map(ListItem)}</ul>`
}
return render()
}
I want to put them in a module which is shared between the client and the server. However, as far as I can tell, although the API pretty much identical, on the server I have to import the functions from the viperHTML module, on the client I have to use the hyperHTML module. Therefore I can not just import the functions at the top of my shared module, but have to pass to my components at the call site.
Doing so my isomorphic component would look like this:
// component.js
function ListItem(html, item) {
//const html = wire(item) // <- NOTE
function render() {
return html`<li>${item.foo}</li>`
}
return render()
}
function List(html, itemHtmls /* :( tried to be consistent */, items) {
//const html = wire(items) // <- NOTE
function render() {
return html`<ul>${items.map(function(item, idx) {
return ListItem(itemHtmls[idx], item)
})}</ul>`
}
return render()
}
Calling the components from the server:
// server.js
const {hyper, wire, bind, Component} = require('viperhtml')
const items = [{foo: 'bar'}, {foo: 'baz'}, {foo: 'xyz'}]
// wire for the list
const listWire = wire(items)
// wires for the children
const listItemWires = items.map(wire)
const renderedMarkup = List(listWire, listItemWires, items)
Calling from the browser would be the exact same, expect the way hyperhtml is imported:
// client.js
import {hyper, wire, bind, Component} from 'hyperhtml/esm'
However it feels unpleasant to write code like this, because I have a feeling that the result of the wire() calls should live inside the component instances. Is there a better way to write isomorphic hyperHTML/viperHTML components?
update there is now a workaround provided by the hypermorphic module.
The ideal case scenario is that you have as dependency only viperhtml, which in turns brings in hyperhtml automatically, as you can see by the index.js file.
At that point, the client bundler should, if capable, tree shake unused code for you but you have a very good point that's not immediately clear.
I am also not fully sure if bundlers can be that smart, assuming that a check like typeof document === "object" would always be true and target browsers only.
One way to try that, is to
import {hyper, wire, bind, Component} from 'viperhtml'
on the client side too, hoping it won't bring in viperHTML dependencies once bundled 'cause there's a lot you'd never need on the browser.
I have a feeling that the result of the wire() calls should live
inside the component instances.
You could simplify your components using viper.Component so that you'll have render() { return this.html... } and you forget about passing the wire around but I agree with you there's room for improvements.
At that point you only have to resolve which Component to import in one place and define portable components that work on b both client and server.
This is basically the reason light Component exists in the first place, it give you the freedom to focus on the component without thinking about what to wire, how and/or where (if client/server).
~~I was going to show you an example but the fact you relate content to the item (rightly) made me think current Component could also be improved so I've created a ticket as follow up for your case and I hope I'll have better examples (for components) sooner than later.~~
edit
I have updated the library to let you create components able to use/receive data/items as they're created, with a code pen example.
class ListItem extends Component {
constructor(item) {
super().item = item;
}
render() {
return this.html`<li>${this.item.foo}</li>`;
}
}
class List extends Component {
constructor(items) {
super().items = items;
}
render() {
return this.html`
<ul>${this.items.map(item => ListItem.for(item))}</ul>`;
}
}
When you use components you are ensuring yourself these are portable across client/server.
The only issue at this point would be to find out which is the best way to retrieve that Component class.
One possible solution is to centralize in a single entry point the export of such class.
However, the elephant in the room is that NodeJS is not compatible yet with ESM modules and browsers are not compatible with CommonJS so I don't have the best answer because I don't know if/how you are bundling your code.
Ideally, you would use CommonJS which works out of the box in NodeJS and is compatible with every browser bundler, and yet you need to differentiate, per build, the file that would export that Component or any other hyper/viperHTML related utilities.
I hope I've gave you enough hints to eventually work around current limitations.
Apologies if for now I don't have a better answer. The way I've done it previously used external renders but it's quite possibly not the most convenient way to go with more complex structures / components.
P.S. you could write those functions just like this
function ListItem(item) {
return wire(item)`<li>${item.foo}</li>`;
}
function List(items) {
return wire(items)`<ul>${items.map(ListItem)}</ul>`;
}

how to make global variable and functions which can be accessible in all the components in angular 4

I am struggling with global variables as I want some variables which I need to access in all the components so what should i do in angular 4.
I've tried something like this:
Created one file with the name of global.ts and made a class with the name GlobalComponent something like this
export class GlobalComponent {
globalVar:string = "My Global Value";
}
and am using it on HeaderComponent by importing and making instance and it's working fine but this is very long process to import in each and every files to get it available.
So I want it to be available on all the components without importing it.
Is there any way or trick to achieve this? Any suggestion would be appreciated :)
As #bgraham is suggesting, it is definitely better to let angular injector to instantiate the service.
But you could also export just a simple object with key-value pairs, including functions. Then you can simply import it and use it (without the instantiation part).
globals.ts file:
export const GLOBALS = {
globalVar: 'My Global Value',
globalFunc: () => alert('123')
};
your.component.ts file:
import { GLOBALS } from './globals.ts';
console.log(GLOBALS.globalVar);
GLOBALS.globalFunc();
Btw I don't think there is a way how to get rid of the import statement - that is part of how typescript works...
I don't think what you want to do is possible and it's probably not a good idea.
The import statements are how webpack (or other bundlers) are able to build a tree to figure out which files to include. So it might be tricky to get global files built into all your bundles.
Also I would add, I'm not sure just importing the static file is the way to go either. It's kind of quick and dirty, which maybe is what you want I guess, but for production apps I would recommend making an angular service and injecting it.
export class GlobalVariablesService {
globalVar:string = "My Global Value";
}
This way you can mock these for unit tests or potentially pass in different variables depending on your changing needs in the future.
If you need these to update and push that into lots of components throughout your app, you might look into Redux. Its pretty handy for that kind of thing.
Sorry, perhaps not the answer you were hoping for
Simply create constant file - constant.ts under src folder.
Now import constant.ts file whenever you require parameter to be called
It will look like so
export const constant = {
COSNT_STRING : 'My Global Value',
}
how to use constant:
1) Import file.
2) constant.CONST_STRING
Also this is a good practice from future prospective, if you want to modify response just made change in one file not in 800 files.

Categories

Resources