原项目是在webpack里配置entry为列表,遍历多个活动页面打包。现改造项目拆分为独立的项目,又想能够在需要时遍历所有项目打包,所以在根目录加了一个build命令,并写了一个脚本去执行。

命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"name": "@mono/dsp",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "npm run build:prod",
"build:dev": "cross-env NODE_ENV=production VUE_APP_ENV=dev node build/index.mjs",
"build:test": "cross-env NODE_ENV=production VUE_APP_ENV=test node build/index.mjs",
"build:uat": "cross-env NODE_ENV=production VUE_APP_ENV=uat node build/index.mjs",
"build:prod": "cross-env NODE_ENV=production VUE_APP_ENV=prod node build/index.mjs"
},
...
}

在根目录新建build目录存放脚本:

功能实现

首先遍历活动目录,拿到所有项目路径。这里用fs和path模块即可。当前路径用__dirname。

1
2
3
4
5
6
const fs = require('fs')
const path = require('path')
const getChildFolders = (dir) => {
const children = fs.readdirSync(dir);
return children.filter(name => fs.statSync(path.resolve(dir, name)).isDirectory())
}

然后遍历执行打包命令。这里用到child_process模块,开启子线程进行编译。

1
2
3
4
5
6
7
8
9
const cp = require('child_process')

const cmd = `cd ${url} && npm run build:${env}`
cp.exec(cmd, {
cwd: process.cwd()
}, function (err, stdout, stderr) {
console.log(err || stdout || stderr)
resolve()
})

这里一开始执行就特别的卡,因为同时开了太多子进程去打包。将遍历改为异步。

1
2
3
4
5
6
const asyncForEach = async (arr, fn) => {
for (let item of arr) {
// console.log(item)
await fn(item)
}
}

待打包完,执行部署,将项目的dist目录复制到指定目录并重命名。这里可以用fs来实现,发现有个fs-extra更方便。将fs引入改为fs-extra,包含所有fs的方法,原先fs使用不用更改。

1
2
3
4
5
6
7
8
const fs = require('fs-extra')
const path = require('path')
const deployProgram = async (dir, name, dir2) => {
const src = path.join(dir, name, 'dist')
const target = path.join(dir2, name)
fs.copySync(src, target)
}

到这里,基本功能已经实现,加一些console就能看到打包的状况。

优化

我们再引入chalk对console做一些美化,并打印进度。这里引入progress组件,看进度更方便一些。

此时遇到一个eslint报错,说不能require es模块。原来npm install chalk安装的模板只支持es module。改成import引入chalk,又遇到另一个报错,说不支持在node环境用import。因此,我们把这两个脚本后缀名改成mjs,然后所有require改成import。

这时还有一个报错,说是__dirname未定义,这个变量只能在node环境用。上网找了下解决方案。

1
2
3
4
5
import { dirname } from "node:path"
import { fileURLToPath } from "node:url"

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

另外node版本在14以下不支持在最外层直接使用await。改用一个async函数包裹下。

最终代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import utils from './utils.mjs'
import path from 'path'
import fs from 'fs-extra'
import ProgressBar from 'progress';
// const chalk = require('chalk')
import chalk from 'chalk';
import { dirname } from "node:path"
import { fileURLToPath } from "node:url"

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// 环境
const env = process.env.VUE_APP_ENV
const NODE_ENV = 'production'

// 目录
const activityPath = path.join(__dirname, '../packageActivity')
const subpagePath = path.join(__dirname, '../packageSubpage')
const deployPath = path.join(__dirname, '../releases')
const subpageDeployPath = path.join(__dirname, '../releases/subpage')

// 清空releases目录
console.log(chalk.blue('清空releases目录'))
fs.emptyDirSync(deployPath)

// 读取活动目录,
const arr1 = utils.getChildFolders(activityPath)
// 读取二级页目录,
const arr2 = utils.getChildFolders(subpagePath)

console.log(chalk.blue('开始遍历项目'))
console.log(`当前环境:${chalk.blue(env)}`)

// 进度
const total = arr1.length + arr2.length
let finish = 0
const bar = new ProgressBar(':bar :current/:total\n', { total: total });

// 活动遍历打包,放到/releases
const init = async () => {
await utils.asyncForEach(arr1, async item => {
await utils.buildProgram(activityPath, item, env)
await utils.deployProgram(activityPath, item, deployPath)
finish++
bar.tick(1)
})

// 二级页,遍历打包,放到/releases/subpage/
await utils.asyncForEach(arr2, async item => {
await utils.buildProgram(subpagePath, item, env)
await utils.deployProgram(subpagePath, item, subpageDeployPath)
finish++
bar.tick(1)
})

if (finish === total) {
console.log(chalk.green('全部打包完成,可以部署releases目录到release仓库'))
}
}
init()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import fs from 'fs-extra'
import path from 'path'
import cp from 'child_process'
// const chalk = require('chalk')
import chalk from 'chalk';
import { dirname } from "node:path"
import { fileURLToPath } from "node:url"

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);


const getChildFolders = (dir) => {
const children = fs.readdirSync(dir);
return children.filter(name => fs.statSync(path.resolve(dir, name)).isDirectory())
}

const buildProgram = (dir, name, env) => {
// console.log(dir)
const url = path.join(dir, name)
return new Promise((resolve, reject) => {
const cmd = `cd ${url} && npm run build:${env}`
console.log(`开始打包: ${chalk.yellow(name)}`)
console.log(`打包命令:${chalk.yellow(cmd)}`)
cp.exec(cmd, {
cwd: process.cwd()
}, function (err, stdout, stderr) {
console.log(`打包结束: ${chalk.yellow(name)}`)
console.log(err || stdout || stderr)
resolve()
})
})
}

const asyncForEach = async (arr, fn) => {
for (let item of arr) {
// console.log(item)
await fn(item)
}
}


const deployProgram = async (dir, name, dir2) => {
console.log(`开始部署: ${chalk.yellow(name)}`)
const src = path.join(dir, name, 'dist')
const target = path.join(dir2, name)
fs.copySync(src, target)
console.log(`部署完成: ${chalk.yellow(name)}`)
}

export default {
getChildFolders,
buildProgram,
asyncForEach,
deployProgram,
}

运行效果