background

The H5 project of the business is based on the Node application encapsulated by Koa. The front end adopts the multi-page mode, and the directory is as follows:

├ ─ ─ bin ├ ─ ─ app │ ├ ─ ─ controllers │ ├ ─ ─ routes │ └ ─ ─ views ├ ─ ─ the SRC │ ├ ─ ─ pages │ ├ ─ ─ commonCopy the code

When adding a new page, do this:

  • Added a route to app/routes
  • Add middleware functions corresponding to routes in APP/Controllers
  • Add view template HTML to app/ Views
  • SRC/Pages create a new page folder, and inside.vue .js .lessAnd other documents

For a lazy person, this copy-and-paste process is unbearable! And it’s very easy to miss something, the service doesn’t start up and you have to check the PM2 log for errors, it takes a few minutes just to initialize a new page…

An NPM script was written to do it in one click, where the flow and skill points were recorded.

Feature preview:

1. Define commands

NPM run initPage –{path}, for example: NPM run initPage –{path}

npm run initPage --pages/test
Copy the code

2. Verify parameters

Create init-page/index.js in the bin directory and obtain and verify the parameters

// Check parameter (filename)
const argv = JSON.parse(process.env.npm_config_argv).original;
let fileName;
argv.forEach((item) = > {
    if (item.indexOf(The '-') = = =0) {
        fileName = item.replace(The '-'.' '); }});if (/[^\w-/]/g.test(fileName)) {
    error('Please enter the correct file path! \n${fileName}`);
    process.exit(0);
}
Copy the code

3. Determine the directory

After verifying the parameters, you need to determine where the new page is added, which is the target path. Here it is agreed that the base path of the input parameter path is SRC /pages, that is, if the input parameter is test, the target path is SRC /pages/test, but usually our group pages are written in SRC /pages/main, so we make a hint. The inquirer is needed to define the command line interaction.

if(! bizDir.includes(fileName.split('/') [0])) {
    warn('SRC /pages' does not have this directory:${fileName.split('/') [0]}\n`);
    inquirer
        .prompt([
            {
                name: 'useDefault'.message: 'Do you want to add it to 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);
}
Copy the code

4. Add controllers and Routers

Here you need to add functions or attributes to the file, so naturally you need to use AST and related tools: Babylon, babel-traverse, babel-types, babel-Template, babel-generator. Take adding a Controller as an example

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'{}))// Add a controller
                                    const ast = controllerTmpl({
                                        CONTROLLER_NAME: t.identifier(controllerName),
                                        PATH: t.stringLiteral(fileName),
                                    });
                                    operatePath.insertBefore(ast);
                                    // Add attributes for the export
                                    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 is configured with ');
                    resolve();
                });
            });
        } else {
            fs.writeFile(controller, newControllerFile(fileName), (err) => {
                if (err) throw err;
                success(`${name}.js is configured with '); resolve(); }); }}); });Copy the code

5. Eslint fix

New files generated by syntactic conversions do not conform to the ESLint specification. This uses the node child_process module and the nice command line loading ORA

const {exec} = require('child_process');
const ora = require('ora');

const loading = ora('eslint fixing ... ');
const projectRootPath = path.resolve(__dirname, '.. /.. / ');

const eslintFix = (a)= > 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 done');
        resolve();
    });
});
Copy the code

6. Start the service listening page

Check whether the Docker container is running and enter the container to start the service

const ifStartNow = (runing, fileName) = > {
    inquirer
        .prompt([
            {
                name: 'startNow'.message: 'Do you want to start the project immediately? (Y/n)',
            },
        ])
        .then((answer) = > {
            if (answer.startNow === ' ' || answer.startNow === 'Y' || answer.startNow === 'y') {
                if (runing) {
                    console.log('\n entering container... ');
                    execSync(`docker exec -it xxx-container bash -c "npm run dev --${fileName}"`, {
                        cwd: projectRootPath,
                        stdio: 'inherit'}); }else {
                    console.log('\n Starting container: NPM start... ');
                    console.log('Please enter the listening page later${fileName}`);
                    execSync('npm start', {
                        cwd: projectRootPath,
                        stdio: 'inherit'}); }}else {
                console.log('\n starts container execution later${chalk.bold.blue(`npm run dev --${fileName}`)}View the page ~ '); }}); };Copy the code

Making: github.com/onlyil/init…