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.
- Encapsulation of 2D and 3D public Util
- Rollup of personal NPM packages
- 2d React Project Setup (VITE)
- 3d React Project Setup (Webpack)
- 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:
- 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;
- Dependency management will become very simple, easy to do version dependency management and automatic version number updates
- Publishing NPM packages is also particularly easy, extracting public methods directly to the public package, which can be quickly published to NPM
- 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
-
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
- Prettier: The core code for the prettier plug-in
- 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
- 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
- Eslint detects these files and automatically fixes them
- Prettier — write is then used for automatic detection
- 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
- www.zhihu.com/question/31…
- Juejin. Cn/post / 703668…
- 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