Skip to content

Run examples with php wasm - #1097

Merged
derickr merged 6 commits into
php:masterfrom
soyuka:examples-php-wasm
Dec 3, 2024
Merged

Run examples with php wasm#1097
derickr merged 6 commits into
php:masterfrom
soyuka:examples-php-wasm

Conversation

@soyuka

@soyukasoyuka commented Oct 16, 2024

Copy link
Copy Markdown
Contributor

Adds a PHP Wasm binary to run documentation examples in the browser. We discussed this with @pronskiy (
fixesphp/doc-en#3259).

php-doc-2.mp4

I've builtin PHP wasm 8.4.0-dev and I'm not sure what would be preferred to keep this up to date. If you want to reproduce the build see:

soyuka/php-wasm#7

Clone that branch and execute:

docker buildx bake
cp build/* ../phpdoc/web-php/js

Work sponsored by Les-Tilleuls.coop inline with what we built at the API Platform Playground more informations on a blog post

@github-actions

github-actionsBot commented Oct 16, 2024

Copy link
Copy Markdown
Contributor

🚀 Regression report for commit 837e36b is at https://web-php-regression-report-pr-1097.preview.thephp.foundation

@github-actions

github-actionsBot commented Oct 16, 2024

Copy link
Copy Markdown
Contributor

🚀 Preview for commit 837e36b can be found at https://web-php-pr-1097.preview.thephp.foundation

@soyuka

This comment was marked as duplicate.

Comment threadjs/interactive-examples.js Outdated

async function main() {
const buffer = [];
const {ccall} = await phpBinary({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const{ccall}=awaitphpBinary({
const{ccall}=awaitphpBinary({

(mixed tabs and spaces)

Comment threadjs/interactive-examples.js Outdated
Comment on lines +37 to +39
document.querySelectorAll('.example').forEach((example) => {
const button = document.createElement('button');
const phpcode = example.querySelector('.phpcode');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
document.querySelectorAll('.example').forEach((example)=>{
constbutton=document.createElement('button');
constphpcode=example.querySelector('.phpcode');
document.querySelectorAll('.example').forEach((example)=>{
constbutton=document.createElement('button');
constphpcode=example.querySelector('.phpcode');

Comment threadjs/interactive-examples.js Outdated
Comment on lines +44 to +45
button.innerText = 'Run code';
button.onclick = function() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
button.innerText='Run code';
button.onclick=function(){
button.innerText='Run code';
button.onclick=function(){

Comment threadjs/interactive-examples.js Outdated
Comment on lines +54 to +57
};

phpcode.parentNode.insertBefore(button, phpcode);
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
};
phpcode.parentNode.insertBefore(button,phpcode);
});
};
phpcode.parentNode.insertBefore(button,phpcode);
});

Comment threadjs/interactive-examples.js Outdated
Comment on lines +4 to +16
const container = document.createElement('div');
container.classList.add('screen', 'example-contents');
const title = document.createElement('p');
title.innerText = 'Output: ';
container.appendChild(title)
const div = document.createElement('div');
div.classList.add('examplescode');
container.appendChild(div);
const pre = document.createElement('pre');
pre.classList.add('examplescode');
pre.innerText = output;
div.appendChild(pre)
return container;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
constcontainer=document.createElement('div');
container.classList.add('screen','example-contents');
consttitle=document.createElement('p');
title.innerText='Output: ';
container.appendChild(title)
constdiv=document.createElement('div');
div.classList.add('examplescode');
container.appendChild(div);
constpre=document.createElement('pre');
pre.classList.add('examplescode');
pre.innerText=output;
div.appendChild(pre)
returncontainer;
constcontainer=document.createElement('div');
container.classList.add('screen','example-contents');
consttitle=document.createElement('p');
title.innerText='Output: ';
container.appendChild(title);
constdiv=document.createElement('div');
div.classList.add('examplescode');
container.appendChild(div);
constpre=document.createElement('pre');
pre.classList.add('examplescode');
pre.innerText=output;
div.appendChild(pre);
returncontainer;

CS

Comment threadjs/interactive-examples.js Outdated
button.innerText = 'Run code';
button.onclick = function() {
if (lastOutput && lastOutput.parentNode) {
lastOutput.parentNode.removeChild(lastOutput)

@stofstofOct 16, 2024

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

much simpler: lastOutput.remove() (there is no reason to care about unmaintained browsers not supporting the remove method when you are adding a feature requiring WebAssembly to work)

Comment threadjs/interactive-examples.js Outdated
let lastOutput = null

document.querySelectorAll('.example').forEach((example) => {
const button = document.createElement('button');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the created button should use the button type instead of the default submit type, to avoid issues in case this ends up being used in a place being inside a form.

Comment threadjs/interactive-examples.js Outdated
lastOutput.parentNode.removeChild(lastOutput)
}

ccall("phpw_run", null, ["string"], ['?>' + phpcode.innerText]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this use .innerText or .textContent here ? innerText is impacted by the styles of the page

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no idea 🤷

Comment threadjs/interactive-examples.js Outdated
buffer.length = 0;
};

phpcode.parentNode.insertBefore(button, phpcode);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use phpcode.before() instead

@stof

stof commented Oct 16, 2024

Copy link
Copy Markdown

Disabling many extensions as you did in soyuka/php-wasm#7 is likely to make many examples non working though (in your build, even JSON functions are not available)

@soyuka

Copy link
Copy Markdown
ContributorAuthor

Disabling many extensions as you did in soyuka/php-wasm#7 is likely to make many examples non working though (in your build, even JSON functions are not available)

Definitely, there are a few things to discuss, I can enable most extensions, some will require additional libraries (for example dom or xml require libxml) and it'll add weight to the build (now it's "only" 3.6 MB).

Comment threadinclude/footer.inc
$path = dirname(__DIR__) . '/js/' . $filename;
echo '<script type="module" src="/cached.php?t=' . @filemtime($path) . '&amp;f=/js/' . $filename . '"></script>' . "\n";
}
?>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe that this deserves only one line?

Comment threadjs/interactive-examples.js Outdated
@pronskiy

Copy link
Copy Markdown
Member

As I understand, php binary is always loaded now, right? If yes, does it make sense to load it only when the user clicks "Run" for the first time?

@soyuka

Copy link
Copy Markdown
ContributorAuthor

done

@soyuka

Copy link
Copy Markdown
ContributorAuthor

Note that we should use brotli or gzip though, I'm unfamiliar with the server architecture behind the php docs website, should I add this directly to the cached.php file?

@Girgias

Copy link
Copy Markdown
Member

@derickr is likely the person that would know this.

@jimwins

Copy link
Copy Markdown
Member

It looks like php.net isn't particularly well set-up for encoding content. You can see how CSS/JS/etc is served up by the cached.php file, and testing with curl --compressed -I shows that it doesn't get encoded. HTML content does get gzip-encoded, at least.

I'm not sure how much of the encoding happens on our server vs. what Myra does on the edge, but there's probably room for improvement there even outside of encoding the WASM bundle appropriately. The current web configuration isn't in the systems repo yet, though.

@lwlwilliam

lwlwilliam commented Oct 18, 2024

Copy link
Copy Markdown
Contributor

If the modified code can still be highlighted, that would be even better.

@stof

stof commented Oct 18, 2024

Copy link
Copy Markdown

@lwlwilliam this would require bringing an actual code editor (Codemirror or Monaco) instead of just using contenteditable

@pronskiy

Copy link
Copy Markdown
Member

@lwlwilliam, @stof, it makes sense for sure, but perhaps as step two in a separate PR? Just to keep this one smaller and delivered sooner. I'm pretty sure some unforeseen bugs will be discovered when this is deployed, and it'll be easier to fix them with smaller steps.

@pronskiy

Copy link
Copy Markdown
Member

@soyuka, what do you think about adding the PHP version in the output label?
Screenshot 2024-10-21 at 11 42 10 AM

@derickr

Copy link
Copy Markdown
Member

Myra already caches cached.php with a 2 minute TTL (and 1 minute in case of a 404), but there are no settings for specific compressions:

prefix	/cached.php	2 minutes	1 minute

I can ask whether they can do on the fly-compression, but I am not sure whether they do.

To answer @jimwins, the config isn't in the systems repo, as nothing on php-web4 has been "php-ified" yet. I think Sascha set it up with nginx in his own way. There is no /local/systems either. The nginx configuration does not have any compression options set either.

I believe adding it to cached.php with the right headers (content-type and compression-type), with storing the compressed file should work. You should be able to test that yourself on your local development environment too.

@soyuka

soyuka commented Oct 21, 2024

Copy link
Copy Markdown
ContributorAuthor

I believe adding it to cached.php with the right headers (content-type and compression-type), with storing the compressed file should work. You should be able to test that yourself on your local development environment too.

I gave a try
diff --git a/cached.php b/cached.php
index 766afb2e..c8055ef7 100644
--- a/cached.php+++ b/cached.php@@ -46,6 +46,14 @@
header("Content-Type: application/javascript");
} elseif (substr($abs, -4) == ".css") {
header("Content-Type: text/css");
+} elseif (substr($abs, -4) == ".wasm") {+	header("Content-Type: application/wasm");+}++$gzipped = $abs . '.gz';+if (file_exists($gzipped) && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && false !== strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) {+ $abs = $gzipped;+	header("Content-Encoding: gzip");
}
readfile($abs);

But as the Javascript is requiring the php-web.wasm file it's not ideal and should be left to the HTTP server in my opinion. For example, I'm running the docs using FrankenPHP:

20241021_21h20m16s_grim

@pronskiy added version:

20241021_21h22m16s_grim

Comment threadjs/interactive-examples.js Outdated
Co-authored-by: lwlinux <1053317536@qq.com>
@derickr

Copy link
Copy Markdown
Member

I don't quite understand whether your "try" was succesful or not. I can only see an image.

The javascript should surely be calling /cached.php?.... and not the .wasm file directly...?

And we deploy on nginx, so you should test with that, and not with something else. I have just added the nginx configuration to the systems repository: https://github.com/php/systems/tree/master/php-web4/nginx

@shaedrich

Copy link
Copy Markdown

@pronskiy added version:

Should there be only one version or all of the supported in a dropdown/select?

@pronskiy

Copy link
Copy Markdown
Member

@shaedrich, for the sake of simplicity of this first step, it's better to stick to just one version. The dropdown sounds like a good idea for the next step.

@cookieguru

Copy link
Copy Markdown
Contributor

Probably want to add

js/php-web.wasmfilter=lfsdiff=lfsmerge=lfsbinary

to .gitattributes

@pronskiy

Copy link
Copy Markdown
Member

@derickr, @saundefined, @sy-records, folks, do you think this is ready to merge?

@sy-recordssy-records left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@derickrderickr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am getting failures when I try to check out this branch:

remote: Enumerating objects: 31, done.
remote: Counting objects: 100% (31/31), done.
remote: Compressing objects: 100% (15/15), done.
remote: Total 31 (delta 17), reused 27 (delta 16), pack-reused 0 (from 0)
Unpacking objects: 100% (31/31), 1.20 MiB | 1.79 MiB/s, done.
From https://github.com/php/web-php
* [new ref] refs/pull/1097/head -> pr/1097
M	distributions
Switched to branch 'pr/1097'
Encountered 1 file that should have been a pointer, but wasn't:
js/php-web.wasm
error: cannot rebase: You have unstaged changes.
error: Please commit or stash them.

It is perhaps because something is on LFS, or not? The file just looked checked in here, so what is the deal with adding lfs to .gitattributes?

If it is LFS, then you need to provide instructions as to how to check it out properly.

@soyuka

Copy link
Copy Markdown
ContributorAuthor

@derickr I reverted the change on gitattributes, may you try again?

@derickr

Copy link
Copy Markdown
Member

Merged, thanks so much!

It should be live in the next hour or so.

@derickr

Copy link
Copy Markdown
Member

@soyuka Now that is merged, could you create an article on the wiki with very succinct steps as to how to rebuild the needed assets?

@bohwaz

bohwaz commented Dec 3, 2024

Copy link
Copy Markdown

hey guys, the WASM file doesn't appear to be compressed by the server?

Edit: from reading the discussion I thought it should have been served compressed.

@soyuka

Copy link
Copy Markdown
ContributorAuthor

@derickr to what script may I add the php.wasm build on https://github.com/php/systems/ ?

hey guys, the WASM file doesn't appear to be compressed by the server?

Would php/systems#67 work? IMO we should get rid of cached.php and leave assets compression and cache management to nginx (or caddy).

@cookieguru

Copy link
Copy Markdown
Contributor

I meant to comment on this before it got merged, but removing it from LFS was not the way to go. Running a git clone/pull now needs to download an additional 3.6 MB for this commit alone, and that will increase every time the wasm file is updated. By placing it in LFS, only the version tied to the commit would have ever need to be fetched. I think a simple git lfs install would have fixed the original issue

@derickr

Copy link
Copy Markdown
Member

@cookieguru We don't want to use LFS just yet - it's probably better to not have the WASM file in the repo at some point, but that causes problems too for deployment.

@derickr

Copy link
Copy Markdown
Member

Let's continue this discussion in php/systems#67 (only).

@saundefined

Copy link
Copy Markdown
Member

@soyuka@pronskiy@Girgias

What do you think to add selector with active PHP versions (e.g. 8.1-8.4)?

if the code behavior differs between versions, we display these differences in the documentation,
I think interactive examples would be more illustrative.

I suppose this way we can do away with &example.outputs.*; blocks altogether in the future.

@pronskiy

Copy link
Copy Markdown
Member

@saundefined, it totally makes sense to me.
How do you think it would work from a user’s perspective?

For example if the dropdown defaults to PHP 8.4, I hit Run, and it shows the 8.4 output. Then I switch the dropdown to 8.3 and hit Run again. Would it replace the output or show the diff somehow?

@derickr

derickr commented Dec 11, 2024 via email

Copy link
Copy Markdown
Member

@Girgias

Copy link
Copy Markdown
Member

@derickr part of your concern seems to be fixed by: #1180

But not all examples produce output, which is a different issue.

@shaedrich

shaedrich commented Dec 16, 2024

Copy link
Copy Markdown

@soyuka@pronskiy@Girgias

What do you think to add selector with active PHP versions (e.g. 8.1-8.4)?

I suggested the same thing:

@pronskiy added version:

Should there be only one version or all of the supported in a dropdown/select?

so, I'd love to have that as well. Maybe, now is the right time to implement it, now that the initial implementation is merged

After that, we might even have a feature to compare outputs of two different versions 🤔 🤯

@pronskiy

pronskiy commented Dec 16, 2024

Copy link
Copy Markdown
Member

Having multiple versions also means you need to have users download the wasm binary multiple times. I really don't think we should focus on that.

@derickr, not necessarily. Any version of Wasm should be downloaded only when the user clicks “Run” for the first time.

For example:

  • I visit php.net, and 8.4 is selected by default. No Wasm is loaded initially.
  • I press “Run,” and the 8.4 Wasm binary is loaded and cached in the browser.
  • I switch to version 8.3 using the dropdown and press “Run.” The 8.3 Wasm binary is then loaded and cached in the browser.
  • Now, if I switch between 8.4 and 8.3, the examples run smoothly because the corresponding Wasm binaries are already cached.

@shaedrichshaedrich mentioned this pull request Jan 10, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Interactive code examples on php.net

13 participants

@soyuka@stof@pronskiy@Girgias@jimwins@lwlwilliam@derickr@shaedrich@cookieguru@bohwaz@saundefined@dunglas@sy-records