You can follow along with the development of this the new API on the next
branch.
Project History
Originally, remirror was built as a fun side project; a challenge to myself to see if I could create an extensible editor for React. It was inspired by the implementations of slate, draftjs, tiptap and @atlaskit/editor. I hoped to create something with great performance, excellent built in plugins, and popular prebuilt editors for easier consumption.
It turned out, several people had started to bump into some problems of the currently available editors and looked to remirror as a solution. Although happy with the interest, I was also aware of the limitations of remirror. While I wanted to push forward with some of my initial plans, I quickly bumped into limitations. I just couldn’t justify the time needed to work on the project at the expense of actual paid work. It started to look like remirror would become a project which died before it had ever had the chance to truly live.
Recently, the situation has changed. I am now able to divert more of my attention to reworking the API and integrating the project into an ambitious startup seeking to build their realtime collaboration hub with remirror. They see the potential of this project and have chosen to back it in the most amazing way.
Changes
The rest of this post will outline what changes are coming soon.
Documentation first approach
A new documentation first approach will be taken to developing the next version of remirror. The docs site will be rebuilt with this in mind.
Currently documentation is almost non-existent. This is true even for some of the most basic features. With the incoming changes to the API, it feels like a perfect time to address this situation.
The priority is adding detailed documentation around:
- Creating a new editor from scratch.
- Creating the editor as a controlled component.
- Using the editor packages (e.g.
@remirror/editor-social
). - Detailed instructions on each standalone extension -
@remirror/extension-*
. - Styling the editor.
New design principles
- Make it feel like React
- Users shouldn’t ever need to be aware that under the hood we are using a dom based library (prosemirror).
- Don’t surprise the user
- Similar to the above, there should be no edge cases where the user is forced to exit from the expected React paradigm.
- Treat TypeScript as a first class citizen.
- This library will always be useable with JavaScript but it should aim for an incredible TypeScript user experience.
- Auto completions, auto suggestions, and first class inline documentation.
- Like an invisible hand the types should make the user feel like they’re being guided to the right solution.
- Avoid magic.
- Avoid magic strings or concepts that solve one problem but increase complexity of the API namespace.
- Keep the API namespace small.
- There should be one way to accomplish a task.
- Right now there is a
ManagedRemirrorProvider
, aRemirrorProvider
, aRemirrorManager
. As a first time user of the library this is very confusing. Reduce the namespace. Make it possible to do things in a well tested and simple to understand way.
Improved Core API
Improved configuration
EDIT: The following was replaced with a single Options
interface which can be configured as explained in the updated docs.
~The configuration of each extension will be split into two parts.~
- ~
config
~ - Options which update the schema and core functionality of the editor.- e.g. an option which updates the types of content accepted by a NodeExtension would be a
config
option since a change would require the entire editor to be updated and any now invalid contents to be removed or updated.
- e.g. an option which updates the types of content accepted by a NodeExtension would be a
- ~
props
~ - Options which are only used during the runtime operations.- e.g. most callbacks will fall under this option. The
MentionExtension
hasonChange
andonExit
options which will become props since they are only called after the editor has been inserted into the DOM.
- e.g. most callbacks will fall under this option. The
No exposed class
class
Classes will be removed from the public API. You will never need to use class SimpleExtension extends Extension {
or new SimpleExtension()
. There will be a better API for creating extensions. This will avoid the issues that can crop up when classes are extended in compiled code which uses functions to represent classes.
import { BaseExtensionConfig } from '@remirror/core';
interface Config extends BaseExtensionConfig {}
interface Props {
message: string;
}
// Create an extension which logs on the `enter` key being pressed.
// There are three extensions `plain` | `node` | `mark` and the extension creator allows for the creation of all three.
const LogWhenEnterKeyExtension = ExtensionCreator.plain<Config, Props>({
name: 'logWhenEnterKey',
defaultConfig: {},
defaultProps: { message: 'No message defined' },
keys: ({ config, props }) => ({
Enter: () => {
console.log(props.message);
return true; // Also runs the next action.
},
}),
});
// Creates the extension instance which can be used within the editor.
const logWhenEnterKey = LogWhenEnterKeyExtension.of({ message: 'enter key pressed' });
EDIT: The above was abandoned. While it would be nice to hide the classes from the public API, it wasn’t possible to do this in a way that also met another key goal of First Class Typescript Support. Without classes every part of the object that can be configured needs to be assigned a generic parameter. I had started with this approach, but once I reached 7 generic parameters I realised it was becoming unmanageable.
With TypeScript classes are types. This means that the class can have properties inferred at any time after declaration. Objects and functions don’t have that luxury. The type must be declared upfront.
I’ll write a blog post as to why I took this decision later on.
Improved React API
This builds on the post from @benjie.
@remirror/react
API will be simplified to only include the following exports (not including types).
<RemirrorProvider extensionManager={extensionManager} />
- Injects the editor context into the sub components.useRemirror()
- Retrieves the remirror context when used within a component wrapped by the RemirrorProvider. It provides utilities likegetRootProps
which is a function that should be called to decide the root dom element for the prosemirror editor.useExtension(Creator: ExtensionConstructor, options: OptionsFrom<ExtensionConstructor>): Extension
useManager(lazyCreator: () => Extension[], settings?: ManagerSettings)
- Configure theRemirrorManager
which takes a function that returns a list of extensions as the first parameter and all manager related settings as the second parameter.
As a result hooks will be the preferred way for configuring React
editors.
The following is an example of how a small editor with bold
and italic
support would be set up.
import { BoldExtension, ItalicExtension } from '@remirror/core-extensions';
import { RemirrorProvider, useManager, useExtension } from '@remirror/react';
// Set up the component to provide the functionality for the editor
const SmallEditor = () => {
const { getRootProps, commands } = useRemirror(); // Picked from the context.
// Configure the extension options
useExtension(BoldExtension, { weight: '800' });
<div>
<button onClick={() => commands.bold()}>bold<button>
<button onClick={() => commands.italic()}>italic<button>
<div {...getRootProps()} />
</div>
};
const SmallEditorWrapper = () => {
const extensionManager = useManager(() => [new ItalicExtension(), new BoldExtension]);
return (
<RemirrorProvider extensionManager={extensionManager}>
<SmallEditor />
</RemirrorProvider>
);
};
For more information on how the API will feel in a more complex setup see this comment by @benjie.
Styling
Currently, there are a several @remirror/ui-*
packages which were meant to make styling of the API much easier. Unfortunately due to a lack of documentation this has not really materialized and right now the packages are very confusing.
Work and thought is still going into how this can be made better and I’ll update this section when more is known.
EDIT: Styling is now available via, CSS, emotion
, styled-components
and pure dom manipulation. You can choose which you prefer based on your project.
Development playground
Similar to TypeScript, babel and other prominent libraries it’s important to have a playground which speeds up development and provides a way to explore the features of remirror.
The playground will allow users to:
- Swap out different extensions to see their impact.
- Try different editors and mix and match functionality.
- Shareable playgrounds for reproducing bugs and sharing designs.
- Export TypeScript code for implementing the created editors in their projects.
Serialization and deserialization
The question on how to persist the data produced by remirror is one that often comes up in the issues. This is something that I would like to address in the next version.
Collaboration
One of the main selling points of Prosemirror is the focus on a consumable collaboration effort. This has been made a priority and will be addressed in the next version.
Improved commands
Right now commands are methods that run on the prosemirror state and dispatch a transaction. They return true
when successful and false
when otherwise. The next version of the API will provide an abstraction for the commands which allows for chaining.
Chaining
// Within the `RemirrorProvider` Context.
const { chain } = useRemirror();
chain
.insertText('hello')
.selectAll()
.bold()
.run(); // Run executes the commands
Task List
Core
- Create a todo list of what needs to change.
- Create new documentation site.
- Add details on how to contribute to the next branch.
- Write up the new API and what will change.
- Create new playground package.
- Add
/playground
url to the docs site. - Add playground page to storybooks and update the contributing guide.
- Add tests for the playground
- Add
-
Update to new classless api (internally classes can be used but externally no)- ~Split up options into
config
andprops
.~ - ~Create new class factories and decide on type inference mechanics.~
- Update all extensions to the new API
- ~Split up options into
- Move over to the new hooks API
- Remove old API methods (deprecate them in the main branch).
- Add new hooks methods
Other
- Refactor e2e tests from
puppeteer
toplaywright
. - Refactor build process.
- Split up ci builds into multistep process and test for supported node versions.
- Add
playground
persistent urls for reporting bugs or requesting features. - Integrate
sherlock
for verifying bugs. - Enable code generation from
playground
.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:29
- Comments:16 (10 by maintainers)
I’m tentatively targeting feature parity (with the current master) in prerelease within 4 - 5 weeks (~21st - 28th~ ~Apr~ ~May~ 5th - 12th June). After that it’s all about how many bugs surface before the final
1.0.0
release.I don’t want to rush that stage to avoid the need for drastic API changes in the future.
Also, I really like what you’re doing with https://github.com/webclipper/web-clipper. It looks lovely.
Edit - Updated months from April to May Edit - Updated 28th May to 12th June
@ted-mccarthyfinch glad to hear you’re excited. I’ll be putting a starter pack together soon, hopefully tomorrow.