Hi, everyone, I’m Fly brother, I used to write blog warehouse, still use native HTML and JS, did not introduce TS, and some engineering things, so I rebuilt a set of front-end project architecture based on Lerna + YARN Monrepo warehouse, The main thing is to learn the output of some things later, the whole shelf set up first.

  1. Encapsulation of 2D and 3D public Util
  2. Rollup of personal NPM packages
  3. 2d React Project Setup (VITE)
  4. 3d React Project Setup (Webpack)
  5. Build a set of CLI based on WebPack 5

Each project has some specific dependencies, but there are also some common dependencies. Such as esLint, some basic configuration of Babel, or some generic script files. After reading this article you can learn to build the Monorepo front-end engineering project from 0 to 1 as shown below:

Why use MONOREPO

Monorepo is a software development strategy for storing multiple project code in a repository (“mono” comes from the Greek μόνος meaning single, while “repo”, obviously, is an abbreviation for repository). Putting the code of different projects in the same code repository may seem strange at first, but in fact, this kind of code management method has many benefits, whether it is the world-class Internet enterprises Google, Facebook, Babe, a well-known open source project team, uses the Monorepo strategy to manage their code. This is the official source library for Taro:

As for his merits, they are as follows:

  1. Code reuse is easy: since all project code is centralized in a code repository, it’s easy to isolate common business components or tools across projects and reference them in-code via TypeScript, Lerna, or other tools;
  2. Dependency management will become very simple, easy to do version dependency management and automatic version number updates
  3. Publishing NPM packages is also particularly easy, extracting public methods directly to the public package, which can be quickly published to NPM
  4. One of the biggest benefits is the avoidance of duplicate packages, reduced disk space, and reduced build time

Both of these benefits can be accomplished by a full-fledged package management tool, which in the case of front-end development is YARN (1.0 +) or NPM (7.0 +) through a feature called workspaces (⚠️ note: The NPM that supports workspaces is still not the LTS version.

YARN

Here we install YARN globally

npm install yarn -g
Copy the code

Then create a new folder into the directory to execute

yarn init  
Copy the code

Package.json is generated at the root of the project

At this point we are in the project root directory

New Packages directory

Add the following field workspace to package.json

{" name ":" yarn - test ", "version" : "1.0.0", "private" : true, "workspaces:" [" packages / * "], "main" : "index.js", "license": "MIT" }Copy the code

Represents that workspaces are all subdirectories under Packages,

Private: true Indicates that the root directory of the project will not be published

Suppose the project has two packages: foo and bar:

mono-demo/
|--package.json
|--packages/
|  |--foo/
|  |  |--package.json
|  |--bar/
|  |  |--package.json
Copy the code

yarn workspace <workspace_name>

Run the specified command in the specified package.

# add react to foo Var var var var var var var var var var var var var var var var var var var var var var var var var var var Run the yarn workspace bar run test command from package.json in barCopy the code

yarn workspaces run

Run the specified command in all packages, or an error will be reported if there is no corresponding command in a package.

# Run the yarn workspaces run build command on package.json in package (foo, bar)Copy the code

yarn workspaces info [–json]

View the Workspace dependency tree in your project.

For example, my bar relies on Foo as follows:

/ / bar/package. Json {" name ":" bar ", "version" : "1.0.0", "dependencies" : {" foo ":" ^ 1.0.0 "}}Copy the code

The dependency structure in the project looks like this (assuming that the version of foo/package.json matches the dependent version of bar, otherwise another matching foo will be installed) :

/package.json
/yarn.lock
​
/node_modules
/node_modules/foo -> /packages/foo
​
/packages/foo/package.json
/packages/bar/package.json
Copy the code

Running YARN Workspaces info yields the following output:

yarn workspaces 
{
  "bar": {
    "location": "packages/bar",
    "workspaceDependencies": [
      "foo"
    ],
    "mismatchedWorkspaceDependencies": []
  },
  "foo": {
    "location": "packages/foo",
    "workspaceDependencies": [],
    "mismatchedWorkspaceDependencies": []
  }
}
Copy the code

For example, some of my dependencies are common to all packages like ESLint, Babel… We just use the following command and add a -w

yarn <add|remove> -W

  • -w: –ignore-workspace-root-check allows dependencies to be installed in the root directory of the workspace

Manage root dependencies.

DevDependencies yarn add eslint -d -wCopy the code

LERNA

Lerna is one of the mainstream monorepo management tools in the community, integrating dependency management, version release management and other functions. The directory structure of projects managed using Learn is similar to yarn Workspace.

We install at root

yarn add lerna -D -W
Copy the code

Then perform

npx lerna init
Copy the code

Lerna.json is then generated in the project

Let’s do the following configuration

{
  "packages": ["packages/*"],
  "command": {
    "run": {
      "npmClient": "yarn"
    },
    "publish": {
      "ignoreChanges": ["ignored-file", "*.md"],
      "message": "chore(release): publish",
      "registry": "https://npm.pkg.github.com"
    }
  },
  "version": "independent",
  "useWorkspaces": true,
  "npmClient": "yarn"
}
​
Copy the code

Workspace is also used to specify that the project uses YARN for package management

There is an important field “version”: “independent”,

This means that the Lerna project allows maintainers to increase package versions independently of each other using independent schemas. With each release, you are prompted for each package that has changed to specify whether it is a patch, minor, major, or custom change. Standalone mode allows you to update versions of each package more specifically and makes sense for groups of components. I’m going to use this semantic-release NPM package if you’re interested.

Here I introduce some lerna commands: you can go to Github lerna to see more

Lerna bootstrap: equivalent to lerna link + YARN install, used to create compliant links and install dependency packages.

Lerna run: Executes NPM script in all subprojects like a for loop, and it is smart enough to recognize dependencies and execute commands from the root dependency;

Lerna exec: Like Lerna run, commands are executed in dependent order, except that it can execute any command, such as shell scripts;

Git commit > git commit > git commit > git commit > git commit > git commit > git commit > git commit > git commit > git commit > git commit

Lerna add: Add local or remote packages as dependencies to the current Monorepo repository. This command is important because it allows Lerna to identify and track dependencies between packages

TSCONFIG

As a TS project, install ts in the project root directory

yarn add typesript -D -W
Copy the code

Start by generating tsconfig.json in your project

npx tsc --init
Copy the code

Json is generated in the root directory of the project. We put the base tsconfig.json here, and then create a new project to generate tsconfig.json which inherits from the root directory tsconfig.json something like this

{ "extends": ".. /.. /tsconfig.json", "compilerOptions": { "target": "es2018", "module": "ESNext", "outDir": "./dist" }, "include": [". / SRC / / *. * * ts "] / / * match zero or more characters (not including directory separator) * * / recursive match any subdirectory}Copy the code

This is a subproject of tsconfig.json

As for the tsconfig.json file detailed configuration, you can baidu yourself.

BABEL

Babel configuration files are merged in the same way as TypeScript, or even simpler, by declaring it in a.babelrc file in a subproject:

{ "extends": ".. /.babelrc" }Copy the code

SCRIPTS

Let’s create a new scripts folder globally which could be a shell file or it could be a TS file. We all know that ts files are not directly executable, they are compiled into JS and then executed, which is too much trouble. The good news is that the community already provides TS-Node that allows you to run TS files directly

You can use ts-node + a TS file to execute the ts file directly by modifying the require hook (module._extensions [‘.ts’]).

Do ts compilation in require hook, and then directly execute the compiled JS, so as to achieve the effect of directly execute TS file.

So we override the module._extensions [‘.ts’] method to read the contents of the file, then call TS.transpilemodule to convert ts to JS, and then call module._compile to process the compiled JS.

yarn add ts-node -D -W
Copy the code

Create a new test.ts file

const foo = {
  baz: {
    a: 1,
  },
}
console.log(foo)
Copy the code

Then in the package. The json

Write the following script:

 "test": "ts-node ./scripts/test.ts "
Copy the code

Then run yarn test

One of the problems with ts-Node is the file reference problem. If your TS script files reference other packages of the current project, you may get an execution error

We create a new util in the package and create an index.ts file

const add = (a: number, b: number) => a + b
export default add
Copy the code

I then do the alias configuration in the root directory tsconfig.json

"BaseUrl" : ". / packages ", / / root path map, "paths" : {" @ fly/util ": [". / util/index. Ts"]}Copy the code

We introduce this addition function in the script

import add from '@fly/util'
​
console.error(add(2, 3))
Copy the code

And then go ahead

The following error is reported

Tsconfig. json “module”: “CommonJS” will be used in the implementation of ts-Node

Import add from ‘@fly/util

const add = require("@fly/util")
Copy the code

If you are a Webpack project, you can configure alias resolution, which will do path replacement,

However, when we write scaffolding, it is impossible to use webpack is node environment. How can I solve this problem

The community also offers solutions

Use it to load modules whose location is specified in the path section of tsconfig.json. Support for loading at run time and through the API.

Typescript emulates the node.js runtime resolution strategy of modules by default. But it also allows path-mapping, allowing arbitrary module paths to be specified (without a “/” or “.” Start) and map it to a physical path in the file system. The typescript compiler resolves these paths from tsconfig, so it can compile. However, if you try to execute a compiled file using Node (or TS-Node), it will only look in the node_modules folder all the way to the root of the file system, so it will not find the module specified in the tsconfig path. This is important, so we just reported an error. That’s why and that’s what this library is for.

yarn add tsconfig-paths -D -W
Copy the code

How to use it

ts-node --project customLocation/tsconfig.json -r tsconfig-paths/register "test/**/*.ts"
Copy the code

Common JS for ts config.json is best here because we are in node environment

So I create a new tsConfigs in the project to store the different TS configurations and also inherit the root directory

{ "extends": ".. /tsconfig", "compilerOptions": { "module": "CommonJS" } }Copy the code

Execute the command

ts-node --project ./tsconfigs/cmj.json -r tsconfig-paths/register  ./scripts/test.ts 
Copy the code

The execution is very comfortable. If you see this and think it helps, please give it a thumbs up

But there’s a problem here:

This is actually the configuration of the ESLint import, if you have configured it install the following NPM

yarn  add eslint-import-resolver-typescript -D -W
Copy the code

The name alone suggests a strong relevance to this question. As you can see from the README project, the lib can find the correct.ts and.tsx files in the TypeScript project with eslint-plugin-import, and also recognize the path configuration of tsconfig.json (path alias 2). Even projects such as Monorepo with multiple projects in a Git repository are supported.

It is also easy to use in esLint’s “import/resolver”: just point to the path of tsconfig where path is currently configured, and esLint will automatically recognize it and not report errors.

{ "plugins": ["import"], "rules": { "import/no-unresolved": "error" }, "settings": { "import/parsers": {// Use TypeScript parser" @typescript-eslint/parser": [".ts", ".tsx"]}, "import/resolver": Json "typescript": {// Read type definitions "alwaysTryTypes" from <roo/>@types: True,}, / / using the specified path tsconfig. Json, < root > / path/to/folder/tsconfig json "typescript" : {" directory ": "./path/to/folder"}, // monorepos type of multi-tsconfig. json // glob type of matching pattern "typescript": {"directory": "./packages/*/tsconfig.json"}, // or array "typescript": {"directory": ["./packages/module-a/tsconfig.json", "./packages/module-b/tsconfig.json"]}, // typescript: {"directory": [ "./packages/*/tsconfig.json", "./other-packages/*/tsconfig.json" ] } } } }Copy the code

That’s the official usage, so let’s get into the details of how ESLint is used

ESLINT

ESLINT

For the esLint configuration we could do the same thing, create.eslintrc for our project root and then each of our subprojects also inherits external.eslintrc

Ok, so let’s start installing ESLint

yarn add eslint -D -W
Copy the code

Then we generate the esLint configuration file

npx eslint --init
Copy the code

Since our project is based on React and TS, we will choose according to the following options

The.eslint.yml configuration file will appear in our root directory. You can also choose the configuration file format you like, personally I prefer YAML

env:
  browser: true
  node: true
extends:
  - plugin:react/recommended
  - eslint:recommended
  - airbnb
parser: '@typescript-eslint/parser'
parserOptions:
  ecmaFeatures:
    jsx: true
  ecmaVersion: 2020
  sourceType: module
plugins:
  - react
  - '@typescript-eslint'
  - react-hooks
rules: 
Copy the code

The following file will appear and I will go through each of the following configuration files

ENV

  1. Eslint specifies the environment you want to enable in ESLint. We are the front-end, so node and browser are supported by the official website

    EXTENDS

    This can be in the form of a file path or a downloaded plugin package. In this case, the general NPM package format

    It looks something like this

    eslint-config-packagename
    Copy the code

    We can omit the previous eslint-config when configuring, I’m using Airbnb configuration.

    Or the name of the installed package

    eslint-plugin-packagename
    Copy the code

    The eslint-plugin can then be omitted and used as follows

    plugin:xxxx/recommended
    Copy the code

    Or an eslint subdirectory that inherits from the root directory, for example:

    extends: .. /.. /.eslintrcCopy the code

    When everything is ready, our project catalog should look something like this:

    . ├ ─ ─ package. Json ├ ─ ─ the eslintrc └ ─ ─ packages / │ ├ ─ ─ tsconfig. Json │ ├ ─ ─ the babelrc ├ ─ ─ project_1 / │ ├ ─ ─ index. The js │ ├ ─ ─ . Eslintrc │ ├ ─ ─. Babelrc │ ├ ─ ─ tsconfig. Json │ └ ─ ─ package. The json └ ─ ─ ─ project_2 / ├ ─ ─ index. The js ├ ─ ─ the eslintrc ├ ─ ─ the babelrc ├ ─ ─ tsconfig. Json └ ─ ─ package. The jsonCopy the code

    PARSER

    ESLint uses Espree as its parser by default, but since our project is a TS file, install it

    yarn add @typescript-eslint/parser
    Copy the code

    The purpose of TypeScript is to convert TypeScript into estREE compatible form for use in ESLint. Eslint also operates on AST, but does not support TS, so it does a layer conversion to JS.

    parserOption

    A parser must have parsed parameters

    • JSX: true // Indicates that JSX syntax is supported but React applies specific semantics to JSX syntax that ESLint does not recognize. If you are using React and want React semantics support, we recommend using eslint-plugin-react. EcmaVersion: 2020 // Default JS version sourceType: module // ESM modeCopy the code

    PLUGINS

    As mentioned above, JSX syntax that ESLint does not recognize applies specific semantics to parse-parameter configurations that support JSX syntax. If you are using React and want React semantics support, we recommend using eslint-plugin-react. This is called a plugin. React and React-hooks are installed

    yarn add eslint-plugin-react  eslint-plugin-react-hooks
    Copy the code

    We can then configure plugins and also omit the ESlint-plugin

RULES

This is the definition of rules. You can check the official list for the specific configuration

---
rules:
  eqeqeq: off
  curly: error
  quotes:
    - error
    - double
Copy the code

And then if you install the plug-in

The default plugin configuration can be overridden by plugin names/rules, such as the following

'react-hooks/rules-of-hooks': 2
'react-hooks/exhaustive-deps': 2
Copy the code

With VSCODE integration

 "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
  }
Copy the code

PRETTIER

Prettier actually came in to solve the code format problem, right? This is something ESLint cannot do

Prettier Prettier in Chinese, Prettier is a popular code formatting tool, let’s see how it can be used in conjunction with ESLint. First we need to install three dependencies:

Install the NPM package

yarn add prettier eslint-config-prettier eslint-plugin-prettier  -D -W
Copy the code
  1. Prettier: The core code for the prettier plug-in
  2. Eslint-config-prettier: Resolves the conflict between the style specification in ESLint and the style specification in Prettier by taking the style specification of Prettier as the standard
  3. Eslint-plugin-prettier: The use of prettier as the ESLint specification

Then create the. Prettierrc file in the root of the project:

{
    "printWidth": 120,
    "semi": false,
    "trailingComma": "all",
    "singleQuote": true,
    "arrowParens": "always"
}
Copy the code

Eslintrc.js file to add prettier:

extends:
  - plugin:react/recommended
  # - plugin:import/recommended
  - eslint:recommended
  - airbnb
  - prettier
  
plugins:
  - react
  - '@typescript-eslint'
  - react-hooks
  # - import
  - prettier
​
Copy the code

Add prettier to extends and plugins, respectively

HUSKY and Lint-staged build code workflows

Before I get there, let me briefly describe what Husky and Lint-staged things actually do.

HUSKY

Husky is now a must-have tool for front-end engineering. The NPM package basically provides hooks to run script commands before git commits. And now I can actually do it

yarn add husky -D -W
Copy the code

Step 2: Add the prepare script to packgae.json

{
  "scripts": {
    "prepare": "husky install"
  }
}
Copy the code

The prepare script is automatically executed after NPM install with no parameters. The husky install command will create the. Husky/directory and specify it as the directory where git hooks are located.

Step 3 Add Git hooks

npx husky add .husky/pre-commit "yarn lint-staged"
Copy the code

The.husky directory will appear in our root directory

Then write the pre-commit script

#! /bin/sh . "$(dirname "$0")/_/husky.sh" yarn lint-stagedCopy the code

Since we have not yet installed Lint-staged, direct passage will report errors

LINT-STAGED

yarn add lint-staged -D -W
Copy the code

Lint-staged can be used to perform code tests on files in Git staging, and then we can also do something about it

We added the following configuration to package.json

 "lint-staged": {
    "*.@(js|ts|tsx)": [
      "eslint --ext .ts,.tsx,.js --fix",
      "prettier --write",
      "git add"
    ],
    "*.@(yml|yaml)": [
      "prettier --parser yaml --write"
    ],
    "*.md": [
      "prettier --parser markdown --write"
    ],
    "*.json": [
      "prettier --parser json --write"
    ]
  },
Copy the code

Let’s look at the first one, when matching a file ending in JS or TS or TSX, we can do something with esLint and prettirer

  1. Eslint detects these files and automatically fixes them
  2. Prettier — write is then used for automatic detection
  3. Finally it is added to the staging area

Let’s run to see what it looks like:

You will notice that it seems to have succeeded, but it failed because I also added a hook to husky to normalize commit-msg before committing the command

We enter the following command

npx husky add .husky/commit-msg 'yarn commitlint --edit "$1"' 
Copy the code

And then in the.husky catalog this image comes up

Since we’re running the script Commitlint, we install the same script

yarn  add commitlint  @commitlint/config-conventional  @commitlint/config-lerna-scopes -D -W
Copy the code

Commitlint/config-Conventional The default Angular team commit specification is used here

@commitlint/config-lerna-scopes Since we are LERna multiple packages are mainly used to restrict packages in packages that are missing.

API ├ packages ├ ─ ─ ─ ─ app └ ─ ─ web ❯ echo "build (API) : change something in the API 's build" | commitlint ⧗ input: build (API) : Change something in API's build stocking found 0 problems, 0 warnings ❯ echo "test(foo): This won 't pass "| commitlint ⧗ input: test (foo) : This won't pass * scope must be one of [API, APP, web] [scope-enum] * Found 1 problems, 0 warningsCopy the code

Foo is not a package in the project, so an error is reported.

Create a new file in the root directory of the project: commitlint.config.js

module.exports = {
  extends: ['@commitlint/config-conventional', '@commitlint/config-lerna-scopes'],
}
Copy the code

In this way, we will be able to comply with our above limitations when submitting the project code

Generate the CHANGELOG

First, what is CHANGELOG and why is it needed? It records all the commit information of your project, categorizes the version, quickly jumps to that commit record, and even shows the creator who changed the person information at a glance to find the bug 😂. It makes it easy to know which version of a project does what and what bugs, etc. Also convenient to check the bug, for the submission of records at a glance, not one to turn over to check.

Here we install the package

yarn  add  standard-version -D -W
Copy the code

Here, use standard-version to automatically generate CHANGELOG. We will not talk about the conventional changelog, after all, it recommended us to use standard-version (this is the thing of the same team, based on the Conventional Changelog).

Semantic-release and this one is generated

As for the difference between the two? We look at the

Semantic-release automates the entire package release workflow, including determining the next release number, generating release notes, and releasing packages.

While both are based on the same foundation of structured commit messages, the standard version takes a different approach, handling version control, change log generation, and Git tagging for you without automatic push (to GitHub) or publication (to the NPM registry). Using the standard version only affects your local Git repository – it doesn’t affect remote resources at all. After running the standard version, you can view the release status, correct errors, and follow the release strategy that makes the most sense for your code base. We think they’re great tools, and we encourage people to use semantic publishing instead of standard versions if it makes sense for their use cases

Then we add this script to package.json

 "release": "standard-version",
Copy the code

Add.versionrc.js to the project root directory to make our changeLog look nice

Module. Exports = {types: [{type: 'feat' section: '✨ the Features | new Features'}, {type:' fix 'section: '🐛 Bug Fixes | Bug Fixes'}, {type:' init 'section:' 🎉 init | initialization '}, {type: 'docs' section: '✏ ️ Documentation | document'}, {type: 'style' section: '💄 Styles | style'}, {type: 'refactor, section: '♻ ️ Code Refactoring | Code Refactoring'}, {type: 'perf' section: '⚡ Performance Improvements | Performance optimization'}, {type: 'test' section: '✅ Tests | test'}, {type: "revert", section: '⏪ revert | back'}, {type: 'build' section: '📦 • Build System | packaged Build'}, {type: 'chore, section:' 🚀 chore | dependent/Build/engineering tools'}, {type: 'ci' section: '👷 Continuous Integration | CI configuration'},],}Copy the code

Then add a pre-push script to Husky

npx husky add .husky/pre-commit "yarn release"
Copy the code

This will automatically generate Changelog when we commit git push code

reference

  1. www.zhihu.com/question/31…
  2. Juejin. Cn/post / 703668…
  3. Juejin. Cn/post / 686847…

The last

Thank you very much for watching it. If you think it’s good, you can give it a thumbs up at 👍🏻. The project source is currently on Github

github.com/wzf1997/fly has been open source, interested in learning about it