业务的 h5 工程是基于 Koa 封装的 node 应用,前端采用的是多页面的方式,目录大概以下:javascript
├── bin
├── app
│ ├── controllers
│ ├── routes
│ └── views
├── src
│ ├── pages
│ ├── common
复制代码
在新增一个页面的时候,须要这样作:html
.vue
.js
.less
等文件对一个懒人来讲,这种复制粘贴的过程简直不能忍!并且极容易漏掉某个地方,服务启不起来还要查 pm2 日志来找错误,仅仅初始化一个新页面就要几分钟。。。前端
因而就写了一个 npm script 来一键搞定,在此记录下流程和技能点。vue
既然是初始化页面,就叫他 initPage
,传参遵循了工程的 dev
和 build
命令的传参方式(虽然有点怪):npm run initPage --{path}
,例如:java
npm run initPage --pages/test
复制代码
在 bin 目录下新建 init-page/index.js
,首先进行参数的获取和校验node
// 校验参数(文件名)
const argv = JSON.parse(process.env.npm_config_argv).original;
let fileName;
argv.forEach((item) => {
if (item.indexOf('--') === 0) {
fileName = item.replace('--', '');
}
});
if (/[^\w-/]/g.test(fileName)) {
error(`请输入正确的文件路径!\n${fileName}`);
process.exit(0);
}
复制代码
参数校验完毕,须要肯定新页面加在哪里,也就是目标路径。这里约定输入参数路径的基路径为 src/pages
,也就是说若是输入的参数为 test ,目标路径就是 src/pages/test
,可是一般咱们组的页面大多写在 src/pages/main
,因此作了一个提示。 这里须要用到 inquirer 定义命令行交互。git
if (!bizDir.includes(fileName.split('/')[0])) {
warn(`src/pages 下没有该目录:${fileName.split('/')[0]}\n`);
inquirer
.prompt([
{
name: 'useDefault',
message: '是否要加在 src/pages/main 目录下?(Y/n)'
},
])
.then((answer) => {
let targetDirPath;
if (answer.useDefault === '' || answer.useDefault === 'Y' || answer.useDefault === 'y') {
targetDirPath = path.resolve(bizDirPath, 'main', fileName);
} else {
targetDirPath = path.resolve(bizDirPath, fileName);
}
initPage(targetDirPath, fileName);
});
} else {
const targetDirPath = path.resolve(bizDirPath, fileName);
initPage(targetDirPath, fileName);
}
复制代码
这里须要在相应文件里新增函数或属性,因此天然须要用到 AST,相关的工具:babylon
、babel-traverse
、babel-types
、babel-template
、babel-generator
。 以添加 controller 为例github
const addController = (fileName) => new Promise((resolve, reject) => {
const nameArr = fileName.split('/');
const name = nameArr[0];
const controllerName = toCamel(fileName);
const controller = path.resolve(projectRootPath, 'app/controllers', `${name}.js`);
isExist(controller).then((exist) => {
controllerExist = exist;
if (exist) {
fs.readFile(controller, 'utf8', (err, data) => {
if (err) throw err;
const ast = parse(data);
traverse(ast, {
ExpressionStatement(path) {
const operatePath = path;
path.traverse({
MemberExpression(path) {
if (path.get('object').isIdentifier({name: 'module'})) {
// 添加控制器
const ast = controllerTmpl({
CONTROLLER_NAME: t.identifier(controllerName),
PATH: t.stringLiteral(fileName),
});
operatePath.insertBefore(ast);
// 添加导出的属性
const rightPath = path.parentPath.get('right');
if (rightPath) {
const propertiesPath = rightPath.get('properties');
const lastChild = propertiesPath[propertiesPath.length - 1];
lastChild.insertAfter(t.objectProperty(t.identifier(controllerName), t.identifier(controllerName), false, true));
}
}
path.skip();
},
});
path.skip();
},
});
const output = generate(ast, {}, data);
fs.writeFile(controller, output.code, (err) => {
if (err) throw err;
success(`${name}.js 已配置`);
resolve();
});
});
} else {
fs.writeFile(controller, newControllerFile(fileName), (err) => {
if (err) throw err;
success(`${name}.js 已配置`);
resolve();
});
}
});
});
复制代码
经过语法转换生成的新文件存在不符合 eslint 规范的问题,这里用到 node child_process
模块,以及好看的命令行 loading ora
docker
const {exec} = require('child_process');
const ora = require('ora');
const loading = ora('eslint fixing ...');
const projectRootPath = path.resolve(__dirname, '../../');
const eslintFix = () => new Promise((resolve, reject) => {
loading.start();
exec('./node_modules/.bin/eslint --ext .js --fix app/controllers/** app/route/**', {
cwd: projectRootPath,
}, (err, stdout, stderr) => {
if (err) {
loading.fail();
error(err);
resolve();
return;
}
loading.succeed();
success('eslint fix 已完成');
resolve();
});
});
复制代码
判断 docker 容器是否运行并进入容器启动服务npm
const ifStartNow = (runing, fileName) => {
inquirer
.prompt([
{
name: 'startNow',
message: '是否当即启动项目?(Y/n)',
},
])
.then((answer) => {
if (answer.startNow === '' || answer.startNow === 'Y' || answer.startNow === 'y') {
if (runing) {
console.log('\n正在进入容器...');
execSync(`docker exec -it xxx-container bash -c "npm run dev --${fileName}"`, {
cwd: projectRootPath,
stdio: 'inherit',
});
} else {
console.log('\n正在启动容器:npm start ...');
console.log(`稍后请输入监听页面${fileName}`);
execSync('npm start', {
cwd: projectRootPath,
stdio: 'inherit',
});
}
} else {
console.log(`\n稍后启动容器执行 ${chalk.bold.blue(`npm run dev --${fileName}`)} 查看页面~`);
}
});
};
复制代码
Github: github.com/onlyil/init…