¿El estilo de nodo requiere javascript en el navegador?

83

¿Existen bibliotecas para javascript en el navegador que brinden la misma flexibilidad / modularidad / facilidad de uso que las de Node require?

Para proporcionar más detalles: la razón requirees tan buena es que:

  1. Permite que el código se cargue dinámicamente desde otras ubicaciones (lo cual es estilísticamente mejor, en mi opinión, que vincular todo su código en el HTML)
  2. Proporciona una interfaz coherente para la construcción de módulos.
  3. Es fácil que los módulos dependan de otros módulos (por lo que podría escribir, por ejemplo, una API que requiera jQuery para poder usar jQuery.ajax()
  4. El javascript cargado tiene un alcance , lo que significa que podría cargar var dsp = require("dsp.js");y podría acceder dsp.FFT, lo que no interferiría con mivar FFT

Todavía tengo que encontrar una biblioteca que haga esto de manera efectiva. Las soluciones alternativas que suelo usar son:

  • coffeescript-concat : es bastante fácil requerir otros js, pero debe compilarlo, lo que significa que es menos bueno para un desarrollo rápido (por ejemplo, compilar API en la prueba)

  • RequireJS : es popular, sencillo y resuelve 1-3, pero la falta de alcance es un verdadero factor decisivo (creo que head.js es similar porque carece de alcance, aunque nunca he tenido la oportunidad de usarlo. De manera similar, los LABj pueden cargar y mitigar los.wait() problemas de dependencia, pero aún no tiene alcance)

Por lo que puedo decir, parece haber muchas soluciones para la carga dinámica y / o asíncrona de javascript, pero tienden a tener los mismos problemas de alcance que simplemente cargar js desde HTML. Más que cualquier otra cosa, me gustaría una forma de cargar javascript que no contamine el espacio de nombres global en absoluto, pero que aún me permita cargar y usar bibliotecas (tal como lo hacen los requisitos del nodo).

ACTUALIZACIÓN 2020: Los módulos ahora son estándar en ES6 y, a partir de mediados de 2020, la mayoría de los navegadores los admiten de forma nativa . Los módulos admiten carga síncrona y asincrónica (usando Promise). Mi recomendación actual es que la mayoría de los proyectos nuevos deben usar módulos ES6 y usar un transpilador para recurrir a un solo archivo JS para navegadores heredados.

Como principio general, el ancho de banda de hoy también suele ser mucho más amplio que cuando hice esta pregunta originalmente. Por lo tanto, en la práctica, podría elegir razonablemente usar siempre un transpilador con módulos ES6 y concentrar su esfuerzo en la eficiencia del código en lugar de la red.

EDICIÓN ANTERIOR (o si no le gustan los módulos ES6): desde que escribí esto, he usado ampliamente RequireJS (que ahora tiene una documentación mucho más clara). RequireJS realmente fue la elección correcta en mi opinión. Me gustaría aclarar cómo funciona el sistema para las personas que están tan confundidas como yo:

Puede utilizarlo requireen el desarrollo diario. Un módulo puede ser cualquier cosa devuelta por una función (normalmente un objeto o una función) y tiene un alcance como parámetro. También puede compilar su proyecto en un solo archivo para su implementación r.js(en la práctica, esto es casi siempre más rápido, aunque requirepuede cargar scripts en paralelo).

La principal diferencia entre RequireJS y el estilo de nodo require como browserify (un proyecto genial sugerido por tjameson) es la forma en que se diseñan y requieren los módulos:

  • RequireJS usa AMD (Definición de módulo asíncrono). En AMD, requiretoma una lista de módulos (archivos javascript) para cargar y una función de devolución de llamada. Cuando ha cargado cada uno de los módulos, llama a la devolución de llamada con cada módulo como parámetro para la devolución de llamada. Por lo tanto, es realmente asincrónico y, por lo tanto, se adapta bien a la web.
  • Node usa CommonJS. En CommonJS, requirees una llamada de bloqueo que carga un módulo y lo devuelve como un objeto. Esto funciona bien para Node porque los archivos se leen fuera del sistema de archivos, que es lo suficientemente rápido, pero funciona mal en la web porque cargar archivos sincrónicamente puede llevar mucho más tiempo.

En la práctica, muchos desarrolladores han usado Node (y por lo tanto CommonJS) antes de ver AMD. Además, muchas bibliotecas / módulos están escritos para CommonJS (agregando cosas a un exportsobjeto) en lugar de para AMD (devolviendo el módulo desde la definefunción). Por lo tanto, muchos desarrolladores de Node convertidos en web quieren usar bibliotecas CommonJS en la web. Esto es posible, ya que <script>se bloquea la carga desde una etiqueta. Soluciones como browserify toman módulos CommonJS (Node) y los envuelven para que pueda incluirlos con etiquetas de script.

Por lo tanto, si está desarrollando su propio proyecto de múltiples archivos para la web, le recomiendo encarecidamente RequireJS, ya que es realmente un sistema de módulos para la web (aunque, en términos justos, creo que AMD es mucho más natural que CommonJS). Recientemente, la distinción se ha vuelto menos importante, ya que RequireJS ahora le permite usar esencialmente la sintaxis CommonJS. Además, RequireJS se puede usar para cargar módulos AMD en Node (aunque prefiero node-amd-loader ).

Alex Churchill
fuente
1
Nota RequireJS realmente admite la modularidad y el alcance de lata. Desde que lo pregunté, lo he usado un poco más ampliamente. En mi opinión, está repleto de funciones, pero requiere mucha lectura de documentación para usarlo de manera efectiva, y necesita alguna forma de carga síncrona de primera clase antes de que sea perfecto.
Alex Churchill
1
¿Es tan significativa la distinción de ser asincrónico? cada vez que necesito un código, básicamente estoy bloqueado para poder continuar porque define funciones y necesito hacer cualquier cosa ...
Michael

Respuestas:

17

Echa un vistazo a ender . Hace mucho de esto.

Además, browserify es bastante bueno. He usado require-kiss ¹ y funciona. Probablemente hay otros.

No estoy seguro de RequireJS. Simplemente no es lo mismo que el de node. Puede tener problemas con la carga desde otras ubicaciones, pero podría funcionar. Siempre que haya un método provide o algo que se pueda llamar.

TL; DR : recomendaría browserify o require-kiss.


Actualizar:

1: require-kiss ahora está muerto y el autor lo ha eliminado. Desde entonces he estado usando RequireJS sin problemas. El autor de require-kiss escribió pakmanager y pakman . Divulgación completa, trabajo con el desarrollador.

Personalmente, me gusta más RequireJS. Es mucho más fácil de depurar (puede tener archivos separados en desarrollo y un solo archivo implementado en producción) y se basa en un "estándar" sólido.

beatgammit
fuente
Genial, todavía no he probado browserify, pero parece exactamente lo que necesito.
Alex Churchill
El vínculo a require-kiss parece estar muerto. La (re) búsqueda simple no condujo a ninguna parte, ¿adónde fue?
Joel Purra
@JoelPurra - require-kiss ha sido eliminado y reemplazado por pakmanager. Recomiendo require-js ahora. Actualicé la respuesta.
beatgammit
buena respuesta aquí hombre :), ¿le importaría revisar la pregunta que acabo de hacer que es similar a esta (pero diferente al mismo tiempo)? stackoverflow.com/questions/43237875/…
Webeng
16

Escribí un pequeño script que permite la carga asíncrona y síncrona de archivos Javascript, lo que podría ser de alguna utilidad aquí. No tiene dependencias y es compatible con Node.js y CommonJS. La instalación es bastante sencilla:

$ npm install --save @tarp/require

Luego, simplemente agregue las siguientes líneas a su HTML para cargar el módulo principal:

<script src="/node_modules/@tarp/require/require.min.js"></script>
<script>Tarp.require({main: "./scripts/main"});</script>

Dentro de su módulo principal (y cualquier submódulo, por supuesto) puede usarlo require()como lo conoce de CommonJS / NodeJS. Los documentos completos y el código se pueden encontrar en GitHub .

Torben
fuente
1
Esto es increíblemente genial. ¡Gracias por escribir esto!
OldTimeGuitarGuy
Gracias, @OldTimeGuitarGuy :)
Torben
10

Una variación de la gran respuesta de Ilya Kharlamov , con algo de código para que funcione bien con las herramientas de desarrollo de Chrome.

//
///- REQUIRE FN
// equivalent to require from node.js
function require(url){
    if (url.toLowerCase().substr(-3)!=='.js') url+='.js'; // to allow loading without js suffix;
    if (!require.cache) require.cache=[]; //init cache
    var exports=require.cache[url]; //get from cache
    if (!exports) { //not cached
            try {
                exports={};
                var X=new XMLHttpRequest();
                X.open("GET", url, 0); // sync
                X.send();
                if (X.status && X.status !== 200)  throw new Error(X.statusText);
                var source = X.responseText;
                // fix (if saved form for Chrome Dev Tools)
                if (source.substr(0,10)==="(function("){ 
                    var moduleStart = source.indexOf('{');
                    var moduleEnd = source.lastIndexOf('})');
                    var CDTcomment = source.indexOf('//@ ');
                    if (CDTcomment>-1 && CDTcomment<moduleStart+6) moduleStart = source.indexOf('\n',CDTcomment);
                    source = source.slice(moduleStart+1,moduleEnd-1); 
                } 
                // fix, add comment to show source on Chrome Dev Tools
                source="//@ sourceURL="+window.location.origin+url+"\n" + source;
                //------
                var module = { id: url, uri: url, exports:exports }; //according to node.js modules 
                var anonFn = new Function("require", "exports", "module", source); //create a Fn with module code, and 3 params: require, exports & module
                anonFn(require, exports, module); // call the Fn, Execute the module
                require.cache[url]  = exports = module.exports; //cache obj exported by module
            } catch (err) {
                throw new Error("Error loading module "+url+": "+err);
            }
    }
    return exports; //require returns object exported by module
}
///- END REQUIRE FN
Lucio M. Tato
fuente
¡Gracias Lucio! He estado buscando una solución mínima como esta durante mucho tiempo. Lo extendí para admitir rutas relativas. Vea abajo.
Trausti Kristjansson
8

Me doy cuenta de que puede haber principiantes que busquen organizar su código. Esta es 2020 , y si usted está pensando en una aplicación modular JS, que debe empezar a trabajar con la NGP y webpack en este momento.

Aquí hay algunos pasos simples para comenzar:

  1. En la raíz de su proyecto, ejecute npm init -ypara inicializar un proyecto npm
  2. Descargue el paquete de módulos Webpack: npm install webpack webpack-cli
  3. Cree un archivo index.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>App</title>
</head>
<body>

    <script src="_bundle.js"></script>
</body>
</html>

Preste especial atención al _bundle.jsarchivo: este será un archivo JS final generado por el paquete web, no lo modificará directamente (siga leyendo).

  1. Crea un <project-root>/app.jsen el que importarás otros módulos:
const printHello = require('./print-hello');

printHello();
  1. Cree un print-hello.jsmódulo de muestra :
module.exports = function() {
    console.log('Hello World!');
}
  1. Cree <project-root>/webpack.config.jsy copie y pegue lo siguiente:
var path = require('path');

module.exports = {
  entry: './app.js',
  output: {
    path: path.resolve(__dirname),
    filename: '_bundle.js'
  }
};

En el código anterior, hay 2 puntos:

  • La entrada app.jses donde escribirás tu código JS. Importará otros módulos como se muestra arriba.
  • la salida _bundle.jses su paquete final generado por webpack. Esto es lo que verá su html al final.

-7. Abra su package.jsy reemplácelo scriptscon el siguiente comando:

  "scripts": {
    "start": "webpack --mode production -w"
  },
  1. Y, finalmente, hacer funcionar el reloj de la escritura app.jsy generar el _bundle.jsarchivo ejecutando: npm start.
  2. ¡Disfruta de la codificación!
Ilyas Assainov
fuente
1
en 2020 esto debería marcarse como la respuesta correcta
p13rnd
5
(function () {
    // c is cache, the rest are the constants
    var c = {},s="status",t="Text",e="exports",E="Error",r="require",m="module",S=" ",w=window;
    w[r]=function R(url) {
        url+=/.js$/i.test(url) ? "" : ".js";// to allow loading without js suffix;
        var X=new XMLHttpRequest(),module = { id: url, uri: url }; //according to the modules 1.1 standard
        if (!c[url])
            try {
                X.open("GET", url, 0); // sync
                X.send();
                if (X[s] && X[s] != 200) 
                    throw X[s+t];
                Function(r, e, m, X['response'+t])(R, c[url]={}, module); // Execute the module
                module[e] && (c[url]=module[e]);
            } catch (x) {
                throw w[E](E+" in "+r+": Can't load "+m+S+url+":"+S+x);
            }
        return c[url];
    }
})();

Es mejor no utilizarlo en producción debido al bloqueo. (En node.js, require () es una llamada de bloqueo bien).

Ilya Kharlamov
fuente
¿No debería ser "exportaciones: {}" una propiedad de "módulo"? y la convocatoria sea (R, module.exports, module)
Lucio M. Tato
@ LucioM.Tato No estoy seguro, no puedo ver ninguna mención de module.exports en los módulos 1.1 estándar . Siempre puede llamar a require (module.id) para obtener las exportaciones
Ilya Kharlamov
Si. Tienes razón, estaba pensando en la implementación de módulos de node.js. Agregué algunas modificaciones al código en su muy buena respuesta, para que funcione bien con Chrome Dev Tools (lo estoy usando como IDE de tiempo de depuración). Publicaré el código como otra respuesta a esta pregunta, en caso de que sea útil para otra persona.
Lucio M. Tato
1

Require-stub : proporciona compatibilidad con nodos requireen el navegador, resuelve tanto módulos como rutas relativas. Utiliza una técnica similar a TKRequire (XMLHttpRequest). El código resultante es completamente navegable, ya que require-stubpuede servir como reemplazo de watchify.

dy_
fuente
0

Aquí hay una extensión de la fantástica respuesta de Lucio M. Tato que permite la carga recursiva de módulos con rutas relativas.

Aquí hay un proyecto de github para albergar la solución y un ejemplo de cómo usarlo:

https://github.com/trausti/TKRequire.js

Para usar TKRequire.js, incluya la siguiente línea en su encabezado

<script type = "text / javascript" src = "./ TKRequire.js"> </script>

Luego cargue los módulos como en node.js:

var MyModule = require ("./ relativo / ruta / a / MyModule.js");

Trausti Kristjansson
fuente
Gracias Trausti. Si está usando javascript, debería consultar github.com/luciotato/LiteScript (beta). PD: ¿A qué nivel de CC Saga estás jugando ahora? : P
Lucio M. Tato
los enlaces están muertos, ¿cómo podríamos tomar y echar un vistazo a su código?
PA.