preface

Front-end scaffolding front-end engineering is a important tool to enhance the efficiency of the team, thus building a scaffold for the front-end engineer is a cannot obtain the skill, and the industry for the deployment of the scaffold is relatively small, generally are related to business related engineering scaffold building templates, This paper aims to provide some scaffolding practices related to front-end deployment, hoping to help students who build engineering links.

architecture

For a scaffold, not only to achieve a deployment scheme, so in scaffolding architecture design, the use of plug-in plug-in model, through the plug-in mechanism will be required to provide functional parts

directory

  • packages
    • @pnw
      • cli
      • cli-service
      • cli-shared-utils
      • cli-ui
    • test
  • scripts
    • bootstrap.js
    • dev.js
    • prod.js
    • release.js
  • env.json

case

Use @pnw/ CLI scaffolding to build, use the command PNW deploy to build the deployment directory, follow the CI/CD process, where default.conf is mainly used for nginx build, Dockerfile is used for image build, yamL file is mainly used for K8S-related build

The source code

cli

The core of the deployment part is an invocation of deployFn in deploy.js, which is implemented in cli-plugin-deploy

create.js

const { isDev } = require('.. /index');

console.log('isDev', isDev)

const { Creator } = isDev ? require('.. /.. /cli-service') : require('@pnw/cli-service');

const creator = new Creator();

module.exports = (name, targetDir, fetch) = > {
    return creator.create(name, targetDir, fetch)
}
Copy the code

deploy.js

const { isDev } = require('.. /index');

const {
    path,
    stopSpinner,
    error,
    info
} = require('@pnw/cli-shared-utils');

const create = require('./create');

const { Service } = isDev ? require('.. /.. /cli-service') : require('@pnw/cli-service');

const { deployFn } = isDev ? require('.. /.. /cli-plugin-deploy') :require('@pnw/cli-plugin-deploy');

// console.log('deployFn', deployFn);

async function fetchDeploy(. args) {
    info('fetchDeploy executes')
    const service = newService(); service.apply(deployFn); service.run(... args); }async function deploy(options) {
    // Customize the deploy content TODO
    info('deploy executes')
    const targetDir = path.resolve(process.cwd(), '. ');
    return await create('deploy', targetDir, fetchDeploy)
}

module.exports = (. args) = > {
    returndeploy(... args).catch(err= > {
        stopSpinner(false)
        error(err)
    })
};
Copy the code

cli-plugin-deploy

Implement the core part of the deployment part, where Build, nginx, Docker, YAML, etc., are mainly used to generate template content files

build.js

exports.build = config= > {
    return `npm install
npm run build`
};
Copy the code

docker.js

exports.docker = config= > {
    const {
        forward_name,
        env_directory
    } = config;
    return ` FROM harbor. Dcos. NCMP. Unicom. Local/platpublic/nginx: 1.20.1 COPY. / dist/usr/share/nginx/HTML /${forward_name}/
COPY ./deploy/${env_directory}/default.conf /etc/nginx/conf.d/
EXPOSE 80`
}
Copy the code

nginx.js

exports.nginx = config= > {
	return `client_max_body_size 1000m; server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; gzip_static on; }} `
}
Copy the code

yaml.js

exports.yaml = config= > {
  const {
    git_name
  } = config;
  return `apiVersion: apps/v1
kind: Deployment
metadata:
  name: ${git_name}
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ${git_name}
  template:
    metadata:
      labels:
        app: ${git_name}
    spec:
      containers:
        - name: ${git_name}
          image: harbor.dcos.ncmp.unicom.local/custom/${git_name}:1.0
          imagePullPolicy: Always
          resources:
            limits:
              cpu: 5
              memory: 10G
            requests:
              cpu: 1
              memory: 1G
          ports:
            - containerPort: 80`
}
Copy the code

index.js

// const { fs, path } = require('@pnw/cli-shared-utils');

const { TEMPLATES } = require('.. /constant');

/ * * * *@param {*} Template Template path *@param {*} Config Specifies the parameter */ used to inject templates
function generate(template, config) {
    // console.log('template', template);
    return require(`. /${template}`).generateJSON(config);
}


function isExitTemplate(template) {
    return TEMPLATES.includes(template)
}

TEMPLATES.forEach(m= > {
    Object.assign(exports, {[`${m}`] :require(`. /${m}`)})})exports.createTemplate = (tempalteName, env_dirs) = > {
    if( isExitTemplate(tempalteName) ) {
        return generate(tempalteName, {
            // git_name: 'fescreenrj',
            // forward_name: 'rj',
            env_dirs
        });
    } else {
        return `${tempalteName} is NOT A Template, Please SELECT A correct TEMPLATE`}}Copy the code

main.js

const {
    createTemplate
} = require('./__template__');

const { isDev } = require('.. /index');

console.log('isDev', isDev);

const {
    REPO_REG,
    PATH_REG
} = require('./reg');

const {
    TEMPLATES
} = require('./constant');

const {
    path,
    fs,
    inquirer,
    done,
    error
} = isDev ? require('.. /.. /cli-shared-utils') : require('@pnw/cli-shared-utils');

/ * * * *@param {*} TargetDir Absolute path to the target folder *@param {*} FileName fileName *@param {*} FileExt file extension *@param {*} Data file contents */
const createFile = async (targetDir, fileName, fileExt, data) => {
    // console.log('fileName', fileName);
    // console.log('fileExt', fileExt);
    let file = fileName + '. ' + fileExt;
    if(! fileExt) file = fileName;// console.log('file', file)
    await fs.promises.writeFile(path.join(targetDir, file), data)
            .then(() = > done('Create a file${file}Successful `))
            .catch(err= > {
                if (err) {
                    error(err);
                    returnerr; }}); }/ * * * *@param {*} TargetDir Target path address * for the directory to be created@param {*} ProjectName Name of the directory you want to create *@param {*} Configuration string * in templateStr directory@param {*} The argument */ obtained from the answers command line
const createCatalogue = async (targetDir, projectName, templateMap, answers) => {
    const templateKey = Object.keys(templateMap)[0],
        templateValue = Object.values(templateMap)[0];

    // console.log('templateKey', templateKey);

    // console.log('templateValue', templateValue);

    // Get the various utility functions corresponding to the template
    const {
        yaml,
        nginx,
        build,
        docker
    } = require('./__template__') [`${templateKey}`];

    // Get the environment folder
    const ENV = templateValue.ENV;

    console.log('path.join(targetDir, projectName)', targetDir, projectName)
    // 1. Create a folder
    await fs.promises.mkdir(path.join(targetDir, projectName)).then(() = > {
            done('Create the project directory${projectName}Successful `);
            return true
        })
        .then((flag) = > {
            // console.log('flag', flag);
            // Get build Options
            const buildOptions = templateValue.FILE.filter(f= > f.KEY == 'build') [0];

            // console.log('buildOptions', buildOptions);
            
            flag && createFile(path.join(targetDir, projectName), buildOptions[`NAME`], buildOptions[`EXT`], build());
        })
        .catch(err= > {
            if (err) {
                error(err);
                returnerr; }}); ENV.forEach(env= > {
        fs.promises.mkdir(path.join(targetDir, projectName, env))
            .then(() = > {
                done('Create the project directory${projectName}/${env}Successful `);
                return true;
            })
            .then(flag= > {
                // Get docker Options
                const dockerOptions = templateValue.FILE.filter(f= > f.KEY == 'docker') [0];
                flag && createFile(path.join(targetDir, projectName, env), dockerOptions[`NAME`], dockerOptions[`EXT`], docker({
                    forward_name: answers[`forward_name`].env_directory: env
                }));
                // Get yamL Options
                const yamlOptions = templateValue.FILE.filter(f= > f.KEY == 'yaml') [0];
                flag && createFile(path.join(targetDir, projectName, env), yamlOptions[`NAME`], yamlOptions[`EXT`], yaml({
                    git_name: answers[`repo_name`]}));// Get nginx Options
                const nginxOptions = templateValue.FILE.filter(f= > f.KEY == 'nginx') [0];
                flag && createFile(path.join(targetDir, projectName, env), nginxOptions[`NAME`], nginxOptions[`EXT`], nginx());
            })
            .catch(err= > {
                if (err) {
                    error(err);
                    returnerr; }}); }); }/ * * * *@param {*} ProjectName Name of the generated directory *@param {*} TargetDir Absolute path */
 module.exports = async (projectName, targetDir) => {
    let options = [];
    async function getOptions() {
        return fs.promises.readdir(path.resolve(__dirname, './__template__')).then(files= > files.filter(f= > TEMPLATES.includes(f)))
    }

    options = await getOptions();

    console.log('options', options);

    const promptList = [{
            type: 'list'.message: 'Please select the application template you want to deploy'.name: 'template_name'.choices: options
        },
        {
            type: 'checkbox'.message: 'Please select the environment you want to deploy'.name: 'env_dirs'.choices: [{
                    value: 'dev'.name: 'Development Environment'
                },
                {
                    value: 'demo'.name: 'Demo Environment'
                },
                {
                    value: 'production'.name: 'Production environment']}, {},type: 'input'.name: 'repo_name'.message: 'It is recommended to use the abbreviation of the current Git repository as the mirror name'.filter: function (v) {
                return v.match(REPO_REG).join(' ')}}, {type: 'input'.name: 'forward_name'.message: 'Please use a name that matches the URL path rule'.filter: function (v) {
                return v.match(PATH_REG).join(' ')}},]; inquirer.prompt(promptList).then(answers= > {
        console.log('answers', answers);
        const {
            template_name
        } = answers;
        // console.log('templateName', templateName)
        // Get the template string
        const templateStr = createTemplate(template_name, answers.env_dirs);
        // console.log('template', JSON.parse(templateStr));
        const templateMap = {
            [`${template_name}`] :JSON.parse(templateStr) } createCatalogue(targetDir, projectName, templateMap, answers); })};Copy the code

cli-service

Service and Creator are two base classes that provide basic related services, respectively

Creator.js

const {
    path,
    fs,
    chalk,
    stopSpinner,
    inquirer,
    error
} = require('@pnw/cli-shared-utils');

class Creator {
    constructor(){}async create(projectName, targetDir, fetch) {
        // const cwd = process.cwd();
        // const inCurrent = projectName === '.';
        // const name = inCurrent ? path.relative('.. /', cwd) : projectName;
        // targetDir = path.resolve(cwd, name || '.');
        // if (fs.existsSync(targetDir)) {
        // const {
        // action
        // } = await inquirer.prompt([{
        // name: 'action',
        // type: 'list',
        // message: `Target directory ${chalk.cyan(targetDir)} already exists. Pick an action:`,
        // choices: [{
        // name: 'Overwrite',
        // value: 'overwrite'
        / /},
        / / {
        // name: 'Merge',
        // value: 'merge'
        / /},
        / / {
        // name: 'Cancel',
        // value: false
        / /}
        / /]
        / /}])
        // if (! action) {
        // return
        // } else if (action === 'overwrite') {
        // console.log(`\nRemoving ${chalk.cyan(targetDir)}... `)
        // await fs.remove(targetDir)
        / /}
        // }

        awaitfetch(projectName, targetDir); }}module.exports = Creator;
Copy the code

Service.js

const { isFunction } = require('@pnw/cli-shared-utils')

class Service {
    constructor() {
        this.plugins = [];
    }
    apply(fn) {
        if(isFunction(fn)) this.plugins.push(fn)
    }
    run(. args) {
        if( this.plugins.length > 0 ) {
            this.plugins.forEach(plugin= >plugin(... args)) } } }module.exports = Service;
Copy the code

cli-shared-utils

Common tool library, the third party and node related core module convergence to this inside, unified output and magic change

is.js

exports.isFunction= fn= > typeof fn === 'function';
Copy the code

logger.js

const chalk = require('chalk');
const {
    stopSpinner
} = require('./spinner');

const format = (label, msg) = > {
    return msg.split('\n').map((line, i) = > {
        return i === 0 ?
            `${label} ${line}` :
            line.padStart(stripAnsi(label).length + line.length + 1)
    }).join('\n')};exports.log = (msg = ' ', tag = null) = > {
    tag ? console.log(format(chalkTag(tag), msg)) : console.log(msg)
};

exports.info = (msg, tag = null) = > {
    console.log(format(chalk.bgBlue.black(' INFO ') + (tag ? chalkTag(tag) : ' '), msg))
};

exports.done = (msg, tag = null) = > {
    console.log(format(chalk.bgGreen.black(' DONE ') + (tag ? chalkTag(tag) : ' '), msg))
};

exports.warn = (msg, tag = null) = > {
    console.warn(format(chalk.bgYellow.black(' WARN ') + (tag ? chalkTag(tag) : ' '), chalk.yellow(msg)))
};

exports.error = (msg, tag = null) = > {
    stopSpinner()
    console.error(format(chalk.bgRed(' ERROR ') + (tag ? chalkTag(tag) : ' '), chalk.red(msg)))
    if (msg instanceof Error) {
        console.error(msg.stack)
    }
}
Copy the code

spinner.js

const ora = require('ora')
const chalk = require('chalk')

const spinner = ora()
let lastMsg = null
let isPaused = false

exports.logWithSpinner = (symbol, msg) = > {
  if(! msg) { msg = symbol symbol = chalk.green('✔')}if (lastMsg) {
    spinner.stopAndPersist({
      symbol: lastMsg.symbol,
      text: lastMsg.text
    })
  }
  spinner.text = ' ' + msg
  lastMsg = {
    symbol: symbol + ' '.text: msg
  }
  spinner.start()
}

exports.stopSpinner = (persist) = > {
  if(! spinner.isSpinning) {return
  }

  if(lastMsg && persist ! = =false) {
    spinner.stopAndPersist({
      symbol: lastMsg.symbol,
      text: lastMsg.text
    })
  } else {
    spinner.stop()
  }
  lastMsg = null
}

exports.pauseSpinner = () = > {
  if (spinner.isSpinning) {
    spinner.stop()
    isPaused = true}}exports.resumeSpinner = () = > {
  if (isPaused) {
    spinner.start()
    isPaused = false}}exports.failSpinner = (text) = > {
  spinner.fail(text)
}
Copy the code

conclusion

Front-end engineering link is not only the front application program of building, at the same time also need to pay attention to the construction of the entire front-end upstream and downstream related, including but not limited to: UI build, test, building, deploying, building, etc., for the front-end engineering, all things should be able to abstract into templated can be engineered, such ability authors efficiency, promote the development experience and efficiency, ‘!!!!!!

reference

  • Vue – cli source code
  • Leo: Open source scaffolding tools from an engineering perspective