phpv8 / v8js

V8 Javascript Engine for PHP — This PHP extension embeds the Google V8 Javascript Engine
http://pecl.php.net/package/v8js
MIT License
1.84k stars 200 forks source link

Support for ES Modules #456

Open joehoyle opened 3 years ago

joehoyle commented 3 years ago

I believe recent versions of V8 have native support for ESM modules (https://v8.dev/features/modules). I don't think this is bridged to V8Js via the setModuleLoader though (which perhaps just does something like provide a require function declaration to the JS runtime).

Reading https://stackoverflow.com/questions/52023157/how-would-one-enable-and-use-es6-modules-in-the-v8-javascript-engine, it looks like this is a new set of APIs on the V8 API. So, would this be possible to integrate into v8js?

chrisbckr commented 1 year ago

NodeJS uses the ".mjs" file extension, the package.json "type" field, or the --input-type flag. How this can be implemented? V8Js::executeModule()? And if after that I call V8Js::executeScript()? It's possible? Every module has their unique context. They will share the context of the scripts that are executed with V8JS::executeScript() like browsers do?

Just throwing some thoughts about how this can be implemented! 😄

redbullmarky commented 1 year ago

It feels like the only way to implement it natively would be to essentially treat the initial executed code as a module itself, so that import would work similar to require Perhaps wrapping the executed code with an internal entry script of sorts.

I managed to get something working a year or two back, albeit using babel to transpile code before executing. Just not in a native way.

redbullmarky commented 1 year ago

@chrisbckr This was part of the original test I played around with:

<?php
// requires babel standalone from here:
// https://github.com/babel/babel-standalone
$v8 = new \V8Js();

// code to transpile
$v8->sourceCode = <<<EOF
import * as stuff from './foo';

a = () => {
    print("hello there from inside an arrow function!!...");
    return "return value from a function";
}
EOF;

// actual transpiler code
$script = <<<EOF
Babel.transform(PHP.sourceCode, {presets:['es2015']}).code;
EOF;

// include babel & execute our transpiler
$v8->executeString(file_get_contents(__DIR__ . '/babel.min.js'));
$transpiled = $v8->executeString($script);

// v8js's return mechanism and 'strict' mode...V8Js's return mechanism requires 
// assigning to an undefined variable.
$transpiled = str_replace('"use strict"', '', $transpiled);

echo "TRANSPILED TO:\n\n" . $transpiled . "\n\n\n";
echo "-----------\nEXECUTING:\n\n";

// now execute the transpiled code
$v8 = new \V8Js();
$v8->setModuleLoader(function($filename) { 
   print("Loading {$filename} via ES6 module...\n"); 
});
print_r($v8->executeString($transpiled)());
chrisbckr commented 1 year ago

I will look on this to use here for now, I really liked. Today I have typescript compiler embedded, like deno do. 😅 So, why not babel? 😆

But to run es6 modules on v8 it's a little different than "normal" scripts, maybe some of the commonjs implementation can be reused but with de v8's module calls.

joehoyle commented 1 year ago

FYI I've also / mostly moved work from v8js to php-deno (https://github.com/joehoyle/php-deno) which has support for Es6 modules natively (and also have the TypeScript compiler via swc embedded)

chrisbckr commented 1 year ago

I implemented a basic compileModuleString, executeModule and executeModuleString. Lacks imports resolution. It just return the referrer module itself for testing purposes. If someone is interested, my code is here: https://github.com/chrisbckr/v8js/tree/php8-es6_modules