javascript - online - browserify vs webpack
¿Configurar un plugin jQuery genérico con Browserify-shim? (4)
Estoy usando browserify-shim y quiero usar un plugin jQuery genérico. He revisado los documentos de Browserify-shim varias veces y parece que no puedo entender lo que está pasando y / o cómo sabe dónde colocar plugins, adjuntarlo al objeto jQuery, etc. Aquí está mi archivo package.json. :
"browser": {
"jquery": "./src/js/vendor/jquery.js",
"caret": "./src/js/vendor/jquery.caret.js"
},
"browserify-shim": {
"caret": {
"depends": ["jquery:$"]
}
}
Según el ejemplo dado en la documentación de browserify-shim, no deseo especificar exportaciones porque este complemento (y la mayoría, si no todos los complementos jQuery) se adjuntan al objeto jQuery. A menos que esté haciendo algo incorrecto arriba, no entiendo por qué no funciona (me sale un error diciéndome que la función no está definida) cuando lo uso. Vea abajo:
$(''#contenteditable'').caret(5); // Uncaught TypeError: undefined is not a function
Entonces mi pregunta es, ¿cómo se configura un plugin jQuery genérico (que se adhiere al objeto jQuery) con browserify y browserify-shim?
Después de volver a visitar esto y probar algunas cosas más, finalmente me di cuenta de lo que está haciendo browserify-shim y cómo usarlo. Para mí, había un principio clave que tenía que comprender antes de que finalmente entendiera cómo usar browserify-shim. Básicamente, hay dos maneras de utilizar browserify-shim para dos casos de uso diferentes: exponer y calzar.
Fondo
Digamos que desea simplemente colocar una etiqueta de secuencia de comandos en su marcado (para pruebas o razones de rendimiento como el almacenamiento en caché, CDN y similares). Al incluir una etiqueta de secuencia de comandos en el marcado, el navegador accederá al script, lo ejecutará y muy probablemente adjunte una propiedad en el objeto de ventana (también conocido como global en JS). Por supuesto, se puede acceder a esto haciendo myGlobal
o window.myGlobal
. Pero hay un problema con cualquiera de las sintaxis. No sigue la especificación CommonJS, lo que significa que si un módulo comienza a admitir la sintaxis CommonJS ( require()
), no podrá aprovecharlo.
La solución
Browserify-shim le permite especificar un global que le gustaría "exponer" a través de la sintaxis de common require()
. Recuerda, puedes hacer var whatever = global;
o var whatever = window.global;
pero NO podías hacer var whatever = require(''global'')
y esperar que te diera el módulo / lib correcto. No se confunda con el nombre de la variable. Podría ser cualquier cosa arbitraria. Básicamente, estás convirtiendo una variable global en una variable local. Suena estúpido, pero es el triste estado de JS en el navegador. De nuevo, la esperanza es que una vez que una lib sea compatible con la sintaxis de CommonJS, nunca se adjunte a sí misma a través de un objeto global en la ventana. Lo que significa que DEBE usar la sintaxis require()
y asignarla a una variable local y luego usarla donde la necesite.
Nota: Encontré la nomenclatura variable ligeramente confusa en los documentos / ejemplos de browserify-shim. Recuerde, la clave es que desea incluir una lib como si fuera un módulo CommonJS que se comporta correctamente. Entonces, lo que terminas haciendo es decir a browserify que cuando requieras myGlobal require(''myGlobal'')
realmente solo quieres que te den la propiedad global en el objeto window que es window.myGlobal
.
De hecho, si tiene curiosidad sobre lo que hace la función requerida, es bastante simple. Esto es lo que sucede debajo del capó:
var whatever = require(''mygGlobal'');
se convierte ...
var whatever = window.mygGlobal;
Exposición
Entonces, con ese trasfondo, veamos cómo exponemos un módulo / lib en nuestra configuración de browserify-shim. Básicamente, dices dos cosas a browserify-shim. El nombre con el que desea acceder cuando llama a require()
y al global que debería esperar encontrar en el objeto de la ventana. Así que aquí es donde aparece la sintaxis global:*
veamos un ejemplo. Quiero incluir jquery como una etiqueta de script en index.html para obtener un mejor rendimiento. Esto es lo que tendría que hacer en mi configuración (esto sería en package.json o en un archivo JS de configuración externa):
"browserify-shim": {
"jquery": "global:$"
}
Entonces esto es lo que eso significa. He incluido jQuery en otro lugar (recuerde, browserify-shim no tiene idea de dónde colocamos nuestra etiqueta, pero no necesita saber), pero todo lo que quiero es que se le otorgue la propiedad $
en el objeto ventana cuando requiera el módulo con el parámetro de cadena "jquery". Para ilustrar más. Yo también podría haber hecho esto:
"browserify-shim": {
"thingy": "global:$"
}
En este caso, tendría que pasar "thingy" como parámetro a la función require para recuperar una instancia del objeto jQuery (que acaba de obtener jQuery de window.$
):
var $ = require(''thingy'');
Y sí, una vez más, el nombre de la variable podría ser cualquier cosa. No hay nada de especial en que $
sea lo mismo que la propiedad global $
utiliza la biblioteca jQuery real. Aunque tiene sentido usar el mismo nombre para evitar confusiones. Esto termina haciendo referencia a la propiedad $
en el objeto de la ventana, según lo seleccionado por el valor global:$
en el objeto browserify-shim
en package.json.
Shimming
Ok, entonces eso cubre mucho exponer. La otra característica principal de browserify-shim es shimming. Entonces, ¿qué es eso? Shimming hace esencialmente lo mismo que exponer, excepto que en lugar de incluir el lib o el módulo en el marcado HTML con algo así como una etiqueta de script, le dices a browserify-shim dónde tomar el archivo JS localmente. No es necesario utilizar la sintaxis global:*
. Así que volvamos a nuestro ejemplo de jQuery, pero esta vez supongamos que no estamos cargando jQuery desde un CDN, sino simplemente empaquetándolo con todos los archivos JS. Así que aquí está cómo se vería la configuración:
"browser": {
"jquery": "./src/js/vendor/jquery.js", // Path to the local JS file relative to package.json or an external shim JS file
},
"browserify-shim": {
"jquery": "$"
},
Esta configuración le dice a browserify-shim que cargue jQuery desde la ruta local especificada y luego tome la propiedad $ del objeto window y la devuelva cuando requiera jQuery con un parámetro de cadena para la función require de "jquery". Nuevamente, para fines ilustrativos, también puede cambiarle el nombre a cualquier otra cosa.
"browser": {
"thingy": "./src/js/vendor/jquery.js", // Path to the local JS file relative to package.json or an external shim JS file
},
"browserify-shim": {
"thingy": "$"
},
Que podría ser requerido con:
var whatever = require(''thingy'');
Recomiendo consultar los documentos browserify-shim para obtener más información sobre la sintaxis long-hand utilizando la propiedad exports
y también la propiedad depends
que le permite indicar a browserify-shim si una lib depende de otra lib / module. Lo que he explicado aquí se aplica a ambos. Espero que esto ayude a otros a esforzarse por comprender lo que en realidad está haciendo browser-shim y cómo usarlo.
Shimming anónimo
El calce anónimo es una alternativa a browserify-shim que le permite transformar libs como jQuery en módulos UMD usando la opción de --standalone
.
$ browserify ./src/js/vendor/jquery.js -s thingy > ../dist/jquery-UMD.js
Si lo dejaste caer en una etiqueta de script, este módulo agregaría jQuery al objeto window como thingy
. Por supuesto, también podría ser $
o lo que quieras.
Sin embargo, si se require
en su paquete de aplicación browserify''d, var $ = require("./dist/jquery-UMD.js");
, tendrá jQuery disponible dentro de la aplicación sin agregarlo al objeto ventana.
Este método no requiere browserify-shim y explota el conocimiento CommonJS de jQuery donde busca un objeto de module
y pasa un indicador noGlobal
a su fábrica que le dice que no se una al objeto de ventana.
Estaba usando WordPress. Por lo tanto, me vi forzado a usar el jQuery de wordpress core, disponible en el objeto window
.
Generaba un error slick()
no definido cuando intenté usar el complemento slick()
de npm. Agregar browserify-shim
no ayudó mucho.
Hice algunas excavaciones y descubrí que require(''jquery'')
no siempre era consistente.
En mi archivo JavaScript del tema, llamaba a jquery de wordpress core.
Pero, en el slick
plugin jquery, llamaba a la última jquery desde los módulos de nodos.
Finalmente, pude resolverlo. Por lo tanto, compartir la configuración de package.json
y gulpfile
.
package.json:
"browserify": { "transform": [ "browserify-shim" ] }, "browserify-shim": { "jquery": "global:jQuery" },
gulpfile.babel.js:
browserify({entries: ''main.js'', extensions: [''js''], debug: true}) .transform(babelify.configure({ presets: ["es2015"] })) .transform(''browserify-shim'', {global: true})
Hacer la transformación ''browserify-shim'' fue una parte crucial, me faltaba antes. Sin él, browserify-shim
no era consistente.
Para todos, que busca un ejemplo concreto:
El siguiente es un ejemplo de los archivos package.json
y app.js
para un complemento jQuery que se adjunta al objeto jQuery
/ $
, por ejemplo: $(''div'').expose()
. No quiero que jQuery
sea una variable global ( window.jQuery
) cuando lo requiera, es por eso que jQuery está configurado en ''exports'': null
. Sin embargo, como el complemento espera un objeto jQuery
global al que pueda adjuntarse, debe especificarlo en la dependencia después del nombre de archivo: ./jquery-2.1.3.js:jQuery
. Además, necesita exportar el jQuery
global cuando usa el complemento, incluso si no lo desea, porque el complemento no funcionará de otra forma (al menos este en particular).
paquete.json
{
"name": "test",
"version": "0.1.0",
"description": "test",
"browserify-shim": {
"./jquery-2.1.3.js": { "exports": null },
"./jquery.expose.js": { "exports": "jQuery", "depends": [ "./jquery-2.1.3.js:jQuery" ] }
},
"browserify": {
"transform": [
"browserify-shim"
]
}
}
app.js
// copy and delete any previously defined jQuery objects
if (window.jQuery) {
window.original_jQuery = window.jQuery;
delete window.jQuery;
if (typeof window.$.fn.jquery === ''string'') {
window.original_$ = window.$;
delete window.$;
}
}
// exposes the jQuery global
require(''./jquery.expose.js'');
// copy it to another variable of my choosing and delete the global one
var my_jQuery = jQuery;
delete window.jQuery;
// re-setting the original jQuery object (if any)
if (window.original_jQuery) { window.jQuery = window.original_jQuery; delete window.original_jQuery; }
if (window.original_$) { window.$ = window.original_$; delete window.original_$; }
my_jQuery(document).ready(function() {
my_jQuery(''button'').click(function(){
my_jQuery(this).expose();
});
});
En el ejemplo anterior, no quería que mi código estableciera ningún elemento global, pero tuve que hacerlo temporalmente para que el complemento funcione. Si solo necesita jQuery, puede hacerlo y no necesita ninguna solución alternativa: var my_jQuery = require(''./jquery-2.1.3.js'')
. Si estás de acuerdo con que tu jQuery se exponga como global, entonces puedes modificar el ejemplo anterior de package.json
así:
"browserify-shim": {
"./jquery-2.1.3.js": { "exports": "$" },
"./jquery.expose.js": { "exports": null, "depends": [ "./jquery-2.1.3.js" ] }
Espero que ayude a algunas personas, que estaban buscando ejemplos concretos (como yo, cuando encontré esta pregunta).
Solo para completar, aquí hay un método que explota la conciencia de CommonJS de jQuery para evitar tener que preocuparse por la contaminación del objeto de la window
sin tener que calzar realmente.
Caracteristicas
- jQuery incluido en el paquete
- plugin incluido en el paquete
- sin contaminación del objeto de la
window
Config
En ./package.json , agregue un nodo de browser
para crear alias para las ubicaciones de los recursos. Esto es puramente por conveniencia, no hay necesidad de ajustar realmente nada porque no hay comunicaciones entre el módulo y el espacio global (etiquetas de script) .
{
"main": "app.cb.js",
"scripts": {
"build": "browserify ./app.cb.js > ./app.cb.bundle.js"
},
"browser": {
"jquery": "./node_modules/jquery/dist/jquery.js",
"expose": "./js/jquery.expose.js",
"app": "./app.cb.js"
},
"author": "cool.blue",
"license": "MIT",
"dependencies": {
"jquery": "^3.1.0"
},
"devDependencies": {
"browserify": "^13.0.1",
"browserify-shim": "^3.8.12"
}
}
Método
- Debido a que jQuery es consciente de CommonJS en estos días, detectará la presencia del objeto de
module
provisto por browserify y devolverá una instancia, sin agregarla al objeto de lawindow
. - En la aplicación,
require
jquery y agréguelo al objetomodule.exports
(junto con cualquier otro contexto que deba compartirse). - Agregue una sola línea al inicio del complemento para solicitar que la aplicación acceda a la instancia de jQuery que creó.
- En la aplicación, copie la instancia de jQuery en
$
y use jQuery con el complemento. - Browserify la aplicación, con opciones predeterminadas, y suelte el paquete resultante en una etiqueta de script en su HTML.
Código
app.cb.js
var $ = module.exports.jQuery = require("jquery");
require(''expose'');
$(document).ready(function() {
$(''body'').append(
$(''<button name="button" >Click me</button>'')
.css({"position": "relative",
"top": "100px", "left": "100px"})
.click(function() {
$(this).expose();
})
);
});
en la parte superior del complemento
var jQuery = require("app").jQuery;
en el HTML
<script type="text/javascript" src="app.cb.bundle.js"></script>
Fondo
El patrón utilizado por jQuery es llamarlo de fábrica con un indicador noGlobal
si detecta un entorno CommonJS. No agregará una instancia al objeto de window
y devolverá una instancia como siempre.
El contexto CommonJS es creado por browserify por defecto. A continuación se muestra un extracto simplificado del paquete que muestra la estructura del módulo jQuery. Eliminé el código que trata sobre el manejo isomórfico del objeto de la window
en aras de la claridad.
3: [function(require, module, exports) {
( function( global, factory ) {
"use strict";
if ( typeof module === "object" && typeof module.exports === "object" ) {
module.exports = factory( global, true );
} else {
factory( global );
}
// Pass this if window is not defined yet
} )( window, function( window, noGlobal ) {
// ...
if ( !noGlobal ) {
window.jQuery = window.$ = jQuery;
}
return jQuery;
}) );
}, {}]
El mejor método que encontré es hacer que las cosas funcionen en el sistema de módulos de nodos y luego funcionará siempre después de la navegación.
Simplemente use jsdom para ajustar el objeto de la window
para que el código sea isomorfo. Luego, solo concéntrese en hacer que funcione en el nodo. Luego, reduzca el tráfico entre el módulo y el espacio global y finalmente navegúyalo y simplemente funcionará en el navegador.