edit-text

edit-text is a Markdown-compatible document editor that supports collaborative editing. Its server and client are written in Rust, and its frontend uses TypeScript and WebAssembly.

For installation instructions, please read Getting Started.

Getting Started

Requirements

Installing rustup: edit-text is written in Rust, and so you will need a Rust compiler in order to serve the application. Rust may be installed using your system package manager, but the preferred way to download and install Rust is through the rustup toolchain available at rustup.rs. To check if you have rustup installed, you can run the following command:

$ rustup show active-toolchain
nightly-2018-09-25-x86_64-apple-darwin  # for example

This command should print a rust version that is equivalent to the contents of the ./rust-toolchain file. This is the version of the nightly Rust compiler the project currently depends on. This file is updated periodically; rustup will automatically manage downloading and using the correct compiler version for you.

Installing Node.js: To build the frontend you will need to install something called Node.js and Yarn. To install Node.js, see installation instructions for your OS. To check if you have a recent version of Node.js installed, see if the output of this command is >= v6.0.0:

$ node -v
v10.12.0  # for example

The frontend is written partly in TypeScript, and the build tool uses Yarn to install and manage its JavaScript package dependencies. To install yarn, follow the installation instructions for your OS or just run npm i -g yarn. To see if Yarn is installed and available:

$ yarn -v
v1.10.1  # for example

Usage

Clone the repository from Github:

git clone https://github.com/tcr/edit-text

Build commands are executed using the ./tools script. You can rebuild individual edit-text components with ./tools server-build, ./tools frontend-build, etc. Run ./tools help for more information.

Run the server

The production configuration of edit-text is a long-running server process, and one or many WebAssembly + TypeScript clients running in the browser that connect to it.

You can build the WebAssembly client as well as the frontend webpack module using the following command:

./tools frontend-build

This cross-compiles all frontend code and pulls in the compiled WebAssembly binary, using wasm-bindgen to talk to the frontend.

In your terminal session, you can then run this command to start the server:

./tools server

Now open http://localhost:8000/ and you are brought to a welcome page to start editing text!

Development Workflow

A simple development pattern is to launch ./tools server in one window, and to watch and recompile frontend code whenever it's modified with this command in another window:

./tools frontend-watch

This command watches the edit-frontend directory and continuously builds its after each change. The frontend will periodically display a notification if a newer version of the client code has been compiled.

After you make changes to edit-server/, edit-common/, or oatie/, you should kill and re-run the ./tools server command to rebuild and launch it. The frontend-watch command will automatically rebuild code that it depends on for you.

Development

edit-text uses a build system written in Rust, ./tools, which is a basic wrapper over cargo and npm commands and other essential project functionality. This is used to invoke tests as well as launch the edit-text server.

  1. System Overview
  2. Build with ./tools
  3. Tests

System Overview

edit-text is built from the ground up as a collaborative text editor. It uses operational transform to merge updates from multiple clients, so it requires a synchronizing server. The server is also in charge of storing page content, so that every page can be shared via its URL.

There are four components in the system:

  • The Server, which serves HTTP content, a GraphQL endpoint for performing page-level commands, and a WebSocket endpoint for synchronizing document content.

  • The Client, which can connect to a server and synchronize its document content. It sends client-side modifications (in the form of operations) to the server, and receives updated content (in the form of operations) from the server after any client submits an update.

  • The Controller, which receives UI-level event updates from the frontend and converts it into operations on the client document.

  • The Frontend, which is the editor UI. The current document is rendered as a component inside the frontend, and interactions with this component are forwarded to the controller. The frontend also manages the toolbar, notifications, and dialog boxes.

Each of these four components can be controlled by their commands, defined in commands.rs, effectively providing an asynchronous API for each component in the system.

Here is a diagram representing communication between the Server, Client, Controller, and Frontend:

Server (Rust) Client Client ... Client (Rust B C A + WebAssembly) Controller (TypeScript) Frontend

Notice that Client and Controller are part of the same component. This is useful from an API perspective: commands that are addressed to the client will always originate from the server, and commands addressed to the Controller will always originate from the frontend. On the implementation level, however, Client and Controller are the same process.

The server is a command-line program called edit-server. In release mode, it bundles all client-side code and can be uploaded to a server to run the program directly.

The edit-text client is written in Rust and can be run both in the browser (to power the editor) or from the command line (for tools like the client proxy, and client replay). If you use edit-text in its normal configuration, the Client, Controller, and Frontend all run in your browser as WebAssembly and JavaScript. In proxy mode, the Client and Controller run as a command line program.

The Frontend is written in TypeScript.

Crate/Module overview

The top-level crates/modules are these:

  • oatie/ is the operational transform library
  • edit-common/ contains code shared by all edit-* crates
  • edit-client/ contains the Client and Controller
  • edit-server/ contains the Server binary
  • edit-frontend/ contains the Frontend code as a Node module compiled with webpack

Build with ./tools

edit-text has a custom build script written in Rust that you invoke by running ./tools in the project root directory. This is a basic wrapper over cargo and npm commands and other essential project functionality, and provides an easy way to launch the edit-text server and compile the frontend JavaScript bundle.

To see a list of build commands, open the project directory in your terminal and run the following command:

./tools help

NOTE: If you are on Windows running in cmd.exe, you will need to invoke the build tool with .\tools instead. Please substitute ./tools with .\tools throughout this guide.

Building the Server

To build the edit-text server:

./tools server-build

To build and launch the server on HTTP port 8000:

./tools server

Building the Frontend

The frontend is all the JavaScript code that runs in the browser, and optionally including the WASM build system. To build the frontend, run this from the root directory:

./tools frontend-build

If you want to launch a long-lived script to build the frontend and rebuild each time a frontend file changes:

./tools frontend-watch

Just compiling the WebAssembly client

Building just the frontend WebAssembly component generated from edit-client can be done using this command:

./tools wasm-build

This will compile the wasm bundle and save it to edit-frontend/src/bindgen, which will be linked with the frontend code bundle. WASM is automatically compiled during the frontend-build or frontend-watch steps.

Testing

This command will run all unit tests as well as integration tests (end-to-end testing using a machine-controlled browser).

./tools test

If you're in a continuous integration (CI) environment, you can perform all relevant test steps for your branch by running:

./tool ci

Client Proxy

If you are testing changes to the edit-client library, you have the option of choosing between running client code in the browser (via WebAssembly) or running it in a local Rust process, having all commands proxied through websockets.

./tools client-proxy

Building the book

You can build the book with the book-build command:

./tools book-build

Or watch for all changes as they are being made with book-watch.

./tools book-watch

By navigating to http://localhost:3000/, you'll see the page refresh automatically as you edit markdown files under docs-src/.

Running edit-text with a client in proxy mode (for debugging)

NOTE: You can skip this section if you are just getting started.

Debugging WebAssembly code is harder (in most ways) than debugging a local Rust binary. edit-text supports running the client as an independent "proxy". An edit-text server running in one terminal connects to a client proxy running in another terminal, and communicates with frontend code running in the browser (TypeScript) over WebSockets. This client proxy is all code that would normally be cross-compiled to WebAssembly, but runs locally in your terminal and supports the same backtrace and debugging support as a local binary.

You'll need two terminal sessions to run in this mode. First, start the server, and specify that you want to connect to a client proxy using --client-proxy. Without this argument, the server will expect server connections from WebAssembly instead.

./tools server --client-proxy [--release]

In another terminal session, you can start the proxy. (It's recommended you compile in release mode, as client code is much slower in debug mode.)

./tools client-proxy [--release]

Then you can open http://localhost:8000/ as before in your browser, and monitor the client-proxy terminal for status of the clients that your browser is connected to.

You will see any failures appear in the client-proxy code that would appear in the browser console when in WASM mode. If you encounter a panic or fatal error, this "proxy" mechanism of debugging usually gives much more information about where the error originated. Note that aside from running as a binary, there should be no differences in behavior between the client-proxy and the client in Webassembly.

Testing

There are two types of tests, integration tests and unit tests.

Unit Tests

To run unit tests, run ./tools test unit.

Integration Tests

Integration tests use a "headless" browser to run

Run ./tools test integration to run the test suite with integrated tests. Run ./tools test all to run unit tests and integration tests.

This runs simulated tests using headless browsers running concurrent editing operation. You should install a WebDriver implementation such as geckodriver:

  • macOS: brew install geckodriver
  • Ubuntu: npm install -g geckodriver
  • Windows: choco install selenium-gecko-driver