Update scripts to WIP site for REPL

workaround for copy static files

add REPL to WIP home page

WIP site download REPL nightly

scripts build WIP Netlify and dev
This commit is contained in:
Luke Boswell 2023-10-19 14:51:46 +11:00
parent 77d2136d00
commit eb3e97fd95
No known key found for this signature in database
GPG Key ID: F6DB3C9DB47377B0
6 changed files with 445 additions and 72 deletions

View File

@ -17,6 +17,7 @@ cd $SCRIPT_RELATIVE_DIR
rm -rf build/
cp -r public/ build/
mkdir build/wip # for WIP site
# download fonts just-in-time so we don't have to bloat the repo with them.
DESIGN_ASSETS_COMMIT="4d949642ebc56ca455cf270b288382788bce5873"
@ -36,13 +37,14 @@ curl -fLJO https://github.com/roc-lang/roc/archive/www.tar.gz
REPL_TARFILE="roc_repl_wasm.tar.gz"
curl -fLJO https://github.com/roc-lang/roc/releases/download/nightly/$REPL_TARFILE
tar -xzf $REPL_TARFILE -C repl
tar -xzf $REPL_TARFILE -C wip # note we also need this for WIP repl
rm $REPL_TARFILE
ls -lh repl
ls -lh wip
popd
pushd ..
echo 'Generating builtin docs...'
cargo --version
# We set ROC_DOCS_ROOT_DIR=builtins so that links will be generated relative to
@ -92,10 +94,8 @@ $roc run www/generate_tutorial/src/tutorial.roc -- www/generate_tutorial/src/inp
mv www/build/tutorial/tutorial.html www/build/tutorial/index.html
# for new wip site
mkdir www/build/wip
$roc run www/wip_new_website/main.roc -- www/wip_new_website/content/ www/build/wip
cp -r www/wip_new_website/static/site.css www/build/wip
cp -r www/build/fonts www/build/wip/fonts
$roc run www/wip_new_website/main.roc -- www/wip_new_website/content/ www/build/wip/
cp -r www/wip_new_website/static/* www/build/wip/
# cleanup
rm -rf roc_nightly roc_releases.json

View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Use this script to for testing the WIP site locally without downloading assets every time.
# NOTE run `bash www/build.sh` to cache local copy of fonts, and repl assets etc
rm -rf dist/
mkdir dist
mkdir dist/wip
cp -r ../build/wip/* dist/wip/
roc run main.roc -- content/ dist/wip/
cp -r static/* dist/wip/
cp -r ../build/fonts/ dist/fonts/
simple-http-server -p 8080 --nocache --index -- dist/

View File

@ -1,54 +0,0 @@
#!/usr/bin/env roc
app "website-builder"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" }
imports [
pf.Task.{ Task },
pf.Cmd,
]
provides [main] to pf
main =
# TODO take dist folder name and main.roc path as args once https://github.com/roc-lang/basic-cli/issues/82 is fixed
# TODO add function to remove boilerplate
# Remove dist folder
{} <-
Cmd.new "rm"
|> Cmd.args ["-rf", "dist/"]
|> Cmd.status
|> Task.onErr \_ -> crash "Failed to remove dist folder"
|> Task.await
# Build site
{} <-
Cmd.new "roc"
|> Cmd.args ["run", "main.roc", "--", "content/", "dist/wip/"]
|> Cmd.status
|> Task.onErr \_ -> crash "Failed to build site"
|> Task.await
# Copy static files
{} <-
Cmd.new "cp"
|> Cmd.args ["-r", "static/site.css", "dist/wip/"]
|> Cmd.status
|> Task.onErr \_ -> crash "Failed to copy static files"
|> Task.await
# Copy font files - assume that www/build.sh has been run previously and the
# fonts are available locally in ../build/fonts
{} <-
Cmd.new "cp"
|> Cmd.args ["-r", "../build/fonts/", "dist/fonts/"]
|> Cmd.status
|> Task.onErr \_ -> crash "Failed to copy static files"
|> Task.await
# Start file server
{} <-
Cmd.new "simple-http-server"
|> Cmd.args ["-p", "8080", "--nocache", "--index", "--", "dist/"]
|> Cmd.status
|> Task.onErr \_ -> crash "Failed to run file server; consider intalling with `cargo install simple-http-server`"
|> Task.await
Task.ok {}

View File

@ -18,7 +18,6 @@ A work-in-progress programming language that aims to be fast, friendly, and func
</div>
<div class="home-goals-column">
<h3 class="home-goals-title">Friendly</h3>
<h2 class="home-goals-title">Friendly</h2>
<p class="home-goals-description">Roc aims to be a user-friendly language with a friendly community of users. This involves the set of tools Roc includes, and also the spirit of the community of Roc programmers. <a class="home-goals-learn-more" href="/design_goals.html#friendly">What does <i>friendly</i> mean here?</a></p>
</div>
<div class="home-goals-column">
@ -28,19 +27,21 @@ A work-in-progress programming language that aims to be fast, friendly, and func
## Try Roc
<!-- TODO WebREPL to go here -->
<link rel="stylesheet" href="/wip/repl.css" />
<div id="repl">
<code class="history">
<div id="help-text"></div>
<div id="history-text"><div id="loading-message">Loading REPL WebAssembly module…please wait!</div></div>
</code>
<section id="source-input-wrapper">
<textarea rows="5" autofocus id="source-input" placeholder="You can enter Roc code here once the REPL loads!"
disabled></textarea>
</section>
</div>
<script type="module" src="/wip/repl.js"></script>
</div>
The code below shows a Roc application which prints `Hello World!` to the terminal. It does this using the [roc-lang/basic-cli](https://github.com/roc-lang/basic-cli) platform.
```roc
app "hello-world"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" }
imports [pf.Stdout]
provides [main] to pf
main =
Stdout.line "Hello, World!"
```
## Examples
We have developed a number of smaller code [examples](https://github.com/roc-lang/examples) which demonstrate how to use Roc. These cover a range of topics from basic syntax to more advanced features such as random number generation and using the popular `Task` feature.

View File

@ -0,0 +1,173 @@
#repl {
display: flex;
flex-direction: column;
}
/* h1 {
font-family: "Merriweather";
font-size: 48px;
line-height: 48px;
padding: 48px 0;
margin: 0;
color: var(--header-link-color);
} */
#source-input-wrapper {
position: relative;
width: 100%;
box-sizing: border-box;
}
#source-input-wrapper::before {
content: "» ";
position: absolute;
left: 15px;
top: 18px;
line-height: 16px;
height: 16px;
z-index: 2;
font-family: var(--font-mono);
color: var(--cyan);
/* Let clicks pass through to the textarea */
pointer-events: none;
}
#source-input {
width: 100%;
font-family: var(--font-mono);
color: var(--code-color);
background-color: var(--code-bg);
display: inline-block;
height: 76px;
padding: 16px;
padding-left: 36px;
border: 1px solid transparent;
margin: 0;
margin-bottom: 2em;
box-sizing: border-box;
}
#source-input:focus {
border: 1px solid var(--cyan);
box-sizing: border-box;
outline: none;
}
/* li {
margin: 8px;
} */
.history {
padding: 1em;
padding-bottom: 0;
}
#help-text,
#history-text {
white-space: pre-wrap;
}
#history-text {
margin-top: 16px;
}
#loading-message {
text-align: center;
/* approximately match height after loading and printing help message */
height: 96px;
}
.history-item {
margin-bottom: 24px;
}
.history-item .input {
margin: 0;
margin-bottom: 8px;
}
.history-item .output {
margin: 0;
}
.panic {
color: red;
}
.input-line-prefix {
color: var(--cyan);
}
.color-red {
color: red;
}
.color-green {
color: var(--green);
}
.color-yellow {
color: var(--orange);
}
.color-blue {
color: var(--cyan);
}
.color-magenta {
color: var(--magenta);
}
.color-cyan {
color: var(--cyan);
}
.color-white {
/* Really this isn't white so much as "default text color." For the repl, this should be black
in a light color scheme, and only white in dark mode. The name could be better! */
color: black;
}
@media (prefers-color-scheme: dark) {
.color-white {
color: white;
}
}
.bold {
font-weight: bold;
}
.underline {
text-decoration: underline;
}
/* Mobile-friendly screen width */
@media only screen and (max-width: 767px) {
h1 {
font-size: 24px !important;
margin: 0;
padding: 16px 0;
text-align: center;
}
#repl {
margin: 0;
padding: 0;
min-height: calc(100vh - var(--top-bar-height));
}
code.history {
flex-grow: 1;
}
#source-input {
margin: 0
}
#loading-message {
margin: 0;
}
}

View File

@ -0,0 +1,238 @@
// The only way we can provide values to wasm_bindgen's generated code is to set globals
window.js_create_app = js_create_app;
window.js_run_app = js_run_app;
window.js_get_result_and_memory = js_get_result_and_memory;
// The only place we use console.error is in wasm_bindgen, where it gets a single string argument.
console.error = function displayErrorInHistoryPanel(string) {
const html = `<div class="panic">${string}</div>`;
updateHistoryEntry(repl.inputHistoryIndex, false, html);
};
import * as roc_repl_wasm from "/wip/roc_repl_wasm.js";
// ----------------------------------------------------------------------------
// REPL state
// ----------------------------------------------------------------------------
const repl = {
elemHistory: document.getElementById("history-text"),
elemSourceInput: document.getElementById("source-input"),
inputQueue: [],
inputHistory: [],
inputHistoryIndex: 0,
inputStash: "", // stash the user input while we're toggling through history with up/down arrows
textDecoder: new TextDecoder(),
textEncoder: new TextEncoder(),
compiler: null,
app: null,
// Temporary storage for the address of the result of running the user's code.
// Used while control flow returns to Rust to allocate space to copy the app's memory buffer.
result_addr: 0,
};
// Initialise
repl.elemSourceInput.addEventListener("input", onInput);
repl.elemSourceInput.addEventListener("keydown", onInputKeydown);
repl.elemSourceInput.addEventListener("keyup", onInputKeyup);
roc_repl_wasm.default("/wip/roc_repl_wasm_bg.wasm").then(async (instance) => {
repl.elemHistory.querySelector("#loading-message").remove();
repl.elemSourceInput.disabled = false;
repl.elemSourceInput.placeholder = "Type some Roc code and press Enter.";
repl.elemSourceInput.focus();
repl.compiler = instance;
// Get help text from the compiler, and display it at top of the history panel
try {
const helpText = await roc_repl_wasm.entrypoint_from_js(":help");
const helpElem = document.getElementById("help-text");
helpElem.innerHTML = helpText.trim();
} catch (e) {
// Print error for Roc devs. Don't use console.error, we overrode that above to display on the page!
console.warn(e);
}
});
// ----------------------------------------------------------------------------
// Handle inputs
// ----------------------------------------------------------------------------
function onInput(event) {
// Have the textarea grow with the input
event.target.style.height = event.target.scrollHeight + 2 + "px"; // +2 for the border
}
function onInputKeydown(event) {
const ENTER = 13;
const { keyCode } = event;
if (keyCode === ENTER) {
if (!event.shiftKey && !event.ctrlKey && !event.altKey) {
// Don't advance the caret to the next line
event.preventDefault();
const inputText = repl.elemSourceInput.value.trim();
repl.elemSourceInput.value = "";
repl.elemSourceInput.style.height = "";
repl.inputQueue.push(inputText);
if (repl.inputQueue.length === 1) {
processInputQueue();
}
}
}
}
function onInputKeyup(event) {
const UP = 38;
const DOWN = 40;
const { keyCode } = event;
const el = repl.elemSourceInput;
switch (keyCode) {
case UP:
if (repl.inputHistory.length === 0) {
return;
}
if (repl.inputHistoryIndex == repl.inputHistory.length - 1) {
repl.inputStash = el.value;
}
setInput(repl.inputHistory[repl.inputHistoryIndex]);
if (repl.inputHistoryIndex > 0) {
repl.inputHistoryIndex--;
}
break;
case DOWN:
if (repl.inputHistory.length === 0) {
return;
}
if (repl.inputHistoryIndex === repl.inputHistory.length - 1) {
setInput(repl.inputStash);
} else {
repl.inputHistoryIndex++;
setInput(repl.inputHistory[repl.inputHistoryIndex]);
}
break;
default:
break;
}
}
function setInput(value) {
const el = repl.elemSourceInput;
el.value = value;
el.selectionStart = value.length;
el.selectionEnd = value.length;
}
// Use a queue just in case we somehow get inputs very fast
// We want the REPL to only process one at a time, since we're using some global state.
// In normal usage we shouldn't see this edge case anyway. Maybe with copy/paste?
async function processInputQueue() {
while (repl.inputQueue.length) {
const inputText = repl.inputQueue[0];
repl.inputHistoryIndex = createHistoryEntry(inputText);
repl.inputStash = "";
let outputText = "";
let ok = true;
if (inputText) {
try {
outputText = await roc_repl_wasm.entrypoint_from_js(inputText);
} catch (e) {
outputText = `${e}`;
ok = false;
}
}
updateHistoryEntry(repl.inputHistoryIndex, ok, outputText);
repl.inputQueue.shift();
}
}
// ----------------------------------------------------------------------------
// Callbacks to JS from Rust
// ----------------------------------------------------------------------------
// Load Wasm code into the browser's virtual machine, so we can run it later.
// This operation is async, so we call it before entering any code shared
// with the command-line REPL, which is sync.
async function js_create_app(wasm_module_bytes) {
const { instance } = await WebAssembly.instantiate(wasm_module_bytes);
// Keep the instance alive so we can run it later from shared REPL code
repl.app = instance;
}
// Call the `main` function of the user app, via the `wrapper` function.
function js_run_app() {
const { wrapper, memory } = repl.app.exports;
// Run the user code, and remember the result address
// We'll pass it to Rust in the next callback
repl.result_addr = wrapper();
// Tell Rust how much space to reserve for its copy of the app's memory buffer.
// We couldn't know that size until we actually ran the app.
return memory.buffer.byteLength;
}
// After Rust has allocated space for the app's memory buffer,
// we copy it, and return the result address too
function js_get_result_and_memory(buffer_alloc_addr) {
const appMemory = new Uint8Array(repl.app.exports.memory.buffer);
const compilerMemory = new Uint8Array(repl.compiler.memory.buffer);
compilerMemory.set(appMemory, buffer_alloc_addr);
return repl.result_addr;
}
// ----------------------------------------------------------------------------
// Rendering
// ----------------------------------------------------------------------------
function createHistoryEntry(inputText) {
const historyIndex = repl.inputHistory.length;
repl.inputHistory.push(inputText);
const firstLinePrefix = '<span class="input-line-prefix">» </span>';
const otherLinePrefix = '<br><span class="input-line-prefix">… </span>';
const inputLines = inputText.split("\n");
if (inputLines[inputLines.length - 1] === "") {
inputLines.pop();
}
const inputWithPrefixes = firstLinePrefix + inputLines.join(otherLinePrefix);
const inputElem = document.createElement("div");
inputElem.innerHTML = inputWithPrefixes;
inputElem.classList.add("input");
const historyItem = document.createElement("div");
historyItem.appendChild(inputElem);
historyItem.classList.add("history-item");
repl.elemHistory.appendChild(historyItem);
return historyIndex;
}
function updateHistoryEntry(index, ok, outputText) {
const outputElem = document.createElement("div");
outputElem.innerHTML = outputText;
outputElem.classList.add("output", ok ? "output-ok" : "output-error");
const historyItem = repl.elemHistory.children[index];
historyItem.appendChild(outputElem);
// Scroll the page to the bottom so you can see the most recent output.
// window.scrollTo(0, document.body.scrollHeight);
}