На Хабре уже проскакивали упоминания о совместимых или систем-копиях Wolfram Mathematica, но реализованных на других языках, как, скажем, Mathics. Автор статьи @ITSummaупомянул в самом начале
На Mathics такое не получится, как и многие другие примеры из этого списка тоже не сработают. Вообще, для Wolfram Language (WL) практически невозможно создать полноценный интерпретатор с открытым исходным кодом, потому что многие встроенные решатели являются проприетарной собственностью компании Вольфрама. Однако попытаться можно.
Сложно поспорить с этим утверждением, однако, возможен компромиссный вариант, позволяющий использовать все те же "решатели", но с немного иной open-source оберткой снаружи. В качестве ответа я представляю систему, которая не только воспроизводит многие ключевые функции блокнота Mathematica с нуля, но и расширяет функционал гораздо дальше, чем там, где очертил его границы Стивен Вольфрам, создав эту потрясающую систему более 30-ти лет назад.
::: Это не готовый продукт и не замена Wolfram Mathematica
Вставка для привлечения внимания

Здесь мне потребовалось завлечь пользователей этим замечательным корабликом. То, на чем он написан, - это ни что иное как Wolfram Language и то, где он исполняется здесь и сейчас, - Ваш браузер (для тех, кто кликнул на картинку).
документация (наполняется)
Но я обманул Вас, это не open-source блокнот, а нечто другое, но не менее важное, о чем Вы узнаете позже в секциях ниже. Начнем с привычных разделов...
Блокнот? Швейцарский нож
Код, иллюстрации, data-science, презентации - все сегодня возможно написать в пределах скевоморфиозного вида интерфейсов - блокнота


Ячейки разного типа это безусловно преимущество, особенно это касается типа Markdown, когда его "привезут" в Wolfram Mathematica - неизвестно.

Про презентации мы еще поговорим позже.
Бесплатный сыр
Важно отделять Wolfram Language от того, что его реализует - Wolfram Mathematica. Однако это также не совсем верно, так как Wolfram Mathematica это язык и интерфейс к нему (фронтенд), которые, вероятно, соразмерны друг с другом.
(Около-)Свободная реализация языка (см. комментарии про ограничения)со стандартными библиотеками уже давно доступна - это Wolfram Engine, который подобно Питону можно подключить в качестве скриптового языка к чему-угодно.
Отличия интерфейса блокнотов Mathematica от других
Некоторые из вас могут посчитать следующие пункты полезными или бесполезными конкретно для вашей работы или подхода к программированию, однако, нельзя опускать сам факт их реализации - это технически и концептуально сложная задача, которая была великолепно решена. Такое нельзя найти в Jupyter, Obsevable (d3-express), VSCode Notebook API.
Синтаксический сахар
Возведем идею формочек с цветом, которые многие видят в Visual Studio Code, редактируя какие-нибудь CSS цвета

в бесконечность и получим
Graphics3D[Sphere[]] % /. Sphere -> Cuboid

Здесь основная идея состоит в том, что сам график с точки зрения среды - это набор выражений и символов. Когда он рисуется на экране - это все еще тот же набор символов и выражений, с которым можно взаимодействовать. А трехмерный куб - это просто одна из возможных интерпретаций.
Выходные ячейки - редактируемы
Я не знаю почему, но почти все блокнотные интерфейсы просто игнорируют эту опцию


Мы получили результат - это тоже выражение. Почему бы не использовать его в последующих вычислениях?
Возможно, языков программирования, которые могли бы воспользоваться этим на благо просто мало.
Двумерный математический ввод
Здесь я явно предвзят, так как являюсь физиком-теоретиком. Что вам нравится больше?
1/Sqrt[2]
В редакторе Хабра это сложно показать, но возможность миксовать код и математические выражения подобные тем, что в LaTeX, - это потрясающе. Представьте, если обобщить это, писать код, вставлять изображения или другие интерактивные объекты в то время, как редактор будет это видеть и обрабатывать как все тот же код. Очевидно, это работа для регулярных выражений.
Зачем изобретать велосипед с открытым исходным кодом
Очевидный вопрос, ведь рынок уже удовлетворен тем, что создает WRI. Время привести недостатки
Wolfram Mathematica
Проприетарный формат/среда, который/ая стоит дорогоТяжелый интерфейс (в плане отзывчивости), нестабильный UI (краш, фриз это обычное дело)
Клиент и среда связаны, сложно вести удаленную сессию с телефона/тостера
Нельзя делиться блокнотами с поддержкой интерактивности
Кривой экспорт в PDF и только статические графики/изображения
Нельзя встроить блокнот на сайт/блог
Wolfram Cloud
Тяжелый и тормозной фронтенд, браузер задыхается при рендере даже текстовых ячеек. Нельзя вставить более 3-5 ячеек внутрь блога/сайта как iframe.
Строгая политика к облачным файлам: либо подписка, либо удаление, если отсутствует активность
Ограниченный функционал графики и отображения выражений
Работает исключительно при наличии интернета
Превратится в тыкву при желании WRI (Wolfram Research Inc)
Неужели нельзя сделать все "хорошо". Взглянем на Jupyter Notebook, к примеру. Там нет этих недостатков, весь блокнот уместится в единый HTML файл

Эта портативность и легкость заразительна. Взглянем на Observable, где великолепно решены проблемы с интерактивностью и динамикой, чего очень не хватает в Jupyter

Интересная особенность Observable - любая переменная считается динамической. Это все равно, что если бы в Wolfram Mathematica весь блокнот был внутриDynamicModule.
Тернистый путь разработки экосистемы
Здесь могла быть просто демонстрация готового проекта. Но вряд ли кто-то поспорит: Хабр - торт, когда можно чему-то научиться после прочтения текста или узнать что-то новое.
Чтобы решить проблему портативности и совместимости, нет никакого другого варианта, как использовать веб-браузер, который гарантирует, что все будет работать предсказуемо на любой платформе или системе.
WebGUI к консоли
У нас есть Wolfram Engine - это консольное приложение, поддерживающее stdin/stdout и ничего более, за исключением библиотек работы с файлами, сокетами и парочкой инструментов для OpenCL и CUDA вычислений. Пример Jupyter показал, что HTTP сервер с WebSockets протоколом для быстрого обмена TCP-подобными сообщениями с клиентским приложением работает круто. Есть ли HTTP сервер для Wolfram Language?..
Нет, но его можно всегда написать. Эта героическая задача была решена с нуля @KirillBelovTest. Можете почитать здесь. Причем не только про сервер, но и про высокоскоростной интерфейс сокетов (sockets), написанный им же с нуля на чистом Си для поддержания кроссплатформерности.
Таким образом, задача по прикручиванию веб-интерфеса складывается из достаточно типичных для современного веба блоков

В качестве шаблонизатора я написал WSP (Wolfram Script Pages) как PHP-подобный язык, только для Wolfram Language. Но сейчас он был замещен его наследником WLX (Wolfram Language XML), вдохновленным синтаксисом JSX.
Пример, как это может выглядеть
(* package manager to make sure you will get the right version *) PacletInstall["JerryI/LPM"]; << JerryI`LPM` PacletRepositories[{ Github -> "https://github.com/KirillBelovTest/Objects", Github -> "https://github.com/KirillBelovTest/Internal", Github -> "https://github.com/JerryI/CSocketListener", Github -> "https://github.com/KirillBelovTest/TCPServer", Github -> "https://github.com/KirillBelovTest/HTTPHandler", Github -> "https://github.com/KirillBelovTest/WebSocketHandler", Github -> "https://github.com/JerryI/wl-misc", Github -> "https://github.com/JerryI/wl-wlx" }] (* packages for HTTP server *) <<KirillBelov`Objects` <<KirillBelov`Internal` <<KirillBelov`CSockets` <<KirillBelov`TCPServer` <<KirillBelov`HTTPHandler` <<KirillBelov`HTTPHandler`Extensions` (* WLX scripts *) <<JerryI`WLX` <<JerryI`WLX`Importer` <<JerryI`WLX`WLJS` (* setting the directory of the project *) SetDirectory[If[StringQ[NotebookDirectory[]], NotebookDirectory[], DirectoryName[$InputFileName]]] Print["Staring HTTP server..."]; tcp = TCPServer[]; tcp["CompleteHandler", "HTTP"] = HTTPPacketQ -> HTTPPacketLength; tcp["MessageHandler", "HTTP"] = HTTPPacketQ -> http; (* main app file *) index := ImportComponent["index.wlx"]; http = HTTPHandler[]; http["MessageHandler", "Index"] = AssocMatchQ[<|"Method" -> "GET"|>] -> Function[x, index[x]] SocketListen[CSocketOpen["127.0.0.1:8010"], tcp@# &]; StringTemplate["open http://``:``/"][httplistener[[1]]["Host"], httplistener[[1]]["Port"]] // Print; While[True, Pause[1]];
где в директории, откуда вы запускаете скрипт, находятся два файла
index.wlx
Main = ImportComponent["main.wlx"]; <Main Request={$FirstChild}/>
А также файл с самим "приложением"
main.wlx
(* /* HTML Page */ *) <html> <head> <title>WLX Template</title> <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet"/> </head> <body> <div class="min-h-full"> <header class="bg-white shadow"> <div class="flex items-center mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8"> <h1 class="text-3xl font-bold tracking-tight text-gray-900">Title</h1> </div> </header> <main> <div class="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8"> Local time <TextString><Now/></TextString> </div> </main> </div> </body> </html>
Зайдя на страницу в браузере 127.0.0.1:8010, можно будет увидеть следующее

Как видно, все страницы представляют собой обычные HTML-документы с расширенным синтаксисом, таким образом, что теги, начинающиеся с заглавной буквы, считаются выражениями Wolfram Language
<TextString><Now/></TextString>
Формируя страницы из компонент, можно писать своего рода "веб-приложения". Далее я не буду вдаваться в подробности этого подхода, так как объем материала тянет на отдельную публикацию.
Интерпретатор языка Wolfram в браузере
Зачем? Он же уже есть!
Вернемся к простой задаче, как показать график из консоли, если кто-то не заплатил 300$ WRI. Откроем терминал и введем wolframscript, затем
Plot[x, {x,0,1}]
и увидим следующее
- Graphics -
На самом деле можно вытащить больше информации, применив
ExportString[Plot[x, {x,0,1}], "ExpressionJSON"]
[ "Graphics", [ "Line", [ "List", [ "List", 2.040816326530612e-8, 2.040816326530612e-8 ], [ "List", 3.0671792055962676e-4, 3.0671792055962676e-4 ],
Это ни что иное, как "рецепт" приготовления этого блюда. Остается найти повара, точнее написать. Как и на чем? Кажется очевидным, исходя из факта того, что у нас будет WebGUI
//набор будущих функций const core = {}; //интерпретатор const interpretate = (expr, env = {}) => { if (typeof expr === 'string') return expr; //строка if (typeof expr === 'number') return expr; //число //значит это выражение WL const args = expr.slice(1); return core[expr[0]](args, env); }
Окей, теперь давайте объявим выражение List. Я думаю, следующее будет ясным без дополнительных разъяснений
//async это круто! core.List = async (args, env) => { const list = []; const copy = {...env}; for (const i of args) { //запишем результат интерпретации списка или массива WL в массив list //env передается как глубокая копия, для того, чтобы изменения ее внутри не влияли на обзекты снаружи списка list.push(await interpretate(i, copy)); } return list; }
Почему так сложно? Покажу пример использования List
Graphics[{Red, Point[{-0.5,0}], {Green, Point[{0,0}]}, Point[{0.5, 0}]}]

Здесь видно, что {} или по-другому List[] изолирует "shared" параметры среды внутри от других листов, которые не являются вложенными. По этой причине в версии JS мы копируем переменную env, которая будет хранить такие опции, как цвет, толщина, да и все что угодно.
Остается реализовать Line, RGBColor, саму функцию Graphics и мы уже можем строить графики. Полный код приведен здесь, однако я приведу пример на псевдо-языке, как это может выглядеть
core.Line = async (args, env) => { const data = await interpretate(args, env); env.canvas.putLine(data, env.color); return null; } core.RGBColor = async (args, env) => { const color = await interpretate(args, env); env.color = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; }

В целом, нужно также рассмотреть ситуации, когда вложенные выражения меняются со временем и бегать по соответствующей ветке древа для того, чтобы пересчитать положение линий и т.д., например, для таких случаев

Важно заметить, что ядро Wolfram здесь никак не вовлекается, разве что для непосредственного семплирования функции, находящейся в Plot. Живую демонстрацию того, что может этот интерпретатор, если наполнить его всеми необходимыми примитивами, можно увидеть на странице с документацией.
Добавив еще пару функций и библиотеку THREE.js, получается делать такие картинки
VectorPlot3D[{x, y, z}, {x, -1, 1}, {y, -1, 1}, {z, -1, 1}, VectorColorFunction -> Function[{x, y, z, vx, vy, vz, n}, ColorData["ThermometerColors"][x]]][[1]]; %/. {RGBColor[r_,g_,b_] :> Sequence[RGBColor[r/50,g/50,b/50], Emissive[RGBColor[r,g,b], 5]],}; Graphics3D[{%, Roughness[0], Sphere[{0,0,0}, 0.9]}, Lighting->None, RTX->True]

Graphics3D[{ Blue, Cylinder[], Red, Sphere[{0, 0, 2}], Yellow, Polygon[{{-3, -3, -2}, {-3, 3, -2}, {3, 3, -2}, {3, -3, -2}}] }]

Как связать Wolfram Kernel и Javascript машину?
Нужен наиболее эффективный способ передачи данных. Кроме того, если это будет интерфейс блокнота, нужен API.
Мне никогда не нравилась идея классических API, которые сейчас имеются для взаимодействия фронтенда с бэкендом у современных приложений. Я испытываю легкое чувство неловкости, объявляя что-то подобное
//где-то на сервере/клиенте switch(command) { 'ping': printf('Pong!'); break; ... } //где-то на клиенте/сервере send({command: 'ping', payload: []});
Я, конечно, утрирую, но это точно плохой путь для блокнота Wolfram Language. У нас есть вебсокеты и интерпретатор, верно?
(* сервер *) serialize = ExportString[#, "ExpressionJSON"]&; WebSocketSend[client, Alert["Hello world!"] // serialize]
/* клиент */ const Socket = new WebSocket("ws://127.0.0.1:port"); Socket.onmessage = function (event) { interpretate(JSON.parse(event.data)); }; //какая-то функция нужн��я на фроентенде core.Alert = async (args, env) => { const text = await interpretate(args[0], env); alert(text); }
Разве не прелесть? Мы можем разговаривать с UI на том же языке, на котором работает ядро. Очевидно, что если целиться на ячеечную структуру блокнота, пригодятся также и такие функции
FrontEndCreateCell[...] FrontEndDeleteCell[...] FrontEndEvaluate[...] ...
Для обратной связи мы можем воспользоваться тем же форматом JSON, так как ничего не стоит отправить данные от JS по каналу веб-сокетов и на стороне Wolfram Kernel сделать подобное
ImportString[input, "JSON"] // HandlerFunction
либо еще проще и быстрее, минуя JSON
input // ToExpression
Многие скажут БЕЗОПАСНОСТЬ, однако, для локального приложения это не вреднее, чем позволять жить у себя NodeJS серверу с, в принципе, неограниченными правами на чтение/запись и запуск любого системного процесса.
Его величие - редактор
Это, вероятно, чуть ли не самое сердце любого блокнотного интерфейса. Самые очевидные функции могут быть получены почти любым JS редактором кода:
подсветка синтаксиса (желательно, любого)
навигация как в привычных редакторах, так и в Vim
скорость и легкость
Однако, вспомним про синтаксический сахар и требование к "редактируемости" выходных ячеек.
Как отобразить график внутри кода?
Декорации - этот концепт был введен еще давно до появления JS и веб-редакторов кода, но в полной мере воплощен только в CodeMirror 6. Представьте себе, что мы можем написать некий виджет, который заменяет собой выражение в виде строки
//выражение, которое ищется и заменяется const ExecutableMatcher = (ref) => { return new MatchDecorator({ regexp: /FrontEndExecutable\["([^"]+)"\]/g, decoration: match => Decoration.replace({ widget: new ExecutableWidget(match[1], ref), }) }) }; //сам виджет class ExecutableWidget extends WidgetType { constructor(name, ref) { super(); this.ref = ref; this.name = name; } eq(other) { return this.name === other.name; } //та самая функция которая заменяет текст на DOM элемент toDOM() { let elt = document.createElement("div"); //абстрактно создаем объект и исполняем его this.fobj = new ExecutableObject(this.name, elt); this.fobj.execute() this.ref.push(this.fobj); return elt; } ignoreEvent() { return true; } destroy() { } }
Это, так называемые, ReplacingDecorations. Исходный текст под ними остается нетронутым, а декорируемое выражение атомизируется, занимая место лишь одного символа для каретки. В этой связи возникает простая и элегантная идея отображения всех интерактивных объектов как выражение-"ключевая строка" FrontEndExecutable["id"] с ссылкой на объект JSON, где будет находиться рецепт для интерпретатора, чтобы отобразить красивый график

Остается лишь создать правила, по которым выражения будут заменяться на ключевые строки и передавать параллельно сопутствующие данные в виде JSON.
Не пугайтесь абстрактного кода, позже будет ссылка на CodeSandbox, где эти игры с редактором CodeMirror можно попробовать самим.
Что насчет математических выражений?
Грубо говоря, как отобразить дробь? А дробь в дроби в дроби ... Я полагаю, что лучше один раз показать на примере
и как это можно "закодировать"
CMFraction[1, CMSqrt[6]]
Остается пробежаться регулярными выражениями и распарсить это в редакторе как
Editor
CMFraction
Editor
CMSqrt
Editor
Зачем там написано Editor - я хотел лишь подчеркнуть, что числитель и знаменатель дроби, как и ячейка под корнем, обязаны быть такими же текстовыми редакторами с подсветкой синтаксиса, как и "основной" редактор

Итого на такое выражение потребуется создать 3 инстанса CodeMirror 6. Что не так плохо. А что на счет матриц?
CMGrid[{{1,0,0}, {0,1,0}, {0,0,1}}]

Итого 10 редакторов! Хотите 26? Тогда попробуйте посмотреть результат этого выражения
Table[If[PrimeQ[i], Framed[i, Background->Yellow], i], {i, 1, 100}]
Это скриншот со страницы документации проекта, где это работает вживую

Когда число доходит до 50-100, главный редактор уже значительно тяжелее переваривает изменения в дочерних редакторах.
Я оформил это расширение как отдельный NPM пакет, так люди могут использовать его в своих проектах с Wolfram Language независимо от фронтенда. Ссылка на песочницу.
в песочнице сочетания
Ctrl+-,Ctrl+/на выделенном коде создадут индекс и дробь, соотвественно.
Портативность
Так как редактор и ячейки все равно уже "живут" в браузере, значит, экспорт блокнота в HTML файл не составит труда. В предыдущих секциях мы договорились использовать веб-сокеты для управления структурой блокнота, соотвественно, если просто записать последовательность команд при старте блокнота вроде
commands = { FrontEndCreateCell[...], FrontEndCreateCell[...], ... };
и эмулировать это с помощью Javascript при открытии HTML-файла, то эффект будет тот же, что и в настоящем блокноте. Все необходимые библиотеки можно "утащить" туда же.
К примеру, документация к этому проекту сделана подобным образом

Сам факт того, что в браузере "крутится" обрезанная версия интерпретатора Wolfram Language, позволяет переносить часть логики напрямую в браузер. Таким образом, можно сохранить частичную интерактивность, даже без запущенного ядра Wolfram Engine.
Open-source блокнотный интерфейс Wolfram Language
В англоязычной среде и документации он встречается под названием WLJS Frontend. Почему так? Это не так важно.
документация (наполняется)
paypal
Если скомбинировать все методы и подходы, описанные в предыдущих частях, то получится следующее приложение

Особенность в том, что это всего лишь веб-сервер. А само приложение - это страница HTML с самым ванильным Javascript (за исключением библиотек, необходимых для отрисовки графики). Таким образом
пользователь может изменять стиль всего интерфейса;
ядро может произвольно менять структуру документа, а также вызывать любой Javascript код на ней (привет
eval());фронтенд доступен с любого устройства, способного открывать заглавную страницу Хабра;
можно экспортировать блокнот в HTML с частичным сохранением интерактивности;
оно принадлежит Вам целиком, не нуждается в интернете и работает локально.
Разумеется для удобства есть версия, где оно обернуто в ElectronJS, что позволяет привнести привычные для системных приложений доступы к проводнику и полноценному контекстному, а также оконному меню.
Ячейки
Зачем меня принуждают писать на Wolfram Language, когда я хочу сделать красивую диаграмму. Мне вообще-то нравится Mermaid

Идея обращения к анонимному файлу .mermaid мне кажется красивой. Давайте также обратимся к Markdown
.md # Hey, how was your day? I think it was fine. It is <?wsp TextString[Now] ?> and I am still writting my post for Habr

Нет, мне вообще на самом деле нравится Tailwind, и я хочу оформлять свои данные с помощью него
.html <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet"/>
.html <ul role="list" class="divide-y divide-gray-100"> <?wsp Table[ ?> <li class="bg-white shadow my-1"> <span class="flex justify-between round gap-x-6 px-3 py-5 hover:bg-sky-100"> <div class="flex gap-x-4"> <div class="min-w-0 flex-auto"> <p class="text-sm font-semibold leading-6 text-gray-900"><?wsp RandomWord[] ?></p> <p class="mt-1 truncate text-xs leading-5 text-gray-500"><?wsp RandomWord[] ?></p> </div> </div> </span> </li> <?wsp , {i,10}] ?> </ul>

Да зачем мне все эти сложности, я хотел график построить, но если бы можно было его еще покрутить...

Нет, я на самом деле хотел записать в файл
filename.txt Hello World

JS Cells
Сильной стороной являются ячейки типа .js, так как сам фронтенд написан в основном на JS. Как я уже описал выше, на сервере и на клиенте работают интерпретаторы Wolfram Language, соотвественно, подписываться на события друг друга или вызывать функции можно напрямую
.js const element = document.createElement('span'); core.ShowText = (args, env) => { element.innerText = await interpretate(args[0], env); } return element;
и затем из ячейки WL
ShowText["This is a text"] // FrontSubmit
Если пойти дальше, можно делать вещи чуть более сложные
С помощью расширения wljs-esm-support, можно также подключить Node и бандлер ESBuild, тогда у вас появится возможность использовать любой пакет с NPM. Как и сделал я, когда мне понадобилось подключить свой контроллер Nintendo Pro.
LLM Chatbook
Для каждой задачи подойдет свой язык - это бесспорно, но еще лучше, если эту задачу решат за тебя
.llm Plot a butterfly curve using Wolfram Language

Это дополнение было разработано @KirillBelovTest, который также является автором сервера и сейчас также активно принимает участие в разработке.
Благодаря тому, что llm имеет доступ ко всем ячейкам, и его вывод ничем не отличается от пользовательских ячеек, складывается приятное иммерсивное ощущение, что это не чат-бот, а некий гик, который случайно забежал и набрал что-то с клавиатуры в блокноте.
Редактор
Как и было описано ранее, он поддерживает математический ввод и синтаксический мёд в полной мере благодаря CodeMirror 6
Вопрос, как сделать autocomplete для тех символов, которые объявил пользователь? Как оказалось, с 1999 года в Wolfram Kernel есть следующая функция
$NewSymbol = Print["Name: ", #1, " Context: ", #2] &
Таким образом, можно буквально отслеживать все, что было создано за текущую сессию, и отправлять эти данные в браузер.
Динамика и интерактивность
Разумеется, что нельзя соревноваться с Mathematica, не имея в арсенале инструментов для создания динамических графиков и ползунков.
В процессе создания я значительно переработал этот концепт. Меня раздражала непредсказуемость поведения динамических выражений в Mathematica, которые рано или поздно приводили к падению всего приложения.
Зачем пересчитывать все заново, когда поменялись данные, если можно делать это селективно
core.Line = () => { //получаем все данные //обрабатываем //рисуем canvas.putLine(); } core.Line.update = () => { //обновляем canvas.updateLine(); }
Я к тому, что у каждой функции должен быть метод для обновления, если данные поменялись. И на каждом выражении можно принять решение о том, как и что пересчитать.
Минусом такого подхода является пожалуй то, что этот метод нужно писать вручную для каждого "важного" для пользователя выражения (в основном графические примитивы), как в Mathematica по-умолчанию интерпретатор проходится по всему древу одинаково, что при первом запуске, что при обновлении данных.
Следующее изменением - событийно-ориентированный подход. Возьмем слайдер
slider = InputRange[-1,1,0.1, "Label"->"Length"]
и привяжем к нему функцию-обработчик
EventHandler[slider, Function[l, length = l]]; EventFire[slider, 0]; (* шарахнем один раз, чтобы все инициализировалось *)
А теперь сам элемент, который будет под контролем
Graphics[{Cyan, Rectangle[{ -1,1 }, {length // Offload, -1}] }]

Очевидно, это сразу больше кода, однако, хирургическая точность таких методов позволяет эффективно обновлять данные
Такую отзывчивость сложно представить в Mathematica. Либо такой пример
Обладателей Nvidia RTX приглашаю взглянуть на эти две сферы
Graphics3D[{ {Emissive[Red], Sphere[{0,0,2}]}, {White, Sphere[]} }, Lighting->None, RTX->True]

Расширяемость
Разумеется, имеется система плагинов/расширений, где возможно добавить новые типы ячеек, влиять на ход исполнения ячеек, расширять библиотеку функций и т.п. Сам проект собран из более 10 расширений, половина из которых являются системными и могут работать отдельно.
Хороший пример - анимация на сайте конференции Wolfram Saint-Petersburg 2023, где используются всего лишь два компонента:
wljs-interpreter - интерперататор WL
wljs-graphics-d3 - библиотека реализующая примитивы
Graphics
Слайды/Презентация из компонентов
Работая в академической среде, мне никогда не нравилось готовить презентации к докладам, на визуальное исполнение которых уходит большая часть времени, вместо самого содержания. Почему так? Поясню:
для обработки данных используется среда A
для визуализации среда Б
для слайдов среда С
Передача данных между ними осуществляется путем сериализации в файл, что, скажем, не очень быстро и удобно. Ах да, не забудем про
перетаскивание блоков с информацией / копирование их на другие слайды
В open-source сообществе уже есть решения на этот счет, скажем, - RevealJS с возможностью писать слайды на языке Markdown. Однако, здесь все равно не хватает компонент и, как собственно, способа передачи графических данных.
Markdown поддерживает HTML из коробки, значит, у нас уже есть доступ к стилям и оформлению, если хочется. Допустим, как сделать две колонки?
.html <div> <div style="width:50%; float:left" >1</div> <div style="width:50%; float:right">2</div> </div>
Было бы здорово сделать такой компонент. С использованием WLX это возможно
.wlx Columns[C1_, C2_] := With[{SR = If[NumberQ[Ratio], 100.0 Ratio, 50]}, <div> <div style="width: {SR}%; float:left;"> <C1/> </div> <div style="width: {100-SR}%; float:right;"> <C2/> </div> </div> ]
Теперь вернемся к нашим слайдам, мы ведь с этого начали
.slide # Title <Columns> <Identity> First column </Identity> Second one </Columns>

Не обращайте внимание на Identity оператор, так как он нужен чтобы подсказать WL, что вторая фраза - это уже второй аргумент к функции Columns.
А что насчет графиков?
Plt3D = Graphics3D[Cuboid[]];
.slide # Embed some figures Even 3D <div style="text-align: center; display: inline-flex;"> <Plt3D/> </div> Try to move it using your mouse

Можно привязаться к событиям: появление фрагмента на слайде или его смена, либо вставлять напрямую компоненты ввода (ползунки) и кнопки. Ниже представлена презентация, которую я использовал на докладах в 2023 году
Она, как и все другие примеры, доступна в самом приложении фронтенда через оконное меню File - Open Examples.
Приложения на WLX
Здесь пример, как можно использовать динамику и язык разметки WLX, чтобы набросать простенькую утилиту с графическим интерфейсом для распознавания таблиц с картинок
.wlx LeakyModule[{img, output1, output2, Ev, pipe, EditorRaw, EditorProcessed}, (* поле drop file *) Ev = InputFile["Drop an image here"]; (* вешаем на него обработчик *) EventHandler[Ev, Function[file, (* импорт по формату и само распознование текста *) pipe = ImportByteArray[file["data"]//BaseDecode, FileExtension[file["name"]]//ToUpperCase]; pipe = Binarize[pipe]; pipe = TextRecognize[pipe]; output1 = ToString[pipe, InputForm]; output2 = ToString[(ToExpression /@ StringSplit[#, " "]) &/@ StringSplit[pipe, " "], InputForm]; ]]; (* выходные значения *) output1 = "- none -"; output2 = "- none -"; (* два поля вывода с подсветкой синтаксиса *) EditorRaw = EditorView[output1 // Offload] // CreateFrontEndObject; EditorProcessed = EditorView[output2 // Offload] // CreateFrontEndObject; (* шаблон разметки выходной ячейки в HTML (WLX) *) <div> <div style="display: flex;"><Ev/></div> <p>Raw string</p> <div style="margin-top: 0.5em; margin-bottom: 0.5em; display: flex; border: 2px dashed skyblue;"><EditorRaw/></div> <p>Processed string</p> <div style="margin-top: 0.5em; margin-bottom: 0.5em; display: flex; border: 2px dashed deepskyblue;"><EditorProcessed/></div> </div> ]
Видео в действии
Вывод ячейки в окно
Это побочная функция, созданная для возможности показывать слайды. Однако, также может быть полезной, когда блокнот становится слишком большим для навигации. Мой рабочий стол обычно выглядит как-то так

Ограничения
Обойти достижения WRI последних 20-лет двум разработчикам за год невозможно и бессмысленно (у нас нет такой цели и не будет). WLJS Frontend это альтернативный инструмент со своими преимуществами и недостатками, где для решения архитектурных проблем в одних областях были приняты компромиссные решения в других, но не замена.
@KirillBelovTestи я постарались скомпилировать бинарные файлы компонент веб-сервера под каждую платформу, однако, различия все же встречаются, что периодически пополняет банк Issues на гитхабе. Если нужна "горячая поддержка", вступайте в группу поддержки в Телеграмме.
Из других примеров, до сих пор нет функции Circle в пакете Graphics, просто потому, что она редко используется в типичных plot-функциях Mathematica и чьи-то руки не дошли до того, чтобы написать десяток строчек кода на JS. Тем не менее, большая часть функций, которая касается построения данных по точкам, уже покрыта - смотрите здесь.
Проект развивается и дополняется почти каждый день. Это не готовый продукт, в отличие от Wolfram Mathematica.
"Этот список" из цитаты в начале статьи
Вызов брошен, а как же ответ? Вот адаптированные сниппеты из списка, который показал автор
d=theta@t-phi@t; sol = NDSolve[{#''@t==-#4#2''[t]Cos@d-##3#2'@t^2Sin@d-Sin@#@t&@@@{{theta,phi,1,.5},{phi,theta,-1,1}},theta@0==2,phi@0==1,theta'@t==phi'@t==0/.t->0},{theta,phi},{t,0,60}];
With[{h = {Sin@#@#2,-Cos@#@#2}&}, With[{f = theta~h~u+phi~h~u /. First[sol], m1 = theta~h~u /. First[sol]}, LeakyModule[{points, time = 0., handler, task}, EventHandler[EvaluationCell[], {"destroy"->Function[Null, TaskRemove[task]]}]; handler := (points = Table[f, {u, 0., time,0.1}]; pendulum1 = Table[m1, {u, {time}}] // First; pendulum2 = points // Last;); handler; task = SetInterval[ time = time + 0.1; handler; If[time > 59., TaskRemove[task]]; , 70]; Graphics[{ Line[points // Offload], PointSize[0.05], Red, Point[pendulum1 // Offload], Point[pendulum2 // Offload], Line[{pendulum1 // Offload, pendulum2 // Offload}] }, Controls->True, Axes->True, TransitionDuration->10, TransitionType->"Linear"] ] ] ]
и демонстрация, если это запустить в блокноте

Другой "сниппет" из той же ветки
StreamPlot[{x^2,y},{x,0,3},{y,0,3}]

LeakyModule[{data, frame, i = 1}, data = CellularAutomaton[{224,{2,{{2,2,2},{2,1,2},{2,2,2}}},{1,1}}, Table[RandomInteger[{0,1}], {x,200}, {y,400}], 50]; frame = 255 data[[i]]; EventHandler[EvaluationCell[], {"destroy"->Function[Null, TaskRemove[task]]}]; task = SetInterval[ i = i + 1; frame = 255 data[[i]]; If[i > 49, data = CellularAutomaton[{224,{2,{{2,2,2},{2,1,2},{2,2,2}}},{1,1}}, data//Last, 50]; i = 0; ]; , 50]; Image[frame // Offload] ]

SphericalPlot3D[Re[Sin[\[Theta]]Cos[\[Theta]]Exp[2I*\[CurlyPhi]]],{\[Theta],0,\[Pi]},{\[CurlyPhi],0,2\[Pi]}]

Спасибо за внимание ?
UPD: Грамматика
