Description
Have you read the Contributing Guidelines on issues?
- I have read the Contributing Guidelines on issues.
Motivation
I did a very naïve attempt to migrate to ESM in #5816, but I far underestimated the difficulty in pushing it through. After some pondering, I think this should be rolled out progressively.
Status of ESM
ESM is a new type of Node modules system, replacing the old common JS system (require
+ module.exports
). For the engine, the parsing goal is different ("module" or "script"), so a file needs to be determined as ESM or CJS before executing it, either through the .mjs
extension or through the "type": "module"
entry in the nearest package.json.
If a file is ESM, it can import from other ES modules. However, it may not be able to import all CJS modules, depending on how the CJS module is structured, because exported symbols, per the ES spec, need to be lexically determined, while CJS can be far more dynamic.
If a file is CJS, it can't import ESM modules, unless the await import()
dynamic import is used. However, this is against the norm of how most modules are imported. This effectively means as soon as a considerable number of the dependencies are ESM, we have to migrate ourselves.
Many packages on NPM are now ESM-only, most notably the packages by Sindre Sorhus, and MDX v2, which is the pillarpost of our architecture.
Benefits
- Unlock future dependency upgrades. Many popular libraries are seeking to upgrade to ESM; if we can be ESM, we can interoperate with them. For example, MDX v2, chalk...
- A similar transpilation target for client and server code; no need for multiple tsconfigs
- Permit top-level awaits
Blockers
- TypeScript has very lame support of ESM transpilation so far. It seems their deferred ESM support still won't land in 4.6, so in the meantime we may have to run all our Node code with
--experimental-specifier-resolution=node
and keep the old resolution - Jest doesn't seem to like importing ESM dependencies
- We use
import-fresh
to bypass cache and always import fresh modules for hot reloading, etc. But ES Modules don't expose caching manipulation yet - Community dividing: although the ESM migration will surely be a major version, it means in the foreseeable future after that, some plugins will not be compatible with the new version (especially those that import
utils
andlogger
)
Actions needed
- Removing
__filename
and__dirname
. These globals don't exist in the ESM scope, replaced by theimport.meta.url
- Setting
target: 'nodenext'
in the tsconfig. - Changing our import paths. ESM requires the
index.js
name and the.js
extension to be explicit. - Setting
"type": "module"
in our package.json. - Tweaking the configuration for related tools?
Plan
- Config files: JS config files like
docusaurus.config.js
andsidebars.js
can be allowed in ESM once we figure out how to bypass cache and fresh-import ESM - Utils: this includes
utils
,utils-validation
,logger
. They should always be distributed as dual-package because plugin authors are likely to import them as CJS. - Core: the biggest blocker is still the extensive use of
import-fresh
. Migration to ESM means we can import both CJS and ESM plugin modules withawait import
. However, because users only interact with the core through its own CLI, we don't have to care about others importing this. - Plugins: migration of plugins can only happen after migration of core (or at least solving
import-fresh
in the core), because they have to be imported as ESM.
Related issues/PRs
If an issue or PR is related to the ESM migration process, please link to this meta-issue and we will add it to the list below for tracking purposes.
- Support ESM config file #5379
- misc: migrate all packages to ESM-only #5816
- misc: convert all internal scripts to ESM #6286
- refactor: mark all functions that import external modules as async #6521
- refactor: convert CLI entry points to ESM; migrate create-docusaurus to ESM #6661
- refactor: ensure lodash is default-imported #6716
- refactor: import jest as global; unify import style of some modules #6898
- refactor(theme-common): mark package as ESM #6899
- feat(core): allow plugin lifecycles to return relative paths #6921
- feat(core): support docusaurus.config.cjs as default file name #7371
- fix(core): make generated files have unambiguous module types #7379