2014-12-17 22 views
6

Ho un plugin che estende un modulo originale.
Dovrebbe solo modificare il modulo, quando richiesto esplicitamente.Come implementare un plugin, che modifica il modulo originale solo quando richiesto?

Problema:
Non appena richiesto una volta, il modulo originale viene modificato per sempre, anche nei casi in cui il plug-in non è una dipendenza.
L'ordine non importa qui, è sufficiente richiedere il plugin una volta.

Esempio:

define("main", [], function() { 
    return {opt: "A"}; 
}); 
define("plugin", ["main"], function(obj) { 
    obj.opt = "B"; 
}); 
require(["main", "plugin"], function(obj) { 
    console.log(obj.opt); // should log B 
}); 
require(["main"], function(obj) { 
    console.log(obj.opt); // should log A but logs B 
}); 

Credo che la strada da percorrere è quello di dire in qualche modo bisogno di ricaricare sempre main dalla fonte invece di utilizzare la versione memorizzata nella cache.
Non ho idea di come, però.
O forse c'è un modo ancora più elegante?

Per favore illuminami, ragazzi.

Fiddle: http://jsfiddle.net/r75e446f

UPDATE: Alcuni potrebbero trovare importante sapere che ho bisogno di questo per il mio ambiente di test di unità karma per testare un modulo con e senza il plugin.

UPDATE2: Cerca di seguito la mia soluzione.

risposta

0

Così ho trovato il modo di realizzare, quello che voglio e ho pensato di condividere qui.

La risposta è denominata "contesto", che è un'opzione nella configurazione requirejs.

http://requirejs.org/docs/api.html#multiversion

Ecco come ho implementato la mia soluzione:

var reqOne = require.config({ 
    context: 'one' 
}); 
var reqTwo = require.config({ 
    context: 'two' 
}); 
reqOne(["main", "plugin"], function(obj) { 
    console.log(obj.opt); // logs B 
}); 
reqTwo(["main"], function(obj) { 
    console.log(obj.opt); // logs A 
}); 

Purtroppo questo non funziona in the fiddle, perché il secondo richiede cercherà di caricare il modulo principale esterno e non riesco a caricare file per jsfiddle.

Ma il solo fatto che provi a farlo senza utilizzare il modulo "principale" già caricato nell'altro richiede, dovrebbe essere la prova che il suo metodo funziona.
Se le definizioni sono esternalizzate in file singoli, questo funziona come un incantesimo.

Grazie per tutti coloro che intervenne.

+0

Non appena la tua app diventa sostanziale, questo causa problemi. Ogni contesto è ** totalmente ** isolato dagli altri. Se tutti i contesti utilizzano le stesse librerie di terze parti (che deve essere il caso se si carica la stessa app in diversi contesti), allora devono caricare tutte queste librerie. Se usano jQuery, caricheranno jQuery più volte. jQuery può o non può rilevare che è già caricato (dipende dal fatto che si usi '' noConflict''). RequireJS deve ancora recuperare la lib e eseguirla più volte, il che è tutt'altro che ottimale. Se progetti correttamente la tua applicazione, puoi evitare tutto questo. – Louis

+0

Louis, tecnicamente, sei corretto e come discusso più volte qui ne sono consapevole. Questa però è esattamente la risposta alla mia domanda in questo caso, perché non ho chiesto "come renderlo più elegante o appropriato", ma "come fare questo". Il motivo (che ho aggiunto in seguito) è che la mia applicazione è un ambiente di test. Quindi qui non importa affatto che le librerie vengano caricate più volte. In realtà è desiderato. Gli utenti finali non si preoccupano del fatto che il plugin sia costantemente caricato, se necessario una volta. In realtà è più conveniente per loro. Ancora una volta: questo è esattamente quello che sto cercando. –

+1

Nel tuo commento affermi "Non ho chiesto" come renderlo più elegante o appropriato "". Tuttavia, questo è direttamente dalla tua domanda: "O forse c'è un modo ancora più elegante?" – Louis

4

I moduli RequireJS sono singleton. Se carichi main una, due, dieci volte, otterrai sempre lo stesso modulo. Quindi se modifichi il suo stato, viene modificato per tutti i moduli che lo utilizzano. È possibile dire a RequireJS di non definire il modulo, ma io non lo consiglio poiché renderà il tuo codice oscuro.

Se volevo fare ciò che si sta cercando di fare mi piacerebbe progettare il mio codice di qualcosa di simile:

<script> 
    define("main", [], function() { 
     function Main (opt) { 
      this.opt = opt; 
     } 
     return Main; 
    }); 
    define("plugin1", [], function() { 
     return { 
      install: function (main) { 
       main.opt += " plugin1"; 
      } 
     }; 
    }); 
    define("plugin2", [], function() { 
     return { 
      install: function (main) { 
       main.opt += " plugin2"; 
      } 
     }; 
    }); 
    // Case 1: no plugins 
    require(["main"], function(Main) { 
     var main = new Main("A"); 
     console.log(main.opt); 
    }); 
    // Case 2: only plugin1 
    require(["plugin1", "main"], function (plugin1, Main) { 
     var main = new Main("A"); 
     plugin1.install(main); 
     console.log(main.opt); 
    }); 
    // Case 3: only plugin2 
    require(["plugin2", "main"], function (plugin2, Main) { 
     var main = new Main("A"); 
     plugin2.install(main); 
     console.log(main.opt); 
    }); 
    // Case 4: plugin1 and plugin2 
    require(["plugin1", "plugin2", "main"], function (plugin1, plugin2, 
                Main) { 
     var main = new Main("A"); 
     plugin1.install(main); 
     plugin2.install(main); 
     console.log(main.opt); 
    }); 

In sostanza, fare ciò che è comune a tutti i casi una classe Main che può essere inizializzato in costruzione e che possono essere modificati dai plugin. Quindi ciascun plugin può installarsi su Main. Il codice sopra è un'illustrazione minima di come potrebbe essere fatto. In un progetto reale, la soluzione finale dovrebbe essere progettata per tenere conto delle esigenze specifiche del progetto.

+0

io continuo a pensare che cosa ho bisogno è una qualche forma di reinizializzazione di un modulo dalla sorgente definita. Hai accennato a non definirlo, ma non è questo, penso. –

+0

Dal punto di vista dei requisiti, sono d'accordo sul fatto che questa sia la soluzione più corretta. In realtà è venuto anche in una discussione con un collega. Il fatto è che gli utenti non sono abituati a inizializzare manualmente i plugin. Vogliono includerli e poi lavorare. Di solito non si preoccupano nemmeno di modificare il modulo a livello globale, lo so. Ma come ho detto nel mio altro commento (http://goo.gl/gzew0e) il mio caso è il test. Quindi dovrei fare una soluzione, non l'utente. Non ho menzionato questo prima per motivi di brevità. –

0

Vorrei sollevare la mia comprensione per la discussione .. quando AMD, definiamo lo module. A meno che il modulo (in questo caso è main, la fonte definita) sia in grado di accettare lo plugin e consentire al plug-in di modificare le proprietà, suppongo che non siamo in grado di dirottarlo o che sarà un anti-pattern?

che dire estendiamo/clonare la fonte definita, come

define("plugin", ["main"], function(obj) { 
    return $.extend(true, {}, obj, {opt: "B"}); 
}); 

http://jsfiddle.net/r75e446f/6/

qui il modulo plugin è sempre usando il modulo main come la sua dipendenza, poi, quando si utilizza il modulo usiamo direttamente il modulo plugin e non è più necessario richiedere il modulo main? (A proposito ho capito questa sconfitta la definizione di 'plug-in', ma il mio senso è che abbiamo a che fare con la module come quello che le requireJs progettati per essere)

--2nd approach--

se si vuole davvero farlo in modo plug-in, per quanto riguarda lo loader plugin? come il plugin text usiamo sempre `require ('text! abc.json'). Quindi scrivi un loader e registralo nella configurazione di requireJs, e poi potremmo usarlo. (more)

3

Se non si desidera modificare il modulo originale per tutti i moduli che lo utilizzano, il proprio plug-in non dovrebbe modificare il modulo originale. Invece, fai in modo che il plugin restituisca una copia modificata del modulo originale.

define("main", [], function() { 
    return {opt: "A"}; 
}); 
define("plugin", ["main"], function(obj) { 
    var decorator = {} 
    for (var key in obj) { decorator[key] = obj[key];} 
    decorator.opt = "B"; 
    return decorator 
}); 
require(["main", "plugin"], function(obj, plugin) { 
    console.log(plugin.opt); // should log B 
}); 
require(["main"], function(obj) { 
    console.log(obj.opt); // should log A but logs B 
}); 

Questo funziona senza complicazioni se l'oggetto originale è un semplice oggetto struct simile, senza alcuna funzione. Se ci sono funzioni, o se il tuo oggetto originale è stato costruito usando il Pattern del modulo, allora c'è una forte possibilità di errori sottili nella copia, a seconda di come sono stati definiti i metodi.

EDIT 2015-01-13: Il PO ha chiarito la sua domanda che gli piacerebbe che i suoi test fossero in grado di eseguire sia il modulo originale modificato che quello non modificato senza dover ricaricare la pagina. In tal caso, consiglierei di utilizzare require.undef per scaricare il modulo principale e ricaricarlo senza dover ricaricare l'intera pagina.

+0

Vedo come sia il modo più corretto, ma gli utenti non sono abituati a fare riferimento al plugin come costruttore, ma al modulo principale. Lasciami spiegare: In generale, non penso che sarà un problema, che il plugin modificherà il modulo per sempre. È lo stesso quando si usa jQuery dopo tutto e quindi si aspetta un comportamento. Il mio caso d'uso è tuttavia un test unitario. Nel karma voglio essere in grado di testare il modulo con e senza il plugin caricato. Finora non ho trovato un modo per farlo se non quello di ricaricare l'intera pagina. –

+0

@JanPaepke, ti preghiamo di chiarire la tua domanda originale con il problema dei test, poiché si tratta di un grande cambiamento nel contesto delle domande. Ho modificato la mia risposta per renderlo conto. –

+0

Beh, non penso che sia tanto un gamechanger. Non ho chiesto come renderlo migliore o più "corretto" in termini di un modulo richiesto, ma come avere un plugin solo per modificare un modulo quando è richiesto, che è esattamente ciò di cui ho bisogno, indipendentemente dal contesto. –

-2

Una soluzione più elegante sarebbe quella di adottare un approccio orientato agli oggetti, dove:

  1. moduli ritorno costruttori anziché istanze, e
  2. plugin può quindi essere una sottoclasse di main.

Questa è l'implementazione (fiddle):

console.clear(); 
define("Main", [], function() { 
    return function() { this.opt = "A"; }; 
}); 
define("Plugin", ["Main"], function(Main) { 
    return function() { 
     Main.call(this); 
     this.opt = "B"; 
    }; 
}); 
require(["Plugin"], function(Plugin) { 
    console.log((new Plugin()).opt); // should log B 
}); 
require(["Main"], function(Main) { 
    console.log((new Main()).opt); // should log A 
}); 
Problemi correlati