SeaJS 解析

2018/7/22

资源加载原理

使用 require 加载资源(JavaScript 和 CSS),背后是采用 <link> 和 <script> 来加载 CSS 和 JavaScript。

function request(url, callback, charset) {
  var isCSS = IS_CSS_RE.test(url)
  var node = doc.createElement(isCSS ? "link" : "script")

  if (charset) {
    var cs = isFunction(charset) ? charset(url) : charset
    if (cs) {
      node.charset = cs
    }
  }

  addOnload(node, callback, isCSS, url)

  if (isCSS) {
    node.rel = "stylesheet"
    node.href = url
  }
  else {
    node.async = true
    node.src = url
  }

  // For some cache cases in IE 6-8, the script executes IMMEDIATELY after
  // the end of the insert execution, so use `currentlyAddingScript` to
  // hold current node, for deriving url in `define` call
  currentlyAddingScript = node

  // ref: #185 & http://dev.jquery.com/ticket/2709
  baseElement ?
      head.insertBefore(node, baseElement) :
      head.appendChild(node)

  currentlyAddingScript = null
}

需要注意的是,动态插入 script 标签加载 JS 的方式不会按照插入的顺序来执行。如果想要按插入顺序来执行,可以配置<script async="false">,即采用阻塞的方式,但是这个存在浏览器兼容性问题。

require 加载机制

在 NodeJS 中,require 是执行到当前行是才去加载,但是在浏览器端,由于 <script> 加载无法中断当前执行的代码,相当于是“异步”的,因此无法模拟 NodeJS 中的执行方式。哪么实际是如何加载的呢?用下面的代码来测试一下。

define(function(require) {
    debugger;
    console.log("加载 a.js");
    require("./a.js");
    console.log("加载 b.js");
    require("./b.js");
});

打开控制台,运行到断点时,查看 Network 面板,发现 a.js 和 b.js 都已经加载完了。那么 seajs 是如何做到提前加载 a.js 和 b.js 的呢?原理就是通过将 define 传入的函数转成字符串,然后正则匹配出 require 的资源,提前加载。源码如下:

Module.define = function (id, deps, factory) {
  // ...

  if (!isArray(deps) && isFunction(factory)) {
    deps = parseDependencies(factory.toString())
  }
  
  // ...
}

var REQUIRE_RE = /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^\/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g
var SLASH_RE = /\\\\/g
function parseDependencies(code) {
  var ret = []

  code.replace(SLASH_RE, "")
      .replace(REQUIRE_RE, function(m, m1, m2) {
        if (m2) {
          ret.push(m2)
        }
      })

  return ret
}

其中比较关键的就是匹配 require 的正则(REQUIRE_RE),其复杂度也说明了重要性。

// 去掉 REQUIRE_RE  /g 用 match 查看匹配结果
'require("a.js");'.match(REQUIRE_RE)
// ["require("a.js")", """, "a.js"]
'require("a-b.min.js");'.match(REQUIRE_RE)
// ["require("a-b.min.js")", """, "a-b.min.js"]
'require("http://www.qinshenxue.com/a.js");'.match(REQUIRE_RE)
// ["require("http://www.qinshenxue.com/a.js")", """, "http://www.qinshenxue.com/a.js"]

加载流程

以下面的代码为例,用流程图的形式展示整个加载流程。

// main.js
define(function(require) {
    require("./a.js");
    require("./b.js");
});
// a.js
define(function(require) {
    console.log("a.js");
});
// b.js
define(function(require) {
    console.log("b.js");
});

原创文章,持续完善中,转载请注明出处。本文地址: https://www.qinshenxue.com/article/20180109085513.html