基于lerna开发脚手架
脚手架开发难点
- 分包:将复杂的系统拆分成若干个模块
 - 命令注册(vue create、vue add、vue invoke)
 - 参数解析:
- options全称:--version、--help
 - options简写:-V、-h
 - 带 params 的 options:--path /User/zjw/Desktop/vue-test
 
 
示例:
vue command [options] <params>
- 帮助文档:
- global help
- Usage
 - Options
 - Commands
 
 
 - global help
 
示例:vue 的帮助信息:
Usage: vue <command> [options]
Options:
  -V, --version                              output the version number
  -h, --help                                 output usage information
Commands:
  create [options] <app-name>                create a new project powered by vue-cli-service
  add [options] <plugin> [pluginOptions]     install a plugin and invoke its generator in an already created project
  invoke [options] <plugin> [pluginOptions]  invoke the generator of a plugin in an already created project
  inspect [options] [paths...]               inspect the webpack config in a project with vue-cli-service
  serve [options] [entry]                    serve a .js or .vue file in development mode with zero config
  build [options] [entry]                    build a .js or .vue file in production mode with zero config
  ui [options]                               start and open the vue-cli ui
  init [options] <template> <app-name>       generate a project from a remote template (legacy API, requires @vue/cli-init)
  config [options] [value]                   inspect and modify the config
  outdated [options]                         (experimental) check for outdated vue cli service / plugins
  upgrade [options] [plugin-name]            (experimental) upgrade vue cli service / plugins
  migrate [options] [plugin-name]            (experimental) run migrator for an already-installed cli plugin
  info                                       print debugging information about your environment
  Run vue <command> --help for detailed usage of given command.
- command help
- Usage
 - Options
 
 
vue create 的帮助信息:
Usage: create [options] <app-name>
create a new project powered by vue-cli-service
Options:
  -p, --preset <presetName>       Skip prompts and use saved or remote preset
  -d, --default                   Skip prompts and use default preset
  -i, --inlinePreset <json>       Skip prompts and use inline JSON string as preset
  -m, --packageManager <command>  Use specified npm client when installing dependencies
  -r, --registry <url>            Use specified npm registry when installing dependencies (only for npm)
  -g, --git [message]             Force git initialization with initial commit message
  -n, --no-git                    Skip git initialization
  -f, --force                     Overwrite target directory if it exists
  --merge                         Merge target directory if it exists
  -c, --clone                     Use git clone when fetching remote preset
  -x, --proxy <proxyUrl>          Use specified proxy when creating project
  -b, --bare                      Scaffold project without beginner instructions
  --skipGetStarted                Skip displaying "Get started" instructions
  -h, --help                      output usage information
还有很多,比如:
- 命令行交互
 - 日志打印
 - 命令行文字变色
 - 网络通信:HTTP/WebSocket
 - 文件处理
 
等等......
lerna 介绍
原生脚手架开发痛点分析:
痛点一:重复操作
- 多Package本地link
 - 多Package依赖安装
 - 多Package单元测试
 - 多Package代码提交
 - 多Package代码发布
 
痛点二:版本一致性
- 发布时版本一致性
 - 发布后相互依赖版本升级
 
package越多,管理复杂度越高
lerna 简介:
lerna 是一个优化基于git+npm的多package项目的管理工具。
使用 lerna 开发脚手架优势:
- 大幅减少重复操作
 - 提升操作的标准化
 
lerna 是架构优化的产物,它揭示了一个架构真理:项目复杂度提升后,就需要对项目进行架构优化。架构优化的主要目标往往都是以效能为核心。
lerna 开发脚手架流程(重点)

基于 lerna 开发脚手架:
- 安装 lerna
 
npm install -g lerna
- 创建项目
 
git init zjw-cli-dev && zjw-cli-dev
- 初始化 Lerna 项目
 
lerna init
- 创建 Package
 
lerna create core
- 安装依赖
 
# 安装到所有 package 的 dependencies
lerna add mocha
# 安装到所有 package 的 devDependencies 
lerna add mocha --dev
# 安装到某个 package 的 devDependencies 下,如果之前已经安装过了,请先把 package.json 中的依赖删除
lerna add mocha package/core --dev
- 删除依赖
 
lerna clean
- 重新安装依赖
 
lerna bootstrap
- 执行单元测试
 
# test 命令是在 package.json 配置的 script 命令
lerna run test
- 执行特定包的单元测试
 
# 安装依赖提供的时文件夹名称,而执行命令使用包名称
lerna run --scope @zjw-cli-dev/core test
- link 项目
 
lerna link
- 发布项目
 
lerna publish
使用细节:
lerna init
- 会自动完成 git 初始化,但不会创建 .gitignore,这个必须要手动添加,否则会将 node_modules 目录都上传到 git,如果 node_modules 已经加入 git stage,可使用:
 
git reset HEAD <file>
执行 unstage 操作,如果文件已经被 git 监听到变更,可使用:
git checkout -- <filename>
将变更作废,记得在执行操作之前将文件加入 .gitignore
# gitignore
.vscode
.idea
.DS_Store
node_modules
packages/**/node_modules
lerna-debug.log
lerna create
- 在创建 package 时,为了防止重名在输入 package name 时一般都是创建在某个组织内,如:@zjw-cli-dev/core。在 @ 和 /之间就是组织名称。
 声明在组织内的 package 需要 npm 上创建对应的 组织,否则发布不了。(Add Organization ---> 输入名称,点击 create ---> 点击 skip)
// package.json { ... "name": "@zjw-cli-dev/core", ... }
lerna add
- 第一个参数:添加 npm 包名
 - 第二个参数:本地 package 的路径
 - 选项:--dev:将依赖安装到 devDependencies,不加时安装到 dependencies
 
lerna add <package> [loc] --dev
lerna link
如果未发布上线,需要手动将依赖添加到 package.json 再执行 lerna link
// 如:在 @zjw-cli-dev/core 的 package.json 中 dependencies 添加 @zjw-cli-dev/utils 依赖 { ... dependencies: { "@zjw-cli-dev/utils": "^1.0.0" } ... }
lerna clean
- 只会删除 node_modules,不会删除 package.json 中的依赖
 
lerna exec 和 lerna run
- --scope 属性后添加的是包名,而不是 package 的路径,这点和 lerna add 用法不同
 
lerna publish
- 发布时会自动执行:git add package-lock.json,所以 package-lock.json 不要加入 .gitignore
 - 先创建远程仓库,并且同步一次 master 分支
 - 执行 lerna publish 前先完成 npm login
 - 如果发布的 npm 包名为:@xxx/yyy 的格式,需要先在 npm 注册名为:xxx 的 organization,否则可能会提交不成功
 发布到 npm organization(group) 时默认为 private,所以我们需要手动在 package.json 中添加如下配置:
"publishConfig": { "access": "public" }
lerna 是如何引用本地 lib 库的?
- package.json 中引用本地依赖
 
"dependencies": {
    "lerna": "file:core/lerna"
}
- lerna publish 发布时会将 
file:进行替换 
resolveLocalDependencyLinks() {
    // resolve relative file: links to their actual version range
    const updatesWithLocalLinks = this.updates.filter(node =>
      Array.from(node.localDependencies.values()).some(resolved => resolved.type === "directory")
    );
    return pMap(updatesWithLocalLinks, node => {
      for (const [depName, resolved] of node.localDependencies) {
        // regardless of where the version comes from, we can't publish "file:../sibling-pkg" specs
        const depVersion = this.updatesVersions.get(depName) || this.packageGraph.get(depName).pkg.version;
        // it no longer matters if we mutate the shared Package instance
        node.pkg.updateLocalDependency(resolved, depVersion, this.savePrefix);
      }
      // writing changes to disk handled in serializeChanges()
    });
}