Original address: github.com/jiangtao/bl… Please indicate the source.

At the end of ’16, colleagues started talking about scaffolding. Due to the diversity of the company’s business and the flexibility of the front end, we have to think about more versatile scaffolding. Instead of constantly spending time on configuration as front-end technology evolves. Chef-cli was born. At the beginning of 2018, we sorted out and summarized the things of the previous year and re-enhanced the original scaffold project-next-CLI, which could not only meet the needs of our team, but also meet the needs of others.

project-next-cli

Target users:

  • The company’s business is mixed, but there is a certain accumulation
  • Students and teams who love to do things
  • Develop with github’s extensive development templates

The development of

I have been working at the front end for 13 years. The front end has been developing at a high speed in recent years, with the main performance as follows:

Note: The following development process occurs, please do not tangle with the order [face covering]

  • Library/framework: jQuery, Backbone, Angular, React, vue
  • Modularity: CommonJS, AMD(CMD), UMD, ES Module
  • Task manager: NPM scripts, Grunt, gulp
  • Module packaging tools: R.js, Webpack, rollup, Browserify
  • CSS preprocessor: Sass, Less, Stylus, Postcss
  • Static checker: flow/typescript
  • Testing tools: Mocha, Jasmine, Jest, AVA
  • Code inspection tools: ESLint, jsLint

The development of

In real development, we will encounter a variety of business requirements (scenarios), and choose different technology stacks according to the requirements and scenarios. Due to technological progress and limitations of different browser runtime, we have to configure the corresponding environment and so on, so that we can meet the business requirements.

A graph is drawn to show the relationship between business, configuration (environment), and technology

Front-end Configuration Engineer

So it became clear that a new profession, front-end configuration engineer

The community status quo

Dedicated scaffolding

There are a number of specialized frameworks in the community that are tailored to a single target task. Such as the following scaffolding

  1. vue-cli

Vue-cli provides the use of VUE to develop webpack, PWA and other templates, this article scaffolding reference vuE-CLI implementation.

  1. dva-cli

Dva – CLI is mainly used for DVA development scaffolding

  1. labrador

Labrador is a micro program componentized development framework. Although small programs currently support components, other features of the scaffold are also very nice. Those who are interested can understand.

There are many excellent specialized scaffolds in the community that are not listed here. The front end of the community, so that our generation of front-end extract, forward.

Universal scaffold

  1. yeoman

Yeoman is a strong general-purpose scaffold with a range of tools, but Yeoman publishes specified package names and develops tools with it. You can see the Yeoman Rule for adding generators here

Develop your original intention and goals

Due to the shape of financial companies, diversified business types and iterative development of front-end technology, they were born in order to follow up community development and better accomplish the following goals.

  • Completion of business: concentration, stability and speed
  • Team specifications: code specifications, release process, continuous integration/delivery/deployment
  • Sedimentation: Continuous and steady introduction of new technology
  • Benefits: less overtime work, less wheel building, kpi completion, more meaningful things

prepared

Rely on Github, according to Github API to achieve, as follows:

  1. Access to project
curl -i https://api.github.com/orgs/project-scaffold/repos
Copy the code
  1. Get version
curl -i https://api.github.com/repos/project-scaffold/cli/tags
Copy the code

Implementation logic

Once you get the project list and version number from the Github API, you can build a generic scaffolding. The following code is core and easy to understand.

The overall design

  1. specification
  • Use Node for scaffolding development, version selection> = 6.0.0
  • Choose async/await development to solve the problem of asynchronous callback
  • Compile using Babel
  • Use ESLint specification code
  1. function

Follow the single responsibility principle, each document as a separate module, to solve a separate problem. Can be freely combined, so as to achieve reuse. Here is the final directory structure:

├ ─ ─ LICENSE ├ ─ ─ the README. Md ├ ─ ─ bin │ └ ─ ─ project ├ ─ ─ package. The json ├ ─ ─ the SRC │ ├ ─ ─ the clear, js │ ├ ─ ─ config. Js │ ├ ─ ─ helper │ │ ├ ─ ─ metalAsk. Js │ │ ├ ─ ─ metalsimth. Js │ │ └ ─ ─ render. Js │ ├ ─ ─ index. The js │ ├ ─ ─ init. Js │ ├ ─ ─ the js │ ├ ─ ─ the list. The js │ ├─ project.js │ ├─ uninstall.js │ ├── ├─ better.js │ ├── ├.js │ ├── uninstall.js │ ├── ├.js │ ├── uninstall.js │ ├── ├.js │ ├── uninstall.js │ ├── ├.js │ ├── uninstall.js │ ├── ├.js │ ├ ─ ─ copy. Js │ ├ ─ ─ defs. Js │ ├ ─ ─ git. Js │ ├ ─ ─ loading. The js │ └ ─ ─ rc. Js └ ─ ─ yarn. The lockCopy the code

Let’s look at the implementation logic for each command.

download

  1. use
project i
Copy the code
  1. logic
Github API ===> Get the project list ===> Select a project ===> Get the project version ===> Select a version number ===> Download it to the local repositoryCopy the code

If the data in each step is empty or the file does not exist, the system prompts

  1. The core code

  // Get the github project list
  const repos = await repoList();

  choices = repos.map(({ name }) = > name);
  answers = await inquirer.prompt([
    {
      type   : 'list'.name   : 'repo'.message: 'which repo do you want to install? ',
      choices
    }
  ]);
  // The selected item
  const repo = answers.repo;

  // Project version number bad currency love oh
  const tags = await tagList(repo);

  if (tags.length === 0) {
    version = ' ';
  } else {
    choices = tags.map(({ name }) = > name);

    answers = await inquirer.prompt([
      {
        type   : 'list'.name   : 'version'.message: 'which version do you want to install? ',
        choices
      }
    ]);
    version = answers.version;
  }
  / / download
  await download([repo, version].join(The '@'));
Copy the code

Generating project

  1. use
project init
Copy the code
  1. logic
Get local repository list ===> Select a local project ===> Enter basic information ===> Compile build to temporary file ===> Copy and rename to target directoryCopy the code

If the data in each step is empty, the file does not exist, or the generated directory is duplicated, a message is displayed

  1. The core code

  // Get the local warehouse project
  const list = await readdir(dirs.download);

  // Basic information
  const answers = await inquirer.prompt([
    {
      type   : 'list'.name   : 'scaffold'.message: 'which scaffold do you want to init? '.choices: list
    }, {
      type   : 'input'.name   : 'dir'.message: 'project name'.// Necessary validation
      async validate(input) {
        const done = this.async();

        if (input.length === 0) {
          done('You must input project name');
          return;
        }

        const dir = resolve(process.cwd(), input);

        if (await exists(dir)) {
          done('The project name is already existed. Please change another name');
        }

        done(null.true); }}]);const metalsmith = await rc('metalsmith');
  if (metalsmith) {
    const tmp = `${dirs.tmp}/${answers.scaffold}`;
    // Copy a copy to a temporary directory and compile it in the temporary directory
    await copy(`${dirs.download}/${answers.scaffold}`, tmp);
    await metal(answers.scaffold);
    await copy(`${tmp}/${dirs.metalsmith}`, answers.dir);
    // Delete the temporary directory
    await rmfr(tmp);
  } else {
    await copy(`${dirs.download}/${answers.scaffold}`, answers.dir);
  }
Copy the code

The core code for template engine compilation and implementation is as follows:

/ / metalsmith logic
function metal(answers, tmpBuildDir) {
    return new Promise((resolve, reject) = > {
    metalsmith
	  .metadata(answers)
      .source('/')
      .destination(tmpBuildDir)
      .clean(false)
      .use(render())
      .build((err) = > {
        if (err) {
          reject(err);
          return;
        }
        resolve(true);
      });
  });
}
// Metalsmith Render middleware implementation
function render() {
    return function _render(files, metalsmith, next) {
    const meta = metalsmith.metadata();

    /* eslint-disable */
    
    Object.keys(files).forEach(function(file){
      const str = files[file].contents.toString();

      consolidate.swig.render(str, meta, (err, res) => {
        if (err) {
          return next(err);
        }

        files[file].contents = newBuffer(res); next(); }); }}})Copy the code

Upgrade/degrade the version

  1. use
project update
Copy the code
  1. logic
Get the local repository list ===> Select a local project ===> Get the version information list ===> Select a version ===> Overwrite the original version fileCopy the code

If the data in each step is empty or the file does not exist, the system prompts

  1. The core code
  // Get a list of local repositories
  const list = await readdir(dirs.download);

  // Select a project to upgrade
  answers = await inquirer.prompt([
    {
      type   : 'list'.name   : 'scaffold'.message: 'which scaffold do you want to update? '.choices: list,
      async validate(input) {
        const done = this.async();

        if (input.length === 0) {
          done('You must choice one scaffold to update the version. If not update, Ctrl+C');
          return;
        }

        done(null.true); }}]);const repo = answers.scaffold;

  // Get the version information of the project
  const tags = await tagList(repo);

  if (tags.length === 0) {
    version = ' ';
  } else {
    choices = tags.map(({ name }) = > name);

    answers = await inquirer.prompt([
      {
        type   : 'list'.name   : 'version'.message: 'which version do you want to install? ',
        choices
      }
    ]);
    version = answers.version;
  }
  // Download the overwrite file
  await download([repo, version].join(The '@'))
Copy the code

configuration

The configuration is used to get basic scaffolding Settings, such as Registry, type, and so on.

  1. use
project config set registry koajs Set the local repository download source

project config get registry Get the properties of the local repository setting

project config delete registry # delete attributes set locally
Copy the code
  1. logic
Check local Settings file exists ===> read/writeCopy the code

If the data in each step is empty or the file does not exist, the system prompts

  1. The core code
switch (action) {
    case 'get':
      console.log(await rc(k));
      console.log(' ');
      return true;

    case 'set':
      await rc(k, v);
      return true;

    case 'remove':
      await rc(k, v, true);
      return true;

    default:
      console.log(await rc());
Copy the code

search

Search the remote Github repository for a list of projects

  1. use

project search

Copy the code
  1. logic
Get github project list ===> Enter the search content ===> Return the matching listCopy the code

If the data in each step is empty, a prompt will be given

  1. The core code
 const answers = await inquirer.prompt([
    {
      type   : 'input'.name   : 'search'.message: 'search repo'}]);if (answers.search) {
    let list = await searchList();

    list = list
      .filter(item= > item.name.indexOf(answers.search) > - 1)
      .map(({ name }) = > name);

    console.log(' ');
	  if (list.length === 0) {
		  console.log(`${answers.search} is not found`);
	  }
	  console.log(list.join('\n'));
	  console.log(' ');
  }
Copy the code

conclusion

This is the background of the general-purpose scaffold. There are still some improvements that can be made for both the user and the implementation:

  1. Different sources store different files
  2. Support offline function

Hard wide: If you find project-next-CLI easy to use, welcome star and welcome fork to maintain.