php-wasm
Version:
Run PHP right in the browser or anywhere else JS can run
210 lines (170 loc) • 4.16 kB
JavaScript
import { PhpWeb } from './PhpWeb.mjs';
const runPhpScriptTag = async (element) => {
const scope = {
version: '8.4'
, variant: ''
, stdin: null
, canvas: null
, stdout: null
, stderr: null
, ini: ''
, libs: []
, files: []
, imports: {}
};
if(element.hasAttribute('data-version'))
{
scope.version = element.getAttribute('data-version');
}
if(element.hasAttribute('data-variant'))
{
scope.variant = element.getAttribute('data-variant');
}
if(element.hasAttribute('data-ini'))
{
scope.ini = element.getAttribute('data-ini');
}
if(element.hasAttribute('data-stdin'))
{
scope.stdin = document.querySelector(element.getAttribute('data-stdin'));
}
if(element.hasAttribute('data-stdout'))
{
scope.stdout = document.querySelector(element.getAttribute('data-stdout'));
}
if(element.hasAttribute('data-stderr'))
{
scope.stderr = document.querySelector(element.getAttribute('data-stderr'));
}
if(element.hasAttribute('data-libs'))
{
try
{
scope.libs = JSON.parse(element.getAttribute('data-libs'));
}
catch(error)
{
console.error(error);
}
}
if(element.hasAttribute('data-files'))
{
try
{
scope.files = JSON.parse(element.getAttribute('data-files'));
}
catch(error)
{
console.error(error);
}
}
if(element.hasAttribute('data-imports'))
{
try
{
scope.imports = JSON.parse(element.getAttribute('data-imports'));
}
catch(error)
{
console.error(error);
}
}
if(element.hasAttribute('data-canvas'))
{
scope.canvas = document.querySelector(element.getAttribute('data-canvas'));
}
let stdout = '';
let stderr = '';
let ran = false;
let getCode = Promise.resolve(element.innerText);
if(element.hasAttribute('src'))
{
getCode = fetch(element.getAttribute('src')).then(response => response.text());
}
let getInput = Promise.resolve('');
if(scope.stdin)
{
getInput = Promise.resolve(scope.stdin.innerText);
if(scope.stdin.hasAttribute('src'))
{
getInput = fetch(scope.stdin.getAttribute('src')).then(response => response.text());
}
}
let getImports = Promise.resolve([]);
if(scope.imports)
{
getImports = Promise.all(Object.entries(scope.imports).map(async ([url, names]) => {
const pkg = await import(url);
if(typeof names === 'string')
{
return {[names]: pkg};
}
else if(Array.isArray(names))
{
return names.map(name => ({[name]: pkg[name]}));
}
}));
}
const [code, input, imports] = await Promise.all([getCode, getInput, getImports]);
const flatImports = Object.assign({}, ...imports.flat());
const php = new PhpWeb({
...flatImports,
version: scope.version
, variant: scope.variant
, sharedLibs: scope.libs
, ini: scope.ini
, files: scope.files
, canvas: scope.canvas
});
php.inputString(input);
const outListener = event => {
stdout += event.detail;
if(ran && scope.stdout)
{
scope.stdout.innerHTML = stdout;
}
};
const errListener = event => {
stderr += event.detail;
if(ran && scope.stderr)
{
scope.stderr.innerHTML = stderr;
}
};
php.addEventListener('output', outListener);
php.addEventListener('error', errListener);
php.addEventListener('ready', () => {
php.run(code)
.then(exitCode => exitCode && console.warn('WARNING! PHP exited with code: ' + exitCode))
.catch(error => console.error(error))
.finally(() => {
ran = true;
php.flush();
scope.stdout && (scope.stdout.innerHTML = stdout);
scope.stderr && (scope.stderr.innerHTML = stderr);
});
});
};
const phpSelector = 'script[type="text/php"]';
const runPhpTags = (doc) => {
const phpNodes = doc.querySelectorAll(phpSelector);
for(const phpNode of phpNodes)
{
runPhpScriptTag(phpNode);
}
const observer = new MutationObserver((mutations, observer) => {
for(const mutation of mutations)
{
for(const addedNode of mutation.addedNodes)
{
if(!(addedNode instanceof Element) || !addedNode.matches(phpSelector))
{
continue;
}
runPhpScriptTag(addedNode);
}
}
});
observer.observe(document.body.parentElement, {childList: true, subtree: true});
};
runPhpTags(document);