14th December, 2020
mkdir my-mono-repo
cd my-mono-repo
yarn init-y
Add to package.json
"volta": {
"node": "14.15.1",
"yarn": "1.22.10"
}
Pin node and yarn version
volta pin node yarn
mkdir packages
Add to package.json
"private": true,
"workspaces": [
"packages/*"
],
mkdir packages/types
types
will become your first package: @mr/types
.
It needs a package.json
, so create one
{
"name": "@mr/types",
"version": "0.0.1",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsc -b ."
},
"devDependencies": {
"typescript": "^4.0.3"
}
}
This is a TypeScript project, so you'll need a tsconfig.json
{
"compilerOptions": {
"module": "CommonJS",
"types": [],
"sourceMap": true,
"target": "ES2018",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"declaration": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
- src/Book.ts
- src/User.ts
- src/index.ts
export interface IBook {
id: string;
title: string;
author: string;
}
export function bookToString(book: IBook): string {
return `${book.title}`;
}
export interface IUser {
id: string;
firstName: string;
lastName: string;
}
export function userToString(user: IUser): string {
return `${user.firstName} ${user.lastName}`;
}
export * from "./Book";
export * from "./User";
Finally run yarn
to install and link dependencies, and then try to build the @mr/types
package from within its directory.
cd packages/types
yarn build
you should see that a packages/types/dist
is created for you, and there are a few .js
and .d.ts
files within it (your build output)
TODO Now Create your own package for random utils
- sum
- sub
- times
mkdir packages/utils
utils
will become your first package: @mr/utils
.
It needs a package.json
, so create one
Replicate those configs on utils packages, created previously by you.
we'll need a tsconfig.json
as well, and it'll be almost exactly the same as the one for our @mr/types
and @mr/utils
packages. Repetition is annoying (and an "out of sync" bug waiting to happen), so let's set up one meaningful tsconfig, and extend from it in multiple places.
{
"compilerOptions": {
"module": "CommonJS",
"types": [],
"sourceMap": true,
"target": "ES2018",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"declaration": true
}
}
update packages/types/tsconfig.json
{
"extends": "../tsconfig.settings.json",
"compilerOptions": {
"composite": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
do the same for packages/utils/tsconfig.json
Finally create a packages/tsconfig.json
that refers to each package
{
"files": [],
"references": [{ "path": "utils" }, { "path": "types" }]
}
try running this from the root of your project now
yarn tsc -b packages
both types
and utils
should build!
Each package we create will put its build output in its own folder, so we should set ourselves up for an easy way to destroy it and "build again from scratch".
We can install a workspace dependency (at the root of the project, not a dependency of any package) to handle this in a platform-independent way:
yarn add -WD rimraf
Then, go to types/package.json
and utils/package.json
and make this small change
@@ -7,6 +7,7 @@
"access": "public"
},
"scripts": {
+ "clean": "rimraf dist *.tsbuildinfo",
"build": "tsc -b ."
},
Now, we can go to either of these projects and run yarn clean
to delete build output, and yarn build
to make a fresh build.
15th December, 2020
Keeping the last developments in regard to our simple mono repository, here I am going to setup our code testing using jest framework and linting with eslint.
Those are two technologies highly consensual on javascript ecosystem.
More and more I have been convinced that testing is probably more important that our production code, with that I have been trying to follow the Test Driven Development (TDD) approach with Jest.
ESLint prevents some human error, doing a static analysis of our code alerting to us in regard to some bad practices.
Jest and ESLint are some of the technologies I have been using in all the javascript projects I have been on.
We start by setting up Jest on packages/types
. For that we need to install some dependencies:
cd packages/types
yarn add -D @babel/preset-env @babel/preset-typescript @types/jest jest
Do the same on packages/utils
.
We are using babel to convert our typescript code, in our tests, to javascript. So, we need to add a .babelrc
with needed configs for babel to do its work.
We are going to create a .babelrc
on packages folder, which will be then extended by each package. So ./packages/.babelrc
:
{ "presets": [ ["@babel/preset-env", { "targets": { "node": "10" } }], "@babel/preset-typescript" ] }
Here we configure babel to transpile code for node env (@babel/preset-env) with typescript (@babel/preset-typescript). Presets are a set of rules for babel to trasnpile the code. There are a lot of presets available, for example, if you use react, there is a preset to transpile react code. It is usually used for JSX.
The main purpose of Babel is to transpile our modern
javascript code for old browsers.
Now on ./packages/types
and ./packages/utils
create their on .babelrc
:
{ "extends": "../.babelrc" }
You are almost done with setting up jest. We just need to add a script, on package.json, to execute our tests.
Go to ./packages/types/package.json
and ./packages/utils/package.json
, and add the following on scripts
"test": "jest"
You should now be able to go to ./packages/types
or ./packages/utils
and execute:
yarn test
It will fail be we have no tests. Lets add a simple test on ./packages/types
. Create a folder tests
:
mkdir tests
And a file called book.test.ts
:
import { bookToString } from "@mr/types"; describe("bookToString() tests", function () { test("should return title of book", () => { expect( bookToString({ id: "id", title: "Clean Code", author: "Uncle Bob", }) ).toBe("Clean Code"); }); });
Now, when you execute yarn test
we should this a message telling tests has passed. In this case:
Test Suites: 1 passed, 1 total
16th December, 2020
Keeping the last developments in regard to our simple mono repository, here I am going to setup our linting process with eslint.
ESLint highly consensual on javascript ecosystem. I have been using it in all javascript project I have been on, in the last 3 years. ESLint prevents some human error, doing a static analysis of our code alerting to us in regard to some bad practices.
We will start by installing eslint and needed plugins for typescript. ESLint architecture works around plugins.
yarn add -WD eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser
In the root of our project we have to create an .eslintrc
file with eslint configs for our project. We will follow the same technique of creating a general one config file, and then each package extending it and adapting it as needed. That way it is easier to maintain, when we want update our eslint rules:
{ "env": { "es2021": true }, "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended-requiring-type-checking" ], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": 12 }, "plugins": ["@typescript-eslint"], "rules": { "prefer-const": "error", "@typescript-eslint/no-unsafe-member-access": "off", "@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/no-unsafe-assignment": "off" } }
Here we are using a standard configs with recommended rules from:
- eslint
- @typescript-eslint plugin
For each of our packages we need to create a .eslintrc
as well, extending the general one we have just created:
{ "extends": "../../.eslintrc", "parserOptions": { "project": "tsconfig.json" } }
Replicate it for packages/types
and packages/utils
.
Finally, lets add a script on each package.json to execute eslint on our files:
"lint": "eslint src --ext js,ts"
Add this line to scripts
at package.json
files for packages/types
and packages/utils
.
Go to each package folder an execute:
yarn lint
It should give no errors. However, it is important to have the IDE also alerting you for errors. I am using VSCode and while going to packages/types/src/Book.ts
I have found the following error on VSCode in the first line in regard to export
:
Parsing error: Cannot read file '…/tsconfig.json'.eslint
This does not make sense. It seems that VSCode needs to have a tsconfig.json
at the root of the repository. I have tried to copy a tsconfig.json
file to the root and it solves the problem, but I do not like it. It is hard to maintain and error prone.
After digging a while I found I would need to configure my workspace settings on VSCode with the correct directories for eslint plugin on VSCode. Added this to settings
of workspace file in VSCode:
"eslint.workingDirectories": [ { "directory": "packages/types", "changeProcessCWD": true }, { "directory": "packages/utils", "changeProcessCWD": true } ]
It has the downside of needed to add new entry everytime we have new package. However, I prefer this approach.
More information about it here and here
Now, everything should be ok, but I want to confirm eslint is really checking the rules configured. I am going to packages/utils/src/math.js
and do the following change on sum
function:
export function sum(one: number, two: number): number { let result = one + two; return one + two; }
The VScode should be alerting to an error 'result' is never reassigned. Use 'const' instead
That is because we have configured the following eslint rule
"prefer-const": "error",
IF we execute
yarn lint
Besides the error we also get a warning 'result' is assigned a value but never used
Ok, first things first. Lets change the let
to const
:
export function sum(one: number, two: number): number { let result = one + two; return one + two; }
We now only have the warning on VSCode and while executing yarn lint
, alerting us we are not using result
var. Lets fix it:
export function sum(one: number, two: number): number { const result = one + two; return result; }
We have solved the linting errors.
Next I am going to experiment with (Lerna)[https://github.com/lerna/lerna] to facilitate the management of our packages in a mono repository.
18th December, 2020
(Lerna)[https://github.com/lerna/lerna] provides a mechanism to run a command on each package in our mono repo with one cli. For example run test for all packages
using only a command on the root workspace:
yarn lerna run test
We start by adding lerna
dependency at our workspace level:
yarn add -WD lerna
Now we need to configure lerna
in our repository. We do it by create a lerna.json
file in the root of our repository:
{ "packages": ["packages/*"], "npmClient": "yarn", "version": "0.1.0", "useWorkspaces": true }
We have defined our packages
folder, and marking we are using yarn
and yarn workspaces
. Here we are using a fixed version for the repository. Lerna also supports ìndependent
versioning. For that we would need to change version
to independent
:
"version": "independent"
According to Learn documentation, fixed versioning
:
"Fixed mode Lerna projects operate on a single version line. The version is kept in the lerna.json file at the root of your project under the version key. When you run lerna publish, if a module has been updated since the last time a release was made, it will be updated to the new version you're releasing. This means that you only publish a new version of a package when you need to."
independent versioning
:
"Independent mode Lerna projects allows maintainers to increment package versions independently of each other. Each time you publish, you will get a prompt for each package that has changed to specify if it's a patch, minor, major or custom change."
No we have Lerna configured we are ready to execute:
yarn lerna bootstrap
If we had dependencies between our existing packages, this would take care of "linking" everything up. It will be very useful for further developments, when we have packages depending on each other.
More about the bootstrap
command here.
Now we have Lerna setted up we can start taking advantage of it. For example, lets execute the following command in the root of our repository:
yarn lerna run clean && yarn lerna run build
Each command will be executed in each package with one command. First we clean the build and then generate a new build.
Note: this command should be improved to use prebuild
for clean task. We can also improve it with concurrent execution.
Other useful lerna commands to explore later:
- lerna bootstrap;
- lerna link;
- lerna exec ;
- lerna changed
Lets imagine we want to create a booksService
on utils
creating booksService.ts
file at packages/utils/src
:
import { IBook } from "@mr/types"; const books: IBook[] = [ { id: "1", title: "Clean Code", author: "Uncle Bob", }, { id: "2", title: "The Pragmatic Programmer", author: "Andy Hunt and Dave Thomas", }, ]; export function findBookById(id: string): IBook { const book = books.find(({ id: bookId }) => id === bookId); if (book) { return book; } throw new Error("Not Found"); } export function allBooks(): IBook[] { return books; }
Also add the following at packages/utils/src/index.ts
:
export * from "./booksService";
We need to add @mr/types
dependency to @mr/utils
. With Lerna we could do the following:
yarn lerna add @mr/types --scope=@mr/utils
19th December, 2020
Verdaccio is a local npm proxy we can use locally.
yarn add -WD verdaccio
yarn verdaccio
A npm repository is now available at http://localhost:4873/
Now we need to configure our registry create a .npmrc
file with:
registry=http://localhost:4873
We need to create Verdaccio user to be able to publish packages to our repository. Create it and then do:
yarn login
We should now be able to publish our packages with lerna using:
yarn lerna publish
Not sure why, but I needed to setup the git repository and before any publish lerna ask me to commit any change, which makes sense.
After trying the command I had some problems authenticating in my local registry, lerna was always trying to publish on npm registry.
After a while I tried the following, explicitly configuring my local registry:
yarn lerna publish --yes --registry http://localhost:4873
It worked, and now I have the artifacts on my local verdaccio repository. Another solution, would be Nexus. I have used it in the past, however it requires more work to set up. I will keep experimenting with Verdaccio.
Also, some work need to be done to have it working smoothly in Continuos Integration, Delivery and Deployment environment.
20th December, 2020
cd packages
npx create-react-app spa
Lets update packages/spa/package.json
name:
"name": "@mr/spa",
At our root package.json
at scripts
add:
"scripts": {
"start:spa": "cd packages/spa && yarn start"
},
Lets add our internal libraries:
yarn lerna add @mr/types @mr/utils --scope=@mr/spa
I was expecting it to work, however it fails. For, what I see it is only possible to add a library as dependency at a time, using lerna. I read more about it here
Experiment with
yarn lerna add @mr/utils --scope=@mr/spa
Followed by
yarn lerna add @mr/types --scope=@mr/spa
In this case, we have created our react-app with javascript, so @mr/types
will not be very useful.
Lets start using our books in our React Application. Go to packages/spa/src/App.js
and update it to:
import { allBooks } from "@mr/utils"; function App() { return ( ); } export default App;
Now lets do a change on @mr/utils
and check if we get it. Lets add a tag
property to our books. We will need to change @mr/types
as well.
On @mr/types
I have updated src/Book.ts
to:
export interface IBook { id: string; title: string; author: string; tag?: string; } export function bookToString(book: IBook): string { return `${book.title}`; }
tag
was added as an optional property to IBook
interface, representing our books.
Now, lets go to @mr/utils
. I update books
array at src/booksService.ts
with a tag
for each book:
const books: IBook[] = [ { id: "1", title: "Clean Code", author: "Uncle Bob", tag: "Software", }, { id: "2", title: "The Pragmatic Programmer", author: "Andy Hunt and Dave Thomas", tag: "Software", }, ];
However we see VSCode is warning us with an error. I am going to execute lint
:
yarn lerna run lint
No error was found.
Ok. We are going to link all of our dependencies:
yarn lerna bootstrap
Already up-to-date.
We changed it @mr/types
and @mr/utils
. We need to rebuild it:
yarn lerna run build
And now errors disappeared. We will want to automate this processo to improve the Developer Experience. I will do it later.
For now, lets go back to our Single Page Application in React and show tag
for each book. Our packages/spa/src/App.js
:
import { allBooks } from "@mr/utils"; function App() { return ( ); } export default App;
And it was updated on http://localhost:3000
Next I will try the some for a react-native application.
21th December, 2020
Here I am going to create a react-native application with the typescript template. I should be as easy as:
cd packages
npx react-native init mobile --template react-native-template-typescript
However after this you will see some problems on the ios application. It is not able to install dependencies on ios application.
It happens due to the way workspaces store dependencies, saving the common dependencies on the root node_modules
and not in each package.
For ios application the configurations for dependencies expect a relative path for node_modules folder, something like:
After digging a while I found it is a solved problem, with a simples solution. We need to configure react-native dependencies to not be hoisted by our workspace and be stored in the local node_modules
. This article, explains it very well..
I changed the worksapces
property in our root package.json
to:
"workspaces": { "packages": [ "packages/*" ], "nohoist": [ "**/react-native", "**/react-native/**" ] }
And try to create the react nartive application again. After a while it is created and we should be ready to start our new mobile application.
I just update packages/mobile/package.json
name:
"name": "@mr/mobile",
For now I am going to run it directly on packages/mobile
:
yarn ios
I can see the boilerpla application. I am now going to add our types
and utils
libraries to show our books information as we did in our web application here:
yarn lerna add @mr/utils --scope=@mr/mobile
Followed by
yarn lerna add @mr/types --scope=@mr/mobile
After that my application stops working while trying to auto reload, when I change some application code. It happens due to watchman not being able to reload linked
application with yarn
, done by lerna when application are installed:
error: Error: Unable to resolve module `@babel/runtime/helpers/interopRequireDefault` from `App.tsx`: @babel/runtime/helpers/interopRequireDefault could not be found within the project. If you are sure the module exists, try these steps: 1. Clear watchman watches: watchman watch-del-all 2. Delete node_modules: rm -rf node_modules and run yarn install 3. Reset Metro's cache: yarn start --reset-cache 4. Remove the cache: rm -rf /tmp/metro-* at ModuleResolver.resolveDependency (/Users/nb24696/Experiments/my-mono-repo/packages/mobile/node_modules/metro/src/node-haste/DependencyGraph/ModuleResolution.js:186:15) at ResolutionRequest.resolveDependency (/Users/nb24696/Experiments/my-mono-repo/packages/mobile/node_modules/metro/src/node-haste/DependencyGraph/ResolutionRequest.js:52:18) at DependencyGraph.resolveDependency (/Users/nb24696/Experiments/my-mono-repo/packages/mobile/node_modules/metro/src/node-haste/DependencyGraph.js:287:16) at Object.resolve (/Users/nb24696/Experiments/my-mono-repo/packages/mobile/node_modules/metro/src/lib/transformHelpers.js:267:42) at /Users/nb24696/Experiments/my-mono-repo/packages/mobile/node_modules/metro/src/DeltaBundler/traverseDependencies.js:434:31 at Array.map () at resolveDependencies (/Users/nb24696/Experiments/my-mono-repo/packages/mobile/node_modules/metro/src/DeltaBundler/traverseDependencies.js:431:18) at /Users/nb24696/Experiments/my-mono-repo/packages/mobile/node_modules/metro/src/DeltaBundler/traverseDependencies.js:275:33 at Generator.next () at asyncGeneratorStep (/Users/nb24696/Experiments/my-mono-repo/packages/mobile/node_modules/metro/src/DeltaBundler/traverseDependencies.js:87:24)
I was expecting this problem, because I has happened to me in a project I am working on. There I am using WML to solve the problem, and it works very well. However, it required some setup and an external tool:
wml add packages/utils packages/mobile/node_modules/@mr/utils
wml add packages/utils packages/mobile/node_modules/@mr/types
wml start
For this case, I found another solution, that I think it is a better automation here, and improve the developer experience. This solution is easier to mantain.
The solution is to tell watchman
to look for our packages. We can do it updating metro.config.js
:
/** * Metro configuration for React Native * https://github.com/facebook/react-native * * @format */ const path = require('path'); const watchFolders = [ //Relative path to packages directory path.resolve(__dirname + '/..'), ]; module.exports = { transformer: { getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, inlineRequires: false, }, }), }, watchFolders, };
I have got this ideia here. We will need to restart the application for metro to get those configuration.
After having the application started again, I have changed the packages/mobile/serc/App.tsx
to use our @mr/utils
and @mr/types
packages. The application has reloaded with those modifications well:
import React from 'react'; import {SafeAreaView, ScrollView, View, Text, StatusBar} from 'react-native'; import {allBooks} from '@mr/utils'; import {IBook} from '@mr/types'; const App = () => { const books = allBooks(); return ( <> {allBooks().map(function renderBook(book: IBook): JSX.Element { return ( {book.title} {book.author} {book.tag} ); })} ); }; export default App;
We are now able to update our packages
and having it reload immediately. We just need to remember to rebuild packages
after our changes.
After adding the react-native application I have also found some conflicts on dependency versions in different packages, in this case, in our spa
in regard to babel-jest
, when we try to build
it. Due to the way create-react-app
works I am not able to change the version of babel-jest
directly without ejecting it. I havef added babel-jest
to not be hoisted. I have also removed all node_modules
manually execute yarn
.
I have changed the worksapces
property in our root package.json
to:
"workspaces": { "packages": [ "packages/*" ], "nohoist": [ "**/react-native", "**/react-native/**", "**/babel-jest" ] }
22nd December, 2020
https://www.npmjs.com/package/lerna-dependency-graph
No sure why by if I install lerna-dependency-graph
and try to use it like:
yarn lerna-dependency-graph
I have the following error:
env: node\r: No such file or directory
But if I use the following, it works well:
npx lerna-dependency-graph
I will use this, so far.
https://www.npmjs.com/package/graphviz-cli
yarn add -WD graphviz-cli canvas open-cli
"scripts": { ... "graph:specs": "npx lerna-dependency-graph -o dependency-graph.dot", "graph:png": "yarn graphviz -Tpng -odependency-graph.dot.png dependency-graph.dot", "graph:preview": "open dependency-graph.dot.png", "graph": "yarn graph:specs && yarn graph:png && yarn graph:preview" },
23rd December, 2020
https://github.com/formium/tsdx
npx tsdx create ui-web
I going to choose the template:
react-with-storybook
I will talk about storybook in another post. It will take some time to create the library
Talk about:
I already ran yarn install for you, so your next steps are:
cd ui-web
To start developing (rebuilds on changes):
yarn start
To build for production:
yarn build
To test your library with Jest:
yarn test
Update packages/ui-web/package.json
name to @mr/ui-web
:
"name": "@mr/ui-web",
For now we are going to use the inline css, but it is not the recommended. Since we are using an external lib it will need some more setup to generate an external css to be used on our application, which bundles with webpack. We will let it for another time.
Another solution would be to use CSS in JS, since css is generated with javascript in runtime, it will not need extra setups in regard the build. However, I prefer to use plain css.
Here, I am not taking attention to style:
src/components/BookCard/index.tsx
import React from 'react'; export default function BookCard({ book }: { book: any }) { return ( <div> <h2>{book.title}</h2> <p>{book.author}</p> <small>{book.tag}</small> </div> ); }
Notice, I am using the book: any
. I will fix it later to use our own IBook
type.
src/index.tsx
import BookCard from './components/BookCard'; export { BookCard };
Started having conflicts with spa application in regard to dependency versions. I have explain it here. Now, this happens with babel-loader
and jest
. I am going to had it to nohoist
, clear node_modules
and execute yarn
:
"nohoist": [ "**/react-native", "**/react-native/**", "**/babel-jest", "**/babel-loader", "**/jest" ]
We want to use our new component in our spa application.
yarn lerna add @mr/ui-web --scope=@mr/spa
Lets build all in the root:
yarn lerna run build
Notice, thanks to Lerna our build
execute in the correct order. It would be a nightmare if we had to do it buy hand.
packages/spa/App.js
:
import React from "react"; import { allBooks } from "@mr/utils"; import { BookCard } from "@mr/ui-web"; function App() { return ( <div> <h1>List of Books</h1> <ul> {allBooks().map(function renderBook(book) { return ( <li key={book.id}> <BookCard book={book} /> </li> ); })} </ul> </div> ); } export default App;
Next, I want to create a library for react-native components. And, after that play with react-native-web
and may find a way to uniform components between web and mobile.
24th December, 2020
At packages:
npx tsdx create ui-mobile
Choose react
template.
Update packages/ui-mobile/package.json
name to @mr/ui-mobile
:
"name": "@mr/ui-mobile",
yarn lerna add react-native --scope=@mr/ui-mobile --dev
yarn lerna add @types/react-native --scope=@mr/ui-mobile --dev
src/components/BookCard/index.tsx
import React from 'react'; import { View, Text } from 'react-native'; export default function BookCard({ book }: { book: any }) { return ( <View> <Text>{book.title}</Text> <Text>{book.author}</Text> <Text>{book.tag}</Text> </View> ); }
Notice, I am using the book: any
. I will fix it later to use our own IBook
type.
src/index.tsx
import BookCard from './components/BookCard'; export { BookCard };
An
yarn lerna add @mr/ui-mobile --scope=@mr/mobile
packages/mobile/App.tsx
:
import React from "react"; import { allBooks } from "@mr/utils"; import { BookCard } from "@mr/ui-web"; function App() { return ( <div> <h1>List of Books</h1> <ul> {allBooks().map(function renderBook(book) { return ( <li key={book.id}> <BookCard book={book} /> </li> ); })} </ul> </div> ); } export default App;
Metro watcher with conflits among folders.
undo metro.config.js
Geting back to wml:
wml add packages/utils packages/mobile/node_modules/@mr/utils
wml add packages/utils packages/mobile/node_modules/@mr/types
wml add packages/ui-mobile packages/mobile/node_modules/@mr/ui-mobile
wml start
I am with some problems, in some situations: Solving with wml, similar to
[error] unable to resolve root /Users/blah/project: directory /Users/blah/project is not watched
I had to do something like for two of the links:
watchman watch /Users/nb24696/Experiments/my-mono-repo/packages/utils
watchman watch /Users/nb24696/Experiments/my-mono-repo/packages/ui-mobile
And then
wml start
Seems to be working well. It is not a pretty, neither, easy setup, but I think it is a minor price to pay to hot reload with react-native, hot reload is something we do not have on native development for mobile.
NOTE TRYIED; BUT COULD NOT SOLVE THE PROBLEM
Review this problem again. Watchman was alerting to problems because we have to packages with "example" name. This happens due to the tsdx
template, which supplies an èxample
at èxample
. The ideia of this project is to be a playground to see how to use the code we are distributing with corresponding library.
Trying to solve it going to each example/package.json
and updating its name
and private
to true
to avoid it be published by Lerna. For example:
```packages/ui-web/example/package.json: "name": "@mr/ui-web-example", "private": "true", Put back
metro.configs.js`:
const path = require('path'); const watchFolders = [ //Relative path to packages directory path.resolve(__dirname + '/..'), ]; module.exports = { transformer: { getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, inlineRequires: false, }, }), }, watchFolders, };
TODO: To experiment.
Go to packages
folder:
´´´
npx tsdx create ui-shared
´´´
React with StoryBook template
Update /packages/ui-shared/package.json
name:
"name": "@mr/ui-shared",
Update /packages/ui-shared/example/package.json
name:
"name": "@mr/ui-shared-example",
External dependency for react-native-web:
yarn lerna add react-native-web --scope=@mr/ui-shared --dev
yarn lerna add @types/react-native --scope=@mr/ui-shared --dev
src/components/BookCard/index.tsx
import React from 'react'; import { View, Text } from 'react-native-web'; export default function BookCard({ book }: { book: any }) { return ( <View> <Text>{book.title}</Text> <Text>{book.author}</Text> <Text>{book.tag}</Text> </View> ); }
Notice, I am using the book: any
. I will fix it later to use our own IBook
type.
src/index.tsx
import BookCard from './components/BookCard'; export { BookCard };
To avoid having typescript problems with react-native-web
linked @types/react-native
this way. On packages/ui-shared
:
yarn add -D @types/react-native-web@npm:@types/react-native
I have found this suggestion here
We want to be able to test and visualize our ui compoments idependently, without needing to have a full application. For That we use storybook.
Removed stories/Things.stories.tsx
.
Created stories/BookCard.stories.tsx
:
import React from 'react'; import { Meta, Story } from '@storybook/react'; import { BookCard } from '../src'; const meta: Meta = { title: 'BookCard', component: BookCard, argTypes: { book: { title: 'text', author: 'text', tag: 'text', defaultValue: { title: "Clean Code", author: 'Uncle Bob', tag: 'software' } }, }, parameters: { controls: { expanded: true }, }, }; export default meta; const Template: Story = args => ; // By passing using the Args format for exported stories, you can control the props for a component for reuse in a test // https://storybook.js.org/docs/react/workflows/unit-testing export const Default = Template.bind({}); Default.args = {};
And start storybook:
yarn storybook
Now, we clear examples
project, since we will be using storybook
to see our components working. Also, removed packages/ui-shared/test/blah.test.tsx
. It was using some boilerplates not existing anymore.
Removing react-dom
dependencies, since it is not needed here anymore:
yarn workspace @mr/ui-shared remove react-dom @types/react-dom
yarn lerna add @mr/ui-shared --scope=@mr/spa
We also need to add react-native-web
yarn lerna add react-native-web --scope=@mr/spa
Now we need to update our packages/spa/index.js
:
import "./index.css"; import { AppRegistry } from "react-native"; import App from "./App"; AppRegistry.registerComponent("App", () => App); AppRegistry.runApplication("App", { rootTag: document.getElementById("root"), });
Here we are just following the instructions on react-native-web
documentation here.
And now we update packages/spa/App.js
:
import React from "react"; import "./App.css"; import { allBooks } from "@mr/utils"; import { BookCard } from "@mr/ui-shared"; function App() { return ( ); } export default App;
You can now even remove dependency for @mr/ui-web
.
The interesting thing here is we are still able to use react-dom components as we can see here with h1
component.
yarn lerna add react-native-web --scope=@mr/mobile yarn lerna add react-dom --scope=@mr/mobile
Is needed to have react-dom installed on react-native app due to react-native-web?
Next, we start trying to use this compoment on our web and mobilwe application. Mobile first. Build libraries:
yarn lerna run build
yarn lerna add @mr/ui-shared --scope=@mr/mobile
WML link
wml add packages/ui-shared packages/mobile/node_modules/@mr/ui-shared
Make sure WML is working. If not execute:
wml start
Also needed to do the following on the root of repository. I seem to be with some problems in regard to watchman
.
watchman watch packages/ui-shared
FIXME Estou com vários problemas a linkar as libs no react-native. Sempre que adiciono uma nova lib é um problema. COMO RESOLVER ISTO E FAZER DEVELOPER / USER FRIENDLY
error: Error: Unable to resolve module react-native-web
from node_modules/@mr/ui-shared/dist/ui-shared.cjs.production.min.js
: react-native-web could not be found within the project or in these directories:
../../node_modules
Trying the following
- react-native-web moved to dev on ui-shared
- -react-native-web needs to be installed on mobile
Lerna does not have a remove command, the opposite of yarn add :\
Using yarn workspace @mr/ui-shared remove react-native-web
Install on mobile yarn workspace @mr/mobile add react-native-web
Não estou a conseguir
yarn lerna add postcss --scope=@mr/mobile
https://github.com/postcss/postcss/wiki/PostCSS-8-for-end-users#rollup
yarn lerna add postcss --scope=@mr/ui-web --dev
https://www.npmjs.com/package/rollup-plugin-postcss
yarn lerna add rollup-plugin-postcss --scope=@mr/ui-web --dev
packages/ui-web/tsdx.config.js
:
const postcss = require('rollup-plugin-postcss'); module.exports = { rollup(config, options) { config.plugins.push( postcss({ modules: true, extract: true }) ); return config; }, };
yarn lerna run build --scope=@mr/ui-web
Nothing happens because css was not added yet.
rm -rf dist
packages/ui-web/components/BookCard/styles.modules.css
.card { border: 2px solid black; }
packages/ui-web/components/BookCard/index.tsx
import React from 'react'; // @ts-ignore import styles from './styles.module.css'; export default function BookCard({ book }: { book: any }) { return ( ); }
We need to configure typescript to handle styles.module.css
. For now, I am using \\@ts-ignore
I use extract: true
, so a css file is generated on the final build. When we execute:
yarn lerna run build --scope=@mr/ui-web
a file packages/ui-web/dist/ui-web.cjs.production.min.css
is generated with something like:
.styles-modules_card__2yoe0 { border: 2px solid black; }
styles-module_card__2yoe0
is autogenerated and will be the css class added to our component in final build, as we can see at packages/ui-web/dist/ui-web.cjs.production.min.js
:
return t.createElement("div",{className:"styles-module_card__2yoe0"}
Applications using this library should import this css. If we do not want that we could remove extract:true
and the css will be using through javascript. I think in big projects it could be a performance problem.
Use https://www.npmjs.com/package/storybook-css-modules-preset
pacakges/ui-web/.storybook/main.js
:
module.exports = { stories: ['../stories/**/*.stories.@(ts|tsx|js|jsx)'], addons: [ '@storybook/addon-links', '@storybook/addon-essentials', 'storybook-css-modules-preset', ], // https://storybook.js.org/docs/react/configure/typescript#mainjs-configuration typescript: { check: true, // type-check stories during Storybook build }, };
for now will do the following:
packages/ui-web/src/typings.d.ts
:
declare module '*.css' { const content: { [className: string]: string }; export default content; }
jaredpalmer/tsdx#186 (comment) But would like to havfe this plugin working: https://github.com/mrmckeb/typescript-plugin-css-modules