babel源碼解析之(@babel/plugin-transform-runtime)

前言

前面我們用了一篇很長(zhǎng)的文章介紹了@babel/preset-env,感興趣的可以去看我之前的一篇文章babel源碼解析之(@babel/preset-env),今天我們要分析的是babel的一個(gè)插件瞧筛,叫@babel/plugin-transform-runtime.

簡(jiǎn)介

我們看一下官網(wǎng)對(duì)它的描述:

A plugin that enables the re-use of Babel's injected helper code to save on codesize.

很簡(jiǎn)短的一個(gè)描述信息,翻譯一下大概是:“抽離babel的一些公共工具類用來(lái)減少代碼的大小”赖舟,雖然描述很少齐饮,但是理解起來(lái)好像比較抽象宋欺,下面我們一起結(jié)合demo一步步分析一下。

開始

我們還是繼續(xù)使用我們前面的demo項(xiàng)目

我們先安裝一下@babel/plugin-transform-runtime插件迷殿,

npm install -D @babel/plugin-transform-runtime

然后我們?cè)趕rc目錄底下創(chuàng)建一個(gè)demo.runtime.js用來(lái)測(cè)試鹃彻,

src/demo.runtime.js:

const fn = () => {};

new Promise(() => {});

class Test {
    say(){}
}

const c = [1, 2, 3].includes(1);
var a = 10;

function* helloWorldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
}

可以看到郊闯,除了之前的一些代碼外,我們還加入了一個(gè)es6的generator函數(shù)蛛株,我們直接用一下@babel/plugin-transform-runtime插件团赁,然后用它的默認(rèn)設(shè)置,

babel.config.js:

module.exports = {
    plugins: [
        [
            "@babel/plugin-transform-runtime",
            {
                "absoluteRuntime": false,
                "corejs": 2,
                "helpers": true,
                "regenerator": true,
                "useESModules": false,
                "version": "7.0.0-beta.0"
            }
        ]
    ]
};

我們運(yùn)行babel編譯看結(jié)果:

?  babel-demo git:(v0.0.1) ? npx babel ./src/demo.runtime.js -o ./lib/demo.runtime.js

lib/demo.runtime.js:

const fn = () => {};

new Promise(() => {});

class Test {
  say() {}

}

const c = [1, 2, 3].includes(1);
var a = 10;

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

可以看到谨履,經(jīng)過(guò)runtime插件處理后代碼并沒(méi)有改變欢摄,這是為什么呢?因?yàn)樵谖覀價(jià)untime插件的配置中我們默認(rèn)是關(guān)閉掉一些功能的笋粟,比如我們把runtime的corejs打開怀挠,

babel.config.js:

module.exports = {
    plugins: [
        [
            "@babel/plugin-transform-runtime",
            {
                "absoluteRuntime": false,
                "corejs": 2,
                "helpers": true,
                "regenerator": true,
                "useESModules": false,
                "version": "7.0.0-beta.0"
            }
        ]
    ]
};

再次運(yùn)行看結(jié)果:

?  babel-demo git:(v0.0.1) ? npx babel ./src/demo.runtime.js                         
import _Promise from "@babel/runtime-corejs2/core-js/promise";

const fn = () => {};

new _Promise(() => {});

class Test {
  say() {}

}

const c = [1, 2, 3].includes(1);
var a = 10;

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

?  babel-demo git:(v0.0.1) ? 

可以看到析蝴,自動(dòng)幫我們引入了一個(gè)polyfill(_Promise),那小伙伴要疑問(wèn)了绿淋,es6的語(yǔ)法沒(méi)轉(zhuǎn)換闷畸?是的! 因?yàn)閞untime不做這些語(yǔ)法的轉(zhuǎn)換吞滞,它只能算是一個(gè)轉(zhuǎn)換幫助類佑菩、一個(gè)自動(dòng)添加polyfill的工具,es6語(yǔ)法轉(zhuǎn)換我們上一節(jié)用了preset-env裁赠,所以我們把preset-env加上殿漠,然后把polyfill去掉,最后runtime配置還原到默認(rèn)配置,

babel.config.js:

module.exports = {
    presets:[
        [
            "@babel/preset-env"
        ]
    ],
    plugins: [
        [
            "@babel/plugin-transform-runtime",
            {
                "absoluteRuntime": false,
                "corejs": false,
                "helpers": true,
                "regenerator": true,
                "useESModules": false,
                "version": "7.0.0-beta.0"
            }
        ]
    ]
};

再次運(yùn)行babel看效果:

?  babel-demo git:(v0.0.1) ? npx babel ./src/demo.runtime.js -o ./lib/demo.runtime.js

lib/demo.runtime.js:

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _regeneratorRuntime2 = require("@babel/runtime/regenerator");

var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

var _marked = /*#__PURE__*/_regeneratorRuntime2.mark(helloWorldGenerator);

var fn = function fn() {};

new Promise(function () {});

var Test = /*#__PURE__*/function () {
  function Test() {
    (0, _classCallCheck2.default)(this, Test);
  }

  (0, _createClass2.default)(Test, [{
    key: "say",
    value: function say() {}
  }]);
  return Test;
}();

var c = [1, 2, 3].includes(1);
var a = 10;

function helloWorldGenerator() {
  return _regenerator.default.wrap(function helloWorldGenerator$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.next = 2;
          return 'hello';

        case 2:
          _context.next = 4;
          return 'world';

        case 4:
          return _context.abrupt("return", 'ending');

        case 5:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}

看結(jié)果也看不出什么佩捞,那runtime到底為我們做了什么呢绞幌?我們?cè)囈幌氯绻覀儾皇褂胷untime插件,直接使用preset-env看結(jié)果:

babel.config.js

module.exports = {
    presets:[
        [
            "@babel/preset-env"
        ]
    ],
    plugins: [
        // [
        //     "@babel/plugin-transform-runtime",
        //     {
        //         "absoluteRuntime": false,
        //         "corejs": false,
        //         "helpers": true,
        //         "regenerator": true,
        //         "useESModules": false,
        //         "version": "7.0.0-beta.0"
        //     }
        // ]
    ]
};

運(yùn)行babel看結(jié)果:

"use strict";

var _marked = /*#__PURE__*/regeneratorRuntime.mark(helloWorldGenerator);

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

var fn = function fn() {};

new Promise(function () {});

var Test = /*#__PURE__*/function () {
  function Test() {
    _classCallCheck(this, Test);
  }

  _createClass(Test, [{
    key: "say",
    value: function say() {}
  }]);

  return Test;
}();

var c = [1, 2, 3].includes(1);
var a = 10;

function helloWorldGenerator() {
  return regeneratorRuntime.wrap(function helloWorldGenerator$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.next = 2;
          return 'hello';

        case 2:
          _context.next = 4;
          return 'world';

        case 4:
          return _context.abrupt("return", 'ending');

        case 5:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}

ok一忱! 可以看到莲蜘,在沒(méi)有使用runtime的時(shí)候,我們的_classCallCheck帘营、_defineProperties菇夸、_createClass都是在當(dāng)前代碼中,如果使用了runtime后仪吧,這些方法都會(huì)直接從@babel/runtime/helpers中導(dǎo)入:

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _regeneratorRuntime2 = require("@babel/runtime/regenerator");

var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

var _marked = /*#__PURE__*/_regeneratorRuntime2.mark(helloWorldGenerator);

所以,如果當(dāng)我們有很多需要編譯的文件的時(shí)候鞠眉,每個(gè)文件中都會(huì)有這些方法的定義薯鼠,這樣整個(gè)包就會(huì)很大,runtime把這些方法抽離到一個(gè)公共的地方械蹋,所以可以讓我們打包出來(lái)的源碼變小出皇。

配置

corejs

false, 2, 3 or { version: 2 | 3, proposals: boolean }, defaults to false.

比如:['@babel/plugin-transform-runtime', { corejs: 3 }]

corejs是可以讓當(dāng)前環(huán)境支持es的最新特性的api墊片(polyfill),在babel之前版本在用@babel/polyfill哗戈,從7.4.0版本后就用core-js代替了polyfill郊艘,比如我們之前在代碼中加入全部的polyfill的是這樣的:

import "@babel/polyfill";

換成core-js后可以是這樣的:

import 'core-js/stable';
import 'regenerator-runtime/runtime';

所以core-js是包含了polyfill的特性,更多的core-js內(nèi)容大家可以看官網(wǎng)https://github.com/zloirock/core-js

這里的corejs配置的就是我們將要使用的runtime-corejs的版本唯咬,有2跟3的版本纱注,2版本是3之前的版本,所以3有一些es最新的一些特性胆胰,比如我們demo中的Array.prototy.includes方法狞贱,只有core-js3上才有:

var c = [1, 2, 3].includes(1);
選用corejs的版本 Install command
false npm install --save @babel/runtime
2 npm install --save @babel/runtime-corejs2
3 npm install --save @babel/runtime-corejs3

為了方便更好的分析,我們直接安裝一下runtime-core2跟runtime-core3:

npm install -D @babel/runtime-corejs2 && npm install -D @babel/runtime-corejs3

我們修改一下我們demo項(xiàng)目的配置文件蜀涨,然后先把corejs改成2瞎嬉,

babel.config.js:

module.exports = {
    presets:[
        [
            "@babel/preset-env"
        ]
    ],
    plugins: [
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs": 2,
            }
        ]
    ]
};

然后我運(yùn)行babel看效果:

?  babel-demo git:(v0.0.1) ? npx babel ./src/demo.runtime.js -o ./lib/demo.runtime.js

lib/demo.runtime.js:

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");

var _regeneratorRuntime2 = require("@babel/runtime-corejs2/regenerator");

var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs2/regenerator"));

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));

var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));

var _marked = /*#__PURE__*/_regeneratorRuntime2.mark(helloWorldGenerator);

var fn = function fn() {};

new _promise.default(function () {});

var Test = /*#__PURE__*/function () {
  function Test() {
    (0, _classCallCheck2.default)(this, Test);
  }

  (0, _createClass2.default)(Test, [{
    key: "say",
    value: function say() {}
  }]);
  return Test;
}();

var c = [1, 2, 3].includes(1);
var a = 10;

function helloWorldGenerator() {
  return _regenerator.default.wrap(function helloWorldGenerator$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.next = 2;
          return 'hello';

        case 2:
          _context.next = 4;
          return 'world';

        case 4:
          return _context.abrupt("return", 'ending');

        case 5:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}

可以看到蝎毡,只幫我們加了一個(gè)_promise(Promise的polyfill),我們并沒(méi)看到Array.prototype.includes的墊片氧枣。

我們修改一下配置文件沐兵,把corejs的版本改成3,

babel.config.js:

module.exports = {
    presets:[
        [
            "@babel/preset-env"
        ]
    ],
    plugins: [
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs": 3,
            }
        ]
    ]
};

再次運(yùn)行看結(jié)果便监,

lib/demo.runtime.js:

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));

var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));

var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));

var _context;

var _marked = /*#__PURE__*/_regenerator.default.mark(helloWorldGenerator);

var fn = function fn() {};

new _promise.default(function () {});

var Test = /*#__PURE__*/function () {
  function Test() {
    (0, _classCallCheck2.default)(this, Test);
  }

  (0, _createClass2.default)(Test, [{
    key: "say",
    value: function say() {}
  }]);
  return Test;
}();

var c = (0, _includes.default)(_context = [1, 2, 3]).call(_context, 1);
var a = 10;

function helloWorldGenerator() {
  return _regenerator.default.wrap(function helloWorldGenerator$(_context2) {
    while (1) {
      switch (_context2.prev = _context2.next) {
        case 0:
          _context2.next = 2;
          return 'hello';

        case 2:
          _context2.next = 4;
          return 'world';

        case 4:
          return _context2.abrupt("return", 'ending');

        case 5:
        case "end":
          return _context2.stop();
      }
    }
  }, _marked);
}

可以看到corejs3給我們添加了一個(gè)includes方法當(dāng)成了polyfill扎谎,如果看過(guò)之前preset-env那篇文章的同學(xué)可能會(huì)發(fā)現(xiàn)了,用transform-runtime插件添加的polyfill都是帶有 "_"符號(hào)的變量(可以看成局部變量)茬贵,是不會(huì)污染全局變量的簿透,我們?cè)賮?lái)回顧一下preset-env,我們修改一下配置文件解藻,把runtime插件去掉老充,然后開啟preset-env的polyfill,preset-env的內(nèi)容不懂的小伙伴可以看我之前的那篇文章哦螟左,

babel.config.js:

module.exports = {
    presets:[
        [
            "@babel/preset-env",
            {
                corejs: 3,
                useBuiltIns: "usage"
            }
        ]
    ],
    plugins: [
        // [
        //     "@babel/plugin-transform-runtime",
        //     {
        //         "corejs": 3,
        //     }
        // ]
    ]
};

運(yùn)行看效果啡浊,

lib/demo.runtime.js:

"use strict";

require("core-js/modules/es.array.includes");

require("core-js/modules/es.object.to-string");

require("core-js/modules/es.promise");

require("regenerator-runtime/runtime");

var _marked = /*#__PURE__*/regeneratorRuntime.mark(helloWorldGenerator);

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

var fn = function fn() {};

new Promise(function () {});

var Test = /*#__PURE__*/function () {
  function Test() {
    _classCallCheck(this, Test);
  }

  _createClass(Test, [{
    key: "say",
    value: function say() {}
  }]);

  return Test;
}();

var c = [1, 2, 3].includes(1);
var a = 10;

function helloWorldGenerator() {
  return regeneratorRuntime.wrap(function helloWorldGenerator$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.next = 2;
          return 'hello';

        case 2:
          _context.next = 4;
          return 'world';

        case 4:
          return _context.abrupt("return", 'ending');

        case 5:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}

可以看到,首先的效果跟runtime插件是一樣的胶背,但是preset-env加的polyfill是直接導(dǎo)入corejs然后替換掉全局變量的居灯,這樣會(huì)造成全局變量的污染。

好啦翘鸭,我們順便把runtime插件跟preset-env的區(qū)別都給講了贴见,下面我們結(jié)合babel的源碼具體分析一下transfrom-runtime插件是怎樣結(jié)合@babel/runtime還有corejs對(duì)我們代碼進(jìn)行轉(zhuǎn)換的。

packages/babel-plugin-transform-runtime/src/index.js:

export default declare((api, options, dirname) => {
  api.assertVersion(7);

  const {
    corejs,
    helpers: useRuntimeHelpers = true,
    regenerator: useRuntimeRegenerator = true,
    useESModules = false,
    version: runtimeVersion = "7.0.0-beta.0",
    absoluteRuntime = false,
  } = options;
        let proposals = false;
  let rawVersion;
    
  //如果傳遞的是corejs: {version:3,proposals:true}對(duì)象類型的時(shí)候就拆分version跟proposals字段
  if (typeof corejs === "object" && corejs !== null) {
    rawVersion = corejs.version;
    proposals = Boolean(corejs.proposals);
  } else {
    rawVersion = corejs;
  }
    //獲取corejs版本號(hào)
  const corejsVersion = rawVersion ? Number(rawVersion) : false;
  //校驗(yàn)版本號(hào)
  if (![false, 2, 3].includes(corejsVersion)) {
    throw new Error(
      `The \`core-js\` version must be false, 2 or 3, but got ${JSON.stringify(
        rawVersion,
      )}.`,
    );
  }
    //校驗(yàn)proposals參數(shù)只能出現(xiàn)在corejsVersion版本為3的情況
  if (proposals && (!corejsVersion || corejsVersion < 3)) {
    throw new Error(
      "The 'proposals' option is only supported when using 'corejs: 3'",
    );
  }
  ...
  
  /*
    如果是core3版本的話就依賴“@babel/runtime-corejs3”
    如果是core2版本的話就依賴“@babel/runtime-corejs2”
    默認(rèn)是依賴“@babel/runtime”
  */
   const moduleName = injectCoreJS3
    ? "@babel/runtime-corejs3"
    : injectCoreJS2
    ? "@babel/runtime-corejs2"
    : "@babel/runtime";
    /*
        如果是core3版本并且開啟提案選項(xiàng)的時(shí)候就會(huì)把corejs的根目錄設(shè)置為“core-js”(包含了最新提案的core-js)
        反之會(huì)將corejs的根目錄設(shè)置為“core-js-stable”(穩(wěn)定版本的core-js)
    */
  const corejsRoot = injectCoreJS3 && !proposals ? "core-js-stable" : "core-js";
  ...
}

ok红且,我們看到這里:

/*
        如果是core3版本并且開啟提案選項(xiàng)的時(shí)候就會(huì)把corejs的根目錄設(shè)置為“core-js”(包含了最新提案的core-js)
        反之會(huì)將corejs的根目錄設(shè)置為“core-js-stable”(穩(wěn)定版本的core-js)
    */
  const corejsRoot = injectCoreJS3 && !proposals ? "core-js-stable" : "core-js";

我們沒(méi)有將proposals設(shè)置為true的時(shí)候我們看一下編譯結(jié)果坝茎,

babel.config.js:

module.exports = {
    presets:[
        [
            "@babel/preset-env",
        ]
    ],
    plugins: [
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs": 3,
            }
        ]
    ]
};

lib/demo.runtime.js:

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));

var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));

var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
...

可以看到,runtime插件幫我們安裝的polyfill都是依賴的core-js-stable版本的corejs,如果我們將proposals設(shè)置為true我們看一下效果暇番,

babel.config.js:

module.exports = {
    presets: [
        [
            "@babel/preset-env",
        ]
    ],
    plugins: [
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs": {version: 3, proposals: true},
            }
        ]
    ]
};

lib/demo.runtime.js:

var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/includes"));
...
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/promise"));

可以看到嗤放,當(dāng)設(shè)置proposals為true的時(shí)候,runtime插件依賴的是core-js目錄的polyfill壁酬,我們分別點(diǎn)開“core-js-stable”跟“core-js”的promise目錄看一下有什么區(qū)別次酌,

首先是“core-js-stable”的“@babel/runtime-corejs3/core-js-stable/promise”,

xxxbabel-demo/node_modules/@babel/runtime-corejs3/core-js-stable/promise.js:

module.exports = require("core-js-pure/stable/promise");

然后是“core-js”的“@babel/runtime-corejs3/core-js/promise”舆乔,

xxx/babel-demo/node_modules/@babel/runtime-corejs3/core-js/promise.js:

module.exports = require("core-js-pure/features/promise");

可以看到岳服,都是引用了“core-js-pure”,那么“core-js-pure”又是啥呢希俩?其實(shí)是core-js的另外一個(gè)版本派阱,叫:“純凈的core-js”,也就是說(shuō)不會(huì)污染全局變量的意思斜纪,具體小伙伴可以看core-js的官網(wǎng)里面有詳細(xì)說(shuō)明的贫母。

都是依賴的“core-js-pure”但是下級(jí)目錄就不一樣了文兑,一個(gè)是“stable”一個(gè)是“features”,我們繼續(xù)往下看腺劣,找到這兩個(gè)文件绿贞,

node_modules/core-js-pure/features/promise/index.js:

var parent = require('../../es/promise');
require('../../modules/esnext.aggregate-error');
// TODO: Remove from `core-js@4`
require('../../modules/esnext.promise.all-settled');
require('../../modules/esnext.promise.try');
require('../../modules/esnext.promise.any');

module.exports = parent;

node_modules/core-js-pure/es/promise/index.js:

require('../../modules/es.object.to-string');
require('../../modules/es.string.iterator');
require('../../modules/web.dom-collections.iterator');
require('../../modules/es.promise');
require('../../modules/es.promise.all-settled');
require('../../modules/es.promise.finally');
var path = require('../../internals/path');

module.exports = path.Promise;

可以看到,feature的少了很多內(nèi)容橘原,然后還有依賴了一些“esnext”打頭的模塊籍铁,“esnext”打頭的也就是說(shuō)下一個(gè)es版本中可能會(huì)出現(xiàn)的一些內(nèi)容(處于stage階段,還不怎么穩(wěn)定)趾断。

ok拒名!我們了解corejs2跟3的區(qū)別,然后還分析了proposals參數(shù)芋酌,當(dāng)runtime插件拿到了我們的corejs之后又是怎樣動(dòng)態(tài)的注入到我們的代碼中的呢增显?

比如我們“src/demo.runtime.js”文件中有一個(gè)Promise,那么runtime是怎么注入的呢脐帝?看過(guò)前面preset-env文章的童鞋應(yīng)該是多多少少有點(diǎn)感覺了同云,其實(shí)就是遍歷ast的節(jié)點(diǎn),然后遍歷到Promise的時(shí)候動(dòng)態(tài)的添加上polyfill代碼堵腹,也就是一下代碼:

var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));

我們看一下源碼炸站,

Xxxx/babel-demo/node_modules/@babel/plugin-transform-runtime/lib/index.js:

... 
visitor: {
      ReferencedIdentifier(path) {
        const {
          node,
          parent,
          scope
        } = path;
        const {
          name
        } = node;

        if (name === "regeneratorRuntime" && useRuntimeRegenerator) {
          path.replaceWith(this.addDefaultImport(`${modulePath}/regenerator`, "regeneratorRuntime"));
          return;
        }

        if (!injectCoreJS) return;
        if (_core.types.isMemberExpression(parent)) return;
        if (!hasMapping(BuiltIns, name)) return;
        if (scope.getBindingIdentifier(name)) return;
        path.replaceWith(this.addDefaultImport(`${modulePath}/${corejsRoot}/${BuiltIns[name].path}`, name));
      ...
      

之前寫過(guò)一篇文章介紹過(guò)babel的源碼,然后最后還自定義了一個(gè)插件疚顷,babel源碼解析一旱易,插件返回的就是一個(gè)ast節(jié)點(diǎn)遍歷的鉤子函數(shù),也就是說(shuō)babel在遍歷每一個(gè)節(jié)點(diǎn)的時(shí)候會(huì)觸發(fā)對(duì)應(yīng)插件的鉤子函數(shù)腿堤,也就是說(shuō)當(dāng)解析到"src/demo.runtime.js"中的這段代碼的時(shí)候:

new Promise(function () {});

會(huì)走上面runtime插件的ReferencedIdentifier方法咒唆,然后把當(dāng)前節(jié)點(diǎn)傳過(guò)來(lái),

... 
visitor: {
      ReferencedIdentifier(path) {
        const {
          node,
          parent,
          scope
        } = path;
        const {
          name
        } = node;
                //如果有g(shù)enerator函數(shù)并且useRuntimeRegenerator設(shè)置為true的時(shí)候就添加generatorRuntime的polyfill释液,
        if (name === "regeneratorRuntime" && useRuntimeRegenerator) {
          path.replaceWith(this.addDefaultImport(`${modulePath}/regenerator`, "regeneratorRuntime"));
          return;
        }
                //corejs為false就不添加polyfill直接返回
        if (!injectCoreJS) return
        if (_core.types.isMemberExpression(parent)) return;
        //看當(dāng)前corejs中有沒(méi)有“Promise”的墊片polyfill
        if (!hasMapping(BuiltIns, name)) return;
        if (scope.getBindingIdentifier(name)) return;
    //添加promise polyfill路徑為
  //"@babel/runtime-corejs3/core-js-stable/promise"       
                path.replaceWith(this.addDefaultImport(`${modulePath}/${corejsRoot}/${BuiltIns[name].path}`, name));
      ...
      

可以看到,如果有g(shù)enerator函數(shù)并且“useRuntimeRegenerator”設(shè)置為“true”的時(shí)候就添加generatorRuntime的polyfill装处,“useRuntimeRegenerator”選項(xiàng)我們下面再說(shuō)误债,然后當(dāng)corejs選項(xiàng)不為false的時(shí)候就按照前面說(shuō)的路徑去添加“Promise”的polyfill代碼。

helpers& useESModules

helpers: boolean, defaults to true.

是否運(yùn)行runtime插件添加babel的helpers函數(shù)妄迁,比如我們的classCallCheck寝蹈、extends方法等等,默認(rèn)是開啟的登淘。

useESModules: boolean, defaults totrue`.

是否在添加esm方式的helpers函數(shù)的時(shí)候箫老,默認(rèn)是根據(jù)babel的配置來(lái)選擇。

我們測(cè)試一下這兩個(gè)參數(shù)黔州,

src/demo.runtime.js:

const fn = () => {};

new Promise(() => {});

class Test {
    say(){}
}

const c = [1, 2, 3].includes(1);
var a = 10;

function* helloWorldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
}

babel.config.js:

module.exports = {
    presets: [
        [
            "@babel/preset-env",
        ]
    ],
    plugins: [
        [
            "@babel/plugin-transform-runtime",
            {
                corejs: {version: 3, proposals: true},
                helpers: true,
                useESModules: false
            }
        ]
    ]
};

運(yùn)行看結(jié)果耍鬓,

lib/demo.runtime.js:

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

...

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));
...

可以看到阔籽,當(dāng)helpers為true然后useESModules為false的時(shí)候會(huì)添加一些helper函數(shù),比如我們的createClass跟classCallCheck等等牲蜀,都是從corejs3的helpers目錄下直接取模塊笆制,如果我們把useESModules設(shè)置為true,我們看一下效果涣达,

babel.config.js:

module.exports = {
    presets: [
        [
            "@babel/preset-env",
        ]
    ],
    plugins: [
        [
            "@babel/plugin-transform-runtime",
            {
                corejs: {version: 3, proposals: true},
                helpers: true,
                useESModules: true
            }
        ]
    ]
};

運(yùn)行代碼看效果在辆,

lib/demo.runtime.js:

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));

var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/includes"));

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/esm/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/esm/createClass"));

...

可以看到,useESModules設(shè)置為true的時(shí)候會(huì)從helpers的esm目錄加載對(duì)應(yīng)的模塊度苔,這就是useESModules配置的作用匆篓。

下面我們分析一下源碼,

packages/babel-plugin-transform-runtime/src/index.js:

 return {
    name: "transform-runtime",

    pre(file) {
      //是否開啟了helpers選項(xiàng)
      if (useRuntimeHelpers) {
        file.set("helperGenerator", name => {
                //看當(dāng)前helper是否在可用
          if (
            file.availableHelper &&
            !file.availableHelper(name, runtimeVersion)
          ) {
            return;
          }
            
          const isInteropHelper = HEADER_HELPERS.indexOf(name) !== -1;

          
          const blockHoist =
            isInteropHelper && !isModule(file.path) ? 4 : undefined;
                    //根據(jù)useESModules配置選擇加載helper的目錄
          //useESModules: false(默認(rèn)為helpers)
          //useESModules: true(helpers/esm)
          const helpersDir =
            esModules && file.path.node.sourceType === "module"
              ? "helpers/esm"
              : "helpers";

          return this.addDefaultImport(
            `${modulePath}/${helpersDir}/${name}`,
            name,
            blockHoist,
          );
        });
      }

      const cache = new Map();

      this.addDefaultImport = (source, nameHint, blockHoist) => {
        // If something on the page adds a helper when the file is an ES6
        // file, we can't reused the cached helper name after things have been
        // transformed because it has almost certainly been renamed.
        const cacheKey = isModule(file.path);
        const key = `${source}:${nameHint}:${cacheKey || ""}`;

        let cached = cache.get(key);
        if (cached) {
          cached = t.cloneNode(cached);
        } else {
          cached = addDefault(file.path, source, {
            importedInterop: "uncompiled",
            nameHint,
            blockHoist,
          });

          cache.set(key, cached);
        }
        return cached;
      };
    },

OK寇窑!代碼中有注釋鸦概,我就不詳細(xì)說(shuō)明了。

regenerator

boolean, defaults to true.

是否開啟添加regenerator函數(shù)的polyfill防止全局污染疗认。

描述不是很好理解哈完残,別怕,我們結(jié)合demo跟源碼來(lái)分析横漏,首先我們把regenerator選項(xiàng)關(guān)閉(false)谨设,然后看一下我們demo中的編譯情況,

src/demo.runtime.js:

const fn = () => {};

new Promise(() => {});

class Test {
    say(){}
}

const c = [1, 2, 3].includes(1);
var a = 10;

function* helloWorldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
}

可以看到缎浇,我們?cè)创a中有一個(gè)generator函數(shù)叫“helloWorldGenerator”,然后我關(guān)閉regenerator

babel.config.js:

module.exports = {
    presets: [
        [
            "@babel/preset-env",
        ]
    ],
    plugins: [
        [
            "@babel/plugin-transform-runtime",
            {
                corejs: {version: 3, proposals: true},
                helpers: true,
                useESModules: true,
                regenerator: false
            }
        ]
    ]
};

運(yùn)行看結(jié)果扎拣,

lib/demo.runtime.js:

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/includes"));

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/esm/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/esm/createClass"));

var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/promise"));

var _context;

var _marked = /*#__PURE__*/regeneratorRuntime.mark(helloWorldGenerator);

var fn = function fn() {};

new _promise.default(function () {});

var Test = /*#__PURE__*/function () {
  function Test() {
    (0, _classCallCheck2.default)(this, Test);
  }

  (0, _createClass2.default)(Test, [{
    key: "say",
    value: function say() {}
  }]);
  return Test;
}();

var c = (0, _includes.default)(_context = [1, 2, 3]).call(_context, 1);
var a = 10;

function helloWorldGenerator() {
  return regeneratorRuntime.wrap(function helloWorldGenerator$(_context2) {
    while (1) {
      switch (_context2.prev = _context2.next) {
        case 0:
          _context2.next = 2;
          return 'hello';

        case 2:
          _context2.next = 4;
          return 'world';

        case 4:
          return _context2.abrupt("return", 'ending');

        case 5:
        case "end":
          return _context2.stop();
      }
    }
  }, _marked);
}

可以看到,我們的helloWorldGenerator函數(shù)被preset-env改造過(guò)后變成了:


var _marked = /*#__PURE__*/regeneratorRuntime.mark(helloWorldGenerator);
function helloWorldGenerator() {
  return regeneratorRuntime.wrap(function helloWorldGenerator$(_context2) {
    while (1) {
      switch (_context2.prev = _context2.next) {
        case 0:
          _context2.next = 2;
          return 'hello';

        case 2:
          _context2.next = 4;
          return 'world';

        case 4:
          return _context2.abrupt("return", 'ending');

        case 5:
        case "end":
          return _context2.stop();
      }
    }
  }, _marked);
}

由于preset-env沒(méi)有開啟polyfill選項(xiàng)素跺,然后runtime插件又關(guān)閉了regenerator選項(xiàng)二蓝,所以我們的regeneratorRuntime對(duì)象并沒(méi)有被注入,所以我們打開我們的regenerator選項(xiàng)再試試指厌,

babel.config.js:

module.exports = {
    presets: [
        [
            "@babel/preset-env",
        ]
    ],
    plugins: [
        [
            "@babel/plugin-transform-runtime",
            {
                corejs: {version: 3, proposals: true},
                helpers: true,
                useESModules: true,
                regenerator: true
            }
        ]
    ]
};

運(yùn)行看效果刊愚,

demo.runtime.js:

...
var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));
var _marked = /*#__PURE__*/_regenerator.default.mark(helloWorldGenerator);
function helloWorldGenerator() {
  return _regenerator.default.wrap(function helloWorldGenerator$(_context2) {
    while (1) {
      switch (_context2.prev = _context2.next) {
        case 0:
          _context2.next = 2;
          return 'hello';

        case 2:
          _context2.next = 4;
          return 'world';

        case 4:
          return _context2.abrupt("return", 'ending');

        case 5:
        case "end":
          return _context2.stop();
      }
    }
  }, _marked);
}
...

可以看到,當(dāng)開啟了regenerator選項(xiàng)的時(shí)候踩验,runtime會(huì)自動(dòng)的注入一個(gè)_regenerator對(duì)象鸥诽,用來(lái)替換我們之前的regeneratorRuntime對(duì)象,并且不會(huì)像preset-env一樣會(huì)污染全局箕憾,

以下是“preset-env”添加的regenerator polyfill

require("regenerator-runtime/runtime");

var _marked = /*#__PURE__*/regeneratorRuntime.mark(helloWorldGenerator);
function helloWorldGenerator() {
  return regeneratorRuntime.wrap(function helloWorldGenerator$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.next = 2;
          return 'hello';

        case 2:
          _context.next = 4;
          return 'world';

        case 4:
          return _context.abrupt("return", 'ending');

        case 5:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}

ok牡借!我們的regenerator參數(shù)就講到這里了,下面我們看一下源碼中的操作袭异,

packages/babel-plugin-transform-runtime/src/index.js:

visitor: {
      ReferencedIdentifier(path) {
        const { node, parent, scope } = path;
        const { name } = node;

        // transform `regeneratorRuntime`
        if (name === "regeneratorRuntime" && useRuntimeRegenerator) {
          path.replaceWith(
            this.addDefaultImport(
              `${modulePath}/regenerator`,
              "regeneratorRuntime",
            ),
          );
          return;
        }

可以看到钠龙,源碼中當(dāng)讀到"regeneratorRuntime"變量的時(shí)候,就替換掉"regeneratorRuntime"變量改為以下代碼:

var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));
var _marked = /*#__PURE__*/_regenerator.default.mark(helloWorldGenerator);

absoluteRuntime

boolean or string, defaults to false.

設(shè)置runtime插件從哪個(gè)目錄導(dǎo)入helpers跟polyfill,默認(rèn)是:@babel/runtime-corejs3碴里、@babel/runtime-corejs2或者@babel/runtime沈矿,你也可以設(shè)置其它的路徑,我們用一下看效果:

babel.config.js:

module.exports = {
    presets: [
        [
            "@babel/preset-env",
        ]
    ],
    plugins: [
        [
            "@babel/plugin-transform-runtime",
            {
                corejs: {version: 3, proposals: true},
                helpers: true,
                useESModules: true,
                regenerator: true,
                absoluteRuntime: "./node_modules"
            }
        ]
    ]
};

運(yùn)行看效果:

lib/demo.runtime.js

"use strict";

var _interopRequireDefault = require("xxx/babel-demo/node_modules/@babel/runtime-corejs3/helpers/interopRequireDefault");

var _regenerator = _interopRequireDefault(require("xxx/babel-demo/node_modules/@babel/runtime-corejs3/regenerator"));

var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/includes"));

var _classCallCheck2 = _interopRequireDefault(require("xxx/babel-demo/node_modules/@babel/runtime-corejs3/helpers/esm/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("xxx/babel-demo/node_modules/@babel/runtime-corejs3/helpers/esm/createClass"));

var _promise = _interopRequireDefault(require("xxx/babel-demo/node_modules/@babel/runtime-corejs3/core-js/promise"));

var _context;

var _marked = /*#__PURE__*/_regenerator.default.mark(helloWorldGenerator);

var fn = function fn() {};

new _promise.default(function () {});

var Test = /*#__PURE__*/function () {
  function Test() {
    (0, _classCallCheck2.default)(this, Test);
  }

  (0, _createClass2.default)(Test, [{
    key: "say",
    value: function say() {}
  }]);
  return Test;
}();

var c = (0, _includes.default)(_context = [1, 2, 3]).call(_context, 1);
var a = 10;

function helloWorldGenerator() {
  return _regenerator.default.wrap(function helloWorldGenerator$(_context2) {
    while (1) {
      switch (_context2.prev = _context2.next) {
        case 0:
          _context2.next = 2;
          return 'hello';

        case 2:
          _context2.next = 4;
          return 'world';

        case 4:
          return _context2.abrupt("return", 'ending');

        case 5:
        case "end":
          return _context2.stop();
      }
    }
  }, _marked);
}

可以看到并闲,大部分的polyfill跟helpers函數(shù)都變成“xxx/babel-demo/node_modules/@babel/runtime-corejs3/xxx”细睡,也就是說(shuō)runtime插件可以讓用戶指定truntime依賴的位置,轉(zhuǎn)換過(guò)后就變成一個(gè)絕對(duì)路徑了帝火。

version

runtime中corejs的版本溜徙,比如現(xiàn)在我們的@babel/runtime-corejs2的7.0.1之前是沒(méi)有Math的一些方法的,那么如果你的version值設(shè)置的是<=7.0.0的時(shí)候runtime插件就不會(huì)Math的一些方法給加進(jìn)來(lái)的犀填。

packages/babel-plugin-transform-runtime/src/index.js:

  const { BuiltIns, StaticProperties, InstanceProperties } = (injectCoreJS2
    ? getCoreJS2Definitions
    : getCoreJS3Definitions)(runtimeVersion);

packages/babel-plugin-transform-runtime/src/runtime-corejs2-definitions.js:

export default runtimeVersion => {
  // Conditionally include 'Math' because it was not included in the 7.0.0
  // release of '@babel/runtime'. See issue https://github.com/babel/babel/pull/8616.
  ...
  const includeMathModule = hasMinVersion("7.0.1", runtimeVersion);
     ...(includeMathModule
        ? {
            Math: {
              acosh: { stable: true, path: "math/acosh" },
              asinh: { stable: true, path: "math/asinh" },
              atanh: { stable: true, path: "math/atanh" },
              cbrt: { stable: true, path: "math/cbrt" },
              clz32: { stable: true, path: "math/clz32" },
              cosh: { stable: true, path: "math/cosh" },
              expm1: { stable: true, path: "math/expm1" },
              fround: { stable: true, path: "math/fround" },
              hypot: { stable: true, path: "math/hypot" },
              imul: { stable: true, path: "math/imul" },
              log10: { stable: true, path: "math/log10" },
              log1p: { stable: true, path: "math/log1p" },
              log2: { stable: true, path: "math/log2" },
              sign: { stable: true, path: "math/sign" },
              sinh: { stable: true, path: "math/sinh" },
              tanh: { stable: true, path: "math/tanh" },
              trunc: { stable: true, path: "math/trunc" },
            },
          }
        : {}),  
     ...
}

OK蠢壹,我們的@babel/plugin-transform-runtime全部?jī)?nèi)容就已經(jīng)解析完畢了。

demo項(xiàng)目

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末九巡,一起剝皮案震驚了整個(gè)濱河市图贸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌冕广,老刑警劉巖疏日,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異撒汉,居然都是意外死亡沟优,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門睬辐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)挠阁,“玉大人,你說(shuō)我怎么就攤上這事溯饵∏炙祝” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵丰刊,是天一觀的道長(zhǎng)隘谣。 經(jīng)常有香客問(wèn)我,道長(zhǎng)啄巧,這世上最難降的妖魔是什么寻歧? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮棵帽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘渣玲。我一直安慰自己逗概,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布忘衍。 她就那樣靜靜地躺著逾苫,像睡著了一般卿城。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铅搓,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天瑟押,我揣著相機(jī)與錄音,去河邊找鬼星掰。 笑死多望,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的氢烘。 我是一名探鬼主播怀偷,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼播玖!你這毒婦竟也來(lái)了椎工?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蜀踏,失蹤者是張志新(化名)和其女友劉穎维蒙,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體果覆,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡颅痊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了随静。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片八千。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖燎猛,靈堂內(nèi)的尸體忽然破棺而出恋捆,到底是詐尸還是另有隱情,我是刑警寧澤重绷,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布沸停,位于F島的核電站,受9級(jí)特大地震影響昭卓,放射性物質(zhì)發(fā)生泄漏愤钾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一候醒、第九天 我趴在偏房一處隱蔽的房頂上張望能颁。 院中可真熱鬧,春花似錦倒淫、人聲如沸伙菊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)镜硕。三九已至运翼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間兴枯,已是汗流浹背血淌。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留财剖,地道東北人悠夯。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像峰伙,于是被迫代替她去往敵國(guó)和親疗疟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345