Develop a front-end local debugging command line tool library

Time:2021-9-12

background

During project development, front-end students often meet with local joint debugging and testing environment. There are many methods of joint debugging. InLocal commissioning scheme for front end projectAt the end of this article, I talked about adding a layer of proxy service (forward proxy) between the browser and the local service of the front-end project to help automatically log in and forward the request to obtain static pages, static resources and protected resources after getting the token, so as to realize joint debugging.

principle

Develop a front-end local debugging command line tool library

Process Description:

  • First, start the front-end local project and proxy service locally. The proxy service helps to realize automatic login and get the token
  • The browser enters the access address, and the request will first go to the proxy service. The proxy service will judge whether the request obtains static resources from the front-end local service or protected resources from the back-end service according to the proxy configuration. The requests here can be roughly divided into three types: one is the static page after construction and the static resources (script and style); Second, request protected resources from back-end services; Third, after the page is loaded, obtain static resources from the OSS, usually pictures
  • After the proxy service obtains the resource, it returns it to the browser for rendering

practice

First, create the project folder Fe debug cli, and execute NPM init in the current folder to generate package.json management dependency

Then execute the following command to install the packages required for the project. Use express framework to build agent service; Body parser is used to parse the request body parameters; Request is used to send requests. It also has a very important function that the obtained token can be stored in the memory of the service, and the next request will automatically carry the token; Yargs is used to parse command line parameters; The project intends to introduce typescript, execute TSC — init, generate and initialize tsconfig.json, and compile with TSC watch. TSC watch will listen for file changes, and will compile again as soon as changes occur.

yarn add express body-parser request yargs typescript tsc-watch

Initialize the project space as follows

fe-debug-cli
Bin // stores the script executed by the user-defined command  
CMDS // store the defined subcommands 
Config // configuration file
├── src
    - interfaces // type file
    Route // request route
    Utils // tool class
    App.ts // application master file
Index.js // entry file
├── package.json
└── tsconfig.json

Write the proxy service from the app.ts main file

// app.ts
/* eslint-disable @typescript-eslint/no-var-requires */
import express from 'express';
import bodyParser, { OptionsJson } from 'body-parser';

/**Routing*/
const indexRouter = require('./routes/index');
/**Port*/
const port = process.env.PORT || 2000;
const app = express();

/**Body parsing*/
app.use(bodyParser.json({ extended: false } as OptionsJson));
/**Register routing*/
app.use('/', indexRouter);


app.listen(port, () => {
  Console. Log ('agent started: http://localhost: ${port}`);
});

Request routing processing. When the service starts, it will call the login interface to obtain the token and store it in memory

// src/routes/index.ts
import express, { Request, Response } from 'express';
import { userAgent } from '@src/interfaces';
import { config, getProxy } from '@src/utils';
import request, { RequiredUriUrl, CoreOptions } from 'request';

const router = express.Router();
const ask = request.defaults({ jar: true }); //  Jar indicates the storage login status

//Request forwarding
router.all('*', async (req: Request, res: Response) => {
    const options: RequiredUriUrl & CoreOptions = {
        method: req.method,
        url: getProxy(req.originalUrl),
        headers: {
            'Content-Type': req.headers['content-type'] || req.headers['Content-Type'],
            'User-Agent': userAgent,
        },
    };
    if (req.method === 'POST' && JSON.stringify(req.body) !== '{}') {
        options.json = true;
        options.body = req.body;
    }
    const result = ask(options);
    if (result instanceof Promise) {
        const { result: r } = await result; 
        res.send(r);
        return;
    }
    result.pipe(res);
    return;
});

/**Automatic login*/
const login = () => {
    const host = config.proxy[0].target;
    console.log('url', `${host}/passport/login`);
    const options = {
        method: 'POST',
        url: `${host}/passport/login`,
        headers: {
            'Content-Type': 'multipart/form-data',
            'User-Agent': userAgent,
        },
        formData: {
            ...config.user,
            logintype: 'PASSWORD',
        },
    };
    ask(options, (error) => {
        if (error) throw new Error(error);
        ask({
            method: 'GET',
            url: host,
            headers: {
                'User-Agent': userAgent,
            },
        }, (e, r, b) => {
            if (e) {
                throw new Error(e);
            }
            If (B & & b.indexof ('login ') > - 1 & & b.indexof ('registration') > - 1){
                Console.log (chat. Green. Bold ('login failed, please check! ');
            } else {
                Console.log (chat. Green. Bold ('login succeeded! ');
            }
        });
    });
};

login();

module.exports = router;

Tool class

// src/utils/index.ts
import { IProxy } from '@src/interfaces';

/**Read configuration file*/
// eslint-disable-next-line @typescript-eslint/no-var-requires
export const config = require(process.env.CONFIG_PATH || '../../config/debug.js');

/**Proxy URL*/
export const getProxy = (path: string): string => {
    const { proxy, html } = config;
    const t = proxy.filter((p: IProxy) => p.path.some((s: string) => path.includes(s)));
    return t.length ? t[0].target + path : html + path;
};

The main file before compilation is not executed because the project release will not release the source code, but only the code after compilation and construction

// index.js
const path = require('path');
//Compilation and runtime path mapping are different. Tsconfig.json configuration path mapping only takes effect at compilation time and will not work at runtime. Therefore, module alias library needs to be introduced to help resolve path mapping at runtime
const moduleAlias = require('module-alias');

(function run() {
  moduleAlias.addAlias('@src', path.resolve(__dirname, './dist'));
  //Execute the compiled master file
  require('./dist/app');
})();

Now, the agent service related code is basically completed. Let’s configure the compilation options, package.json and command line parameters

The compiled script needs to be executed in the node environment, so the module attribute needs to be specified as “commonjs”. The configuration of tsconfig.json is as follows:

{
  "compilerOptions": {
    /* Basic Options */
    "incremental": true,                         /* Enable incremental compilation */
    "target": "ES2017",                                /* 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'. */
    "declaration": true,                         /* Generates corresponding '.d.ts' file. */
    "declarationMap": true,                      /* Generates a sourcemap for each corresponding '.d.ts' file. */
    "removeComments": true,                      /* Do not emit comments to output. */
    /* Strict Type-Checking Options */
    "strict": true,                                 /* Enable all strict type-checking options. */
    /* Module Resolution Options */
    "baseUrl": "./",                             /* Base directory to resolve non-absolute module names. */
    "paths": {
      "@src/*": ["./src/*"],
    },                                 /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    "esModuleInterop": true,                        /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    /* Advanced Options */
    "skipLibCheck": true,                           /* Skip type checking of declaration files. */
    "forceConsistentCasingInFileNames": true        /* Disallow inconsistently-cased references to the same file. */
  },
  "exclude": [
    "node_modules", "test", "dist", "**/*spec.ts"
  ],
  "include": [
    "src"
  ]
}

Package.json mainly configures the compilation command, the bin attribute definition command, and the files attribute to define the installed files

"scripts": {
    "build": "rimraf dist && tsc -p tsconfig.json",
    "start:dev": "tsc-watch -p tsconfig.json --onSuccess \"node index.js\"",
    "start:debug": "tsc-watch -p tsconfig.json --onSuccess \"node --inspect-brk index.js\""
},
"bin": {
    "fee": "./bin/index.js"
},
"files": [
    "bin/*",
    "cmds/*",
    "config/*",
    "dist/*",
    "index.js",
    "package.json",
    "tsconfig.json",
    "README.md"
]

Next, parse the command line parameters, #/ Usr / bin / env node is used to indicate that the script is executed in the node environment; Commanddir formulates subcommands, which will execute the entry file index.js to start the agent service

#!/usr/bin/env node
require('yargs')
  .scriptName('fee')
  .usage('Usage: $0 <command> [options]')
  .commandDir('../cmds')
  .demandCommand()
  .example('fee debug -c debug.js -p 3333')
  .help('h')
  .alias('v', 'version')
  .alias('h', 'help')
  .argv;

Subcommand

const fs = require('fs');
const path = require('path');

exports.command = 'debug';

Exports.describe = 'debug application';

exports.builder = yargs => {
    return yargs
        .option('config', {
            alias: 'c',
            default: 'debug.js',
            Describe: 'profile',
            type: 'string',
        })
        .option('port', {
            alias: 'p',
            default: 3000,
            Describe: 'port',
            type: 'number',
        })
    .argv
};

exports.handler = function(argv) {
    const configPath = path.resolve(process.cwd(), argv.config);
    if (!fs.existsSync(configPath)) {
        Console.log ('No configuration file ');
        process.exit();
    }

    process.env.PORT = argv.port;
    process.env.CONFIG_PATH = configPath;
    process.env.NAMESPACE = argv.namespace;
    require('../index.js'); //  Execute entry file
};

Now, there is only the final publishing work left. First log in to the NPM warehouse with NPM login, and then execute the compile command yarn build. Then you can execute NPM publish. Note that you need to switch back to the NPM image before executing the publish command

Develop a front-end local debugging command line tool library

Global installation fee debug cli debugging to see the effect

Develop a front-end local debugging command line tool library

The request sent by the browser does not carry a token on the console. Carrying a token is what the proxy service does

Develop a front-end local debugging command line tool library

Note: a strange phenomenon occurs after the agent service is started. The nail messages are often not sent out and the line is often disconnected. This may be because the agent service agent forwards all requests without distinguishing domain names. Therefore, it is necessary to judge whether the proxy request comes from the localhost: XXX domain name.

Warehouse address:fe-debug-cli