Monorepo 15 minutes to experience lerna

Posted by MikeSnead on Fri, 30 Aug 2019 10:13:13 +0200

hello! hello, everyone, I am a front-end development, committed to sharing technology with you in the simplest and straightforward way of introduction, looking forward to common progress of good young people, ha ha ha

In this article, I will elaborate the following points according to my own project experience:

  • Why use lerna for project management?
  • And the specific implementation steps.
  • Summarize the pits I encountered (life is difficult, I hope my mistakes will help you to make the robbery as soon as possible, then my words are worth it, haha)
  • Project Integration

First, what is Monorepo?

This abbreviation is not a big problem.

Monorepo is a unified source code repository used by an organisation to host as much of its code as possible.

Monorepo is a way of managing organisation code, in which the original module-repo approach is abandoned and all modules are managed in one repo instead.

At present, such as Babel, React, Angular, Ember, Meteor, Jest and so on, Monorepo is used to manage the source code.

In fact, there is another way to manage code called Multrepo, which is to divide the project into multiple modules and create a reporsite for each module to manage. (I will not introduce this section because I have not practiced it. Table Content Reference

Okay, here comes the text.

Why do I manage projects in a monorepo way? (The first paragraph can also be skipped)

At the beginning of the project, the product promised to do two lines of business, A and B, but independent, non-interference, OK, we carried out Git repo alone in the past way. Two months later, A, B, C, D (business was replaced by react-app scaffolding that a stubborn programmer loved, yes, always around. There are some people who only care about their own success, not about the benefits of standardization and unification of the department. E business is online separately. Suddenly, the project was put forward to merge, and the product said: This is a system to call each other in the future, and the whole is called ** Paradise. Yes, the next requirement, the functional modules of each project have been calling each other. Initially, in order to solve this problem, I introduced the git submodule mechanism. It is possible to share some business components, BUT, subsequent development and deployment, etc. are very cumbersome, and the need to move the whole body. Each sub-project should be packaged, git submitted and deployed separately. The workload has always been five times (very repetitive).

To sum up, an ideal development ecology should be like this.

  1. Concerned only with business code, it can be reused directly across services without concern for reuse methods.
  2. When debugging, all the code is in the source code.
  3. In the front-end development environment, multi-Git Repo and multi-Npm are very big resistance. They cause reuse to care about version number and debugging to need Npm Link.

4. When there are many business-coupled common modules and not a scaffolding structure (but all based on REACT or VUE), lerna is suitable for this type of project management.

For the above reasons, I used lerna.

Specific implementation steps

lerna has many functions, you can go to git to browse, I just list the use here.
https://github.com/lerna/lerna/#commands

$ npm install --global lerna
$ git init monoTest
$ cd monoTest
$ lerna init

In this step, you will see the basic directory structure of lerna.

monoTest/
  packages/
  package.json
  lerna.json

Each sub-project should be placed in the packages directory (ready-made projects can be neatly pasted in, new ones can also be built)

Okay, it's time to install node_modules.

$ lerna bootstrap

In this step, npm install and npm link operations are automatically performed for projects (a bit slow & note: the "name" in each project package.json must be different)

After successful installation, there are no files that are common to each other. Enter each sub-project directory, you can start separately. Equivalent to booting in a separate git-repo.

But!

Without public documents, why bother?

SO.

I put the public documents at the level of the project.

packages/
├── commonThings
│   └── package.json
├──A
│   └── package.json
├── B
│   └── package.json
└── C
│    └── package.json
└── D
│    └── package.json
└── E
    └── package.json

Our department's own scaffolding, as long as we configure alias's direction, can directly call public components.
The project runs and packages completely OK.

OK, let's talk about the beautiful create-react-app (D project scaffolding) separately.

Summarize the problems I have encountered.

create-react-app Restricts Extra-src Files

//The error information is as follows:
Module not found: You attempted to import ****** which falls outside of the project src/ directory. Relative imports outside of src/ are not supported. You can either move it inside src/, or add a symlink to it from project's node_modules/.

But I certainly need to use the sub-project level under the public module ah!

I see a lot on the Internet that the solution is to configure git eject - > exposing webpack.config files.

but !

You can learn about react-app-rewired
(I have to say that my colleague has his own pursuit besides his unique pursuit in technology.)

react-app-rewired

A CRA reconfiguration tool, derived from the React community, can customize the app created by CRA scaffolding without eject. The principle is simple: create a new configuration file (config-overrides.js) in the project root directory, transfer the configuration of webpack as a config object into react-app-rewired, modify it with the configuration in config-overrides, and package the project with the modified config object.

If you want to use src files under react-app-rewire, it only takes two steps (less than elephant loading), but there is too little information on the internet. I try to check the documents myself and finally get it done.

Main steps:

  • Remove restrictions on / src

    Configuration in config-overrides.js is as follows:

  //Remove src restrictions
  config.resolve.plugins = [];
  //alias configuration
  '@commonthings': path.join(process.cwd(), '../commonThings')
  • Compile and configure the corresponding loader for external files
  1. New file webpack.config.js
// webpack.config.js
const path = require('path')
module.exports = {
    module: {
        rules: [
            // Process JS with Babel.
            {
                test: /\.(js|jsx|mjs)$/,
                include: [path.join(__dirname, '../happypark_component')],
                exclude: /node_modules[\\\/](?!wbpay-repoch)/,
                loader: require.resolve('babel-loader'),
                options: {
                    plugins: ['transform-decorators-legacy'],
                    presets: [
                        require.resolve("babel-preset-react"),
                        [
                            require.resolve("babel-preset-es2015"),
                            { modules: false }
                        ],
                        require.resolve("babel-preset-stage-0")
                    ],
                    // This is a feature of `babel-loader` for webpack (not Babel itself).
                    // It enables caching results in ./node_modules/.cache/babel-loader/
                    // directory for faster rebuilds.
                    cacheDirectory: true
                },
            }
        ]
    }
}
  1. Configuration in config-overrides.js is as follows:
//Introduce
const webpackConfig = require('./webpack.config');
  //Increase compilation of external files
        config.module.rules[1].oneOf.push(...webpackConfig.module.rules);

config-overrides.js is fully configured as follows:

/* config-overrides.js */
const rewireSass = require('react-app-rewire-scss');
const rewireMobx = require('react-app-rewire-mobx');
const { paths } = require('react-app-rewired');
const path = require("path");
const webpackConfig = require('./webpack.config');

module.exports = {
    webpack: function (config, env) {
        //Increase compilation of external files
        config.module.rules[1].oneOf.push(...webpackConfig.module.rules);

        // For require source file outside of src/. ( remove ModuleScopePlugin )
        config.resolve.plugins = []

        config.resolve.alias = {
            '@components': path.resolve(__dirname, `${paths.appSrc}/components/`),
            '@core': path.resolve(__dirname, `${paths.appSrc}/core/`),
            '@pages': path.resolve(__dirname, `${paths.appSrc}/pages/`),
            '@assets': path.resolve(__dirname, `${paths.appSrc}/assets/`),
            '@commonthings': path.join(process.cwd(), '../commonThings')
        }

        //sass joins
        config = rewireSass(config, env);

        config = rewireMobx(config, env);

        // config.output.path = path.resolve(__dirname, './build/jingcai/h5/');        
        config.output.publicPath = '//js.t.sinajs.cn/c2p/purchase/jingcai/h5/';
        config.output.filename = 'static/js/bundle.js';
        config.output.chunkFilename = 'static/js/[name].chunk.[hash:8].js';

        // hash when adjusting css output
        for (var i = 0, l = config.plugins.length; i < l; ++i) {
            if (config.plugins[i].filename === 'static/css/[name].[contenthash:8].css') {
                config.plugins[i].filename = 'static/css/[name].css';
                break;
            }
        }

        // hash when adjusting css output
        var rules = config.module.rules;
        for (var i = 0, l = rules.length; i < l; ++i) {
            if (rules[i].oneOf) {
                for (var j = 0, jl = rules[i].oneOf.length; j < jl; ++j) {
                    if (rules[i].oneOf[j].options && rules[i].oneOf[j].options.name === 'static/media/[name].[hash:8].[ext]') {
                        rules[i].oneOf[j].options.name = 'static/media/[name].[ext]';
                    }
                }
            }
        }

        return config;
    },

    devServer: function (configFunction) {
        return function (proxy, allowedHost) {
            let config = configFunction(proxy, allowedHost);
            config.disableHostCheck = true;
            // Return your customised Webpack Development Server config.
            return config;
        }
    }

}

ok. At this point, all projects are operational.

Next, there are a series of meaningless duplicate work problems, such as submitting code separately and packaging code separately, in order to simplify the development steps and improve efficiency. I'm going to make continuous integration and continuous deployment of the project.
Scheme selection:

  1. sh file

2.Gitlab ci/cd

  1. ...

Not finished... updated next week.

Topics: Javascript React JSON Webpack git