Pluto.jl

repository·main·Indexed 26 days ago

https://github.com/juliapluto/pluto.jl

A reactive, lightweight notebook environment for Julia featuring a workspace with no hidden state, automatic package management, and interactive UI elements via the @bind macro. Notebooks are saved as pure .jl files and support automatic dependency tracking and reproducibility.

Tokens
2.3K
Snippets
3
Records
13
Agent score
90%

What's inside Pluto.jl

  1. How Pluto localization works

    main

    Pluto uses the i18next library to manage UI translations via standard JSON files. Each language has its own JSON file in the frontend/lang/ directory.

    Key Concepts

    • Fallback: If a translation is missing in the selected language, Pluto falls back to English.
    • Interpolation: Strings can include placeholders like {{variable}} (e.g., "Welcome to {{pluto_logo}}"). You must preserve the exact placeholder syntax in your translation, though you can change its position.
    • Pluralization: To handle different plural forms, use the following suffixes on your keys:
      • _zero
      • _one
      • _two
      • _few
      • _many
      • _other

    Example for pluralization:

    "t_new_messages_zero": "You have no new messages 🥺",
    "t_new_messages_one": "You have {{count}} new message",
    "t_new_messages_other": "You have {{count}} new messages"
  2. Understand the `frontend/imports/` dependency pattern

    main

    All third-party browser dependencies used by the Pluto frontend are wrapped in tiny ES-module files located in frontend/imports/. The rest of the frontend/ directory must import exclusively from this folder; direct CDN URLs should never appear elsewhere in the codebase.

    Each dependency typically consists of two files:

    • Foo.js: A wrapper that imports from a pinned CDN URL and re-exports the required surface. It uses // @ts-ignore or // @ts-nocheck on the CDN import line.
    • Foo.d.ts: A type stub. This can be hand-written, a one-liner re-exporting from an npm package, or auto-generated.

    Naming Convention for Collisions: If an npm package name collides with the wrapper filename (e.g., lodash), use the -es suffix for the wrapper to avoid TypeScript circular alias errors (TS2303).

    • Example: lodash-es.js (wrapper) and lodash-es.d.ts (type stub).
  3. Understand Pluto's reactivity and notebook format

    main

    Pluto notebooks are reactive environments composed of small Julia code cells.

    • Reactivity: When you change a variable or function, Pluto automatically re-runs all cells that depend on it. Dependencies are determined via intelligent syntax analysis.
    • File Format: Notebooks are saved as pure .jl Julia files. This allows you to import them into a regular editor as standard Julia code.
    • Exporting: You can export notebooks as HTML or PDF documents, with the ability to hide code and reorder cells to control the narrative flow.
  4. Setup and contribute a new language to Pluto

    main

    Follow these steps to contribute a new language or dialect to Pluto:

    1. Initial Setup

    1. Fork the Pluto.jl repository.
    2. Clone your fork locally.
    3. Create a new branch for your contribution.

    2. Create the Language File

    Create a new JSON file for your language in the frontend/lang/ directory. Initialize it with the language name in its native script:

    {
        "t_language_name": "Ελληνικά"
    }

    3. Synchronize Keys

    Run the following command to synchronize your new JSON file with the English master file. This will add all existing keys to your file as empty strings so you can fill them in:

    julia update_languages.jl

    4. Translate and Submit

    • Edit the JSON files to add your translations.
    • Commit, push, and submit a pull request to the main Pluto.jl repository.

    5. Testing Locally

    To see your changes in real-time:

    1. In Julia, add your local fork as a development package:
      ] dev ~/path/to/your/Pluto.jl
    2. Start Pluto. The terminal should indicate: "It looks like your are developing the Pluto package, using the unbundled frontend."
    3. Refresh the Pluto browser window to see your updated strings.
    4. When finished, revert to the official version using ] add Pluto.
  5. Build the Pluto frontend using the Pluto Frontend Bundler

    main

    The Pluto Frontend Bundler uses Parcel and a custom resolver (parcel-resolver-like-a-browser) to crawl the Pluto application and bundle its dependencies. This allows for a compiled version of the frontend that can run without an internet connection.

    To build the standard and offline distributions, follow these steps from the frontend-bundler directory:

    cd frontend-bundler
    
    npm install
    
    rm -rf ../frontend/.parcel-cache
    rm -rf ../frontend-dist
    rm -rf ../frontend-dist-offline
    npm run build
    npm run build-offline
  6. Add a new dependency to `frontend/imports/`

    main

    To add a new third-party dependency:

    1. Create Foo.js in frontend/imports/. Import from a pinned CDN URL and re-export only the necessary surface (avoid export * to aid tree-shaking).
    2. Add Foo.d.ts. For packages with existing types, use a stub like import * as Foo from "foo"; export default Foo; and add the package to frontend/package.json devDependencies.
    3. Strict Import Rule: Only import Foo from ./imports/Foo.js within the frontend/ directory. Never use CDN URLs directly in other files.
    4. Perform the type-check, dev smoke-test, and bundler build steps defined in the update guide.
  7. Update a dependency in `frontend/imports/`

    main

    Follow these steps to update a third-party dependency:

    1. Find the version: Locate the @X.Y.Z version string in the relevant *.js file.
    2. Pick a new version: Verify the version via the package's changelog to ensure the re-exported surface hasn't broken.
    3. Edit the URL: Update the version in every URL within that file. Keep the CDN host consistent.
    4. Update the type stub:
      • For npm re-exports (lodash.d.ts): Update frontend/package.json and run npm install in frontend/.
      • For hand-written stubs (immer.d.ts): Adjust manually if the API changed.
      • For auto-generated stubs (Preact.d.ts): Re-download or re-generate the types.
    5. Type-check:
      cd frontend && npm install
      cd .. && tsc --noEmit --strictNullChecks false
    6. Smoke-test in dev: Run Pluto, hard-reload the browser, and test the feature. If code seems stale, wipe frontend/.parcel-cache/.
    7. Bundle-test:
      cd frontend-bundler
      rm -rf ../frontend/.parcel-cache ../frontend-dist ../frontend-dist-offline
      npm run build
      Restart Pluto to serve frontend-dist/ and re-test.
    8. Run E2E suite: Execute the frontend E2E tests (refer to test/frontend/README).
  8. Use the built-in package manager

    main

    Pluto automatically manages a dedicated package environment for every notebook using syntax analysis.

    • Automatic Management: You can directly import any registered Julia package (e.g., Plots or DataFrames) without manual installation steps.
    • Reproducibility: The exact package environment required to run the notebook is stored within the notebook file itself. When sharing a notebook, the recipient's Pluto instance will automatically use the correct package versions.
  9. Use the `t` function for translatable text in JavaScript

    main
    To ensure user-facing text is translatable in the Pluto frontend, do not use raw strings. Instead, use the t function with a localization key. The t function retrieves the string corresponding to the user's preferred language from the JSON localization files.
  10. Create interactive widgets with `@bind`

    main

    Pluto provides a @bind macro to create live connections between browser-based UI widgets and Julia variables. When a user interacts with a widget (like a slider), the bound Julia variable updates, triggering reactivity in all dependent cells.

    For basic UI components like sliders and buttons, use the PlutoUI package.

  11. Special dependency implementation notes

    main

    Certain dependencies in frontend/imports/ require specific handling:

    • AnsiUp.js: Requires .default twice due to jsdelivr CJS re-wrapping.
    • CodemirrorPlutoSetup.js: Pulls from a specific repo. Do not hand-edit CodemirrorPlutoSetup.d.ts; it is generated in the source repo and must be copied verbatim.
    • highlightjs.js: Contains three coordinated URLs (core, julia, and julia-repl). All must be bumped together.
    • immer.js: The default export is pinned to produce for backwards compatibility. setAutoFreeze(false) is required for specific state paths.
    • lang_imports.js: A special file that bulk-imports JSON locale files. Add new locales here and in frontend/lang/lang.js.
    • Preact.js: Coordinates three imports (preact, preact/hooks, htm) using a specific pin (pin=v113&target=es2020).
    • PreactCustomElement.js: This is vendored source, not a CDN import. To update, re-diff against upstream and manually re-apply local modifications.