⚠️ this article for nuggets community first contract article, not authorized to forbid reprint

preface

I wrote a series of DevOps based on Node last year, but the project was too big and expensive to get started, and it didn’t make much sense for small and medium sized teams or beginners, so I started a new series of engineering for these groups.

The new series will build a complete engineering solution from 0 to 1 step by step, and all articles will be unified in the column of “Front-end Engineering”.

background

Let’s start with a list of problems that small teams will most likely encounter:

  1. specification
    • Code has no specification, everyone’s style is arbitrary, and the quality of code delivery is out of control
    • There is no specification for committing a commit, and you can’t know about committing development from a commit
  2. process
    • There was no process, no PRD, no iterative requirements management, and no idea what the project was doing
  3. The efficiency of
    • Constant repetition of work, no accumulation of technology and precipitation
  4. Project quality
    • Projects without specifications must have no quality
    • All test functions rely on manual discovery and regression, time-consuming and laborious
  5. The deployment of
    • Manual construction, deployment, slash-and-burn operation
    • Dependence is not unified, artificial is not controllable
    • No version tracking, rollback, etc

In addition to the above common points, some other human environmental factors are not listed one by one, in fact, summed up as chaos + uncomfortable.

At the same time, in such a team, the team’s own planning is not clear, it is more difficult for individuals to have a clear plan and goals for the future, and it is easy to get stuck in the business and endless cycle.

When you are in a chaotic situation, don’t panic (chaotic times make heroes, why not you), first put things in order, then set a goal and plan, step by step.

engineering

The problems listed above can be solved by introducing an engineered system, so what is engineered?

In a broad sense, all the means to improve efficiency, reduce costs and ensure quality as the purpose, all belong to the category of engineering.

Through a series of specifications, processes, tools to achieve research and development efficiency, automation, quality assurance, service stability, early warning monitoring and so on.

As for the front-end, with the advent of Node, it is possible to penetrate into the field outside the traditional interface development with the help of Node and extend the r&d link to the whole DevOps, thus becoming a front-end engineer without being a “chettograph”.

The picture above shows a simple DevOps process that is both technically difficult and cost effective as a starting point for building an engineering small team.

When the team has no rules and no infrastructure, it is often a good idea to start with the most basic CLI tools and then work through the engineering setup.

So set a small goal to complete a CLI tool that is common to your team and project.

CLI Tool Analysis

Business iteration in a small team is usually faster, so there is less time and opportunity to provide development infrastructure. In order to avoid repeated work in the later period, it is necessary to make a good plan before infrastructure construction, and think about the core that is most lacking at present and the functions that may be needed in the future.

Coding is never the hardest part. The hardest part is not knowing what to do with code.

Following the DevOps process described above, this series will start with a simple plan for the four large modules of the CLI, and then wait until requirements change.

You can design the CLI tool according to the actual situation of your project. This series only provides a technical architecture reference.

build

Usually in small teams, the build process involves preparing multiple environment profiles in one Or more sets of templates, using a build tool like Webpack Or Rollup, building the project using the preset configuration in the template through Shell scripts Or other actions, and then deploying, etc.

This is indeed a simple, generic CI/CD process, but the problem is that any development member of the team can make changes to the released configuration items as long as the final release configuration is out of control.

Even if the build is successful, there may be some unforeseen problems, such as Webpack’s mode is in Dev mode, there is no confusion about compression of the build code, there is no global unified method injection, etc., which can be problematic for production environments.

So you need to take the build configuration and process out of the project template and use the CLI to take over the build process. Instead of reading the configuration from the project, you use the CLI to build with the unified configuration (you can customize a standard set of build configurations for each type of project).

Avoid production problems caused by business development students’ modification of incorrect configurations.

The quality of

As with construction, many common automated tests and routine format validation are ignored in business development for convenience. For example, each person’s development habits can lead to different ESLINT validation rules, making additional changes to THE ESLINT configuration, which is also a point out of control. It is best for one team to use the same set of code validation rules.

So you can also take automated testing and validation out of the project and use the CLI to take over, ensuring that the code format for a certain type of project is consistent across the team.

The template

As for templates, basically every blog that appears today, as long as it’s about the CLI, it has to have a template feature.

Because it is very important for the team to quickly and easily initialize a project or pull code snippets, it is also the most productive and profitable function module for CLI tools, but this chapter will not do too much introduction, put the template in the following blog.

Tool collection

Since it is a tool collection, you can put some common tool classes in it, such as

  1. Image compression (smaller PNG compression), upload CDN, etc
  2. Project upgrades (e.g., generic configuration updated, CLI provides one-click upgrade templates)
  3. Deploy projects and publish NPM packages.
  4. And other repetitive operations can also be included in the tool set

CLI development

After introducing the function design of several modules of CLI, you can enter the process of developing corresponding CLI tools.

Build the infrastructure

The CLI tool development will use TS as the development language, if you have not been exposed to TS at this time, it is a good time to familiarize yourself with the TS development mode.

mkdir cli && cd cli // Create the repository directory
npm init // Initialize package.json
npm install -g typescript // Install global TypeScript
tsc --init // Initialize tsconfig.json
Copy the code

After installing TypeScript globally, initialize tsconfig.json and then modify the configuration to add the compiled folder and output directory.

{
  "compilerOptions": {
    "target": "es5"./* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
    "module": "commonjs"./* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    "outDir": "./lib"./* Redirect output structure to the directory. */
    "strict": true./* Enable all strict type-checking options. */
    "esModuleInterop": true./* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    "skipLibCheck": true./* Skip type checking of declaration files. */
    "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
  },
  "include": [
    "./src"]},Copy the code

This is a simplified configuration, but it is sufficient for current development; you can modify TypeScript configuration items as needed.

ESLINT

Since you’re developing CLI tools from 0, you can start with simple functionality, such as developing an Eslint validation module.

npm install eslint --save-dev // Install the ESLint dependency
npx eslint --init // Initialize the ESLint configuration
Copy the code

Using esLint –init directly allows you to quickly customize the ESLint configuration file.eslintrc.json to suit your project

{
    "env": {
        "browser": true."es2021": true
    },
    "extends": [
        "plugin:react/recommended"."standard"]."parser": "@typescript-eslint/parser"."parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": 12."sourceType": "module"
    },
    "plugins": [
        "react"."@typescript-eslint"]."rules": {}}Copy the code

If your project already has ESlint defined, you can either use your own configuration file or modify the initial configuration according to your project requirements.

Create the ESlint utility class

As a first step, call ESlint directly using the provided Node API against the ESlint Node.js API documentation.

Use useEslintrc:false to disable the.eslintrc configuration of the project itself. Use only the rules provided by the CLI to verify the project code.


import { ESLint } from 'eslint'
import { getCwdPath, countTime } from '.. /util'

// 1. Create an instance.
const eslint = new ESLint({
  fix: true.extensions: [".js".".ts"].useEslintrc: false.overrideConfig: {
    "env": {
      "browser": true."es2021": true
    },
    "parser": getRePath("@typescript-eslint/parser"),
    "parserOptions": {
      "ecmaFeatures": {
        "jsx": true
      },
      "ecmaVersion": 12."sourceType": "module"
    },
    "plugins": [
      "react"."@typescript-eslint",]},resolvePluginsRelativeTo: getDirPath('.. /.. /node_modules') // Specify the loader loading path
});


export const getEslint = async (path: string = 'src') = > {try {
    countTime('Eslint check');
    // 2. Lint files.
    const results = await eslint.lintFiles([`${getCwdPath()}/${path}`]);

    // 3. Modify the files with the fixed code.
    await ESLint.outputFixes(results);

    // 4. Format the results.
    const formatter = await eslint.loadFormatter("stylish");

    const resultText = formatter.format(results);

    // 5. Output it.
    if (resultText) {
      console.log('Please check ===' ', resultText);
    }
    else {
      console.log('perfect! '); }}catch (error) {

    process.exitCode = 1;
    console.error('error===>', error);
  } finally {
    countTime('Eslint check'.false); }}Copy the code

Create a test project

NPM install -g create-react-app create-react-app test-cli create-react-app test-cliCopy the code

The test project uses create-React-app. Of course, you can choose other frameworks or existing projects. This is just a demo, and we will use this project for testing later.

Test the CLI

Create SRC /bin/index.ts, and use commander in demo to develop the command line tool.

#! /usr/bin/env node// This must be added to specify the node runtime environment
import { Command } from 'commander';
const program = new Command();

import { getEslint } from '.. /eslint'

program
  .version('0.1.0 from')
  .description('start eslint and fix code')
  .command('eslint')
  .action((value) = > {
    getEslint()
  })
program.parse(process.argv);
Copy the code

Modify pageage.json to specify run js for bin (location of executable file for each command)

 "bin": {
    "fe-cli": "/lib/bin/index.js"
  },
Copy the code

First run TSC to compile TS code into JS, and then use NPM Link to mount the global, you can use normal.

The specific usage of COMMANDER will not be introduced in detail. Basically, most OF the CLI tools in the market use Commander as a command line tool to develop, and this aspect is also introduced.

To use the esLint plugin, run the fe-cli eslint command. The output is as follows:

Beautify the output

You can see that at this point, the hint is not so obvious, you can use chalk plug-in to beautify the output.

Run the fe-cli eslint command after the test project is deliberately corrected

Now that a simple CLI tool is complete, you can customize more of ESlint’s modules according to your own ideas and plans.

Building blocks

Configure generic Webpack

Usually when developing a business, you use WebPack as a build tool, so the demo will be wrapped in WebPack.

Enter the test project and run the NPM run eject command to expose the WebPack configuration item.

As you can see from the configuration items exposed in the figure above, CRA’s WebPack configuration is still very complex. After all, it is a generic scaffolding that is compatible with various optimized configurations, but currently CRA is built using WebPack 4. As a new development project, the CLI could simply choose WebPack 5 to build the project without incurring technical debt.

Generally speaking, build tool replacement does not affect the business code, if the business code is kidnapped by the build tool, it is recommended to optimize the code.

import path from "path"

const HtmlWebpackPlugin = require('html-webpack-plugin')
const postcssNormalize = require('postcss-normalize');
import { getCwdPath, getDirPath } from '.. /.. /util'interface IWebpack { mode? :"development" | "production" | "none";
  entry: any
  output: any
  template: string
}

export default ({
  mode,
  entry,
  output,
  template
}: IWebpack) => {
  return {
    mode,
    entry,
    target: 'web',
    output,
    module: {
      rules: [{
        test: /\.(js|jsx)$/,
        use: {
          loader: getRePath('babel-loader'),
          options: {
            presets: [
              ' '@babel/preset-env', ], }, }, exclude: [ getCwdPath('./node_modules}, {test: /\.css$/, use: ['style-loader', { loader: 'css-loader', options: { importLoaders: 1, }, }, { loader: 'postcss-loader', options: { postcssOptions: { plugins: [ [ 'postcss-preset-env', { ident: "postcss" }, ], ], }, } } ], }, { test: /\.(woff(2)? |eot|ttf|otf|svg|)$/, type: 'asset/inline', }, { test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], loader: 'url-loader', options: { limit: 10000, name: 'static/media/[name].[hash:8].[ext]', }, }, ] }, plugins: [ new HtmlWebpackPlugin({ template, filename: 'index.html', }), ], resolve: { extensions: [ '', '.js', '.json', '.sass']},}}Copy the code

This is a simplified version of webPack 5 configuration, with the corresponding Commander command added.

program
  .version('0.1.0 from')
  .description('start eslint and fix code')
  .command('webpack')
  .action((value) = > {
    buildWebpack()
  })
Copy the code

Now you can command line into the test project and execute fe-CLI WebPack to get the following build product

The following figure is the product built with CRA. Following the build product in the figure, it is obvious that there are still many areas for optimization to use the simplified version of WebPack 5 configuration. If you are interested in it, you can optimize it by yourself.

At this point, if you are familiar with the build section, most of the dependencies in the build are from the test project, except for webPack configuration items. How do you determine whether the React version or other dependencies are unified?

The normal operation is to lock the version through the template, but the business students can still adjust the inconsistency caused by the version dependency, and cannot guarantee the consistency of the dependency.

Since the entire build is taken over by the CLI, you only need to consider moving all dependencies to the project dependencies where the CLI is located.

Dependency resolution

The Webpack configuration item adds the following two items, specifying the load path of the dependent loader. Instead of reading from node_modules where the project is located, it reads from node_modules where the CLI is located.

resolveLoader: {
  modules: [getDirPath('.. /.. /node_modules')]},// Change the loader dependency path
resolve: {
  modules: [getDirPath('.. /.. /node_modules')]},// Change the dependency path of normal modules
Copy the code

Also change Babel’s presets path to an absolute path, pointing to CLI node_modules (presets are read from the boot path by default).

{
    test: /\.(js|jsx)$/,
    use: {
      loader: getRePath('babel-loader'),
      options: {
        presets: [
          getRePath('@babel/preset-env'),
          [
            getRePath("@babel/preset-react"),
            {
              "runtime": "automatic"},],},},exclude: [
      [getDirPath('.. /.. /node_modules')]]}Copy the code

After changing the dependencies, test the effects together by removing all the dependencies on the test project node_modules

Then execute fe-CLI Webpack to build the project using the CLI dependencies.

As you can see, you can already build a project using the CLI without installing any dependencies.

At present, the dependency and build processes of all projects have been taken over by THE CLI, and the dependency and build processes can be managed in a unified manner. If the dependency needs to be upgraded, the CLI can be used to upgrade the dependency in a unified manner. Meanwhile, business development students cannot change the version dependency.

This solution should be implemented according to the actual needs. If all the dependencies are from CLI tools, the impact of version upgrade will be very large and very passive. Compatibility measures should be taken. Decide which dependencies can be taken from the project and which dependencies need to be enforced.

A note for confused Coder

For those of you who mentioned those problems at the beginning, you often get caught up in the business, and it’s really time consuming and boring to write this kind of basic project. Easy to get bored with work, coding feel boring.

This is normal, most people have this experience and similar thoughts, but I hope you can think more, in the boring, boring, repetitive work to find pain points, opportunities. Only close to the business, familiar with the business, to have the opportunity to optimize, innovation, creation.

All infrastructure is business dependent to work best.

Spend half an hour each day thinking about how you can improve your work today, not just with your code but with other tools or by introducing new processes.

At the same time, don’t just limit yourself to the thinking stage. If you have an idea, try to put it into practice. Spend half an hour more on coding or finding tools.

When you accumulate all these little things and ideas and finally integrate them into a system, you will find that you can think and plan the next stage at a higher level, and all the experiences are the cornerstone of your future growth.

Always believe a word: efforts will not be let down, pay will eventually return. Every line of code you type is a step up in the future.

The last

The CLI tools described in this chapter are not yet complete. As a starting point for engineering, more iterations of the CLI will be needed.

Those of you who are interested in engineering can check out the Front End Engineering column to help you build DevOps for your team.