mirror of
https://github.com/phpv8/v8js.git
synced 2024-12-22 15:11:53 +00:00
Merge pull request #184 from stesie/cust-module-normalisation
Allow custom module path normalisation
This commit is contained in:
commit
28f061101e
13
README.md
13
README.md
@ -79,6 +79,19 @@ class V8Js
|
||||
public function setModuleLoader(callable $loader)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Provide a function or method to be used to normalise module paths. This can be any valid PHP callable.
|
||||
* This can be used in combination with setModuleLoader to influence normalisation of the module path (which
|
||||
* is normally done by V8Js itself but can be overriden this way).
|
||||
* The normaliser function will receive the base path of the current module (if any; otherwise an empty string)
|
||||
* and the literate string provided to the require method and should return an array of two strings (the new
|
||||
* module base path as well as the normalised name). Both are joined by a '/' and then passed on to the
|
||||
* module loader (unless the module was cached before).
|
||||
* @param callable $normaliser
|
||||
*/
|
||||
public function setModuleNormaliser(callable $normaliser)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Compiles and executes script in object's context with optional identifier string.
|
||||
* A time limit (milliseconds) and/or memory limit (bytes) can be provided to restrict execution. These options will throw a V8JsTimeLimitException or V8JsMemoryLimitException.
|
||||
|
31
tests/commonjs_cust_normalise_001.phpt
Normal file
31
tests/commonjs_cust_normalise_001.phpt
Normal file
@ -0,0 +1,31 @@
|
||||
--TEST--
|
||||
Test V8Js::setModuleNormaliser : Custom normalisation #001
|
||||
--SKIPIF--
|
||||
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$JS = <<< EOT
|
||||
var foo = require("./test");
|
||||
EOT;
|
||||
|
||||
$v8 = new V8Js();
|
||||
|
||||
$v8->setModuleNormaliser(function($base, $module) {
|
||||
var_dump($base, $module);
|
||||
return [ "", "test" ];
|
||||
});
|
||||
|
||||
$v8->setModuleLoader(function($module) {
|
||||
print("setModuleLoader called for ".$module."\n");
|
||||
return 'exports.bar = 23;';
|
||||
});
|
||||
|
||||
$v8->executeString($JS, 'module.js');
|
||||
?>
|
||||
===EOF===
|
||||
--EXPECT--
|
||||
string(0) ""
|
||||
string(6) "./test"
|
||||
setModuleLoader called for test
|
||||
===EOF===
|
33
tests/commonjs_cust_normalise_002.phpt
Normal file
33
tests/commonjs_cust_normalise_002.phpt
Normal file
@ -0,0 +1,33 @@
|
||||
--TEST--
|
||||
Test V8Js::setModuleNormaliser : Custom normalisation #002
|
||||
--SKIPIF--
|
||||
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$JS = <<< EOT
|
||||
var foo = require("./test");
|
||||
EOT;
|
||||
|
||||
$v8 = new V8Js();
|
||||
|
||||
// setModuleNormaliser may redirect module requirement
|
||||
// to a different path (and even rename the module)
|
||||
$v8->setModuleNormaliser(function($base, $module) {
|
||||
var_dump($base, $module);
|
||||
return [ "path/to", "test-foo" ];
|
||||
});
|
||||
|
||||
$v8->setModuleLoader(function($module) {
|
||||
print("setModuleLoader called for ".$module."\n");
|
||||
return 'exports.bar = 23;';
|
||||
});
|
||||
|
||||
$v8->executeString($JS, 'module.js');
|
||||
?>
|
||||
===EOF===
|
||||
--EXPECT--
|
||||
string(0) ""
|
||||
string(6) "./test"
|
||||
setModuleLoader called for path/to/test-foo
|
||||
===EOF===
|
41
tests/commonjs_cust_normalise_003.phpt
Normal file
41
tests/commonjs_cust_normalise_003.phpt
Normal file
@ -0,0 +1,41 @@
|
||||
--TEST--
|
||||
Test V8Js::setModuleNormaliser : Custom normalisation #003
|
||||
--SKIPIF--
|
||||
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$JS = <<< EOT
|
||||
var foo = require("./test");
|
||||
var bar = require("test");
|
||||
EOT;
|
||||
|
||||
$v8 = new V8Js();
|
||||
|
||||
// Caching is done based on the identifiers passed back
|
||||
// by the module normaliser. If it returns the same id
|
||||
// for multiple require calls, the module loader callback
|
||||
// will be called only once (as the others are cached)
|
||||
$v8->setModuleNormaliser(function($base, $module) {
|
||||
var_dump($base, $module);
|
||||
return [ "path/to", "test-foo" ];
|
||||
});
|
||||
|
||||
$v8->setModuleLoader(function($module) {
|
||||
print("setModuleLoader called for ".$module."\n");
|
||||
if($module != "path/to/test-foo") {
|
||||
throw new \Exception("module caching fails");
|
||||
}
|
||||
return 'exports.bar = 23;';
|
||||
});
|
||||
|
||||
$v8->executeString($JS, 'module.js');
|
||||
?>
|
||||
===EOF===
|
||||
--EXPECT--
|
||||
string(0) ""
|
||||
string(6) "./test"
|
||||
setModuleLoader called for path/to/test-foo
|
||||
string(0) ""
|
||||
string(4) "test"
|
||||
===EOF===
|
42
tests/commonjs_cust_normalise_004.phpt
Normal file
42
tests/commonjs_cust_normalise_004.phpt
Normal file
@ -0,0 +1,42 @@
|
||||
--TEST--
|
||||
Test V8Js::setModuleNormaliser : Custom normalisation #004
|
||||
--SKIPIF--
|
||||
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$JS = <<< EOT
|
||||
var foo = require("foo");
|
||||
EOT;
|
||||
|
||||
$v8 = new V8Js();
|
||||
|
||||
// If a module includes another module, $base must be set to the
|
||||
// path of the first module (on the second call)
|
||||
$v8->setModuleNormaliser(function($base, $module) {
|
||||
var_dump($base, $module);
|
||||
return [ "path/to", $module ];
|
||||
});
|
||||
|
||||
$v8->setModuleLoader(function($module) {
|
||||
print("setModuleLoader called for ".$module."\n");
|
||||
switch($module) {
|
||||
case "path/to/foo":
|
||||
return "require('bar');";
|
||||
|
||||
case "path/to/bar":
|
||||
return 'exports.bar = 23;';
|
||||
}
|
||||
});
|
||||
|
||||
$v8->executeString($JS, 'module.js');
|
||||
?>
|
||||
===EOF===
|
||||
--EXPECT--
|
||||
string(0) ""
|
||||
string(3) "foo"
|
||||
setModuleLoader called for path/to/foo
|
||||
string(7) "path/to"
|
||||
string(3) "bar"
|
||||
setModuleLoader called for path/to/bar
|
||||
===EOF===
|
@ -89,6 +89,10 @@ static void v8js_free_storage(void *object TSRMLS_DC) /* {{{ */
|
||||
zval_ptr_dtor(&c->pending_exception);
|
||||
}
|
||||
|
||||
if (c->module_normaliser) {
|
||||
zval_ptr_dtor(&c->module_normaliser);
|
||||
}
|
||||
|
||||
if (c->module_loader) {
|
||||
zval_ptr_dtor(&c->module_loader);
|
||||
}
|
||||
@ -362,6 +366,7 @@ static PHP_METHOD(V8Js, __construct)
|
||||
c->memory_limit = 0;
|
||||
c->memory_limit_hit = false;
|
||||
|
||||
c->module_normaliser = NULL;
|
||||
c->module_loader = NULL;
|
||||
|
||||
/* Include extensions used by this context */
|
||||
@ -687,6 +692,24 @@ static PHP_METHOD(V8Js, clearPendingException)
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ proto void V8Js::setModuleNormaliser(string base, string module_id)
|
||||
*/
|
||||
static PHP_METHOD(V8Js, setModuleNormaliser)
|
||||
{
|
||||
v8js_ctx *c;
|
||||
zval *callable;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &callable) == FAILURE) {
|
||||
return;
|
||||
}
|
||||
|
||||
c = (v8js_ctx *) zend_object_store_get_object(getThis() TSRMLS_CC);
|
||||
|
||||
c->module_normaliser = callable;
|
||||
Z_ADDREF_P(c->module_normaliser);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ proto void V8Js::setModuleLoader(string module)
|
||||
*/
|
||||
static PHP_METHOD(V8Js, setModuleLoader)
|
||||
@ -1005,6 +1028,11 @@ ZEND_END_ARG_INFO()
|
||||
ZEND_BEGIN_ARG_INFO(arginfo_v8js_clearpendingexception, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_v8js_setmodulenormaliser, 0, 0, 2)
|
||||
ZEND_ARG_INFO(0, base)
|
||||
ZEND_ARG_INFO(0, module_id)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_v8js_setmoduleloader, 0, 0, 1)
|
||||
ZEND_ARG_INFO(0, callable)
|
||||
ZEND_END_ARG_INFO()
|
||||
@ -1038,6 +1066,7 @@ static const zend_function_entry v8js_methods[] = { /* {{{ */
|
||||
PHP_ME(V8Js, checkString, arginfo_v8js_checkstring, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED)
|
||||
PHP_ME(V8Js, getPendingException, arginfo_v8js_getpendingexception, ZEND_ACC_PUBLIC)
|
||||
PHP_ME(V8Js, clearPendingException, arginfo_v8js_clearpendingexception, ZEND_ACC_PUBLIC)
|
||||
PHP_ME(V8Js, setModuleNormaliser, arginfo_v8js_setmodulenormaliser, ZEND_ACC_PUBLIC)
|
||||
PHP_ME(V8Js, setModuleLoader, arginfo_v8js_setmoduleloader, ZEND_ACC_PUBLIC)
|
||||
PHP_ME(V8Js, registerExtension, arginfo_v8js_registerextension, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
|
||||
PHP_ME(V8Js, getExtensions, arginfo_v8js_getextensions, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
|
||||
|
@ -50,6 +50,7 @@ struct v8js_ctx {
|
||||
v8js_tmpl_t global_template;
|
||||
v8js_tmpl_t array_tmpl;
|
||||
|
||||
zval *module_normaliser;
|
||||
zval *module_loader;
|
||||
std::vector<char *> modules_stack;
|
||||
std::vector<char *> modules_base;
|
||||
|
115
v8js_methods.cc
115
v8js_methods.cc
@ -8,6 +8,7 @@
|
||||
+----------------------------------------------------------------------+
|
||||
| Author: Jani Taskinen <jani.taskinen@iki.fi> |
|
||||
| Author: Patrick Reilly <preilly@php.net> |
|
||||
| Author: Stefan Siegl <stesie@brokenpipe.de> |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
@ -207,12 +208,106 @@ V8JS_METHOD(require)
|
||||
}
|
||||
|
||||
v8::String::Utf8Value module_id_v8(info[0]);
|
||||
|
||||
const char *module_id = ToCString(module_id_v8);
|
||||
char *normalised_path = (char *)emalloc(PATH_MAX);
|
||||
char *module_name = (char *)emalloc(PATH_MAX);
|
||||
char *normalised_path, *module_name;
|
||||
|
||||
v8js_commonjs_normalise_identifier(c->modules_base.back(), module_id, normalised_path, module_name);
|
||||
if (c->module_normaliser == NULL) {
|
||||
// No custom normalisation routine registered, use internal one
|
||||
normalised_path = (char *)emalloc(PATH_MAX);
|
||||
module_name = (char *)emalloc(PATH_MAX);
|
||||
|
||||
v8js_commonjs_normalise_identifier(c->modules_base.back(), module_id, normalised_path, module_name);
|
||||
}
|
||||
else {
|
||||
// Call custom normaliser
|
||||
int call_result;
|
||||
zval *z_base, *z_module_id, *normaliser_result;
|
||||
|
||||
MAKE_STD_ZVAL(z_base);
|
||||
MAKE_STD_ZVAL(z_module_id);
|
||||
|
||||
zend_try {
|
||||
{
|
||||
isolate->Exit();
|
||||
v8::Unlocker unlocker(isolate);
|
||||
|
||||
ZVAL_STRING(z_base, c->modules_base.back(), 1);
|
||||
ZVAL_STRING(z_module_id, module_id, 1);
|
||||
|
||||
zval **params[2] = {&z_base, &z_module_id};
|
||||
call_result = call_user_function_ex(EG(function_table), NULL, c->module_normaliser,
|
||||
&normaliser_result, 2, params, 0, NULL TSRMLS_CC);
|
||||
}
|
||||
|
||||
isolate->Enter();
|
||||
|
||||
if (call_result == FAILURE) {
|
||||
info.GetReturnValue().Set(isolate->ThrowException(V8JS_SYM("Module normaliser callback failed")));
|
||||
}
|
||||
}
|
||||
zend_catch {
|
||||
v8js_terminate_execution(isolate);
|
||||
V8JSG(fatal_error_abort) = 1;
|
||||
call_result = FAILURE;
|
||||
}
|
||||
zend_end_try();
|
||||
|
||||
zval_ptr_dtor(&z_base);
|
||||
zval_ptr_dtor(&z_module_id);
|
||||
|
||||
if(call_result == FAILURE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if an exception was thrown
|
||||
if (EG(exception)) {
|
||||
// Clear the PHP exception and throw it in V8 instead
|
||||
zend_clear_exception(TSRMLS_C);
|
||||
info.GetReturnValue().Set(isolate->ThrowException(V8JS_SYM("Module normaliser callback exception")));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Z_TYPE_P(normaliser_result) != IS_ARRAY) {
|
||||
zval_ptr_dtor(&normaliser_result);
|
||||
info.GetReturnValue().Set(isolate->ThrowException(V8JS_SYM("Module normaliser didn't return an array")));
|
||||
return;
|
||||
}
|
||||
|
||||
HashTable *ht = HASH_OF(normaliser_result);
|
||||
int num_elements = zend_hash_num_elements(ht);
|
||||
|
||||
if(num_elements != 2) {
|
||||
zval_ptr_dtor(&normaliser_result);
|
||||
info.GetReturnValue().Set(isolate->ThrowException(V8JS_SYM("Module normaliser expected to return array of 2 strings")));
|
||||
return;
|
||||
}
|
||||
|
||||
zval **data;
|
||||
ulong index = 0;
|
||||
HashPosition pos;
|
||||
|
||||
for (zend_hash_internal_pointer_reset_ex(ht, &pos);
|
||||
SUCCESS == zend_hash_get_current_data_ex(ht, (void **) &data, &pos);
|
||||
zend_hash_move_forward_ex(ht, &pos)
|
||||
) {
|
||||
|
||||
if (Z_TYPE_P(*data) != IS_STRING) {
|
||||
convert_to_string(*data);
|
||||
}
|
||||
|
||||
switch(index++) {
|
||||
case 0: // normalised path
|
||||
normalised_path = estrndup(Z_STRVAL_PP(data), Z_STRLEN_PP(data));
|
||||
break;
|
||||
|
||||
case 1: // normalised module id
|
||||
module_name = estrndup(Z_STRVAL_PP(data), Z_STRLEN_PP(data));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
zval_ptr_dtor(&normaliser_result);
|
||||
}
|
||||
|
||||
char *normalised_module_id = (char *)emalloc(strlen(normalised_path)+1+strlen(module_name)+1);
|
||||
*normalised_module_id = 0;
|
||||
@ -305,16 +400,6 @@ V8JS_METHOD(require)
|
||||
convert_to_string(module_code);
|
||||
}
|
||||
|
||||
// Check that some code has been returned
|
||||
if (Z_STRLEN_P(module_code)==0) {
|
||||
zval_ptr_dtor(&module_code);
|
||||
efree(normalised_module_id);
|
||||
efree(normalised_path);
|
||||
|
||||
info.GetReturnValue().Set(isolate->ThrowException(V8JS_SYM("Module loader callback did not return code")));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a template for the global object and set the built-in global functions
|
||||
v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
|
||||
global->Set(V8JS_SYM("print"), v8::FunctionTemplate::New(isolate, V8JS_MN(print)), v8::ReadOnly);
|
||||
@ -347,7 +432,7 @@ V8JS_METHOD(require)
|
||||
// Enter the module context
|
||||
v8::Context::Scope scope(context);
|
||||
// Set script identifier
|
||||
v8::Local<v8::String> sname = V8JS_SYM(normalised_module_id);
|
||||
v8::Local<v8::String> sname = V8JS_STR(normalised_module_id);
|
||||
|
||||
v8::Local<v8::String> source = V8JS_STRL(Z_STRVAL_P(module_code), Z_STRLEN_P(module_code));
|
||||
zval_ptr_dtor(&module_code);
|
||||
|
Loading…
Reference in New Issue
Block a user