Previously, we discussed various aspects of code design concerning Canvas
. Now, let’s delve into project practices. As I mentioned in earlier articles, I approached this with a learning mindset and a curiosity for technology. Consequently, aside from some utility libraries like ArcoDesign
, ResizeObserve
, and Jest
, the data structure in packages/delta
, the plugin system in packages/plugin
, and the core engine in packages/core
were all implemented manually. Therefore, in addition to learning about Canvas
, I actually engaged in some project engineering practices.
Related articles about the Canvas
resume editor project:
Let’s start by discussing why we should use monorepo
. To illustrate, I’ll reference a pitfall I encountered before. In my previous rich-text editor project, DocEditor, everything was written in a single independent src
directory. There were no issues during the project’s operation, but I wanted to extract the editor as an NPM
package. The bundling process using Rollup
was fine, but the problems arose during the referencing. When I tried to incorporate the document editor's NPM
package into the resume editor, I discovered that a module had been incorrectly TreeShaken
. You can still see this compatibility issue in the editor.
This issue meant that while I had no problems in dev
mode, after building, this part of the code was mistakenly removed, causing issues with the editor's wrapper
node, preventing items like lists from being added correctly. This doesn’t imply that independent package projects are problematic, but rather that management can be more complex than it appears, particularly regarding entry points when packaging as an NPM
package. Now, my rich-text editor package has evolved into 4
independent packages with distinct roles, eliminating this issue.
Speaking of packaging, I encountered another pitfall. Have you ever experienced the classic Invalid hook call
error in React
? When I broke it down into independent packages, I received this error. However, I had marked "react": ">=16"
in peerDependencies
of package.json
, which should ensure it directly uses the React
version installed with the package, avoiding version inconsistency. Rules of Hooks were also not the issue because everything was working fine before the package split. Eventually, I discovered that I had not resolved peerDependencies
in rollup
, which caused jsx-runtime
to be bundled. Even though both React
versions were 17.0.2
, this led to the execution of two independent lexical scopes of React Hooks
, resulting in the error.
CanvasEditor │── packages │ ├── core │ ├── delta │ ├── plugin │ ├── react │ └── utils ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── tsconfig.json
With monorepo
, managing all sub-projects becomes effortless, especially for projects that require publishing Npm
packages. Splitting sub-modules is a great choice, and achieving framework-agnostic view layers is even more meaningful. Moreover, monorepo
offers numerous benefits for overall project management; for instance, when bundling the entire application, we do not need to publish new packages for each sub-project before bundling. Instead, we can directly place the compilation process at the workspace
level, ensuring consistency across the entire project, simplifying the build process and continuous integration workflow, and allowing all projects to share build scripts and tool configurations. Additionally, all projects and modules share a single version control system, facilitating unified version management and change tracking, and aiding in synchronizing updates across these interdependent projects.
Having discussed the benefits of using pnpm + monorepo
for project management, let’s delve into my best practices for applying TS
and Rspack
within a Monorepo
. I wonder if you have encountered the following two issues:
Subproject TS
declarations do not take effect immediately after changes; compiling the subproject is necessary. If during the compilation process, the dist
or other output packages are deleted, you'll encounter errors in vsc
or other editors stating that TS
cannot find the reference declarations. In this case, you must use the command to Reload TypeScript Project
to eliminate the errors. However, if you don't delete the output packages, you may face some hidden issues. For example, if a file was originally named a.tsx
and needs to be moved to a similarly named a
directory and renamed to index.tsx
for some reason, after doing this, you may find that changing the code in the index.tsx
does not trigger updates. You must restart the application’s webpack
or other compilers because it still references the original file. While these types of issues may not be complex, troubleshooting them can be time-consuming.
Changing TS
code in a subproject requires recompiling the subproject because the project is managed under a monorepo
, which includes workspace
references in the package.json
. In fact, the workspace
is referenced within node_modules
, meaning even though it's a subproject, it still needs to adhere to the rules of node_modules
. Typically, it needs to be compiled to js
in order to be executed, making it cumbersome to need to run a full build every time code is modified. Of course, you can usually monitor changes using the -w
command, but that adds an extra step. Moreover, for projects with alias
, simply using tsc
for compilation may not suffice. Additionally, in a monorepo
environment, if many subprojects require this, particularly in scenarios where a full compilation is necessary instead of incremental, the overall compilation time for the project can become excessively lengthy.
Now, let’s address the first issue: the TS
declaration changes in subprojects not taking effect immediately. As mentioned, subprojects in a monorepo
are managed and referenced through node_modules
. Therefore, by default, they still need to follow the rules of node_modules
, specifically that the types
field in the packages.json
points to the TS
declaration files. Is there a way to modify this behavior? Certainly! We can configure the path
in the root tsconfig.json
of the entire project to resolve this issue perfectly. Once we set it up as follows, we can jump to the declaration in the root directory of the subproject by holding Ctrl
and clicking the left mouse button. A point to pay attention to here is that it is not recommended to set "baseUrl": "."
in the project because it can lead to some peculiar path reference issues. Therefore, aside from needing to package the Npm
tsconfig.build.json
, relative path configuration is used directly in the resume editor project.
After resolving the TS
declaration issues in the project, let’s discuss the compilation issue. This problem appears more complex, as TS
declarations are purely type declarations and do not directly affect the compilation of the project's core code, except for type checking. To ensure our code directly points to the subproject in Rspack
without having to adhere to the node_modules
rules, it's straightforward to configure resolve.alias
. This way, when we directly modify the TS
code, the editor can immediately respond with incremental compilation.
In fact, Rspack
does a lot for us; for instance, it compiles even the TS
files in node_modules
. However, for some projects created using CRA
, this configuration can be a bit tricky. Nonetheless, we can also leverage customize-cra
to achieve this. Additionally, we'll need to disable some plugins like ModuleScopePlugin
. Below is the configuration for the rich text editor project DocEditor.
Additionally, the resume editor is a purely front-end project. One significant advantage of such a project is that it can run just by using static resources. If we leverage GitHub Action
, we can deploy directly within the repository via Git Pages
and access it seamlessly through GitHub Pages
. This way, a complete DEMO
can be presented directly in the repository.
In this discussion, we talked about the rationale behind using Monorepo
and briefly touched on the advantages of pnpm workspace
. We also addressed two practical issues encountered in the development of sub-projects, namely TS
compilation and project compilation, demonstrating relevant practices in Monorepo
, Rspack
, and Webpack
projects. Finally, we briefly discussed how to utilize GitHub Action
to deploy an online DEMO
directly on Git Pages
. In the subsequent articles, we will delve into how to design capabilities for hierarchical rendering and event management.