Voyons comment configurer un ~ HTML/JS/CS minimal + “application” alimentée par WebR sur un serveur que vous possédez. Ce sera du vanilla JS (c’est-à-dire pas de React/Vue/npm/bundler) que vous pourrez pirater à volonté.
TL ; DR : vous pouvez trouver la source de l’application et suivre les modifications qui y sont apportées sur GitHub si vous voulez vous lancer directement.
Dans le docs/
répertoire dans le référentiel GH, vous verrez un exemple d’utilisation de ceci dans GH Pages. Le voici en direct : https://hrbrmstr.github.io/webr-app/index.html. Vous trouverez ci-dessous des informations sur ce que vous devez faire pour cela.
Si tout s’est bien passé, vous devriez voir la sortie d’un appel à WebR ici (cela peut prendre quelques secondes) :
Configurer votre serveur
Je vais essayer de continuer à mettre à jour cela avec les nouvelles versions de WebR. La version actuelle est 0.1.0 et vous pouvez la récupérer à partir de : https://github.com/r-wasm/webr/releases/download/v0.1.0/webr-0.1.0.tar.gz.
WebR à l’échelle du système
Tu devrais lire cette rubrique dans la documentation WebR officielle avant de continuer.
J’utilise un serveur à l’échelle /webr
répertoire sur mon rud.is
domaine afin que je puisse l’utiliser sur n’importe quelle page que je sers.
Les performances de WebR en souffriront s’il ne peut pas utiliser SharedArrayBuffer
s. Donc, j’ai ces en-têtes activés sur mon /webr
annuaire:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
J’utilise nginx, donc ça ressemble à:
location ^~ /webr {
add_header "Cross-Origin-Opener-Policy" "same-origin";
add_header "Cross-Origin-Embedder-Policy" "require-corp";
}
YMMV.
Pour faire bonne mesure (et au cas où je déplacerais des choses), je colle ces en-têtes sur mon répertoire d’application qui utilisera WebR. Je ne les utilise pas à l’échelle du serveur, cependant.
Et ils l’appellent un MIME. UN MIME !
WebR est un Module Javascriptet vous devez vous assurer que les fichiers avec un mjs
l’extension ont un Type MIME de text/javascript
ou certains navigateurs ne seront pas satisfaits.
Un moyen typique pour les serveurs Web de savoir comment communiquer cela est via un mime.types
déposer. Ce n’est pas vrai pour tous les serveurs Web, et j’ajouterai des étapes pour ceux qui utilisent une manière différente de configurer cela. L’entrée devrait ressembler à ceci :
text/javascript mjs;
Test de la configuration WebR
Vous devriez pouvoir accéder à ce chemin sur votre serveur Web dans votre navigateur et voir l’application de console WebR. Si vous le faites, vous pouvez continuer. Sinon, laissez un problème et je peux essayer de vous aider à le déboguer, mais c’est dans la mesure du possible pour moi.
Installation de l’application
Nous allons creuser un peu dans l’application, mais vous voulez probablement la voir fonctionner, alors installons cette ~ application minimale.
Mon application de démonstration personnelle est ancrée sur /webr-app
sur mon rud.is
serveur Web. Voici comment le reproduire :
# Go someplace safe
$ cd $TMPDIR
# Get the app bundle
# You can also use the GH release version, just delete the README after installing it.
$ curl -o webr-app.tgz https://rud.is/dl/webr-app.tgz
# Expand it
$ tar -xvzf webr-app.tgz
x ./webr-app/
x ./webr-app/modules/
x ./webr-app/modules/webr-app.js
x ./webr-app/modules/webr-helpers.js
x ./webr-app/css/
x ./webr-app/css/simple.min.css
x ./webr-app/css/app.css
x ./webr-app/main.js
x ./webr-app/index.html
# 🚨 GO THROUGH EACH FILE
# 🚨 to make sure I'm not pwning you!
# 🚨 Don't trust anything or anyone.
# Go to the webserver root
$ cd $PATH_TO_WEBSERVER_DOC_ROOT_PATH
# Move the directory
$ mv $TMPDIR/webr-app .
# Delete the tarball (optional)
$ rm $TMPDIR/webr-app.tgz
Frappez ce chemin sur ton serveur Web et vous devriez voir ce que vous avez vu sur le mien.
Structure d’application alimentée par WebR
.
├── css # CSS (obvsly)
│ ├── app.css # app-specific ones
│ └── simple.min.css # more on this in a bit
├── index.html # The main app page
├── main.js # The main app JS
└── modules # We use ES6 JS modules
├── webr-app.js # Main app module
└── webr-helpers.js # Some WebR JS Helpers I wrote
CSS simples
Si vous souscrivez à mon bulletin, vous savez que je joue avec des tonnes d’outils et de frameworks. Veuillez utiliser ce que vous préférez. Pour les personnes qui ne font pas normalement ce genre de choses, j’ai inclus une copie de CSS simples b/c, eh bien, c’est simple utiliser. S’il te plaît utiliser cette ressource pour vous familiariser avec celui-ci si vous continuez à l’utiliser.
Modules Javascript
Lorsque je suis en mode “hack” (comme je l’étais les premiers jours après le lancement de WebR), je reviens à de vieilles mauvaises habitudes. Nous ne les reproduirons pas ici.
Nous utilisons Modules Javascript comme structure du projet. Nous ne «regroupons» pas (aspirant tous les fichiers de support d’application dans un seul fichier minifié) car toutes les personnes R ne sont pas des experts en outils JS. Nous ne les utilisons pas non plus car ils ne sont vraiment pas nécessaires, et j’aime garder les choses simples et aussi sans dépendance que possible.
Dans index.html
vous verrez cette ligne :
<script type="module" src="https://securityboulevard.com/2023/03/almost-bare-bones-webr-starter-app/amp/./main.js"></script>
Cela indique au navigateur de charger ce fichier JS comme s’il s’agissait d’un module. Pendant que vous lisez (vous a fait lire le lien MDN, ci-dessus, droite?), les modules nous donnent des noms/objets/fonctionnalités de portée locale et une protection contre le sabotage des noms importés.
Notre module principal contient toutes les fonctionnalités essentielles de notre application, qui ne font rien de plus que :
- charge WebR
- Vous indique à quelle vitesse il a été chargé + instancié
- Yanks
mtcars
à partir de la session R instanciée (mtcars
était la troisième “chose” que j’ai tapée dans R, donc mon cerveau l’utilise par défaut). - En fait un tableau HTML à l’aide de D3.
C’est assez petit pour inclure ici:
import { format } from "https://cdn.skypack.dev/d3-format@3";
import * as HelpR from './modules/webr-helpers.js'; // WebR-specific helpers
// import * as App from './modules/webr-app.js'; // our app's functions, if it had some
console.time('Execution Time'); // keeps on tickin'
const timerStart = performance.now();
import { WebR } from '/webr/webr.mjs'; // service workers == full path starting with /
globalThis.webR = new WebR({
WEBR_URL: "/webr/", # our system-wide WebR
SW_URL: "/webr/" # what ^^ said
});
await globalThis.webR.init();
// WebR is ready to use. So, brag about it!
const timerEnd = performance.now();
console.timeEnd('Execution Time');
document.getElementById('loading').innerText = `WebR Loaded! (${format(",.2r")((timerEnd - timerStart) / 1000)} seconds)`;
const mtcars = await HelpR.getDataFrame(globalThis.webR, "mtcars");
console.table(mtcars);
HelpR.simpleDataFrameTable("#tbl", mtcars);
globalThis
est un objet JS spécial qui vous permet de déplacer des éléments dans l’environnement JS global. Pas nécessaire à 100 %, mais si vous souhaitez utiliser le même contexte WebR dans d’autres blocs de modules d’application, voici comment procéder.
Concentrons-nous sur les trois dernières lignes.
const mtcars = await HelpR.getDataFrame(globalThis.webR, "mtcars");
Cela utilise une fonction d’assistance que j’ai créée pour obtenir un objet de trame de données de R d’une manière plus compatible pour la plupart des bibliothèques JS et JS que l’objet JS WebR par défaut toJs()
la fonction convertit tous les objets R en.
console.table(mtcars);
Cela crée un joli tableau dans la console des outils de développement du navigateur. Je l’ai fait pour que vous puissiez ouvrir la console pour le voir, mais je veux aussi que vous inspectiez le contenu de l’objet (tapez simplement mtcars
et appuyez sur entrée/retour) pour voir ce joli format.
Nous passons dans un contexte WebR dont nous savons qu’il fonctionnera, puis n’importe quel Code R qui évaluera et renverra une trame de données. C’est à vous (pour le moment) de vous assurer que le code s’exécute et qu’il renvoie une trame de données.
La dernière ligne :
HelpR.simpleDataFrameTable("#tbl", mtcars);
appelle une autre fonction d’assistance pour créer le tableau.
AideR
Je peux éventuellement blablater avec éloquence et complètement sur ce qu’il y a dedans modules/webr-helpers.js
. Pour l’instant, permettez-moi de me concentrer sur quelques points, d’autant plus qu’il y a des doux Commentaires JSDoc.
Tout d’abord, parlons un peu plus de ces commentaires.
J’utilise VS Code pour ~60% de mes opérations quotidiennes et je l’ai utilisé pour ce projet. Si vous ouvrez la racine du projet dans VS Code et sélectionnez/survolez simpleDataFrameTable
dans cette dernière ligne, vous obtiendrez une aide au format agréable. VS Code est câblé pour cela (d’autres éditeurs/IDE le sont aussi), donc je vous encourage à faire un usage libéral des commentaires JSDoc dans vos propres fonctions/modules.
Maintenant, regardons derrière le rideau de getDataFrame
:
export async function getDataFrame(ctx, rEvalCode) {
let result = await ctx.evalR(`${rEvalCode}`);
let output = await result.toJs();
return (Promise.resolve(webRDataFrameToJS(output)));
}
Le export
indique à l’environnement JS que cette fonction est disponible si elle est importée correctement. Sans le export
la fonction est locale au module.
let result = await ctx.evalR(`${rEvalCode}`);
Une application appropriée utiliserait JS try
/catch
erreurs potentielles. Il y a un exemple de cela dans le code de l’application fantaisie React sur le site de WebR. Nous faisons fi de la prudence et évaluons tout ce qui nous est donné. En théorie, nous devrions demander à R de s’assurer qu’il s’agit d’une trame de données, ce que nous ne pouvons pas faire du côté de JS depuis la ligne suivante :
let output = await result.toJs();
affichera le type sous forme de list
(avant JC data.frame
s sont list
s).
J’ajouterai probablement d’autres assistants à un module d’assistance plus autonome, mais je soupçonne que R d’entreprise me battra pour cela, donc je n’investirai probablement pas trop de temps dessus, du moins en externe.
Attendre! Attendre! Dis-moi (sur await
) !
Avant de pouvoir parler de la dernière ligne :
return (Promise.resolve(webRDataFrameToJS(output)));
parlons brièvement de asynchrone opérations en JS.
L’environnement JavaScript de votre navigateur est monothread. async
-hronous ops laisse passer du code aux threads pour éviter de bloquer les opérations de page. Ceux-ci sont exécutés “à tout moment”, donc tout ce que vous obtenez est une promesse insipide et superficielle d’exécution de code et potentiellement de vous donner quelque chose en retour.
Nous utilisons explicitement await
pour quand nous vraiment besoin du code pour s’exécuter et, dans ce cas, donnez-nous quelque chose en retour. Nous pouvons continuer à enchaîner les appels de fonction asynchrones, mais – si nous devons nous assurer que le code s’exécute et/ou que nous récupérons des données – nous devrons éventuellement tenir notre promesse de le faire ; ainsi, Promise.resolve
.
Servir WebR à partir de pages GitHub
Le docs/
répertoire dans le référentiel affiche une version de travail sur les pages GH.
main.js
a besoin de quelques ajustements :
// This will use Posit's CDN
import('https://webr.r-wasm.org/latest/webr.mjs').then( // this wraps the main app code
async ({ WebR }) => {
globalThis.webR = new WebR({
SW_URL: "/webr-app/" // 👈🏼 needs to be your GHP main path
});
await globalThis.webR.init();
const timerEnd = performance.now();
console.timeEnd('Execution Time');
document.getElementById('loading').innerText = `WebR Loaded! (${format(",.2r")((timerEnd - timerStart) / 1000)} seconds)`;
const mtcars = await HelpR.getDataFrame(globalThis.webR, "mtcars");
console.table(mtcars);
HelpR.simpleDataFrameTable("#tbl", mtcars);
}
);
Moar à venir
S’il vous plaît, appuyez cette application de tableau de bord terriblement codée pour voir une utilisation plus fantaisiste. Je vais convertir cela en modules et étendre un peu git.
*** Ceci est un blog syndiqué du Security Bloggers Network de rud.is Rédigé par hrbrmstr. Lire le message d’origine sur : https://rud.is/b/2023/03/12/almost-bare-bones-webr-starter-app/
Abonnez-vous à notre page Facebook: https://www.facebook.com/mycamer.net
Pour recevoir l’actualité sur vos téléphones à partir de l’application Telegram cliquez ici: https://t.me/+KMdLTc0qS6ZkMGI0
Nous ecrire par Whatsapp : Whatsapp +44 7476844931