Skip to content

Commit c72dd7d

Browse files
authored
feat: nodejs bakend support esm (#268)
1 parent 8766e88 commit c72dd7d

File tree

3 files changed

+87
-23
lines changed

3 files changed

+87
-23
lines changed

src/legacy/main/NodeJsHelper.cpp

+78-20
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
#include <filesystem>
12
#pragma warning(disable : 4251)
23

3-
#include "main/NodeJsHelper.h"
4-
54
#include "api/EventAPI.h"
65
#include "engine/EngineManager.h"
76
#include "engine/EngineOwnData.h"
87
#include "engine/RemoteCall.h"
8+
#include "fmt/format.h"
99
#include "ll/api/chrono/GameChrono.h"
1010
#include "ll/api/coro/CoroTask.h"
1111
#include "ll/api/io/FileUtils.h"
@@ -14,6 +14,7 @@
1414
#include "ll/api/thread/ServerThreadExecutor.h"
1515
#include "ll/api/utils/StringUtils.h"
1616
#include "main/Global.h"
17+
#include "main/NodeJsHelper.h"
1718
#include "uv/uv.h"
1819
#include "v8/v8.h"
1920

@@ -130,14 +131,17 @@ script::ScriptEngine* newEngine() {
130131
return engine;
131132
}
132133

133-
bool loadPluginCode(script::ScriptEngine* engine, std::string entryScriptPath, std::string pluginDirPath) {
134-
auto mainScripts = ll::file_utils::readFile(ll::string_utils::str2u8str(entryScriptPath));
135-
if (!mainScripts) {
136-
return false;
137-
}
138-
134+
bool loadPluginCode(script::ScriptEngine* engine, std::string entryScriptPath, std::string pluginDirPath, bool esm) {
139135
// Process requireDir
140136
if (!pluginDirPath.ends_with('/')) pluginDirPath += "/";
137+
138+
// check if entryScriptPath is not absolute path
139+
if (auto path = std::filesystem::path(entryScriptPath); !path.is_absolute()) {
140+
entryScriptPath = std::filesystem::absolute(path).string();
141+
}
142+
if (auto path = std::filesystem::path(pluginDirPath); !path.is_absolute()) {
143+
pluginDirPath = std::filesystem::absolute(path).string();
144+
}
141145
pluginDirPath = ll::string_utils::replaceAll(pluginDirPath, "\\", "/");
142146
entryScriptPath = ll::string_utils::replaceAll(entryScriptPath, "\\", "/");
143147

@@ -151,23 +155,58 @@ bool loadPluginCode(script::ScriptEngine* engine, std::string entryScriptPath, s
151155
using namespace v8;
152156
EngineScope enter(engine);
153157

154-
string executeJs = "const __LLSE_PublicRequire = "
155-
"require('module').createRequire(process.cwd() + '/"
156-
+ pluginDirPath + "');"
157-
+ "const __LLSE_PublicModule = require('module'); "
158-
"__LLSE_PublicModule.exports = {};"
159-
+ "ll.export = ll.exports; ll.import = ll.imports; "
160-
161-
+ "(function (exports, require, module, __filename, __dirname) { " + mainScripts.value()
162-
+ "\n})({}, __LLSE_PublicRequire, __LLSE_PublicModule, '" + entryScriptPath + "', '"
163-
+ pluginDirPath + "'); "; // TODO __filename & __dirname need to be reviewed
164-
// TODO: ESM Support
158+
string compiler;
159+
if (esm) {
160+
compiler = fmt::format(
161+
R"(
162+
import('url').then(url => {{
163+
const moduleUrl = url.pathToFileURL('{1}').href;
164+
import(moduleUrl).catch(error => {{
165+
console.error('Failed to load ESM module:', error);
166+
process.exit(1);
167+
}});
168+
}}).catch(error => {{
169+
console.error('Failed to import url module:', error);
170+
process.exit(1);
171+
}});
172+
)",
173+
pluginDirPath,
174+
entryScriptPath
175+
);
176+
} else {
177+
compiler = fmt::format(
178+
R"(
179+
const __Path = require("path");
180+
const __PluginPath = __Path.join("{0}");
181+
const __PluginNodeModulesPath = __Path.join(__PluginPath, "node_modules");
182+
183+
__dirname = __PluginPath;
184+
__filename = "{1}";
185+
(function ReplaeRequire() {{
186+
const PublicModule = require('module').Module;
187+
const OriginalResolveLookupPaths = PublicModule._resolveLookupPaths;
188+
PublicModule._resolveLookupPaths = function (request, parent) {{
189+
let result = OriginalResolveLookupPaths.call(this, request, parent);
190+
if (Array.isArray(result)) {{
191+
result.push(__PluginNodeModulesPath);
192+
result.push(__PluginPath);
193+
}}
194+
return result;
195+
}};
196+
require = PublicModule.createRequire(__PluginPath);
197+
}})();
198+
require("{1}");
199+
)",
200+
pluginDirPath,
201+
entryScriptPath
202+
);
203+
}
165204

166205
// Set exit handler
167206
node::SetProcessExitHandler(env, [](node::Environment* env_, int exit_code) { stopEngine(getEngine(env_)); });
168207

169208
// Load code
170-
MaybeLocal<v8::Value> loadenv_ret = node::LoadEnvironment(env, executeJs.c_str());
209+
MaybeLocal<v8::Value> loadenv_ret = node::LoadEnvironment(env, compiler.c_str());
171210
if (loadenv_ret.IsEmpty()) // There has been a JS exception.
172211
{
173212
node::Stop(env);
@@ -311,6 +350,25 @@ bool doesPluginPackHasDependency(const std::string& dirPath) {
311350
}
312351
}
313352

353+
bool isESModulesSystem(const std::string& dirPath) {
354+
auto dirPath_obj = std::filesystem::path(dirPath);
355+
356+
std::filesystem::path packageFilePath = dirPath_obj / std::filesystem::path("package.json");
357+
if (!std::filesystem::exists(packageFilePath)) return false;
358+
359+
try {
360+
std::ifstream file(ll::string_utils::u8str2str(packageFilePath.make_preferred().u8string()));
361+
nlohmann::json j;
362+
file >> j;
363+
if (j.contains("type") && j["type"] == "module") {
364+
return true;
365+
}
366+
return false;
367+
} catch (...) {
368+
return false;
369+
}
370+
}
371+
314372
bool processConsoleNpmCmd(const std::string& cmd) {
315373
#ifdef LEGACY_SCRIPT_ENGINE_BACKEND_NODEJS
316374
if (cmd.starts_with("npm ")) {

src/legacy/main/NodeJsHelper.h

+7-2
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@ bool stopEngine(script::ScriptEngine* engine);
1717
bool stopEngine(node::Environment* env);
1818
script::ScriptEngine* getEngine(node::Environment* env);
1919

20-
bool loadPluginCode(script::ScriptEngine* engine, std::string entryScriptPath,
21-
std::string pluginDirPath); // raw
20+
bool loadPluginCode(
21+
script::ScriptEngine* engine,
22+
std::string entryScriptPath,
23+
std::string pluginDirPath,
24+
bool esm = false
25+
); // raw
2226

2327
std::string findEntryScript(const std::string& dirPath);
2428
std::string getPluginPackageName(const std::string& dirPath);
2529
bool doesPluginPackHasDependency(const std::string& dirPath);
30+
bool isESModulesSystem(const std::string& dirPath);
2631

2732
bool processConsoleNpmCmd(const std::string& cmd);
2833
int executeNpmCommand(std::string cmd, std::string workingDir = LLSE_NPM_EXECUTE_PATH);

src/lse/PluginManager.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,8 @@ ll::Expected<> PluginManager::load(ll::mod::Manifest manifest) {
189189
if (!NodeJsHelper::loadPluginCode(
190190
scriptEngine,
191191
ll::string_utils::u8str2str(entryPath.u8string()),
192-
ll::string_utils::u8str2str(dirPath.u8string())
192+
ll::string_utils::u8str2str(dirPath.u8string()),
193+
NodeJsHelper::isESModulesSystem(ll::string_utils::u8str2str(dirPath.u8string()))
193194
)) {
194195
return ll::makeStringError("Failed to load plugin code"_tr());
195196
}

0 commit comments

Comments
 (0)