Manifest.JS: un framework strutturale front-end leggero

Aug 21 2020

Ambito di revisione del codice

Il mio obiettivo con questa recensione è ricevere osservazioni generali e suggerimenti per migliorare l'efficienza / facilità di scrittura del front-end di un'applicazione Web con questo framework di base. Voglio concentrarmi su ciò che sembra che dovrebbe fare piuttosto che sui dettagli di ciò che può o non può fare involontariamente. Limita l'ambito a una panoramica generale, che dovrebbe aiutare a risparmiare tempo poiché è un pezzo di codice di buone dimensioni per una revisione.

L'attenzione dovrebbe essere rivolta alla velocità di sviluppo scalabile (gestibile, ristrutturabile), ai modelli di codice globali e alla progettazione del codice delle applicazioni risultanti.

"Ecco cosa sembra che tu stia cercando di ottenere, qui è dove sei riuscito, qui è dove ti manca, oppure ecco una modifica del quadro generale che potrebbe rendere il codice risultante più facile da leggere, mantenere e più veloce da sviluppare . "


Il problema che Manifest.JS intende risolvere

Progettando app web a pagina singola ho trovato due cose che non mi piacevano di ReactJS (almeno per la mia tipica scala di progetto):

  • Ho dovuto scrivere più codice di quanto volessi per realizzare le cose di base
  • Era noioso trasportare le informazioni attraverso l'app, dovevi essenzialmente passare un filo attraverso i componenti per ottenere informazioni dal punto A al punto B, il che rendeva il design strettamente accoppiato e difficile da ristrutturare in seguito

Mi sono sentito in questo modo anche per altri framework di app JS che ho provato. Quindi ho scritto due classi abbastanza semplici che lavorano insieme per creare un modello di sviluppo che preferivo. Il mio obiettivo era che questo mi permettesse di:

  1. Concentra tutto il processo di costruzione di ogni singolo componente di un'app in JS modulare senza doversi preoccupare molto di come ogni componente è collegato all'applicazione esterna.
  2. Non è necessario passare tra più file o lingue per modificare JavaScript, HTML e CSS per creare o mantenere una qualsiasi funzione dell'interfaccia utente.

Accoppiamento sciolto, separazione delle preoccupazioni, flusso di lavoro JS puro, struttura del progetto prevedibile, flusso di dati facile, non più funzionalità del necessario. Questo è l'obiettivo, che questo codice lo raggiunga o meno, non ne sono ancora sicuro.

Nota: JSX fa qualcosa di simile #2, ma avere le due lingue in un file mi è sembrato un po 'strano, volevo che i miei file fossero un linguaggio uniforme piuttosto che JSX intessuto come con React.


Autocritiche:

Finora alcune autocritiche che ho considerato:

  • Quando si tratta di modulare un insieme di Elementsin una classe, potrei fornire un unico modo stabilito per farlo in modo che ci sia un percorso chiaro per lo sviluppatore e nessuna libertà di sviluppare anti-pattern accidentali quando si decide come impacchettare i componenti in moduli modulari File.

  • Il concatenamento è fantastico. Dovrei aggiornare .useper tornare in thismodo da poter concatenare un'azione come

    self.append(new InfoPage().use(subPage, { /* properties */ }).actions.select(true))

    Creare la pagina informazioni, utilizzare il modello di pagina secondaria, passare proprietà univoche e selezionarlo per impostazione predefinita. Inoltre può farli actionrestituire in Elementmodo che possano essere incatenati.


Componenti:

  1. Publisher.js : una semplice classe di passaggio di messaggi per implementare il pattern Pub Sub perché volevo essere in grado di inviare eventi separati dallo spazio dei nomi da qualsiasi posizione nell'app e leggerli altrove, come: publisher.emit("header/select-nav/home", this)e publisher.on("header/select-nav/" + name, data => {}). Inoltre, supporto un terzo boolargomento per supportare l'invio e l'ascolto di eventi su un socket facoltativamente passato nel socket Socket.io, ad esempio let publisher = new Publisher(io()), in modo da poter gestire eventi locali e remoti allo stesso modo.

Utilizzo:

let publisher = new Publisher(io()) // or let publisher = new Publisher()
publisher.on("namespace1/subnamespace2/event-name", data => {}, false)
// third arg set to true tells the event handler to listen for Socket.io events
  1. Element.js : un wrapper per elementi HTML che facilita l'intera generazione e logica dell'HTML dell'app, quindi la logica associata a ciascun componente visibile dell'app è strettamente accoppiata ad essa, mentre tutti i componenti individualmente rimangono liberamente accoppiati tra loro . Ho anche in programma di aggiungere forse anche il supporto per la generazione di classi CSS localmente all'interno di ciascun componente.

Utilizzo:

new Element("div", {          // create a div
  id: "header",               // set the id
  traits: { publisher },      // assign data that will be inherited by any children
  data: { name: "primary" },  // assign data to this element only
  text: "My header text",     // set text
  innerHTML: false,           // set innerHTML
  classes: [],                // assign classes
  attributes: {               // assign attributes
    "contenteditable": "true"
  },
  styles: {},                 // assign styles
  actions: {                  // create actions, which will be wrapped in a
    show: (self, arg1) => {       // wrapper func and receive the Element as
      self.clearStyle("opacity")  // their first argument, followed by any
      self.addClass("visible")    // args passed with Element.action.name()
      console.log("called with Element.actions.show(arg1)")
    },                            
    hide: self => {
      self.removeClass("visible") // remove them
      self.style("opacity", 0)    // or set styles
    }
  },
  callback: self => { // trigger as soon as the element is created
    self.append(new Element("div", {
      id: "important-icon",
      classes: ["hidden", "header-icon"],
      actions: {
        select: () => {
          self.addClass("visible")
          self.removeClass("hidden")
          self.updateText("Selected") // update text
        }
      },
      ready: self => {
        self.on("mouseover", evt => { // handle DOM events
          self.addClass("highlight")
        })
      }
    })) 
  }, 
  ready: self => { // trigger after the element is appended to a parent
    self.traits.publisher.on("events/header/" + self.data.name, data => {
      self.select("#important-icon").actions.select(); 
        // you could of course apply this listener to the icon itself, 
        // but the select feature is convenient in some cases
    })                                    
  }
}).appendTo(document.body)
  1. Controller.js : la convalida dell'input durante il flusso di dati diventa sempre più importante quanto più grande diventa un'applicazione. Quindi dovrebbe essere una scelta, ovviamente, se vuoi usarlo, e l'ho reso disponibile e supportato per convalidare il flusso di dati sia all'interno dell'elemento che nel publisher. Non ho ancora codificato nel supporto per i publisher, ma funzionerà allo stesso modo con Element, con publisher.use(controller). Ma volevo anche un passaggio per passare un input di blueprint a un insieme di elementi che richiedono le stesse proprietà, e ha senso che un controller sia in grado di sovrascrivere l'input corrente che lo attraversa per facilità di test / debug, quindi ho aggiunto un insertmetodo, che (come vedrai nel codice) può e deve essere utilizzato per creare modelli di proprietà degli elementi.

Utilizzo:

let page = new Controller({
  data: data => { // pass a function to validate data however you want
    if (!data.name) return false
    else return true
  },
  traits: true, // pass true to simply ensure a setting is passed
  actions: "object", // pass a string to test against typeof
}).insert({ // and insert specific default data
  traits: {
    publisher
  },
  actions: {
    select: self => {
      let target = "header/select-nav/" + self.data.name.toLowerCase()
      self.traits.publisher.emit(target, this)
      self.addClass("visible")
    }
  },
  ready: self => {
    self.traits.publisher.emit("header/add-nav", self)
  }
});

Element.js:

import Controller from "/js/classes/controller.js"

function isCyclic(obj) {
  var seenObjects = [];

  function detect(obj) {
    if (obj && typeof obj === 'object') {
      if (seenObjects.indexOf(obj) !== -1) {
        return true;
      }
      seenObjects.push(obj);
      for (var key in obj) {
        if (obj.hasOwnProperty(key) && detect(obj[key])) {
          //console.log(obj, 'cycle at ' + key);
          return true;
        }
      }
    }
    return false;
  }

  return detect(obj);
}

function isObject(item) {
  return item && typeof item === 'object' && !Array.isArray(item);
}

function isIterable(item) {
  let type = false;
  if (isObject(item)) type = 'obj';
  else if (Array.isArray(item)) type = 'arr';
  return type;
}

function mergeDeeper(source, target) {
  let allProps = [];
  let sourceProps;
  let type;
  let targetProps;
  if (isObject(source)) {
    sourceProps = Object.keys(source);
    type = 'obj';
  } else if (Array.isArray(source)) {
    sourceProps = source;
    type = 'arr';
  } else {
    return source;
  }
  if (isObject(target)) {
    targetProps = Object.keys(target);
  } else if (Array.isArray(target)) {
    targetProps = target;
  } else {
    debugger
    throw "target missing"
  }
  sourceProps.forEach(prop => {
    allProps.push(prop);
  });
  targetProps.forEach(prop => {
    allProps.push(prop);
  });
  allProps = [...new Set(allProps)];
  let merged
  if (type == 'obj') {
    merged = {};
  } else if (type == 'arr') {
    merged = [];
  }
  allProps.forEach(prop => {
    if (type == "obj") {
      if (source[prop]) {
        if (isIterable(source[prop])) {
          if (isIterable(target[prop])) {
            merged[prop] = mergeDeeper(source[prop], target[prop])
          } else merged[prop] = source[prop]
        } else {
          merged[prop] = source[prop]
        }
      } else {
        if (source[prop] !== undefined) {
          merged[prop] = source[prop]
        } else {
          merged[prop] = target[prop]
        }
      }
    } else {
      let iterable = isIterable(prop);
      if (iterable) {
        let filler
        if (iterable == "obj") filler = {};
        else if (iterable == "arr") filler = [];
        merged.push(mergeDeeper(prop, filler))
      } else {
        merged.push(prop)
      }
    }
  })
  return merged;
}

const collectChildSelectors = (elementWrapper, selectors) => {
  elementWrapper.children.forEach(childWrapper => {
    if (childWrapper.element.id) {
      selectors[childWrapper.element.id] = childWrapper
    }
    if (childWrapper.selector) {
      selectors[childWrapper.selector] = childWrapper
    }
    collectChildSelectors(childWrapper, selectors)
  })
}

const applySettings = function(newSettings) {
  if (!newSettings) throw "bad settings"
  let settings = mergeDeeper(newSettings, {
    text: false,
    innerHTML: false,
    classes: [],
    actions: {},
    data: {},
    attributes: {},
    styles: {},
    traits: {},
    id: false,
    callback: false,
    ready: false,
  });
  if (settings.id) {
    this.element.id = settings.id
    this.selector = settings.id
  }
  if (settings.text) this.element.textContent = settings.text
  if (settings.innerHTML) this.element.innerHTML = settings.innerHTML
  if (settings.selector) {
    this.selector = settings.selector
    this.selectors[settings.selector] = this;
  }
  settings.classes.forEach(className => this.element.classList.add(className))
  Object.keys(settings.attributes).forEach(attributeName => 
      this.element.setAttribute(attributeName,
          settings.attributes[attributeName]))
  Object.keys(settings.styles).forEach(styleName => 
      this.element.style[styleName] = settings.styles[styleName])
  Object.keys(settings.actions).forEach(actionName => 
      this.actions[actionName] = () => settings.actions[actionName](this))
  Object.keys(settings.data).forEach(propertyName => 
      this.data[propertyName] = settings.data[propertyName])
  Object.keys(settings.traits).forEach(propertyName => 
      this.traits[propertyName] = settings.traits[propertyName])
  if (settings.ready) this.ready = settings.ready
  if (settings.callback) settings.callback(this);
}

export default class {
  constructor(tag, settings) {
    this.children = [];
    this.data = {}
    this.actions = {}
    this.traits = {}
    this.selectors = {}
    this.element = document.createElement(tag)
    applySettings.apply(this, [settings])
  }
  use(arg1, arg2) {
    if (arg1 instanceof Controller) {
      let controller = arg1;
      let settings = arg2;
      let mergedSettings = mergeDeeper(settings, controller.insertions);
      controller.test(mergedSettings);
      applySettings.apply(this, [mergedSettings])
    } else if (arguments.length === 1) {
      let settings = arg1;
      applySettings.apply(this, [settings])
    } else {
      throw "bad settings passed to Element"
    }
    return this;
  }
  addEventListener(event, func) {
    this.element.addEventListener(event, func)
  }
  delete() {
    this.parent.removeChild(this.element)
  }
  style(styleName, value) {
    this.element.style[styleName] = value
  }
  clearStyle(styleName) {
    this.element.style[styleName] = ""
  }
  updateText(text) {
    this.element.textContent = text
  }
  updateAttribute(attributeName, attributeContent) {
    this.element.setAttribute(attributeName, attributeContent)
  }
  addClass(className) {
    this.element.classList.add(className)
  }
  removeClass(className) {
    this.element.classList.remove(className)
  }
  on(evt, func) {
    this.element.addEventListener(evt, func)
  }
  select(id) {
    let parts = id.split("#")
    let selector = parts[parts.length - 1];
    if (!this.selectors[selector]) debugger; 
    //throw "bad selector " + selector
    return this.selectors[selector]
  }
  appendTo(elementWrapper) {
    let element
    if (elementWrapper.nodeName) element = elementWrapper
    else {
      element = elementWrapper.element
      this.parent = element
      collectChildSelectors(this, elementWrapper.selectors)
      Object.keys(elementWrapper.traits).forEach(propertyName => 
          this.traits[propertyName] = elementWrapper.traits[propertyName])
    }
    if (this.ready) this.ready(this)
    element.appendChild(this.element)
    return this
  }
  append(elementWrapper) {
    let element
    let wrapped = false
    if (elementWrapper.nodeName) element = elementWrapper
    else {
      wrapped = true
      element = elementWrapper.element
      element.parent = this
      if (element.id) this.selectors[element.id] = elementWrapper
      if (elementWrapper.selector) 
          this.selectors[elementWrapper.selector] = elementWrapper
      this.children.push(elementWrapper)
      collectChildSelectors(elementWrapper, this.selectors)
      Object.keys(this.traits).forEach(propertyName => 
          elementWrapper.traits[propertyName] = this.traits[propertyName])
    }
    if (elementWrapper.ready) elementWrapper.ready(elementWrapper)
    this.element.appendChild(element)
    if (wrapped) return elementWrapper
  }
}

Controller.js:

export default class {
  constructor(settings) {
    this.tests = {};
    Object.keys(settings).forEach(key => {
      let val = settings[key];
      if (typeof val == "boolean") {
        this.tests[key] = input => {
          return input !== undefined
        }
      } else if (typeof val == "string") {
        this.tests[key] = input => {
          return typeof input === val
        }
      } else if (typeof val == "function") {
        this.tests[key] = val;
      }
    })
  }
  test(obj) {
    Object.keys(obj).forEach(key => {
      if (!this.tests[key] || !this.tests[key](obj[key])) {
        console.log("Controller test failed");
        debugger;
      }
    });
  }
  insert(insertion) {
    this.insertions = insertion;
    return this;
  }
}

Publisher.js

export default class {
  constructor(socket) {
    if (socket) this.socket = socket;
    this.events = {};
  }
  on(command, func, socket = false) {
    if (!this.events[command]) this.events[command] = [];
    this.events[command].push(func);
    if (socket && this.socket) socket.on(command, func);
  }
  emit(command, data = {}, socket = false) {
    if (this.events[command]) {
      this.events[command].forEach(func => func(data));
    }
    if (socket && this.socket) socket.emit(command, data);
  }
}

Implementazione

app.js:

import Publisher from "/js/classes/publisher.js"
import Controller from "/js/classes/controller.js"

let publisher = new Publisher(io())

import Header       from "/js/classes/header/header.js"
import Home         from "/js/classes/pages/home/home.js"
import News         from "/js/classes/pages/news/news.js"
import Leaderboard  from "/js/classes/pages/leaderboard/leaderboard.js"
import Account      from "/js/classes/pages/account/account.js"
import Builder      from "/js/classes/pages/builder/builder.js"

let header = new Header(publisher)

let page = new Controller({
  data: true,     // () => { } // validate the value however you choose
  traits: true,   // It's good to have this capability for debugging
  actions: true,  // or for if your boss wants all your data interfaces
  ready: true     // validated because he read it in a hip dev blog
}).insert({       // <- But insertion is the feature you'll be using
  traits: {       // more often to test input data, debug, and like with
    publisher     // this case, apply a single input object to multiple
  },              // Elements
  actions: {
    select: self => {
      let target = "header/select-nav/" + self.data.name.toLowerCase()
      self.traits.publisher.emit(target, this)
      self.addClass("visible")
    }
  },
  ready: self => {
    self.traits.publisher.emit("header/add-nav", self)
  }
});

new Home().use(page, {
  data: {
    name:       "Home",
    iconPath:   "/assets/home/home-1.png",
    cornerPath: "/assets/corners/corner-1.png",
  }
}).appendTo(document.body)

new News().use(page, {
  data: {
    name:       "News",
    iconPath:   "/assets/news/news-1.png",
    cornerPath: "/assets/corners/corner-5.png"
  }
}).appendTo(document.body)

new Leaderboard().use(page, {
  data: {
    name:       "Leaderboard",
    iconPath:   "/assets/leaderboard/leaderboard-1.png",
    cornerPath: "/assets/corners/corner-3.png",
  }
}).appendTo(document.body)

new Account().use(page, {
  data: {
    name:       "Account",
    iconPath:   "./assets/profile/profile-1.png",
    cornerPath: "/assets/corners/corner-4.png",
  }
}).appendTo(document.body)

new Builder().use(page, {
  data: {
    name:       "Builder",
    iconPath:   "./assets/builder/builder-1.png",
    cornerPath: "/assets/corners/corner-2.png",
  }
}).appendTo(document.body).actions.select()

/js/classes/pages/builder/builder.js:

Qui ho usato una sorta di strana returndichiarazione nel costruttore, puramente per scopi visivi perché mi piace usare new ModuleName()nel file in cui è usato, al contrario di una chiamata di funzione, ma puoi farlo in entrambi i casi.

import Element from "/js/classes/element.js"
import NavBar from "/js/classes/pages/builder/nav-bar.js"
export default class {
  constructor() {
    return new Element("div", {
      id: "builder",
      classes: ["page"],
      actions: {
        select: self => {
          let target = "header/select-nav/" + self.data.name.toLowerCase()
          self.traits.publisher.emit(target, this)
          self.addClass("visible")
        }
      },
      ready: self => {
        self.traits.publisher.emit("header/add-nav", self)
        self.actions.select()
      },
      callback: self => {
        self.append(new NavBar());
        // add more elements
      }
    })
  }
}

/js/classes/pages/header/header.js

import Element from "/js/classes/element.js"
import NavIcon from "./header-nav-icon.js"
export default class {
  constructor(publisher) {
    return new Element("div", {
      id: "header",
      traits: { publisher },
      ready: self => {
        self.append(new Element("div", {
          selector: "title-wrapper",
          classes: ["title-wrapper"],
          ready: self => {
            self.append(new Element("div", {
              selector: "location-wrapper",
              classes: ["location-wrapper"],
              ready: self => {
                self.traits.publisher.on("header/add-nav", data => {
                  self.append(new Element("div", {
                    selector: "location-item-wrapper",
                    classes: ["location-item-wrapper"],
                    ready: self => {
                      self.traits.publisher.on("header/select-nav/" + 
                        data.data.name.toLowerCase(), data => {
                        self.addClass("visible")
                      });
                      self.append(new Element("div", {
                        id: data.data.name.toLowerCase() + "-nav",
                        classes: ["location-item", "heading"],
                        text: data.data.name
                      }))
                      self.append(new Element("img", {
                        classes: ["location-item-icon"],
                        attributes: {
                          "src": data.data.iconPath.split(".png")[0] + "-flat.png"
                        }
                      }))
                      self.append(new Element("img", {
                        selector: "corner",
                        classes: ["corner"],
                        attributes: {
                          "src": data.data.cornerPath
                        }
                      }))
                    }
                  }))
                })
              }
            }))
            self.append(new Element("div", {
              selector: "sub-location-wrapper",
              classes: ["sub-location-wrapper", "subheading"]
            }))
          }
        }))
        self.append(new Element("div", {
          selector: "nav-wrapper",
          classes: ["nav-wrapper", "center-contents"],
          ready: self => {
            self.traits.publisher.on("header/add-nav", data => {
              console.log("header/add-nav, data", data.data)
              console.log("adding nav-item")
              self.append(new NavIcon().use({
                data: data.data
              }))
            });
            self.append(new Element("div", {
              classes: ["title-bg-wrapper"],
              ready: self => {
                self.append(new Element("img", {
                  classes: ["title-bg-icon"],
                  attributes: {
                    "src": "./assets/header.png"
                  }
                }))
                self.append(new Element("div", {
                  classes: ["title-bg-text"],
                  innerHTML: "BIDRATE <br/> RENAISSANCE"
                }))
              }
            }))
          }
        }))
      }
    }).appendTo(document.body)
  }
}

Risposte

4 konijn Aug 31 2020 at 11:09

Da una breve recensione;

  • isCyclic-> Vorrei prendere in considerazione gettare objnel JSON.stringifye intercettare l'eccezione rilevante

  • function detect non è un bel nome, va bene per il contesto, ma potrebbe essere migliore

  • //console.log(obj, 'cycle at ' + key); <- commento negativo

  • Il codice utilizza sia vare sete const, c'è un vero valore nell'analisi del codice e nell'uso solo set/const

  • function isObject(item) <- un nome icky poiché in realtà controlli se è un oggetto ma non un Array (che è anche un oggetto), quindi perché non puoi usare questa funzione in if (obj && typeof obj === 'object')

  • function isIterable(item) {<- nome molto icky, il lettore assume restituisce un valore booleano, soprattutto con la prima linea essere false, ma poi anche di tornare objo arr, forse lo chiamano iterableTypeche il ritorno undefined, 'obj'o 'arr'?

  • Stai saltando le parentesi graffe isIterable, non dovresti

  • debugger non appartiene al codice di produzione

  • Questo

    sourceProps.forEach(prop => {
      allProps.push(prop);
    });
    targetProps.forEach(prop => {
      allProps.push(prop);
    });
    

    potrebbe essere

    allProps = allProps.concat(sourceProps).concat(targetProps);
    
  • Sai che solo Object e Array sono iterabili e che la proprietà è iterabile in questo modo

    let filler
    if (iterable == "obj") filler = {};
      else if (iterable == "arr") filler = [];
    

    può essere

    let filler = iterable=="obj"?{}:[];
    
  • Nel complesso vorrei documentarmi sull'operatore ternario, questo

      if (source[prop] !== undefined) {
        merged[prop] = source[prop]
      } else {
        merged[prop] = target[prop]
      }
    

    potrebbe essere cortocircuitato e più leggibile (per me);

     merged[prop] = source[prop]?source[prop]:target[prop];
    

    e in questo caso potrebbe anche essere ridotto a

     merged[prop] = source[prop] || target[prop];
    
  • Il codice ha un uso incoerente del punto e virgola, molto fastidioso da leggere

  • Dovresti scegliere uno standard di denominazione / codifica e rispettarlo, prima functionveniva usata la parola chiave e ora il codice passa a questo;

       const collectChildSelectors = (elementWrapper, selectors) => {
    
  • Non sono sicuro del motivo per cui non stai fornendo tutti i parametri possibili addEventListener

       addEventListener(event, func) {
                this.element.addEventListener(event, func)
       }
    
  • Fai quanto segue 5 volte con parametri diversi, questo potrebbe utilizzare una funzione di supporto per renderlo più leggibile;

     Object.keys(settings.styles).forEach(styleName => 
             this.element.style[styleName] = settings.styles[styleName])
    
2 SᴀᴍOnᴇᴌᴀ Aug 28 2020 at 22:56

Panoramica del quadro generale

Limita l'ambito a una panoramica generale, che dovrebbe aiutare a risparmiare tempo poiché è un pezzo di codice di buone dimensioni per una revisione.

Anche se ho collegato i moduli a un plunker, è ancora difficile per me determinare se il framework potrebbe aiutarmi con una SPA con codice limitato. Vedo molti metodi che accettano selfcome primo (e spesso unico) parametro. Perché non possono operare this? Il contesto non è vincolato correttamente per questo?

Ho creato un modulo emettitore di eventi per un'intervista. I requisiti suonano come il pattern Pub-Sub e ho implementato metodi simili al Publisher. I requisiti richiedevano un modo per avere un gestore "occasionale", nonché un modo per annullare la registrazione di una funzione gestore registrato. Potresti considerare di offrire tale funzionalità con il tuo modulo editore.

La linea di fondo è: se ritieni che questo framework ti consenta di scrivere meno codice di quanto potresti altrimenti con molti altri framework, vai avanti e usalo.

Feedback JS mirato

Ho notato che la constparola chiave appare solo due volte nel tuo codice, per due espressioni di funzione, ad esempio collectChildSelectorse applySettings. Si consiglia di constutilizzare la parola chiave predefinita per tutte le variabili e quindi, se è necessaria la riassegnazione, passare a using let. Inoltre, evitare var, a meno che non sia necessario qualcosa come una variabile globale, ma anche questo è disapprovato.

Alcune parti del codice vengono utilizzate ===per confrontare i valori, ma altre utilizzano ==. Una pratica consigliata è sempre quella di utilizzare un rigoroso confronto dei tipi.

Per una maggiore leggibilità, utilizzare uno stile di virgolette coerente per le stringhe letterali: virgolette singole o doppie ma non entrambe.

mergeDeeper()potrebbe usare l'operatore spread invece di forEach () -> push for sourcePropsetargetProps

allProps.push(...sourceProps, ...targetProps)

Il nome della funzione isIterablesembra alquanto strano dato che può restituire una stringa o un valore booleano. Forse sarebbe un nome più appropriato iterableType- anche se restituisse, falseil chiamante saprebbe che il valore non è iterabile.