業務的 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…