You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Unity的C# JS解释器JInt中原生实现类Node.js的require功能?

Implementing Node.js-like 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.exports object 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 exports object 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, StreamingAssets works on most platforms, but on Android you might need to use UnityWebRequest to read files instead of File.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:20:35