1.create-vite #

1.1 create-vite简介 #

1.2 使用 #

npm init vite
Need to install the following packages:
  create-vite
Ok to proceed? (y) y
√ Project name: ... vite-project
√ Select a framework: » react
√ Select a variant: » react

Scaffolding project in C:\aprepare\t1\vite-project...

Done. Now run:

  cd vite-project
  npm install
  npm run dev

1.3 create-vite源码调试 #

git clone https://github.com/vitejs/vite.git
cd vite
yarn install
packages\create-vite\index.js

.vscode\launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "pwa-node",
            "request": "launch",
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}\\packages\\create-vite\\index.js",
            "args": ["create","vite-project"]
        }
    ]
}

1.4 create-vite功能 #

2.初始化项目 #

2.1 lerna初始化 #

mkdir vite2
cd vite2
lerna init

2.2 使用yarn workspace #

2.2.1 lerna.json #

{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0",
+ "npmClient": "yarn",
+ "useWorkspaces": true
}

2.2.2 package.json #

{
  "name": "root",
  "private": true,
  "devDependencies": {
    "lerna": "^4.0.0"
  },
+  "workspaces": [
+    "packages/*"
+ ]  
}

2.3 创建子包 #

lerna create @vite2/config -y //配置项
lerna create @vite2/create -y //创建项目
lerna create vite2 -y  //核心命令
lerna create @vite2/settings -y  //常量定义
lerna create @vite2/utils -y //工具方法

2.4 安装依赖 #

2.4.1 config\package.json #

packages\config\package.json

{
    "dependencies": {
    "@vite2/settings": "^0.0.0",
    "@vite2/utils": "^0.0.0",
    "fs-extra": "^10.0.0",
    "userhome": "^1.0.0"
   }
}

2.4.2 create\package.json #

packages\create\package.json

{
  "dependencies": {
    "@vite2/settings": "^0.0.0",
    "@vite2/utils": "^0.0.0",
    "chalk": "^4.1.2",
    "clone-git-repo": "^0.0.2",
    "ejs": "^3.1.6",
    "execa": "^5.1.1",
    "fs-extra": "^10.0.0",
    "glob": "^7.1.7",
    "inquirer": "^8.1.2"
  },
}

2.4.3 utils\package.json #

packages\utils\package.json

{
    "dependencies": {
    "@vite2/settings": "^0.0.0",
    "axios": "^0.21.2",
    "cross-spawn": "^7.0.3",
    "userhome": "^1.0.0"
  },
}

2.4.4 vite2\package.json #

packages\vite2\package.json

{
  "dependencies": {
    "@vite2/config": "^0.0.0",
    "@vite2/create": "^0.0.0"
  }
}

2.4.5 publishConfig #

{
  "publishConfig": {
    "access": "public",
    "registry": "http://registry.npmjs.org"
  }
}

vite2

2.5 配置命令 #

2.5.1 package.json #

packages\vite2\package.json

{
  "name": "vite2",
  "version": "0.0.0",
  "dependencies": {
    "@vite2/config":"^0.0.0",
    "@vite2/create":"^0.0.0"
  },
+ "bin":{
+   "vite2": "index.js"
+ }
}

2.5.2 index.js #

packages\vite2\index.js

#!/usr/bin/env node

async function main() {
   let argv = process.argv.slice(2);
   console.log(argv);
}

main().catch((err) => {
    console.error(err);
});

yarn link
yarn global bin 
C:\Users\zhangrenyang\AppData\Local\Yarn\bin
C:\Users\zhangrenyang\AppData\Local\Yarn\Data\link\vite2
npm bin -g
C:\Users\zhangrenyang\AppData\Roaming\npm
vite2 create vite-project

3.实现配置命令 #

3.1 安装依赖 #

yarn workspace @vite2/config  add userhome fs-extra
yarn workspace @vite2/utils  add cross-spawn userhome fs-extra

3.2 settings\index.js #

packages\settings\index.js

//执行命令脚本
exports.COMMAND_SOURCE = `
const args = JSON.parse(process.argv[1]);
const factory = require('.');
factory(args);
`
//配置文件名称
exports.RC_NAME = ".vite3rc";

3.3 config.js #

packages\utils\config.js

const userhome = require("userhome");
const fs = require("fs-extra");
const { RC_NAME } = require("@vite5/settings");
const configPath = userhome(RC_NAME);
let config = {};
if (fs.existsSync(configPath)) {
  config = fs.readJSONSync(configPath);
}
config.configPath=configPath;
module.exports = config;

3.4 executeNodeScript.js #

packages\utils\executeNodeScript.js

const spawn = require("cross-spawn");
async function executeNodeScript({ cwd }, source, args) {
    return new Promise((resolve) => {
      const childProcess = spawn(
        process.execPath,
        ["-e", source, "--", JSON.stringify(args)],
        { cwd, stdio: "inherit" }
      );
      childProcess.on("close", resolve);
    });
}
module.exports = executeNodeScript;

3.5 log.js #

packages\utils\log.js

const log = require('npmlog');
log.heading = 'vite2';
module.exports = log;

3.6 utils\index.js #

packages\utils\index.js

exports.log = require('./log');
exports.executeNodeScript = require('./executeNodeScript');
exports.config = require('./config');

3.7 packages\config\command.js #

packages\config\command.js

const {executeNodeScript} = require('@vite2/utils');
const {COMMAND_SOURCE} = require('@vite2/settings');
const command = {
  command: "config [key] [value]",
  describe: "设置或查看配置项,比如GIT_TYPE设置仓库类型,ORG_NAME设置组织名",
  builder: (yargs) => {},
  handler:async function(argv){
    await executeNodeScript({ cwd: __dirname }, COMMAND_SOURCE,argv);
  },
};
module.exports = command;

3.8 config\index.js #

packages\config\index.js

const fs = require("fs-extra");
const { log ,config} = require("@vite2/utils");
async function factory(argv) {
  const { key, value } = argv;
  console.log('config',config);
  if (key && value) {
    config[key] = value;
    await fs.writeJSON(config.configPath, config, { spaces: 2 });
    log.info("vite3","(%s=%s)配置成功保存至%s", key, value, config.configPath);
  }else if(key){
      console.log('%s=%s',key, config[key]);
  }else{
    console.log(config);
  }
}
module.exports = factory;

3.9 vite2\index.js #

packages\vite2\index.js

#!/usr/bin/env node
const yargs = require("yargs/yargs");
const configCmd = require("@vite2/config/command");
async function main() {
    const cli = yargs();
    cli
    .usage(`Usage: vite2 <command> [options]`)
    .demandCommand(1, "至少需要一个命令")
    .strict() 
    .recommendCommands()
+   .command(configCmd)
    .parse(process.argv.slice(2));
}

main().catch((err) => {
    console.error(err);
});

4.实现创建命令 #

4.1 create\command.js #

packages\create\command.js

const {COMMAND_SOURCE} = require('@vite2/settings');
const {executeNodeScript} = require('@vite2/utils');
const command = {
  command: "create <name>",
  describe: "创建项目",
  builder: (yargs) => {
    yargs.positional("name", {
      type: "string",
      describe: "项目名称",
    });
  },
  handler:async function(argv){
    let args = {name:argv.name,cwd:process.cwd()};
    await executeNodeScript({ cwd: __dirname }, COMMAND_SOURCE,args);
  }
};
module.exports = command;

4.2 create\index.js #

packages\create\index.js

async function factory(argv) {
 console.log('create',argv);
}
module.exports = factory;

4.3 vite2\index.js #

packages\vite2\index.js

#!/usr/bin/env node
const yargs = require("yargs/yargs");
const configCmd = require("@vite2/config/command");
+const createCmd = require("@vite2/create/command");
async function main() {
    const cli = yargs();
    cli
    .usage(`Usage: vite2 <command> [options]`)
    .demandCommand(1, "至少需要一个命令")
    .strict() 
    .recommendCommands()
    .command(configCmd)
+   .command(createCmd)
    .parse(process.argv.slice(2));
}
main().catch((err) => {
    console.error(err);
});

5.创建项目目录 #

5.1 安装 #

yarn workspace @vite2/create  add chalk fs-extra inquirer

5.2 create\index.js #

packages\create\index.js

+const path = require("path");
+const { red } = require("chalk");
+const { prompt } = require("inquirer");
+const fs = require("fs-extra");
+const {config, log } = require("@vite2/utils");
async function factory(argv) {
+    const { cwd, name } = argv;
+    process.chdir(cwd);//切换为当前命令执行的工作目录
+    const { ORG_NAME } = config;
+    if (!ORG_NAME) {
+        throw new Error(red("X") + " 尚未配置组织名称!");
+    }
+    const targetDir = path.join(process.cwd(), name);
+    log.info("vite2", "创建的项目目录为%s", targetDir);
+    try {
+        await fs.access(targetDir);
+        const files = await fs.readdir(targetDir);
+        if (files.length > 0) {
+            const { overwrite } = await prompt({
+                type: "confirm",
+                name: "overwrite",
+                message: `目标目录非空,是否要移除存在的文件并继续?`,
+            });
+            if (overwrite) {
+                await fs.emptyDir(targetDir);
+            } else {
+                throw new Error(red("X") + " 操作被取消");
+            }
+        }
+    } catch (error) {
+        await fs.mkdirp(targetDir);
+    }
+    log.info("vite3", "%s目录已经准备就绪", targetDir);
}
module.exports = factory;

6.下载模板 #

6.1 安装 #

6.2 settings\index.js #

packages\settings\index.js

//执行命令脚本
exports.COMMAND_SOURCE = `
const args = JSON.parse(process.argv[1]);
const factory = require('.');
factory(args);
`
+//配置文件名称
+exports.RC_NAME = ".vite3rc";
+//组织的名称
+exports.ORG_NAME = "ORG_NAME";
+//git仓库类型
+exports.GIT_TYPE = "GIT_TYPE";
+//模板存放名称
+exports.TEMPLATES = ".vite3_templates";

6.3 utils\request.js #

packages\utils\request.js

const axios = require("axios");
const { GIT_TYPE } = require("./config");
const GITEE = "https://gitee.com/api/v5";
const GITHUB = "https://api.github.com";

const BASE_URL = GIT_TYPE === "gitee" ? GITEE : GITHUB;
const request = axios.create({
    baseURL: BASE_URL,
    timeout: 5000,
});

request.interceptors.response.use(
    (response) => {
        return response.data;
    },
    (error) => {
        return Promise.reject(error);
    }
);
module.exports = request;

6.4 withLoading.js #

packages\utils\withLoading.js

async function withLoading(message, fn, ...args) {
  const ora = await import("ora");
  const spinner = ora.default(message);
  spinner.start();
  const result = await fn(...args);
  spinner.succeed();
  return result;
}
module.exports = withLoading;

6.5 packages\utils\index.js #

packages\utils\index.js

exports.log = require('./log');
exports.executeNodeScript = require('./executeNodeScript');
exports.config = require('./config');
+exports.withLoading = require('./withLoading');
+exports.request = require('./request');

6.6 create\index.js #

packages\create\index.js

const path = require("path");
+const { promisify } = require("util");
const { red } = require("chalk");
const fs = require("fs-extra");
+const { prompt } = require("inquirer");
+const userhome = require("userhome");
+const clone = promisify(require('clone-git-repo'));
+const { TEMPLATES } = require("@vite2/settings");
+const { config, log,withLoading, request} = require("@vite2/utils");
async function factory(argv) {
    const { cwd, name } = argv;
    process.chdir(cwd);//切换为当前命令执行的工作目录
+   const { GIT_TYPE,ORG_NAME } = config;
    if (!ORG_NAME) {
        throw new Error(red("X") + " 尚未配置组织名称!");
    }
    const targetDir = path.join(process.cwd(), name);
    log.info("vite2", "创建的项目目录为%s", targetDir);
    try {
        await fs.access(targetDir);
        const files = await fs.readdir(targetDir);
        if (files.length > 0) {
            const { overwrite } = await prompt({
                type: "confirm",
                name: "overwrite",
                message: `目标目录非空,是否要移除存在的文件并继续?`,
            });
            if (overwrite) {
                await fs.emptyDir(targetDir);
            } else {
                throw new Error(red("X") + " 操作被取消");
            }
        }
    } catch (error) {
        await fs.mkdirp(targetDir);
    }
    log.info("vite3", "%s目录已经准备就绪", targetDir);
+    let repos = await withLoading("读取模板列表", async () =>
+        request.get(`/orgs/${ORG_NAME}/repos`)
+    );
+    let { repo } = await prompt({
+        name: "repo",
+        type: "list",
+        message: "请选择模板",
+        choices: repos.map((repo) => repo.name)
+    });
+    let tags = await withLoading("读取标签列表", async () =>
+        request.get(`/repos/${ORG_NAME}/${repo}/tags`)
+    );
+    let { tag } = await prompt({
+        name: "tag",
+        type: "list",
+        message: "请选择版本",
+        choices: tags,
+    });
+    let repository = GIT_TYPE + `:${ORG_NAME}/${repo}`;
+    if(tag)repository+=`#${tag}`;
+    const downloadDirectory = userhome(TEMPLATES);
+    const repoDirectory = `${downloadDirectory}/${repo}/${tag}`;
+    log.info("vite3", "准备下载模板到%s", repoDirectory);
+    try {
+        await fs.access(repoDirectory);
+    } catch (error) {
+        log.info("vite2", "从仓库下载%s", repository);
+        await clone(repository,repoDirectory, {clone:true});
+    }
}
module.exports = factory;

7.渲染模板 #

7.1 安装 #

yarn workspace @vite2/create  add ejs glob execa

ask.json

[
    {
        "name": "projectName",
        "type":"text",
        "message":"请输入项目名称"
    },
    {
        "name": "projectVersion",
        "type": "text",
        "message": "请输入项目版本"
    }
]

7.2 packages\settings\index.js #

packages\settings\index.js

//执行命令脚本
exports.COMMAND_SOURCE = `
const args = JSON.parse(process.argv[1]);
const factory = require('.');
factory(args);
`
//配置文件名称
exports.RC_NAME = ".vite3rc";
//组织的名称
exports.ORG_NAME = "ORG_NAME";
//git仓库类型
exports.GIT_TYPE = "GIT_TYPE";
//模板存放名称
exports.TEMPLATES = ".vite3_templates";
+//重命名文件配置
+exports.RENAME_FILES = {
+    _gitignore: '.gitignore'
+}

7.3 packages\create\index.js #

packages\create\index.js

const path = require("path");
const { promisify } = require("util");
const { red } = require("chalk");
const fs = require("fs-extra");
const { prompt } = require("inquirer");
const userhome = require("userhome");
const clone = promisify(require('clone-git-repo'));
+const glob = promisify(require("glob"));
+const { render } = require("ejs");
+const execa = require("execa");
+const { TEMPLATES,RENAME_FILES } = require("@vite2/settings");
const { config, log, withLoading, request } = require("@vite2/utils");
async function factory(argv) {
    const { cwd, name } = argv;
    process.chdir(cwd);//切换为当前命令执行的工作目录
    const { GIT_TYPE, ORG_NAME } = config;
    if (!ORG_NAME) {
        throw new Error(red("X") + " 尚未配置组织名称!");
    }
    const targetDir = path.join(process.cwd(), name);
    log.info("vite2", "创建的项目目录为%s", targetDir);
    try {
        await fs.access(targetDir);
        const files = await fs.readdir(targetDir);
        if (files.length > 0) {
            const { overwrite } = await prompt({
                type: "confirm",
                name: "overwrite",
                message: `目标目录非空,是否要移除存在的文件并继续?`,
            });
            if (overwrite) {
                await fs.emptyDir(targetDir);
            } else {
                throw new Error(red("X") + " 操作被取消");
            }
        }
    } catch (error) {
        await fs.mkdirp(targetDir);
    }
    log.info("vite3", "%s目录已经准备就绪", targetDir);
    let repos = await withLoading("读取模板列表", async () =>
        request.get(`/orgs/${ORG_NAME}/repos`)
    );
    let { repo } = await prompt({
        name: "repo",
        type: "list",
        message: "请选择模板",
        choices: repos.map((repo) => repo.name)
    });
    let tags = await withLoading("读取标签列表", async () =>
        request.get(`/repos/${ORG_NAME}/${repo}/tags`)
    );
    let { tag } = await prompt({
        name: "tag",
        type: "list",
        message: "请选择版本",
        choices: tags,
    });
    let repository = GIT_TYPE + `:${ORG_NAME}/${repo}`;
    if (tag) repository += `#${tag}`;
    const downloadDirectory = userhome(TEMPLATES);
    const repoDirectory = `${downloadDirectory}/${repo}/${tag}`;
    log.info("vite3", "准备下载模板到%s", repoDirectory);
    try {
        await fs.access(repoDirectory);
    } catch (error) {
        log.info("vite2", "从仓库下载%s", repository);
        await clone(repository, repoDirectory, { clone: true });
    }
+    let ask = path.join(repoDirectory, "ask.json");
+    if (fs.existsSync(ask)) {
+        const askOptions = await fs.readJSON(ask);
+        const result = await prompt(askOptions);
+        const files = await glob(`**/*`, {
+            cwd: repoDirectory,
+            ignore: ['ask.json'],
+            nodir: true
+        });
+        await Promise.all(files.map(file => new Promise(async function (resolve) {
+            let content = await fs.readFile(path.join(repoDirectory, file), 'utf8');
+            let renderContent = await render(content, result);
+            let targetName = RENAME_FILES[file] || file;
+            let targetFile = path.join(targetDir, targetName);
+            await fs.ensureDir(path.dirname(targetFile));
+            await fs.writeFile(targetFile, renderContent, 'utf8');
+            resolve();
+        })));
+    } else {
+        await fs.copy(repoDirectory, targetDir);
+    }
+    process.chdir(targetDir);
+    log.info("vite3", "在%s初始化 Git 仓库", targetDir);
+    await execa("git", ["init"], { stdio: "inherit" });
+    log.info("vite3", "在%s安装依赖", targetDir);
+    await execa("npm", ["install"], { stdio: "inherit" });
+    log.info("vite3", "启动服务");
+    await execa("node", ["./node_modules/esbuild/install.js"], {
+        stdio: "inherit",
+    });
+    await execa("npm", ["run", "dev"], { stdio: "inherit" });
}
module.exports = factory;

8.发布 #

8.1 创建组织 #

8.2 发布 #

package.json

{
  "publishConfig": {
    "access": "public",
    "registry": "http://registry.npmjs.org"
  }
}
npm whoami
npm login
zhangrenyang2000
lerna publish

9.参考 #

9.1 lerna #

命令 功能
lerna bootstrap 安装依赖
lerna clean 删除各个包下的node_modules
lerna init 创建新的lerna库
lerna list 查看本地包列表
lerna changed 显示自上次release tag以来有修改的包, 选项通 list
lerna diff 显示自上次release tag以来有修改的包的差异, 执行 git diff
lerna exec 在每个包目录下执行任意命令
lerna run 执行每个包package.json中的脚本命令
lerna add 添加一个包的版本为各个包的依赖
lerna import 引入package
lerna link 链接互相引用的库
lerna create 新建package
lerna publish 发布

9.2 yarn #

命令 说明
yarn -v 查看yarn版本
yarn config list 查看yarn的所有配置
yarn config set registry https://registry.npm.taobao.org/ 修改yarn的源镜像为淘宝源
yarn config set global-folder "D:\RTE\Yarn\global" 修改全局安装目录, 先创建好目录(global), 我放在了Yarn安装目录下(D:\RTE\Yarn\global)
yarn config set prefix "D:\RTE\Yarn\global\" 修改全局安装目录的bin目录位置
yarn config set cache-folder "D:\RTE\Yarn\cache" 修改全局缓存目录, 先创建好目录(cache), 和global放在同一层目录下
yarn config list 查看所有配置
yarn global bin 查看当前yarn的bin的位置
yarn global dir 查看当前yarn的全局安装位置

9.2 yarn workspace #

作用 命令
查看工作空间信息 yarn workspaces info
给所有的空间添加依赖 yarn workspaces run add lodash
给根空间添加依赖 yarn add -W -D typescript jest
给某个项目添加依赖 yarn workspace create-react-app3 add commander
删除所有的 node_modules lerna clean 等于 yarn workspaces run clean
安装和link所有的名 yarn install 等于 lerna bootstrap --npm-client yarn --use-workspaces
重新获取所有的 node_modules yarn install --force
查看缓存目录 yarn cache dir
清除本地缓存 yarn cache clean
在所有package中运行指定的命令 yarn workspaces run

9.3 yargs #

const yargs = require("yargs/yargs");
const cli = yargs();
cli
    .usage(`Usage: vite2 <command> [options]`)
    .demandCommand(1, "至少需要一个命令")
    .strict()
    .recommendCommands()
    .command({
        command: "create <name>",
        describe: "创建项目",
        builder: (yargs) => {
            yargs.positional("name", {
                type: "string",
                describe: "项目名称",
            });
        },
        handler: async function (argv) {
           console.log(argv);
           //{ _: [ 'create' ], '$0': 'doc\\1.yargs.js', name: 'p1' }
        }
    })
    .parse(process.argv.slice(2));

9.4 node -e #

node -e "console.log(process.argv)" -- a b
node -e "console.log(JSON.parse(process.argv[1]))" -- "{\"name\":\"zhufeng\"}"
node -e "console.log(process.cwd())"
const spawn = require("cross-spawn");
async function executeNodeScript({ cwd }, source, args) {
    return new Promise((resolve) => {
      const childProcess = spawn(
        process.execPath,
        ["-e", source, "--", JSON.stringify(args)],
        { cwd, stdio: "inherit" }
      );
      childProcess.on("close", resolve);
    });
}
module.exports = executeNodeScript;

9.5 clone #

const clone = require('clone-git-repo');
let repository = 'gitee:zhufengtemplate/template-react#v1.0';
clone(repository,'./output', {clone:true},function (err) {
    console.log(err);
})