Я долго искал идеальный фреймворк для технологического блоггинга. Хотелось чего-то, что позволяет не просто писать текст, а сразу показывать результат работы скриптов, визуализировать данные и делать это красиво. При этом важно, чтобы всё было статическим, чтобы можно было хостить бесплатно, без серверов и лишних заморочек.

И вот, наконец, мне повезло — я наткнулся на Observable Framework. Эта штука умеет всё вышеперечисленное и даже больше. В этой заметке расскажу, что это за зверь, как его запустить вместе с GitHub и Azure Static Web Apps, и как подружить всё это дело с PowerShell.

В двух словах об Observable Framework

Observable Framework — это статический генератор сайтов от команды ObservableHQ, который сочетает в себе силу интерактивных ноутбуков и удобство современного фронтенда. В отличие от типичных блог-платформ, он позволяет:

Пишете заметки в Markdown, встраиваете интерактив — и вуаля, у вас современный статичный сайт, готовый к деплою в GitHub Pages или Azure Static Web Apps. Всё интерактивное при этом работает в браузере, без серверной части. Просто, быстро, красиво.

Как настроить Observable Framework

Начать можно буквально в пару кликов — вот отсюда. Репозиторий-шаблон уже содержит devcontainer и всё нужное. Делаем свой репозиторий из шаблона — и можно сразу писать посты.

Новая заметка — просто новый файл, например /src/my-new-post/index.md.

Но Markdown сам по себе скучен. А хочется интерактива: таблиц, графиков, визуализаций. Для этого нужны данные. И вот тут начинается магия.

Observable Framework позволяет вместо данных подсовывать исполняемые скрипты. Например, вы пишете:

const allData = await FileAttachment("allData.csv").csv()

А allData.csv на самом деле не существует. Зато есть allData.csv.ps1. Фреймворк вызовет PowerShell-скрипт, получит результат — и подгрузит данные как будто это был обычный CSV-файл.

Чтобы это работало с PowerShell, нужно внести пару изменений.

1. Добавляем PowerShell в devcontainer

В devcontainer.json добавляем поддержку PowerShell:

{
  "image": "mcr.microsoft.com/devcontainers/universal:2",
  "hostRequirements": {
    "cpus": 4
  },
  
  // skipping stuff
  
  "features": {
    "ghcr.io/devcontainers/features/powershell:1": {
      "version": "latest"
    }
  }
  
  // skipping stuff
}

2. Указываем интерпретатор .ps1 в observablehq.config.js

export default {

  // skipping stuff

  interpreters: {
    ".ps1": ["/usr/bin/pwsh"]
  },

  // skipping more stuff

};

Теперь все .ps1 будут исполняться через pwsh, как и нужно.

Больше про Data Loaders — здесь

Пример PowerShell-скрипта

Создаём /src/my-new-post/data.csv.ps1:

Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/vega/vega/refs/heads/main/docs/data/movies.json' | 
    ConvertFrom-Json | 
    Select-Object -Property Title, 'Worldwide Gross', 'US Gross', 'IMDB Rating' | 
    ConvertTo-Csv

Скрипт просто прочитывает откуда-то из Интернета json файл, фильтрует его, преобразует в csv и возвращает в стандартный вывод, это важно!

Теперь в заметке просто вставляем:

Inputs.table(FileAttachment("data.csv").csv())

И видим интерактивную таблицу прямо на странице.

Примеры живых визуализаций, сделанных по этому принципу:

Azure Static Web Apps

Для публикации используем Azure Static Web Appsбесплатный сервис от Microsoft для хостинга статических сайтов. Интеграция с GitHub простейшая: подключаете репозиторий, указываете папку сборки (dist) — и готово. При этом ,если вы дадите доступ к своему github, то Azure portal сможет создать workflow-файл и нужные секреты в проекте.

Но есть один нюанс: при сборке Azure использует Oryx, который запускается в контейнере без PowerShell. То есть скрипты .ps1 не сработают. Решение — собирать сайт до запуска таски деплоя, а Oryx пусть просто загрузит результат.

Пример рабочего GitHub Actions workflow, собственно того самого, созданного самим Azure, просто слегка подработанного напильником:

name: Azure Static Web Apps CI/CD

on:
  push:
    branches:
      - main
  pull_request:
    types: [opened, synchronize, reopened, closed]
    branches:
      - main

jobs:
  build_and_deploy_job:
    if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
    runs-on: ubuntu-latest
    name: Build and Deploy Job
    steps:
      - uses: actions/checkout@v3
        with:
          submodules: true
          lfs: false

      # ADDING THIS STEP!!!
      - name: Install dependencies and build
        run: |
          npm install
          npm run build

      - name: Build And Deploy
        id: builddeploy
        uses: Azure/static-web-apps-deploy@v1
        with:
          # SKIPPING SECRETS :)
          action: "upload"

          # SETTING CORRECT app_location and output_location
          app_location: "dist"
          api_location: ""
          output_location: "dist"
          
          # DISABLING build, as it was built on the previous step!
          skip_app_build: true
          skip_api_build: true

# SKIPPING OTHER STUFF ....

Теперь ваш сайт собирается на GitHub Actions, а Azure Static Web Apps просто его размещает. Работает стабильно, быстро, и бесплатно.


Если вам нужен блог, в котором можно запускать скрипты, визуализировать данные и при этом не платить за хостинг — Observable Framework + Azure Static Web Apps — это то, что надо.