lin-cms-koa源码阅读之:入口和启动

git仓库地址:https://github.com/TaleLin/lin-cms-koa

一、从入口文件开始

查看package.json,可知入口文件为项目根目录下的index.js文件:

require('@babel/register');

require('./app/starter');

言简意赅,两行代码。第一行引用了@babel/register这个node包,查阅其官网(https://babeljs.io/docs/en/babel-register)可知这个库会修改node提供的require方法,当node程序去require对应后缀名(.es6、.es、.jsx、.mjs和.js)的文件(默认情况下不会去编译node_modules目录里文件)时,这个库会先把库给编译掉再提供给node使用(见下图,往exports对象上绑定了register函数,在register函数里通过hookExtensions函数对指定拓展名的文件添加自定义的compileHook,其中添加hook的addHook方法是从pirates包中导入的)。

 

compileHook的内容很简单,就是判断当前没有在编译的话就把代码编译一下:

let compiling = false;

function compileHook(code, filename) {
  if (compiling) return code;

  try {
    compiling = true;
    return compile(code, filename);
  } finally {
    compiling = false;
  }
}

现在我们看下核心的compile函数:

function compile(code, filename) {
  // merge in base options and resolve all the plugins and presets relative to this file
  const opts = new OptionManager().init(
    // sourceRoot can be overwritten
    {
      sourceRoot: path.dirname(filename) + path.sep,
      ...deepClone(transformOpts),
      filename,
    },
  );

  // Bail out ASAP if the file has been ignored.
  if (opts === null) return code;

  let cacheKey = `${JSON.stringify(opts)}:${babel.version}`;

  const env = babel.getEnv(false);

  if (env) cacheKey += `:${env}`;

  let cached = cache && cache[cacheKey];

  if (!cached || cached.mtime !== mtime(filename)) {
    cached = babel.transform(code, {
      ...opts,
      sourceMaps: opts.sourceMaps === undefined ? "both" : opts.sourceMaps,
      ast: false,
    });

    if (cache) {
      cache[cacheKey] = cached;
      cached.mtime = mtime(filename);
    }
  }

  if (cached.map) {
    if (Object.keys(maps).length === 0) {
      installSourceMapSupport();
    }
    maps[filename] = cached.map;
  }

  return cached.code;
}

这里的主要逻辑是判断是否有缓存了的编译结果,如果有的话就直接使用之前缓存的编译结果没必要重新编译了,如果没有对应的缓存则通过babel.transform方法对源码进行编译处理。所以,项目中其他源码文件里我们就可以使用import/export关键字替代require来进行模块的导入导出了。

二、应用的启动app/starter.js、app/app.js

作用:加载一堆配置文件,然后启动web应用(添加数据解析、跨域支持、静态服务、日志打印等)。代码如下:

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

const { config } = require('lin-mizar/lin/config');

/**
 * 获取配置
 */
function applyConfig () {
  const cwd = process.cwd();
  const files = fs.readdirSync(path.resolve(`${cwd}/app/config`));
  for (const file of files) {
    config.getConfigFromFile(`app/config/${file}`);
  }
  // 加载其它配置文件
  config.getConfigFromFile('app/extensions/file/config.js');
}

const run = async () => {
  applyConfig();
  const { createApp } = require('./app');
  const app = await createApp();
  const port = config.getItem('port');
  app.listen(port, () => {
    console.log(`listening at http://localhost:${port}`);
  });
};

// 启动应用
run();

这里的createApp函数很核心,其内部实现直接看源码中的中文注释即可,见下图:

Author: Yakima
关于作者:楠溪江人,出生于1991年,目前坐标上海。读书时代跳过级、保过送,工作后转过行。2013年本科毕业于北药。看书、码字、敲代码、打羽毛球是我花时间的爱好。曾在某上市药企任国际药品注册岗,现在某高新企业任前端管理岗。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据