Monorepo之-15min体验一下lerna吧

37次阅读

共计 5369 个字符,预计需要花费 14 分钟才能阅读完成。

hello!大家好,我是一名前端开发,致力于用最简单直白的介绍方式来和大家分享技术、期待共同进步的好青年,哈哈


好青年本人在本篇文章里将根据自身项目经历阐述以下几点:

  • 为何利用 lerna 来进行项目管理。
  • 以及具体的实现步骤。
  • 总结一下我遇到的坑(人生艰难,希望我犯过的错能助你们早日渡劫成功,那我在这吭哧吭哧的码字也算值了,哈哈哈)
  • 有关项目集成

先了解一下什么是 Monorepo?

这节略(不)过(想)去(看),问题不大

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

Monorepo 它是一种管理 organisation 代码的方式,在这种方式下会摒弃原先一个 module 一个 repo 的方式,取而代之的是把所有的 modules 都放在一个 repo 内来管理。

目前诸如 Babel, React, Angular, Ember, Meteor, Jest 等等都采用了 Monorepo 这种方式来进行源码的管理。

其实还有一种管理代码的方式叫做 Multrepo,就是将项目分化成为多个模块,并针对每一个模块单独的开辟一个 reporsitory 来进行管理。(本节不做介绍,因为我没实践过。表格内容参考自)

好,接下来正文来啦!

我为什么要用 monorepo 方式管理项目?(第一段也可略过不看)

项目初期,产品信誓旦旦的说,要做 A 和 B 两条线的业务,但是独立开的,互不干扰,OK,我们按照过去的的方式进行单独 Git repo,两个月后,A, B ,C ,D(D 业务被一个执拗的程序员换成了自己爱用的 react-app 脚手架,是的,身边总是会有这样一些人,他们只关心自己顺不顺手,不会在意部门的规范和统一会带来的好处),E 业务分别在线上。突然,项目被提出合并,产品说:以后这就是一套体系,要相互调用,并且整体叫做** 乐园。是的,接下来的需求,各个项目的功能模块一直在相互调用。起初,为了解决这一问题,我引入了 git submodule 机制。是可以共用一些业务组件了,BUT,后续的开发 部署等非常繁琐,需求牵一发而动全身。每个子项目都要分别打包、git 提交、部署。工作量一直是 5 倍状态(非常重复)。

总结来说:一个理想的开发生态应该是这样的

  1. 只关心业务代码,可以直接跨业务复用而不关心复用方式,
  2. 调试时所有代码都在源码中。
  3. 在前端开发环境中,多 Git Repo,多 Npm 是非常大的阻力,它们导致复用要关心版本号,调试需要 Npm Link。

4. 当各个业务耦合公用模块较多,又不是一套脚手架结构(但都是基于 REACT 或 VUE),lerna 很合适做这种类型的项目管理。

基于以上理由,我使用了 lerna。


具体的实现步骤

lerna 有很多功能,大家可以去 git 上浏览,我这里只罗列用到的。
https://github.com/lerna/lerna/#commands

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

以上这一步,你就会看到 lerna 的基础目录结构了。

monoTest/
  packages/
  package.json
  lerna.json

各个子项目都要放在 packages 目录下(现成的项目就整整齐齐的粘贴进去,新建也可)

好,该安装 node_modules 了。

$ lerna bootstrap

以上这一步,会自动为 项目们 进行 npm install 和 npm link 操作(有点慢 && 注意:每个项目 package.json 内的“name”必须不同)

安装成功后,如果没有相互公用的文件。进入每个子项目目录下,就可以分别正常启动啦。等同于在单独的 git-repo 里启动。

但是!

没有公用文件,我们干嘛要折腾呢!

SO.

我把公用文件放在和项目平级

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

我们部门自己的脚手架,只要配置好 alias 的指向即可直接调用公用组件。
项目运行、打包完全 OK。

好,我们单独来说一下美好的 create-react-app(D 项目脚手架)


总结一下我遇到的问题

create-react-app 限制 src 外文件

// 报错信息如下: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/.

可是我势必要用到子项目平级下的公共模块的啊!

我看网上好多说,解决方案是 git eject -> 暴露 webpack.config 文件进行配置。

but !

大家可以了解一下 react-app-rewired
(不得不说,我同事除了在技术上很有“独到”追求以外,还是有自己的追求在的)

react-app-rewired

一个 CRA 再配置的工具,源自 React 社区,可以在不 eject 的情况下自定义配置 CRA 脚手架创建的 app。原理很简单,在项目根目录下新建一个配置文件(config-overrides.js),把 webpack 的配置作为一个 config 对象传入 react-app-rewired,再用 config-overrides 中的配置对其做修改,然后用修改后的 config 对象对项目打包。

如果想在 react-app-rewired 下进行 src 外文件的使用只需两步(比装大象步骤还少),但,网上资料太匮乏了,我自己尝试➕查文档,终于搞定。

主要步骤:

  • 去掉对 /src 的限制

    在 config-overrides.js 中配置如下:

  // 去掉 src 限制
  config.resolve.plugins = [];
  //alias 配置
  '@commonthings': path.join(process.cwd(), '../commonThings')
  • 对外部文件编译配置相应的 loader
  1. 新建文件 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. 在 config-overrides.js 中配置如下:
// 引入
const webpackConfig = require('./webpack.config');
  // 增加对外部文件的编译
        config.module.rules[1].oneOf.push(...webpackConfig.module.rules);

config-overrides.js 全配置如下:

/* 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) {
        // 增加对外部文件的编译
        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 加入
        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';

        // 调整 css 输出时的 hash
        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;
            }
        }

        // 调整 css 输出时的 hash
        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. 此时所有项目皆可运行。

接下里,由于项目还是要分别提交代码,分别打包代码等一系列无意义重复劳作问题存在,为了简化开发步骤,提升效率。我要进行项目的持续集成持续部署
方案选型:

  1. sh 文件

2.Gitlab ci/cd

未完 … 下周更新。

正文完
 0