Skip to content
This repository has been archived by the owner on Mar 29, 2024. It is now read-only.

Question: Module support #23

Closed
aight8 opened this issue Mar 31, 2017 · 5 comments
Closed

Question: Module support #23

aight8 opened this issue Mar 31, 2017 · 5 comments

Comments

@aight8
Copy link

aight8 commented Mar 31, 2017

Hei, so much respect for this library it's awesome :)
I got it to work and wrote a wrapper arround it after lot of experiments and It was educational, will publish it soon.
I have a question regarding modules. I want to use modules on the common js way like in node.js.
I saw some hints in the php stub about modules, but I don't know how to get it to work.
Is this already working? If yes, can you give me any hints how to implement some easy usecases?
Thank you very much!

@pinepain
Copy link
Member

pinepain commented Apr 2, 2017

Hi Sebastian, thanks for writing!

It would be nice to see your wrapper as I also have a one, though, it's heavily in progress at this time and I don't publish it to github yet.

As to modules, there are two types of it from API perspective: native and source-based module. With a native one it's pretty simple: you return some value for require() call.

With source-based you may follow simple path: populate context's global object with necessary exports, require, module variable and then return modules.exports result or you can do like node.js does - wrap module source code and evaluate it with passing necessary values for exports, require, module and so on.

As for caching, it's pretty trivial, just maintain an array of normalized module name to what it exports and on consecutive calls return that value. This is how node.js does afair.

Here is very basic require() implementation I use in one of tests - tests/V8FunctionTemplate_require_implementation.phpt

@pinepain
Copy link
Member

pinepain commented Apr 7, 2017

Closing this issue for now as it not much to do at this time with it. If you feels there is something we missed here - please, drop a comment or feels free to open a new issue.

I'm also working on a library for php-v8 which also provides node-like modules functionality, however, no ETA at this time.

@pinepain pinepain closed this as completed Apr 7, 2017
@aight8
Copy link
Author

aight8 commented Apr 7, 2017

Sorry for the late response. Thanks for the explanation, I see it's little bit more work than expected. I saw the the parallel v8 extension for php that they had a hook function which get the requested module as string passed and you have to return the code/path (don't know anymore). But I thought v8 do a little bit more since it's "module" flag.

I will post the wrapper ASAP here.

@pinepain
Copy link
Member

pinepain commented Apr 7, 2017

The simplest implementation for require() is available in tests/V8FunctionTemplate_require_implementation.phpt:26:41. While php-v8 is more low-level than v8js, there is a bit more to do pass data back to v8 runtime, but basically, if you replace tests/V8FunctionTemplate_require_implementation.phpt:33:37 with calling your external function, you should get very similar behavior to v8js.

I guess something like this should do the job:

Note, this is meta-code which may need to be polished in order to work

use V8\Value;
use V8\Context;
use V8\NumberValue;
use V8\StringValue;
use V8\ObjectValue;
use V8\Script;
use V8\ScriptOrigin;
use V8\FunctionTemplate;
use V8\FunctionCallbackInfo;

interface ModuleLoaderInterface {
    public function load(string $name, Context $context) : Value;
}

class ModuleLoader implements ModuleLoaderInterface {
    public function load(string $name, Context $context): Value
    {
        $isolate = $context->GetIsolate();

        $module_is_native = 'TODO'; // figure it out

        // here you can actually evaluate your script
        if ($module_is_native) {
            // in real life you will normally build necessary v8 value and return it
            return new NumberValue($isolate, 42);
        }

        $path = $name;
        $source = 'TODO'; // you will actually get it somehow from your db, filesystem or so

        $wrapped_source =
            "(function (exports, require, module, __filename, __dirname) {\n" .
            $source . // you will actually get it somehow from your db, filesystem or so
            "\n});";

        $script = new Script($context, new StringValue($isolate, $wrapped_source), new ScriptOrigin($path, -1));

        $func = $script->Run($context);

        $exports = new ObjectValue($context);
        $module_obj = new ObjectValue($context);  // you actually have to build it properly - https://nodejs.org/api/modules.html#modules_the_module_object
        $require = $context->GlobalObject()->Get($context, new StringValue($isolate, 'require');
        $filename = new StringValue($isolate, $path);
        $dirname = new StringValue($isolate, dirname($path));

        // exports, require, module, __filename, __dirname
        $arguments = [$exports, $require, $module_obj, $filename, $dirname];

        return $func->Call($context, $func, $arguments);
    }
}

$loader = new ModuleLoader();

$require_func_tpl_cache = new FunctionTemplate($isolate, function (FunctionCallbackInfo $info) use (&$loaded_cache, &$code, $loader) {
    $context = $info->GetContext();
    $module = $info->Arguments()[0]->ToString($context)->Value();
    if (!isset($loaded_cache[$module])) {
        $loaded_cache[$module] = $loader->load($module, $context);
    }
    $info->GetReturnValue()->Set($loaded_cache[$module]);
});

or even

class CacheableModuleLoader implements ModuleLoaderInterface
{
    /**
     * @var Value[]
     */
    private $cache = [];
    /**
     * @var ModuleLoaderInterface
     */
    private $loader;

    public function __construct(ModuleLoaderInterface $loader)
    {
        $this->loader = $loader;
    }

    public function load(string $name, Context $context): Value
    {
        if (!isset($this->cache[$name])) {
            $this->cache[$name] = $this->loader->load($name, $context);
        }
        
        return $this->cache[$name];
    }
}

$loader = new CacheableModuleLoader(new ModuleLoader());

$require_func_tpl_cache = new FunctionTemplate($isolate, function (FunctionCallbackInfo $info) use (&$loaded_cache, &$code, $loader) {
    $context = $info->GetContext();
    $module = $info->Arguments()[0]->ToString($context)->Value();
    
    $info->GetReturnValue()->Set($loader->load($module, $context));
});

@pinepain pinepain reopened this Apr 7, 2017
@pinepain
Copy link
Member

Thank you for bringing up this question. I plan to release higher level wrapper for php-v8 which should include modules support, so I'm closing this issue for now.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants