How CodeMirror v6 dev setup retrieves packages without a monorepo
In the OSS community, it is quite common to come across monorepo projects where the workspaces and packages are all in a single repository. CodeMirror has a different style to the way packages are managed, each package that is used is a repository on its own. In this article, we analyse what these packages are that CodeMirror uses and how they are installed when you want to setup CodeMirror v6 locally to contribute.
bin/packages.js
Before we analyse the install function, let’s first understand the variours packages that make up the CodeMirror. You
will find these packages listed in bin/packages.js file
These below are the core packages:
exports.core = [
"state",
"view",
"language",
"commands",
"search",
"autocomplete",
"lint",
"collab",
"language-data",
"codemirror",
]
These below are the non-core packages.
exports.nonCore = [
"lang-javascript",
"lang-java",
"lang-json",
"lang-cpp",
"lang-php",
"lang-python",
"lang-go",
"lang-css",
"lang-sass",
"lang-html",
"lang-sql",
"lang-rust",
"lang-xml",
"lang-markdown",
"lang-lezer",
"lang-wast",
"lang-angular",
"lang-vue",
"lang-liquid",
"lang-less",
"lang-yaml",
"legacy-modes",
"theme-one-dark",
"merge"
]
You will find repositories in the CodeMirror organisation for all these packages shown above. There is no single repository containing all these packages like in a monorepo, instead you have all these repositories that make up the CodeMirror.
exports.core and exports.nonCore
You will find that these packages are accessed via a function named loadPackages.
You might be wondering what exports.all here is in the above image, at line 42. It is interesting how the concat is made on exports.core and exports.nonCore and this you have all these packages merged into one array and assigned to exports.all.
exports.loadPackages
let packages = exports.all.map(n => new Pkg(n))
Using map on exports.all, new Pkg class is instantiated using the Pkg class that’s shown below:
class Pkg {
constructor(name) {
this.name = name
this.dir = join(__dirname, "..", name)
this.main = null
if (name != "legacy-modes" && fs.existsSync(this.dir)) {
let files = fs.readdirSync(join(this.dir, "src")).filter(f => /^[^.]+\.ts$/.test(f))
let main = files.length == 1 ? files[0] : files.includes("index.ts") ? "index.ts"
: files.includes(name.replace(/^(theme-|lang-)/, "") + ".ts") ? name.replace(/^(theme-|lang-)/, "") + ".ts" : null
if (!main) throw new Error("Couldn't find a main script for " + name)
this.main = join(this.dir, "src", main)
}
}
}
It technically sets name
, dir
and main
.
let packageNames = Object.create(null)
for (let p of packages) packageNames[p.name] = p
Object.create(null) creates an empty object, that’s fancy way of creating a obj that would otherwise be just obj = {}
and finally below is the return statement.
return {
packages,
packageNames,
buildPackages: packages.filter(p => p.main)
}
About us:
At Thinkthroo, we study large open source projects and provide architectural guides. We have developed reusable Components, built with tailwind, that you can use in your project. We offer Next.js, React and Node development services.
Book a meeting with us to discuss your project.