如何在Unity的C# JS解释器JInt中原生实现类Node.js的require功能?
require() in JInt for Unity Absolutely, you can build a require()-style module loader for JInt in Unity—and this is a totally reasonable approach! When you’ve got an unknown number of scripts spread across folders and subfolders, manually concatenating files is a nightmare, so a dynamic loader makes perfect sense. Let’s break down how to set this up.
How It Works
JInt doesn’t come with a built-in module system, but we can extend its host environment to add our own require() function. The core ideas are:
- Resolve relative/absolute file paths correctly against your entry script (
main.js) - Cache loaded modules to avoid re-executing the same file multiple times
- Isolate module scopes so variables don’t leak between scripts
- Expose a
module.exportsobject for scripts to share functionality
Step-by-Step Implementation
1. Set Up Module Cache & Path Resolution
First, we’ll need a way to track loaded modules and resolve file paths. Create a helper class (or extend your existing JInt manager) with these tools:
using System; using System.Collections.Generic; using System.IO; using Jint; using Jint.Native; using Jint.Native.Object; public class JintModuleLoader { private readonly Engine _engine; private readonly Dictionary<string, JsValue> _moduleCache = new Dictionary<string, JsValue>(StringComparer.OrdinalIgnoreCase); private readonly string _rootPath; // Path to your JS files (e.g., Application.streamingAssetsPath + "/Scripts") public JintModuleLoader(string rootScriptPath) { _rootPath = rootScriptPath; _engine = new Engine(options => { // Register our custom require function globally options.SetValue("require", new JsValue(Require)); }); } // The custom require function private JsValue Require(JsValue thisObj, JsValue[] args) { if (args.Length == 0) { throw new ArgumentException("require() expects a file path argument"); } string requestedPath = args[0].AsString(); string fullPath = ResolveFilePath(requestedPath); // Check cache first if (_moduleCache.TryGetValue(fullPath, out var cachedExports)) { return cachedExports; } // Handle circular references: add empty exports to cache before executing var moduleObj = _engine.Object.Construct(); var exportsObj = _engine.Object.Construct(); moduleObj.Set("exports", exportsObj, PropertyFlag.Writable); _moduleCache[fullPath] = exportsObj; try { // Read the script file string scriptContent = File.ReadAllText(fullPath); // Execute the script in a context with module/exports _engine.Execute($@" (function(module, exports) {{ {scriptContent} }})({moduleObj}, {exportsObj}); "); // Update cache with the final exports (in case the script replaced module.exports) var finalExports = moduleObj.Get("exports"); _moduleCache[fullPath] = finalExports; return finalExports; } catch (FileNotFoundException) { throw new Exception($"require(): File not found: {fullPath}"); } catch (Exception ex) { throw new Exception($"Error executing module {fullPath}: {ex.Message}"); } } // Resolve relative paths to absolute paths private string ResolveFilePath(string requestedPath) { // Add .js extension if missing if (!Path.HasExtension(requestedPath)) { requestedPath += ".js"; } // If it's a relative path, resolve against the root path if (!Path.IsPathRooted(requestedPath)) { requestedPath = Path.Combine(_rootPath, requestedPath); } // Normalize the path to handle ../ and ./ return Path.GetFullPath(requestedPath); } // Load your entry point (main.js) public void LoadMainScript() { string mainPath = Path.Combine(_rootPath, "main.js"); if (!File.Exists(mainPath)) { throw new FileNotFoundException("main.js not found at root path", mainPath); } _engine.Execute(File.ReadAllText(mainPath)); } }
2. Integrate with Unity
In your Unity script, initialize the loader and start with main.js:
using UnityEngine; public class JintRunner : MonoBehaviour { void Start() { // Set the root path to your JS files (adjust this to your project structure) string jsRootPath = Path.Combine(Application.streamingAssetsPath, "JSScripts"); try { var loader = new JintModuleLoader(jsRootPath); loader.LoadMainScript(); Debug.Log("JInt modules loaded successfully!"); } catch (Exception ex) { Debug.LogError($"JInt error: {ex.Message}"); } } }
3. Use require() in Your JS Scripts
Now you can write JS files just like in Node.js. For example:
utils.js
exports.add = function(a, b) { return a + b; };
main.js
const utils = require('./utils'); console.log(utils.add(2, 3)); // Outputs 5 in Unity's console (if you've set up JInt to log to Debug)
Key Considerations
- Circular References: The code above handles basic circular references by adding an empty
exportsobject to the cache before executing the module. This matches Node.js’s behavior, so scripts can reference each other without breaking. - Path Handling: Make sure your root path is correct for all Unity platforms. For example,
StreamingAssetsworks on most platforms, but on Android you might need to useUnityWebRequestto read files instead ofFile.ReadAllText—adjust the file reading logic if needed. - Error Handling: The example includes basic error handling, but you can expand it to log more detailed stack traces or handle syntax errors from JInt.
- Performance: Caching modules ensures you only load each file once, which is efficient even with many scripts.
Is This a Reasonable Approach?
Absolutely! This is a common pattern when embedding JS engines in applications. It lets you organize your code into modular files, reuse logic, and avoid the headache of manually managing script order. It’s especially perfect for your use case where you don’t know the exact number or structure of files in advance.
内容的提问来源于stack exchange,提问作者Epsiloncool

