javascript - Hur kontrollerar man om ett skript körs under Node.js?
Jag har ett skript som jag kräver från ett Node.js-skript, vilket jag vill hålla oberoende av JavaScript-motorn.
Till exempel vill jag göra exports.x=y; endast om det körs under Node.js. Hur kan jag utföra detta test?
När jag ställde den här frågan visste jag inte att Node.js-modulfunktionen är baserad på CommonJS.
För det specifika exemplet jag gav, skulle en mer exakt fråga ha varit:
Hur kan ett skript avgöra om det har krävts som en CommonJS-modul?
Svar #1
Det finns egentligen inget pålitligt sätt att upptäcka om man kör i Node.js eftersom varje webbplats lätt skulle kunna deklarera samma variabler. Men eftersom det som standard inte finns något window-objekt i Node.js kan man istället gå åt andra hållet och kontrollera om man körs i en webbläsare.
Detta är vad jag använder för bibliotek som bör fungera både i en webbläsare och under Node.js:
if (typeof window === 'undefined') {
exports.foo = {};
} else {
window.foo = {};
}
Det kan fortfarande explodera om window är definierad i Node.js, men det finns ingen bra anledning för någon att göra detta, eftersom du uttryckligen skulle behöva utelämna var eller sätta egenskapen på global-objektet.
REDIGERA
För att upptäcka om ditt skript har blivit krävt som en CommonJS-modul, är det återigen inte enkelt. Det enda som CommonJS specificerar är att A: Modulerna kommer att inkluderas via ett anrop till funktionen require och B: Modulerna exporterar saker via egenskaper på exports-objektet. Hur detta implementeras lämnas nu till det underliggande systemet. Node.js kapslar in modulens innehåll i en anonym funktion:
function (exports, require, module, __filename, __dirname) {
Se: https://github.com/ry/node/blob/master/src/node.js#L325
Men försök inte att upptäcka det via någon galen arguments.callee.toString()-grej, använd istället bara mitt exempel ovan som kontrollerar webbläsaren. Node.js är en mycket renare miljö så det är osannolikt att window kommer att deklareras där.
Svar #2
Genom att leta efter CommonJS-stöd, är det så här Underscore.js-biblioteket gör:
Redigera: till din uppdaterade fråga:
(function () {
// Establish the root object, `window` in the browser, or `global` on the server.
var root = this;
// Create a reference to this
var _ = new Object();
var isNode = false;
// Export the Underscore object for **CommonJS**, with backwards-compatibility
// for the old `require()` API. If we're not in CommonJS, add `_` to the
// global object.
if (typeof module !== 'undefined' && module.exports) {
module.exports = _;
root._ = _;
isNode = true;
} else {
root._ = _;
}
})();
Exemplet här behåller modulmönstret.
Svar #3
Jag stötte nyligen på en felaktig detektion av Node som inte är medveten om Node-miljön i Electron på grund av en vilseledande funktionsdetektion. Följande lösningar identifierar processmiljön explicit.
Identifiera endast Node.js
(typeof process !== 'undefined') && (process.release.name === 'node')
Detta kommer att upptäcka om du kör i en Node-process, eftersom process.release innehåller "metadata relaterad till den nuvarande [Node-]versionen".
Efter uppkomsten av io.js (som avbröts för länge sedan) kan värdet av process.release.name också ha blivit io.js.
Bun siktar på full kompatibilitet med Node.js och efterliknar därför Node-beteende.
För att korrekt upptäcka en Node-redo miljö antar jag att du bör kontrollera enligt följande:
Identifiera Node (>= 3.0.0), Bun eller io.js
(typeof process !== 'undefined') &&
(process.release.name.search(/node|io.js/) !== -1)
Detta uttalande testades (vid tidpunkten för det ursprungliga inlägget) med Node 5.5.0, Electron 0.36.9 (med Node 5.1.1) och Chrome 48.0.2564.116.
Identifiera Node (>= 0.10.0), Bun eller io.js
(typeof process !== 'undefined') &&
(typeof process.versions.node !== 'undefined')
@dalueges kommentar fick mig att tänka på ett mer allmänt bevis. Detta bör fungera från Node.js >= 0.10. Jag hittade ingen unik identifierare för tidigare versioner.
Identifiera uttryckligen Bun (>= 1.0.3)
(typeof process !== 'undefined') && process.versions.bun)
Bun lade nyligen till stöd för processobjektet, denna detektionsmetod är hämtad från Buns guide för att upptäcka Bun.
Identifiera uttryckligen Bun (< 1.0.3)
(typeof process !== 'undefined') && process.isBun)
P.s.: Jag postar det svaret här eftersom frågan ledde mig hit, även om frågeställaren sökte ett svar på en annan fråga.
Svar #4
Problemet med att försöka lista ut vilken miljö din kod körs i är att vilket objekt som helst kan ändras och deklareras, vilket gör det nästan omöjligt att ta reda på vilka objekt som är ursprungliga i miljön och vilka som har ändrats av programmet.
Men det finns några knep vi kan använda för att ta reda på säkert vilken miljö du befinner dig i.
Låt oss börja med den allmänt accepterade lösningen som används i underscore-biblioteket:
typeof module !== 'undefined' && module.exportsDenna teknik är faktiskt helt okej på serversidan, eftersom när funktionen require anropas återställs this-objektet till ett tomt objekt och module definieras om för dig igen, vilket innebär att du inte behöver oroa dig för någon extern manipulering. Så länge din kod laddas in med require är du säker.
Men detta faller isär i webbläsaren, eftersom vem som helst enkelt kan definiera module för att få det att verka som om det är det objekt du letar efter. Å ena sidan kan detta vara det beteende du vill ha, men det bestämmer också vilka variabler biblioteksanvändaren kan använda i det globala området. Kanske vill någon använda en variabel med namnet module som har exports i sig för ett annat ändamål. Det är osannolikt, men vem är vi att döma vilka variabler någon annan kan använda, bara för att en annan miljö använder det variabelnamnet?
Tricket, däremot, är att om vi antar att ditt skript laddas i den globala scopet (vilket det kommer att göra om det laddas via en script-tagg) kan en variabel inte reserveras i en yttre closure, eftersom webbläsaren inte tillåter det. Kom ihåg att i Node är this-objektet ett tomt objekt, ändå är module-variabeln fortfarande tillgänglig. Det beror på att den är deklarerad i en yttre closure. Så vi kan då fixa Underscores kontroll genom att lägga till en extra kontroll:
this.module !== moduleMed detta, om någon deklarerar module i det globala området i webbläsaren, kommer det att placeras i this-objektet, vilket kommer att få testet att misslyckas, eftersom this.module kommer att vara samma objekt som module. I Node finns inte this.module, och module finns inom en yttre sluten funktion, så testet kommer att lyckas, eftersom de inte är ekvivalenta.
Således är det slutliga testet:
typeof module !== 'undefined' && this.module !== moduleObservera: Även om detta nu tillåter att module-variabeln används fritt i den globala scopet, är det fortfarande möjligt att kringgå detta i webbläsaren genom att skapa en ny closure och deklarera module inom den, och sedan ladda skriptet inom den closuren. Vid den punkten replikerar användaren helt node-miljön och förhoppningsvis vet vad de gör och försöker göra en node-stil require. Om koden anropas i en script-tagg kommer den fortfarande att vara säker från eventuella nya yttre closurer.
Svar #5
Följande fungerar i webbläsaren om det inte avsiktligt, uttryckligen saboteras:
if(typeof process === 'object' && process + '' === '[object process]'){
// is node
}
else{
// not node
}
Bam.
Svar #6
Här är ett ganska coolt sätt att göra det på också:
const isBrowser = this.window === this;
Detta fungerar eftersom den globala variabeln 'this' i webbläsare har en självreferens som kallas 'window'. Denna självreferens finns inte i Node.
- I webbläsaren är 'this' en referens till det globala objektet, kallat 'window'.
I Node är 'this' en referens till module.exports-objektet.
- 'this' är inte en referens till Node:s globala objekt, som kallas 'global'.
- 'det här' är inte en referens till modulvariabelns deklarationsutrymme.
För att bryta den ovan föreslagna webbläsarkontrollen skulle du behöva göra något liknande följande
this.window = this;
innan kontrollen utförs.
Svar #7
Ytterligare en miljödetektering:
(Betydelse: de flesta svar här är okej.)
function isNode() {
return typeof global === 'object'
&& String(global) === '[object global]'
&& typeof process === 'object'
&& String(process) === '[object process]'
&& global === global.GLOBAL // circular ref
// process.release.name cannot be altered, unlike process.title
&& /node|io\.js/.test(process.release.name)
&& typeof setImmediate === 'function'
&& setImmediate.length === 4
&& typeof __dirname === 'string'
&& Should I go on ?..
}
Lite paranoid, va? Du kan göra detta mer utförligt genom att kontrollera fler globala variabler.
Men GÖR INTE DET!.
Allt detta ovan kan ändå förfalskas/simuleras.
Till exempel för att fejka global-objektet:
global = {
toString: function () {
return '[object global]';
},
GLOBAL: global,
setImmediate: function (a, b, c, d) {}
};
setImmediate = function (a, b, c, d) {};
...
Detta kommer inte att fästas vid Nodes ursprungliga globala objekt men det kommer att fästas vid window-objektet i en webbläsare. Så det kommer att antyda att du är i Node-miljön inuti en webbläsare.
Livet är kort!
Bryr vi oss om vårt miljö är fejkad? Det skulle hända när någon dum utvecklare deklarerar en global variabel kallad global i den globala scope. Eller när någon ond utvecklare på något sätt injicerar kod i vår miljö.
Vi kan förhindra att vår kod körs när vi fångar detta, men många andra beroenden i vår app kan fastna i detta. Så småningom kommer koden att gå sönder. Om din kod är tillräckligt bra bör du inte bry dig om varje liten dum misstag som kan ha gjorts av andra.
Så vadå?
Om du riktar in dig på 2 miljöer: Browser och Node;
"use strict"; och kontrollera antingen helt enkelt för window eller global; och ange tydligt i dokumentationen att din kod endast stöder dessa miljöer. Det är allt!
var isBrowser = typeof window !== 'undefined'
&& ({}).toString.call(window) === '[object Window]';
var isNode = typeof global !== "undefined"
&& ({}).toString.call(global) === '[object global]';
Om det är möjligt för ditt användningsfall; istället för miljödetektion; gör synkron funktionsdetektion inom en try/catch-block. (dessa kommer ta några millisekunder att köra).
t.ex.
function isPromiseSupported() {
var supported = false;
try {
var p = new Promise(function (res, rej) {});
supported = true;
} catch (e) {}
return supported;
}
Svar #8
En enradare kan användas i moderna JavaScript-runtime.
const runtime = globalThis.process?.release?.name || 'not node'
Värdet runtime kommer att vara antingen node eller not node.
Detta bygger på några nyare JavaScript-funktioner. globalThis fastställdes i ECMAScript 2020-specifikationen. Valfri kedjning/nullish-sammanslagning (delen ? i globalThis.process?.release?.name) stöds i V8-motorn från version 8.x+ (som levererades i Chrome 80 & Node 14, släppt den 21/4/2020).
