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.
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:
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 libraryedit-common/
contains code shared by all edit-* cratesedit-client/
contains the Client and Controlleredit-server/
contains the Server binaryedit-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