Pregunta para expertos en Node.js y v8.
Estoy desarrollando una nueva versión de la herramienta de prueba Siesta .
De forma predeterminada, Siesta ejecuta todas las pruebas en el proceso Node.js recién creado. Sin embargo, me gustaría evitar la sobrecarga de generar un nuevo proceso y, en su lugar, brindar la capacidad de ejecutar la prueba en el contexto de JavaScript vacío.
Dicho contexto se puede crear con el módulo vm incorporado. Sin embargo, el contexto creado de esta manera es un contexto de JavaScript vacío, no un contexto de Node.js vacío. Por ejemplo, no tiene un process de variable global:
> require('vm').runInNewContext('process') evalmachine.<anonymous>:1 process ^ Uncaught ReferenceError: process is not defined at evalmachine.<anonymous>:1:1 at Script.runInContext (vm.js:143:18) at Script.runInNewContext (vm.js:148:17) at Object.runInNewContext (vm.js:303:38) at REPL30:1:15 at Script.runInThisContext (vm.js:133:18) at REPLServer.defaultEval (repl.js:484:29) at bound (domain.js:413:15) at REPLServer.runBound [as eval] (domain.js:424:12) at REPLServer.onLine (repl.js:817:10) > Entonces, la pregunta es: ¿cuál es la mejor manera de crear un contexto Node.js nuevo y vacío dentro del mismo proceso? Espero que dicho contexto tenga todos los globales regulares, como process , require , etc. Además, espero que dicho contexto tenga un caché de módulos separado e inicialmente vacío, de modo que incluso si algún módulo se carga en el contexto principal, se cargará de nuevo en el nuevo contexto.
Por supuesto, podría mapear los globales del contexto principal al nuevo contexto, pero eso significaría que esos globales se comparten entre contextos, y mi objetivo es el aislamiento del contexto. Además, la memoria caché de los módulos también se compartirá.
Creo que la diferencia entre JavaScript y el contexto de Node.js es que este último se inicializa con un determinado script. ¿Es posible obtener las fuentes de ese script de alguna manera y ejecutarlo en el nuevo contexto?
¡Gracias!
Una opción que tiene es usar la API fork() 'child_process' , que hace girar un nuevo proceso de Nodo con su propia memoria e instancia V8. Sin embargo, esto podría ser igual o muy similar (al menos en teoría) a lo que está haciendo en este momento.
Tenga en cuenta que los procesos secundarios de Node.js generados son independientes del principal, con la excepción del canal de comunicación IPC que se establece entre los dos. Cada proceso tiene su propia memoria, con sus propias instancias V8. Debido a las asignaciones de recursos adicionales requeridas, no se recomienda generar una gran cantidad de procesos secundarios de Node.js.
Tenga en cuenta que esta bifurcación no es una clonación de todo el proceso en ejecución:
A diferencia de la llamada al sistema POSIX fork(2), child_process.fork() no clona el proceso actual.
Otra opción que parece prometedora es usar la API Node async_hooks para emular ejecuciones en contextos separados. Consulte node-execution-context , puede funcionar para sus requisitos.
Esto es lo que sucede cuando NodeJS carga un nuevo módulo :
(function(exports, require, module, __filename, __dirname) { // Module code actually lives in here });Usan lo que llaman un contenedor de módulos
Entonces tendrías que hacer algo similar usando VM
require('vm').runInNewContext('the code you are running', { module: // your empty module or a wrapper exports: // a reference to the module.exports require: // your empty require or a wapper __filename: // your __filename __dirname: // your __dirname process }) Tuve éxito en la creación de nuevas funciones require vacías válidas después de depurar las partes internas de la clase/función del módulo
Si crea estos 2 archivos siguientes y ejecuta el archivo isolated.js, verá que, de hecho, se está recargando cada vez y, al mismo tiempo, el caché original permanece intacto (aunque creo que las ejecuciones asíncronas podrían tener resultados inesperados)
// isolated.js const Module = require('module') const vm = require('vm') const path = require('path') // this is just to show it wont load it again const print = require('./print') print('main file') const getNewContext = () => { const mod = new Module() const filename = path.join(__dirname, `test-filename-${Math.random().toString().substr(-6)}.js`) const req = Module.createRequire(filename) req.cache = Object.create(null) return { module: mod, // your empty module or a wrapper exports: mod.exports, // a reference to the module.exports require: req, // your empty require or a wapper // require: mod.require, // your empty require or a wapper __filename: filename, __dirname: path.dirname(filename), console, process } } // print('runningInNewContext') const jsCode = ` // What? const log = console.log.bind(null, Date.now()) log({from:'print', cache: require.cache}) const print = require('./print.js') log(print.toString()) print('evaluated code') log({from:'print', cache: require.cache}) ` function customRunInNewContext (jsCode, context) { const { _cache } = Module Module._cache = Object.create(null) vm.runInNewContext(jsCode, context) Module._cache = _cache } customRunInNewContext(jsCode, getNewContext()) customRunInNewContext(jsCode, getNewContext()) // print.js const log = console.log.bind(null, 'PRINT loaded at', new Date().toISOString(), module.parent.filename) process.stdout.write('\n ====> loading print module\n\n') module.exports = function print (...args) { log(...args) }cuando corres
node ./isolated.js Debería ver el mensaje ====> loading print module varias veces