packages/lexical-website/docs/maintainers-guide.md
This is the grimoire of arcane knowledge covering the overall organization of the Lexical monorepo, including its conventions, quirks, and configurations.
The top-level package.json uses
pnpm workspaces to
configure the monorepo. This mostly means that all packages share a
top-level pnpm-lock.yaml and pnpm -C {package} run {command} is often
used to run a command from a nested package's package.json.
Some packages in the monorepo do not get published to npm, for example:
packages/lexical-devtools - browser extension for working with Lexical
sitespackages/lexical-playground - the
playground.lexical.dev demo sitepackages/lexical-website - the lexical.dev
docusaurus website that you may even be reading right nowpackages/shared - internal code that is used by more than one repository
but should not be a public APIIt is required that these packages, and any other package that should not be
published to npm, have a "private": true property in their package.json.
If you have an in-progress package that will eventually be public, but is
not ready for consumption, it should probably still be set to
"private": true otherwise the tooling will find it and publish it.
| Usage | Convention |
|---|---|
| Directory name | packages/lexical-package-name |
| Entrypoint | packages/lexical-package-name/src/index.{ts,tsx} |
| Flow types | packages/lexical-package/flow/LexicalPackageName.js.flow |
| package.json name | @lexical/package-name |
| Documentation | packages/lexical-package-name/README.md |
| Unit Tests | packages/lexical-package-name/src/__tests__/unit/LexicalPackageName.test.{ts,tsx} |
| dist (gitignore'd build product) | packages/lexical-package-name/dist |
| npm (gitignore'd prerelease product) | packages/lexical-package-name/npm |
| www entrypoint | packages/lexical-package-name/LexicalPackageName.js |
Instead of having a single module, some packages may have many modules
(currently only @lexical/react) that are each exported separately.
In that scenario, there should be no index.ts entrypoint file and every module
at the top-level should be an entrypoint. All entrypoints should be a
TypeScript file, not a subdirectory containing an index.ts file.
The update-packages script will ensure that the exports match the files on disk.
The first step in creating a new package is to create the workspace directory
and package.json file. The example we will use is the steps that were used
to create the lexical-eslint-plugin, which will be published to npm as
@lexical/eslint-plugin.
mkdir -p packages/lexical-eslint-plugin
Create the initial package.json file (you can base it on an existing package
or use the template below):
packages/lexical-eslint-plugin/package.json
{
"name": "@lexical/eslint-plugin",
"description": "",
"keywords": [
"lexical",
"editor"
],
"version": "0.14.3",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/facebook/lexical.git",
"directory": "packages/lexical-eslint-plugin"
},
"main": "LexicalEslintPlugin.js",
"types": "index.d.ts",
"bugs": {
"url": "https://github.com/facebook/lexical/issues"
},
"homepage": "https://github.com/facebook/lexical#readme"
}
Some next steps for this package.json before moving on:
mkdir -p packages/lexical-eslint-plugin/src
code packages/lexical-eslint-plugin/src/index.ts
Here are some minimal examples of those files that you might start out with. I've elided the license header, the eslint header/header fixer will help you with that!
<details><summary>packages/lexical-eslint-plugin/src/index.ts
import {name, version} from '../package.json';
const plugin = {
meta: {name, version},
rules: {},
};
export default plugin;
pnpm run update-packages
This will set up the tsconfig, flow, etc. configuration to recognize your new module. It will also create an initial README.md using only the description from the package.json.
mkdir -p packages/lexical-eslint-plugin/src/__tests__/unit
code packages/lexical-eslint-plugin/src/__tests__/unit/LexicalEslintPlugin.test.ts
packages/lexical-eslint-plugin/src/__tests__/unit/LexicalEslintPlugin.test.ts
import plugin from '@lexical/eslint-plugin';
describe('LexicalEslintPlugin', () => {
it('exports a plugin with meta and rules', () => {
expect(Object.keys(plugin).sort()).toMatchObject(['meta', 'rules']);
});
});
This script runs: update-version, update-tsconfig, update-flowconfig, create-docs, and create-www-stubs. This is safe to do at any time and will ensure that package.json files are all at the correct versions, paths are set up correctly for module resolution of all public exports, and that various defaults are filled in.
These scripts can be run individually, but unless you're working on one of these scripts you might as well run them all.
This runs all of the pre-release steps and will let you inspect the artifacts
that would be uploaded to npm. Each public package will have a npm directory, e.g.
packages/lexical/npm that contains those artifacts.
This will also update scripts/error-codes/codes.json, the mapping of production error codes to error messages. It's imperative to commit the result of this before tagging a release.
Check flow, TypeScript, prettier and eslint for issues. A good command to run after committing (which will auto-fix most prettier issues) and before pushing a PR.
Check the Flow types
Check the TypeScript types
Check the TypeScript types of the lexical-devtools extension
Run the unit tests
Run eslint
This will run a build that also extracts the generated error codes.json file.
This should be done, at minimum, before each release, but not in any PR as it would cause conflicts between serial numbers.
It's safe and probably advisable to do this more often, possibly any time a branch is merged to main.
The codes.json file is also updated any time a release build is generated as a failsafe to ensure that these codes are up to date in a release. This command runs a development build to extract the codes which is much faster as it is not doing any optimization/minification steps.
Increment the monorepo version. The -i argument must be one of
minor | patch | prerelease.
The postversion script will:
${npm_package_version}__release branchpnpm run update-version to update example and sub-package monorepo dependenciespnpm install to update the pnpm-lock.yamlpnpm run update-packages to update other generated configpnpm run extract-codes to extract the error codespnpm run update-changelog to update the changelog (if it's not a prerelease)This is typically executed through the version.yml GitHub Workflow which
will also push the tag and branch.
Update the changelog from git history.
Prerequisites: all of the previous release manager scripts, plus creating a tag in git, and likely other steps.
Runs prepare-release to do a full build and then uploads to npm.
This is the current release procedure for public releases, at least as of May 2024 (~0.15.0).
The main branch should be "frozen" during this procedure (no other PRs should be merged during this time). This avoids a mismatch between the contents of the GitHub release (created from main in step 1) and the NPM release (created from main in step 4).
version.yml)pre-release.yml)call-post-release.yml via pre-release.yml) to update the examples[Breaking Change] in the PR's title with documentation of what followup actions consumers of the lexical library need to be aware of.The team page displays core team
members, emeriti, and distinguished contributors. The team.json data is
generated from GitHub contributor information and some predetermined decisions in
the script to acknowledge emeriti and historically important distinguished contributors.
To update the team page data:
pnpm run update-team-data
This fetches the latest contributor data from GitHub and categorizes team members
based on recent activity (last 12 months). See packages/lexical-website/src/data/README.md
for more details on configuration and team categorization logic.