Recruitment advertisement

Resumes can be sent directly to [email protected]

Please provide resume name format: name – position – Years of service – location (e.g. : Zhang SAN – front-end development – five years – Changsha. PDF)


Company: Tencent

Location: Changsha

Position: Web front-end Development engineer

Job Responsibilities:

Responsible for the system research and development of Tencent Cloud DNSPod product, completed the front-end function and back-end logic code implementation of the system, and ensured product quality and development progress.

Job Requirements:

1. Bachelor degree or above, major in computer science, more than 2 years working experience;

2. Proficient in Javascript, HTML, CSS and other front-end development technologies, with a solid foundation;

3. Familiar with current mainstream front-end frameworks (React/Vue, etc.), experience in react and Redux development is preferred;

4. Familiar with HTTP and TCP/IP protocols; Have good security awareness, familiar with common network security attack and defense strategy;

5. Good analytical and problem-solving skills and enthusiasm for learning;

Node.js/PHP development experience is preferred;

7. Plugin developers with WP or DZ experience are preferred

Note: This position is prepared by a wholly-owned subsidiary of Tencent Group”


Company: Tencent

Location: Tencent headquarters in Shenzhen

Position: Senior Web Front-end Development Engineer

Job Responsibilities:

Responsible for the system architecture design and r&d of Tencent Cloud domain name product (DNSPod)

Job Requirements:

1. Bachelor degree or above, major in computer science, at least 5 years working experience;

2. Proficient in Javascript, HTML, CSS and other front-end development technologies, with a solid foundation;

3. Familiar with current mainstream front-end frameworks (React/Vue, etc.), experience in react and Redux development is preferred;

4. Familiar with HTTP and TCP/IP protocols; Have good security awareness, familiar with common network security attack and defense strategy;

5. Good analytical and problem-solving skills and enthusiasm for learning;

Node.js/PHP development experience is preferred;

preface

Front-end time out of the time for Koa2 source code for a simple study, KOA source is a very simple library, for the analysis process, want to hand-in-hand to achieve a type of KOA framework, its code, according to step by step to achieve a simple version of KOA, each step of a Branch, such as: Stpe-1 corresponds to the code that I want to realize the first step. The code is only for my simple learning, and it is not perfect in many places. I just want to experience Koa’s ideas. Here are a few simple things I understand about Koa:

  • The basic core of all NodeJS frameworks is through native librarieshttp or httpsStart a back-end servicehttp.createServer(this.serverCallBack()).listen(... arg)“And then all the requests come inserverCallBackMethod in which we can then handle different requests through interception
  • Koa is an Onion model that is implemented based on middleware. throughuseTo add a middleware,koa-routerIt’s really just akoaAll of our requests will be executed through all of the middleware, as shown in the onion model below

Above is my brief understanding of Koa source code analysis. I will write down my understanding of Koa further later. Koa is a very small and flexible framework, unlike Express, which has integrated many functions. Many functions do not need the third-party framework, such as routing. Koa needs to reference the third-party library KOA-Router to implement routing. Koa and Express do not need to be used. Here are two examples of Koa and Express that implement a simple function.

// Express
const express = require('express')
const app = express()

app.get('/'.function (req, res) {
  res.send('Hello World! ')
})

app.listen(3000, function () {
  console.log('Example app listening on port 3000! ')})Copy the code
// Koa 
var Koa = require('koa'); Var Router = require('koa-router');

var app = new Koa();
var router = new Router();
router.get('/', (ctx, next) => { // ctx.router available }); Router app.use (router.routes()).use(router.allowedMethods()); app.listen(3000);Copy the code

Eggjs is a framework implemented based on the Koa framework. Let’s take a look at the eggJS framework.

Eggjs is basically used

We can quickly build an Egg project framework based on quick start,

$ npm i egg-init -g
$ egg-init egg-example --type=simple
$ cd egg-example
$ npm i
Copy the code

We can start the project quickly with NPM run dev. Then open localhost:7001 and you can see the output of the page:

hi, egg.

This indicates that the initialization of the project has been completed and started successfully. We can now look at the code generated by the Egg project. The code file structure is as follows:

I analyzed the entire file structure and found no entry files like app.js in the whole project (I usually start with entry files when learning a new framework). I found the code under the app folder that should be important to the project:

The controller folder, which is literally a file in the control layer, has a home.js code like this:

'use strict';

const Controller = require('egg').Controller;

class HomeController extends Controller {
  async index() {
    this.ctx.body = 'hi, egg';
  }
}

module.exports = HomeController;

Copy the code

This class inherits from the Egg Controller class. Where does the project reference this Controller class?

Router.js (router.js); router.js (router.js);

'use strict';

/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
  const { router, controller } = app;
  router.get('/', controller.home.index);
};

Copy the code

Router.get (‘/’, controller.home.index); router.get(‘/’, controller.home.index); This. CTX. Body = ‘hi, egg -> Hello world! ‘; , then re-run NPM run dev and find the output is hi, egg -> Hello world! Controller.home. index refers to the index method in home.js, but controller.home.index binds to a controller object.

Let’s learn some eggjs with the following question:

  • There is no similar app.js entry file to runnpm run devHow to start a project (start server, listen on port, add middleware)?
  • Let’s go to the pagehttp://localhost:7001/How to use router.js to find the route and call the corresponding callback function?
  • How is the Controller bound to the Controller object on the app?

Eggjs start

Let’s start by looking at the package.json file of the project we originally created with the egg-init command. Let’s look at scripts.

npm run start
debug
npm run debug
egg-bin debug
egg-bin debug

egg-bin

Eggjs require(options.framework).startCluster(options); Options. framework points to an absolute path D: private\your_project_name\node_modules\egg. Directly execute D: private\your_project_name\node_modules\egg\index.js exposed exports.startCluster = require(‘egg-cluster’).startCluster; StartCluster method. Let’s examine the EGG-Cluster module.

egg-cluster

The egg-cluster project structure is as follows. There are two main files: master.js and app_worker.js.

Master.js is related to multi-threading of NodeJS. Let’s skip this section and directly study the app_worker.js file to learn the startup process of eggJS. Here are the main steps app_worker.js performs.

  1. const Application = require(options.framework).Application;, introduces the eggJS module to which Optons. Framework pointsD:\private\your_project_name\node_modules\egg
  2. const app = new Application(options);(Create an egg instance)
  3. app.ready(startServer);Call the ** ready ** method of the Egg object, whose startServer is a callback function that calls the nodeJS native modulehttp or httpscreateServerCreate a NodeJS service (server = require('http').createServer(app.callback());We’ll look at this method in more detail later).

In the previous three steps, a NodeJS service has been started, listening on the port. Which has solved our first question:

How to start a project (start server, listen on port, add middleware) by running NPM run dev without a similar app.js entry file?

In fact, we have only analyzed the basic process of eggJS startup. We have not yet covered the core function libraries of eggJS, namely ** egg ** and ** egg-core**. Const app = new Application(options); const app = new Application(options); This entry file is used to analyze the core module of EggJS.

egg & egg-core

Egg and egg-core modules have several core classes below them, as follows:

Application(egg/lib/applicaton.js) —–> EggApplication(egg/lib/egg.js) —–> EggCore(egg-core/lib/egg.js) —–> KoaApplication(koa)

From the above relationship, EggJS extends from KOA, so we start our analysis with the constructor of the base class (since the New Application will start from the constructor of the successor class).

EggCore(egg-core/lib/egg.js)

Let’s simplify the constructor as follows

  1. this.lifecycleResponsible for the entire eggJS instance life cycle, which we will analyze in depth later
  1. this.loaderEgg_loader. js (egg-core/lib/loader/egg_loader.jsrouter.js.controller/**.js, as well asservice/**.jsBound to theappFor example, we will focus on this loader.

EggApplication(egg/lib/egg.js)

Let’s simplify the constructor as follows

This constructor also initializes many of the basic attributes, but there is the loadConfig() method of the loader that calls the EggCore constructor. This method, as the name implies, loads the configuration and points to: Egg /lib/loader/ app_worker_loader. js method loadConfig, this method, as follows:

  loadConfig() {
    this.loadPlugin();
    super.loadConfig();
  }

Copy the code

It loads all the plugins and then all the Config.

Enclosing loadPlugin () points to the egg – core/lib/loader/mixin/plgin loadPlugin js method, it will load three plugin:

  • const appPlugins = this.readPluginConfigs(path.join(this.options.baseDir, 'config/plugin.default'));Plugin for application configuration, i.eyour-project-name/config/plugin.jsThat is, special plug-ins that need to be configured for each application
  • const eggPluginConfigPaths = this.eggPaths.map(eggPath => path.join(eggPath, 'config/plugin.default'));, that is, the plug-in configured from the EggJS framework whose path is inegg/config/plugin.jsThat is, the plug-in that comes with the framework
  • process.env.EGG_PLUGINSThe third way is to start the project is to command line with argumentsEGG_PLUGINSPlugins should not be widely used.

This.plugins = enablePlugins; . (We will learn how these plugins work later.)

Will perform next super. LoadConfig () method, it points to the egg – core/lib/loader/mixin/config. Js loadConfig () method, the same four config loads:

  • const appConfig = this._preloadAppConfig();The application config, which is the specific configuration for each application, will load two configurations:
  const names = [
     'config.default',
     `config.${this.serverEnv}`];Copy the code

First will load the corresponding config. The default configuration, which is your project – the name/config/config. The default, js, has nothing to do with running environment configuration, secondly loads associated with running environment configuration, such as: config.prod.js, config.test.js, config.local.js, config.unittest.js

  • All plugin directories are loaded
   if (this.orderPlugins) {
     for (const plugin of this.orderPlugins) {
       dirs.push({
         path: plugin.path,
         type: 'plugin'}); }}Copy the code
  • The egg project directory, the egg/config directory, is loaded
    for (const eggPath of this.eggPaths) {
     dirs.push({
       path: eggPath,
       type: 'framework'}); }Copy the code
  • Go back to the directory where you loaded your application project, which isyour-project-name/config

Finally, mount the merged config on the app instance this.config = target;

We can open an egg/config/config. The default. The js file, you can view, what are the default configuration, one of the configuration is as follows:

  config.cluster = {
    listen: {
      path: ' ',
      port: 7001,
      hostname: ' ',}};Copy the code

Obviously, this should be a configuration for server startup, so we can guess for the moment.

In egg-cluster/lib/app_worker.js, after we initialize the app, we call app.ready(startServer); Method, we can guess that the startServer method is where nodeJS Server is started.

In the startServer method, an HTTP server is initialized. Server = require(‘ HTTP ‘).createserver (app.callback()); Listen server. Listen (… args);; I can view the args parameter:

  const args = [ port ];
      if (listenConfig.hostname) args.push(listenConfig.hostname);
      debug('listen options %s', args); server.listen(... args);Copy the code

Here we add the prot port parameter to args, so we can jump to where prot defines:

const app = new Application(options);
const clusterConfig = app.config.cluster || /* istanbul ignore next */ {};
const listenConfig = clusterConfig.listen || /* istanbul ignore next */ {};
const port = options.port = options.port || listenConfig.port;
Copy the code

We can see the port eventually comes from: app. Config. Cluster. Listen. The port, we’ve learned from here, the use of config eggjs way.

Question:

What if we do not want port 7001 to be opened by default when the eggJS project starts?

We should have the following two ways:

  1. When you run the NPM run debug command, add corresponding parameters
  2. We can add config to our project config/config.default.js to override the default, such as:

module.exports = appInfo => {
  const config = exports = {};

  // use for cookie sign key, should change to your own and keep security
  config.keys = appInfo.name + '_1541735701381_1116';

  // add your config here
  config.middleware = [];
  config.cluster = {
    listen: {
      path: ' ',
      port: 7788,
      hostname: ' ',}};return config;
};

Copy the code

As shown above, when we start the project again, the open port is: 7788.

Think about:

We already know that the corresponding configuration can be carried out in config, what other applications do we have in config?

We know that different configurations will be loaded in different operating environments, so if we call the API at the time of development, the path is http://dev.api.com, but when we go online, the path of the app we call is: http://prod.api.com, we can in the config. Prod. Js configured in apiURL:http://prod.api.com, in the config. Local. Js configuration: apiURL:http://prod.api.com

We then pass app.apiurl from where we called the API.

Application(egg/lib/application.js)

Application(egg/lib/applicaton.js) —–> EggApplication(egg/lib/egg.js) —–> EggCore(egg-core/lib/egg.js) —–> KoaApplication(koa)

EggApplication(egg/lib/egg.js) —–> EggCore(egg-core/lib/egg.js). We now analyze the uppermost class: Application (an egg/lib/applicaton. Js).

Starting with the constructor again, we find an important line of code this.loader.load(); App_worker_loader. js(egg/lib/loader/app_worker_loader.js);

  load() {
    // app > plugin > core
    this.loadApplicationExtend();
    this.loadRequestExtend();
    this.loadResponseExtend();
    this.loadContextExtend();
    this.loadHelperExtend();
    // app > plugin
    this.loadCustomApp();
    // app > plugin
    this.loadService();
    // app > plugin > core
    this.loadMiddleware();
    // app
    this.loadController();
    // app
    this.loadRouter(); // Dependent on controllers
  }
Copy the code

From this method, we know that a large number of configurations are loaded, and we can analyze them one by one:

this.loadApplicationExtend();

This method will load a number of extension methods to the app, which will be loaded at the path app\extend\application.js, which will mount the corresponding objects to the app. (using the method can refer to an egg – the json/app/extend/applicaton. Js or an egg – the session/app/extend/application. Js)

this.loadResponseExtend(); this.loadResponseExtend(); this.loadContextExtend(); this.loadHelperExtend();.

With this. LoadApplicationExtend (); The loading method is the same, but the corresponding names are request.js, Response. js, helper.js, context.js

this.loadCustomApp();

Customized applications, loaded file is under the corresponding project of app. The js (your_project_name/app. Js), the specific code implementation is as follows: (egg – core/lib/loader/mixin/custom. Js)

  [LOAD_BOOT_HOOK](fileName) {
    this.timing.start(`Load ${fileName}.js`);
    for (const unit of this.getLoadUnits()) { 
      const bootFilePath = this.resolveModule(path.join(unit.path, fileName));
      if(! bootFilePath) {continue; } const bootHook = this.requireFile(bootFilePath); // bootHook is the loaded fileif (is.class(bootHook)) {
        // if is boot class, add to lifecycle
        this.lifecycle.addBootHook(bootHook);
      } else if (is.function(bootHook)) {
        // if is boot function, wrap to class
        // for compatibility
        this.lifecycle.addFunctionAsBootHook(bootHook);
      } else {
        this.options.logger.warn('[egg-loader] %s must exports a boot class', bootFilePath);
      }
    }
    // init boots
    this.lifecycle.init();
    this.timing.end(`Load ${fileName}.js`);
  },
Copy the code

** bootHook** is a file loaded if else. App. Js must be exposed is a class or a function, and then call this. Lifecycle. AddFunctionAsBootHook (bootHook); , its code is as follows:

  addFunctionAsBootHook(hook) {
    assert(this[INIT] === false.'do not add hook when lifecycle has been initialized');
    // app.js is export as a funciton
    // call this function in configDidLoad
    this[BOOT_HOOKS].push(class Hook {
      constructor(app) {
        this.app = app;
      }
      configDidLoad() { hook(this.app); }}); }Copy the code

Push the corresponding hooks into the BOOT_HOOKS array of this.lifecycle wrapped as a class and call the corresponding hooks in configDidLoad. This.lifecycle. Init () is then called; To initialize the life cycle:

  init() {
    assert(this[INIT] === false.'lifecycle have been init');
    this[INIT] = true;
    this[BOOTS] = this[BOOT_HOOKS].map(t => new t(this.app));
    this[REGISTER_BEFORE_CLOSE]();
  }
Copy the code

This init method does three things:

  • Mark the INIT state for Lifecycle as: true
  • Instantiate an object from the BOOT_HOOKS class and save it on BOOTS
  • Call the REGISTER_BEFORE_CLOSE method, which calls the beforeClose method of our hook.

this.loadCustomApp(); The method is as follows:

  loadCustomApp() {
    this[LOAD_BOOT_HOOK]('app');
    this.lifecycle.triggerConfigWillLoad();
  },
Copy the code

So take to perform this. Lifecycle. TriggerConfigWillLoad ();

  triggerConfigWillLoad() {
    for (const boot of this[BOOTS]) {
      if (boot.configWillLoad) {
        boot.configWillLoad();
      }
    }
    this.triggerConfigDidLoad();
  }

  triggerConfigDidLoad() {
    for (const boot of this[BOOTS]) {
      if (boot.configDidLoad) {
        boot.configDidLoad();
      }
    }
    this.triggerDidLoad();
  }
Copy the code

Among them the boot. ConfigDidLoad (); Is the hook class defined by app.js, which is processed into hook class:

class Hook {
      constructor(app) {
        this.app = app;
      }
      configDidLoad() { hook(this.app); }}Copy the code

Then app.js is associated with eggjs.

this.loadService();

Find your_project_name/app/service/.js, mount the file name as a property on the context** context, and assign the corresponding js file, exposed method, to the property, as if we were in the following path: Your_project_name/app/service/home. Js, its code is as follows:

'use strict';

// app/service/home.js
const Service = require('egg').Service;

class HomeService extends Service {
  async find() {
    // const user = await this.ctx.db.query('select * from user where uid = ? ', uid);
    const user = [
      {
        name: 'Ivan Fan',
        age: 18,
      },
    ];
    return user;
  }
}

module.exports = HomeService;
Copy the code

We can through in other places: enclosing CTX. Service. Home. The find () method call the inside of the service methods, such as the controller is called:

'use strict';
const Controller = require('egg').Controller;
class HomeController extends Controller {
  async index() {
    // this.ctx.body = 'hi, egg';
    this.ctx.body = await this.ctx.service.home.find();
  }
}
module.exports = HomeController;
Copy the code

this.loadMiddleware();

This method is used to load middleware, which we’ll examine separately later

this.loadController();

This method is to load the controller as follows:

  loadController(opt) {
    this.timing.start('Load Controller');
    opt = Object.assign({
      caseStyle: 'lower',
      directory: path.join(this.options.baseDir, 'app/controller'),
      initializer: (obj, opt) => {
        // return class if it exports a function
        // ```js
        // module.exports = app => {
        //   returnclass HomeController extends app.Controller {}; / / / /} ` ` `if(is.function(obj) && ! is.generatorFunction(obj) && ! is.class(obj) && ! is.asyncFunction(obj)) { obj = obj(this.app); }if (is.class(obj)) {
          obj.prototype.pathName = opt.pathName;
          obj.prototype.fullPath = opt.path;
          return wrapClass(obj);
        }
        if (is.object(obj)) {
          return wrapObject(obj, opt.path);
        }
        // support generatorFunction for forward compatbility
        if (is.generatorFunction(obj) || is.asyncFunction(obj)) {
          return wrapObject({ 'module.exports': obj }, opt.path)['module.exports'];
        }
        return obj;
      },
    }, opt);
    const controllerBase = opt.directory;

    this.loadToApp(controllerBase, 'controller', opt);
    this.options.logger.info('[egg:loader] Controller loaded: %s', controllerBase);
    this.timing.end('Load Controller');
  },
Copy the code

The loading path is: js file under app/ Controller. App. controller can then call the method exposed by js below Controller as follows:

module.exports = app => {
  const { router, controller } = app;
  router.get('/', controller.home.index);
};
Copy the code

The above is to solve our initial question 3:

  • How is the Controller bound to the Controller object on the app?

this.loadRouter();

This method, as the name implies, loads the router as follows:

  loadRouter() {
    this.timing.start('Load Router'); // load router.js this.loadfile (this.resolvemodule (path.join(this.options. BaseDir,'app/router')));
    this.timing.end('Load Router');
  },
Copy the code

Only app/router.js of the corresponding project will be loaded, i.e. the route should have only one entry file. The following Demo:

'use strict';

/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
  const { router, controller } = app;
  router.get('/', controller.home.index);
};

Copy the code

The code above implements routing. But we only add methods to the corresponding route, but how to listen for route changes, and then call different methods? This concerns the use of KOA middleware, which we will examine separately, as well as the KOA-Router

conclusion

  1. Application(egg/lib/applicaton.js) —–> EggApplication(egg/lib/egg.js) —–> EggCore(egg-core/lib/egg.js) —–> KoaApplication(koa)
  2. Eggjs loads the configuration file using loadConfig()
  loadConfig() {
    this.loadPlugin();
    super.loadConfig();
  }
Copy the code
  1. The load() method is used to load a set of related configurations
  load() {
    // app > plugin > core
    this.loadApplicationExtend();
    this.loadRequestExtend();
    this.loadResponseExtend();
    this.loadContextExtend();
    this.loadHelperExtend();
    // app > plugin
    this.loadCustomApp();
    // app > plugin
    this.loadService();
    // app > plugin > core
    this.loadMiddleware();
    // app
    this.loadController();
    // app
    this.loadRouter(); // Dependent on controllers
  }
Copy the code

plan

  1. Explain the basic usage of Eggjs
  2. Use of middleware middleware
  3. Route The principle of using the Router
  4. An egg – cluster analysis
  5. Analysis of egg life cycle