A monorepo hosting a React-based iPhone, complete with apps as npm packages.
Table of Contents
Phone.js was inspired by the amazing project of Tanner Villarete, iPod.js.
The intention behind this project was not only to have fun creating something peculiar, but also to be challenged by the CSS styling involved.
I did also take the opportunity to explore the meander of monorepos with yarn workspaces. It is very interesting how this project leverages this technology. More on this to follow!
Check out the demo here.
-
Clone on your machine via SSH or HTTPS:
Via SSH (suggested):
git clone
Via HTTPS:
git clone
-
Enter folder:
cd phone-js
-
Install dependencies
yarn
-
Start the core package in development mode:
yarn start
-
Open http://localhost:3000 to view it in the browser.
-
Build all packages (
packages/*
) based on the dependency tree:yarn build
-
Final build can be found in
packages/core/build
.
- React v18
- React Router v6
- TypeScript v4.7
- Yarn Plug'n'Play
This repo is managed as a monorepo that is composed of many npm packages.
Quoting babel:
Pros:
- Single lint, build, test and release process.
- Easy to coordinate changes across modules.
- Single place to report issues.
- Easier to setup a development environment.
- Tests across modules are run together which finds bugs that touch multiple modules more easily.
Cons:
- Codebase looks more intimidating.
- Repo is bigger in size.
- Can't npm install modules directly from GitHub
- ???
In this project, the tool used to achieve this is yarn workspaces.
Yarn workspaces does many things:
- It links npm packages contained in the monorepo. This enables us to have a great DX, as we can run all the packages in development mode at the same time and see how they behave together as we code.
- It provides CLI commands that are very useful to manage monorepos. For instance, the
yarn build
script of this project runsyarn workspaces foreach -pt run build
, which builds all the packages in the monorepo based on the dependency tree.
The monorepo contains the following types of packages:
- The
core
package - contains the code that makes up the phone and binds all the "app" packages together. Apps are plugged-in inside phoneApps.ts. - The
app
packages - each package is an "app" that takes place into the phone app gallery. - The
utils
package - contains common utils helpful to code the "apps".
Each "app" packages needs to export an object literal containing the following properties:
Name | Description | Type | Example |
---|---|---|---|
id | Identifier of the app. Needs to be unique. | string | notes |
element | Lazy loaded import of the root component. | LazyExoticComponent | lazy(() => import('./lib/features/Notes/Notes')) |
iconPath | Path to the icon of the application. | string | import launcherIcon from './assets/app-icon.png'; |
title | User facing name of the app. | string | Notes |
route | Route that wraps this app. | string | notes/* |
destination | Initial route. | string | notes |
This project is powered by React Router v6. All the packages have access to the router and each "app" package is able to have its own routes.
Each and every "app" is lazy loaded. This is incredibly important because it means that, no matter how many apps get plugged-in, loading time remains constant.
This monorepo makes use of Yarn Plug'n'Play. Quoting Yarn:
The way Yarn PnP works, it tells Yarn to generate a single Node.js loader file in place of the typical node_modules folder. This loader file, named .pnp.cjs, contains all information about your project's dependency tree, informing your tools as to the location of the packages on the disk and letting them know how to resolve require and import calls.
This system brings many advantages, which you can read here.
One disadvantage, which you may notice while cloning, is that the repository gets sensibly bigger.
This project is available under the MIT license. You can find a copy of the license here.