学习按需构建

需求背景

项目是运营活动内嵌app的网页活动,采用的是MPA模式,快速迭代一期一期的;在部署方面,采用CDN缓存,每次发布的代码都会在服务器上进行增量存储;随着项目的增加,每次全量构建所需花费的时间越来越久有时甚至达到5分钟,对于要经常修改重新打包部署的运营活动,无疑是无法忍受的时间成本;而且全量部署还会导致隐藏的风险,比方后面项目修改了全局公共函数或者组件,可能引发不兼容旧的已经上线的活动而开发测试并不知情的风险; 因此我们设想,是否可以只是将新增的或者新修改的页面进行部署;或者更安全的,自己指定要打包哪个活动就只build该活动是不是更好呢!

思维导索

  1. 匹配文件路径来获取入口点(entry points)的信息【ps优化点: 获取文件创建修改时间,按照最新的排在最前面】
  2. 用Node.js的inquirer模块创建交互式命令行界面,收集用户要打包哪个入口活动;
    1. inquirer.prompt(questions)向用户提问并收集用户输入
    2. inquirer.prompt(questions).then(answers => {})用于处理用户的输入
  3. 我们的项目是部署在GitLab上,所以可以通过GitLab API开发文档手动调用获取或触发CI/CD流水线进行编译部署
  4. 编译时要将上述2中用户输入的用户信息存储起来,最终动态写入到vue.config.json中的多入口的配置中;也就是最终将pages下的key-value改成最终自己在第二步中收集的
  5. 目前来思考🤔一个问题,第一步和第二步都是在本地的node环境中,全局各处变量/函数均可以拿到;但是经过第三步CI/CD是在远端的gitlab容器中在跑多数是Unix系统,用户2中的输入如何存储,存储在哪,CI/CD执行时如何才能读取到呢???带着这个问题,我们边开发边思考

一、 获取MPA模式下所有的活动页面目录

js 复制代码
function getEnterPoints() {
  const moduleFilePathList = glob.sync('./src/activity/**/main.js')
  .map(filePath => {
    const name = filePath.match(/\/activity\/(.+)\/main.js/);
    return {
      outputName: snakeCase(name[1]), // 蛇形命名法(snake_case)作为输出名称
      filePath,
      retFilePath: filePath.replace('./src', '../src')
    }
  });
}

function getFsDirEditLastTime(filePath) {
    const file = filePath.replace("/main.js", "")
    return fs.statSync(file).mtime.getTime()
}

const entryPoints = getEnterPoints()
const entryPointFileNames = 
    entryPoints.sort((a,b) => getFsDirEditLastTime(b.filePath) - getFsDirEditLastTime(a.filePath))
    .map(item => item.outputName)

二、交互式界面收集用户要打包的文件

js 复制代码
const inquirer = require("inquirer");
const axios = require("axios");

// 交互式问题
const questions = [
    {
      type: "checkbox",
      name: "page",
      message: chalk.yellow("**请选择需要发布构建的活动页面**"),
      choices,
    }
]
  
function inquirerPrompt(questions) {
    return inquirer.prompt(questions)
}

三、手动触发一个新的流水线

# Pipelines API文档地址 创建一个新的pipeline

js 复制代码
async function triggerPipeline() {
    const { page } = await inquirerPrompt(questions)
    axios.post(
        `https://gitlab.example.com/api/v4/projects/${yourProjectId}/pipeline`, 
        {
            ref: 'main', // 便于理解,暂时写死branch
            variables: [], // array {'key': 'TEST', 'value': 'test variable'}]
        }
    )
}

注:yourProjectId在你的项目中的Settings/General中可以看到Project ID

三(1)问题-为什么是Create a new pipeline而不是Trigger a pipeline with a token?

Trigger a pipeline with a token 这个功能允许通过使用特定的令牌(token)来触发流水线。每个 GitLab 项目都可以生成一个唯一的触发令牌,用户可以使用该令牌来触发流水线的执行。触发令牌通常用于与外部系统或服务集成,例如在持续集成/持续部署工具中配置触发器,或通过 API 调用来触发流水线。使用触发令牌触发的流水线可以执行与手动创建流水线相同的任务和操作;

Create a new pipeline 这个功能允许用户手动创建一个新的流水线。用户可以在 GitLab 界面上找到这个功能,并通过点击相应的按钮或链接来触发流水线的创建。创建新流水线时,GitLab 会根据预定义的流水线定义文件(通常是 .gitlab-ci.yml 文件)来执行一系列的任务。这些任务可以包括构建、测试、部署等操作,根据流水线定义文件中的配置进行自动化执行。

根据上述介绍,可以看出如果我们需要在打包部署时做一些其他操作,比方测试啊,转换文件等其他操作,那么创建一个流水线,然后在.gitlab-ci.yml 文件中做更多一系列的任务,显然这种方式的可扩展性更好;

三(2)问题-参数ref填写什么?

按照说明填写分支名或者tag; 我们项目是依据分支来确定服务器的,所以会选择branch;那么这个branch怎么来呢? 可以在【二、交互式界面收集用户要打包的文件】中加一个question;

js 复制代码
const questions = [
    {
      type: "list",
      name: "branch",
      message: chalk.yellow("**请选择trigger的分支**"),
      choices: ["main", "release"],
      default: ["main"]
    },
    {
      type: "checkbox",
      name: "buildPage",
      message: chalk.yellow("**请选择trigger的entry point**"),
      choices,
    },
]
这样ref中就可以填写通过拿到inquirerPrompt返回的branch了

三(3)问题:回到思维导索中的第5个问题-如何将我choose的page暂存起来能让CI/CD中使用??

"Create a new pipeline"中有关键字variables我们还没使用,文档提供必有其用;查阅文档克制,在里面是可以添加多个变量并为每个变量指定一个键值对key - value; 所以我可以添加一个名为PAGE_VARIABLE的变量,设置其值为上述选择的page名;这里为了可扩展性,存储成为一个json字符串会更友好

js 复制代码
async function triggerPipeline() {
    const { page, branch } = await inquirerPrompt(questions)
    axios.post(
        `https://gitlab.example.com/api/v4/projects/${yourProjectId}/pipeline`, 
        {
            ref: branch,
            variables: [
                { key: 'PAGE_VARIABLE', value: JSON.stringify({ page, branch }) }
            ], // array {'key': 'TEST', 'value': 'test variable'}]
        }
    )
}

在CI/CD过程中,你可以通过$VARIABLE_NAME的方式来访问这些变量。例如,在一个Job中,你可以使用以下方式输出这个变量的值:

js 复制代码
job_name: 
    script: 
        - echo $PAGE_VARIABLE

这样,当你创建一个新的Pipeline,并携带了变量PAGE_VARIABLE时,CI/CD过程中的Job就可以访问并使用这个变量了。

请注意,通过"Create a new pipeline"页面携带的变量仅适用于该次Pipeline运行,不会影响项目中的全局变量或其他Pipeline运行。如果你需要在多个Pipeline运行之间共享变量,你可以考虑使用项目级别的环境变量或将变量定义在.gitlab-ci.yml文件中。

上述只是将PAGE_VARIABLE打印在终端了,如果我们想将其存储,就可以用cho $PAGE_VARIABLE > ./build/catch.json覆盖式写入到catch.json文件中;如果想在文件末尾追加而不是覆盖就用echo $PAGE_VARIABLE >> ./build/catch.jso

所以接下来执行.gitlab-ci.yml文件时,我们就要将上述的PAGE_VARIABLE写入到一个临时文件中

四、执行流水线定义文件.gitlab-ci.yml

js 复制代码
stages:
  # 记录page
  - record_page
  # 打包构建
  - build
  # 打开表示自动部署
  - deploy
  
record_page:
  stage: record
  # image: centos:7
  image: registry.example.com/library/centos:7
  cache:
    # 写入之后需要缓存,共享给其它stage
    untracked: false
    key: 
      files: 
        - package-lock.json
    paths:
      # 发布模块记录(构建发布用)
      - build/.catch.json
  script:
    - echo $PAGE_VARIABLE > build/.catch.json
  rules:
    # $CI_PIPELINE_SOURCE === "api" -> pipeline 是由 api 触发
    - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "api"
  
main_build:
  # image: node:16.15-bullseye-slim
  image: registry.example.com/library/node:16.15-bullseye-slim
  stage: build
  variables:
    BUILD_MODE: "test"
  script:
      - start_time=$(date +%s)
      - npm install --registry https://registry.npmmirror.com
      - end_time=$(date +%s)
      - echo "脚本执行时间:$((end_time - start_time))秒"
      - npm run build:$BUILD_MODE
  cache:
    untracked: false
    key: 
      files: 
        - package-lock.json
    paths:
      - node_modules
  artifacts:
    paths:
      - dist
    exclude:
      - dist/**/*.map

  rules:
    - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "api"

经过record_page后,就会在远端生成一个./build/catch.json文件,并且其中内容为

json 复制代码
{
    page: 'login',
    branch: 'main'
}

上述最终就是执行npm run build:test

在package.json中我们配置build:test为执行build,并设置环境mode为test

json 复制代码
  "scripts": {
    "build:test": "vue-cli-service build --mode test",
  },

执行build:test就会自动查找vue.config.js中的配置

js 复制代码
functoiin getManualChooseActivityPage() {
   // 这里我们就可以拿到catch.json中写的文件的相关信息,将其配置到构建的多文件入口即可;在此不做缀叙
}
module.exports = {
   pages: getManualChooseActivityPage(),
   outputDir: `./dist/`,
   ......
}

知识点扩充

1. process学习

在 Node.js 中,process 是一个全局对象,提供了与当前 Node.js 进程相关的信息和控制能力。它是一个 EventEmitter 的实例,可以用于处理进程的事件和信号。 process 对象具有许多属性和方法,下面是其中一些常用的:

  1. process.argv:一个包含命令行参数的数组,类似于前面提到的 process.argv。它可以访问传递给 Node.js 脚本的命令行参数。
  2. process.env:一个包含当前进程环境变量的对象。可以通过该对象读取和修改环境变量的值。
  3. process.cwd():返回当前工作目录的路径。
  4. process.exit([code]):退出当前 Node.js 进程。可选的 code 参数指定退出码,默认为 0。
  5. process.on(event, listener):用于注册事件监听器。常见的事件包括 'exit'(进程退出时触发)、'uncaughtException'(捕获未处理的异常)等。
  6. process.stdout:标准输出流。可以使用它来打印信息到控制台。
  7. process.stderr:标准错误流。用于输出错误信息到控制台。
  8. process.stdin:标准输入流。可以通过它读取用户的输入。

除了上述属性和方法,process 还提供了其他一些功能,如内存使用情况的监控、事件循环的控制、信号处理等。通过 process 对象,我们可以获取和控制当前 Node.js 进程的各种信息,以及与其交互。

需要注意的是,process 是一个全局对象,因此无需使用 require 来引入它,可以直接在 Node.js 脚本中使用。

1.1 process.argv

process.argv 是一个Node.js中的全局变量,它包含了当前正在执行的Node.js脚本的命令行参数。它是一个数组,其中的第一个元素是Node.js的可执行文件的路径,第二个元素是正在执行的脚本文件的路径,后续的元素是命令行参数 例如,如果你在命令行中运行以下命令:

js 复制代码
node script.js arg1 arg2 arg3

那么 process.argv 的值将是一个包含以下元素的数组:

js 复制代码
['node', 'script.js', 'arg1', 'arg2', 'arg3']

你可以通过索引访问特定的命令行参数,例如 process.argv[2] 将返回 'arg1'process.argv[3] 将返回 'arg2',以此类推。

相关推荐
明月心9521 天前
git remote add 用法
gitlab
only_Klein1 天前
jenkins流水线报错:Connection reset by peer
ci/cd·kubernetes·gitlab·jenkins·ssl
梁萌2 天前
docker部署gitlab和gitlab runner
docker·eureka·gitlab
johnnyAndCode2 天前
Idea 设置GitLab时使用账密,而不是token的配置方法
gitlab·idea
天外飞雨2 天前
Gitlab使用
gitlab
BUTCHER53 天前
GitLab SSH 密钥配置
运维·ssh·gitlab
明月心9523 天前
GitLab使用
gitlab
明月心9524 天前
gitlab pull requets
gitlab
BUTCHER54 天前
GitLab基本设置
gitlab
张小凡vip4 天前
Kubernetes---gitlab的ci/cd发布基于k8s的项目示例参考
ci/cd·kubernetes·gitlab