瀏覽器支援ES6的最優解決方案

cTomorrow發表於2019-02-20

ES6系列的興起

可以說ECMAScript6是JavaScript歷史上最大的一次變革,ES6的到來為JavaScrip帶來了物件導向的特性,帶來了許多新的語法,也讓這門解釋性語言有了它該有的樣子。總的來說,帶來了無限好處。帶來好處的同時也讓我們在用的時候有了擔憂,使用者的角度是廣闊的,開發者無法提前預知使用者用的瀏覽器到底支援不支援ES6的語法,因此在開發過程中就有個阻礙。babel的興起讓ES6的開發者大顯身手,但是它是把ES6的語法編譯成ES5的語法,也就是瀏覽器支援的語法。我們要知道在ES6興起的時候,V8引擎是對ES6語法有了極大的優化的,濫用了babel不就也放棄了這種優化嗎?這使得ES6只讓開發者更加便於開發,使用者的角度上並沒有體現出任何價值,而且babel編譯出的龐大的ES5檔案,在使用者方法還起到了負面作用。那這到底怎麼辦呢?接下來主角登場。

最優的解決方案

emmmm...主角登場前,電閃雷鳴,烏雲密佈。當我們在開發過程中的時候,JavaScript採用兩種檔案的方式來載入。一種的ES6程式碼寫的檔案,一種的babel編譯成ES5的檔案。在瀏覽器中執行的時候,判斷瀏覽器是否支援ES6,如果支援就載入ES6的檔案,如果不支援就載入ES5的檔案,這樣就良好的解決問題。那麼用什麼判斷呢?主角登場。。。。

type='module'

在script標籤裡面我們都知道有type屬性指定檔案的型別(type='text/script'),這個屬性還有一個值那就是module和nomodule。

  1. module:表示當瀏覽器支援ES6的時候執行的JavaScript程式碼
  2. nomodule:表示當瀏覽器不支援ES6的時候執行的JavaScript程式碼。

我們在專案中建立test.js檔案來寫這樣一段程式碼:當我們在寫好ES6的程式碼的時候不要直接呼叫,阮大佬的import,用ES6 import export的形式匯出

class Test {
    constructor(){
        this.name = 'zhangsan';
    }
    action(){
        console.log(this.name);
    }
}
export default Test;
複製程式碼

在html中:使用ES6的動態import的形式載入進來。

<script type='module'>
    import('test.js').then(_=>{
        console.log('我支援modul');
        new _.default().action();
    })
</script>
複製程式碼

開啟Chrom瀏覽器會發現有這樣的一句輸出

瀏覽器支援ES6的最優解決方案

type=‘nomodule’的情況下我們執行babel編譯

使用babel編譯

安裝babel

npm install --save-dev @babel/core @babel/cli @babel/preset-env

在專案根目錄建立.babelrc檔案,內容如下

{
    "presets" : ["@babel/preset-env"]
}
複製程式碼

使用命令進行編譯

babel test.js --out-file test-bundle.js

test-bundle.js檔案內容如下:

"use strict";
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

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 Test =
/*#__PURE__*/
function () {
  function Test() {
    _classCallCheck(this, Test);

    this.name = 'zhangsan';
  }

  _createClass(Test, [{
    key: "action",
    value: function action() {
      console.log(this.name);
    }
  }]);

  return Test;
}();

var _default = Test;
exports.default = _default;

複製程式碼

我們發現第二行使用的是模組載入export,require之類的東西,但是我們瀏覽器中並沒有這些東西,於是我們採用一個模組載入器來載入這個東西。

System.js

System.js是一個萬能模組載入器,任何使用模組載入的都可以使用它來進行載入。用法也非常簡單。Systemjs的github。我們採用script標籤的形式把這個東西載入進來。並用官網的方式載入test.js

需要注意的是:Systemjs必須指定nomodule,和ES5檔案保持一致才可以。由於我們用於測試和最新的Chrome瀏覽器肯定是支援module的,所以先把nomodule改成module來用於測試

 <!--<script type="nomodule" src="https://cdn.staticfile.org/systemjs/3.0.0/system.js"></script>
 <script type="nomodule">
    System.import('test.js').then(_=>{
        new _.default().action();
    })
 </script>-->
 
 <script type="module" src="https://cdn.staticfile.org/systemjs/3.0.0/system.js"></script>
 <script type="module">
    System.import('test.js').then(_=>{
        new _.default().action();
    })
 </script>
複製程式碼

開啟瀏覽器見證奇蹟

瀏覽器支援ES6的最優解決方案

哎呀報錯了,很懵逼,很焦慮,為什麼會報錯呢? 因為babel編譯的es5的語法採用的模組載入器是export載入,並沒有使用SystemJS的載入方式。可以通過babel外掛來把babel的模組載入方式改成SystemJS的載入方式。

最後的希望

我們在npm上找到這個外掛@babel/plugin-transform-modules-systemjs並安裝

npm install @babel/plugin-transform-modules-systemjs --sav-dev

在.babelrc裡面新增上plugins

{
    "presets" : ["@babel/preset-env"],
    "plugins" : ["@babel/plugin-transform-modules-systemjs"]
}
複製程式碼

此時使用上面babel語法進行編譯,檔案內容如下

"use strict";
System.register([], function (_export, _context) {
  "use strict";

  var Test;

  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; }

  return {
    setters: [],
    execute: function () {
      Test =
      /*#__PURE__*/
      function () {
        function Test() {
          _classCallCheck(this, Test);

          this.name = 'zhangsan';
        }

        _createClass(Test, [{
          key: "action",
          value: function action() {
            console.log(this.name);
          }
        }]);

        return Test;
      }();

      _export("default", Test);
    }
  };
});
複製程式碼

第二行我們發現有這樣一個東西System.register([], function (_export, _context),與之前的編譯不一樣了。此時就是使用SystemJS的模組載入機制。我們開啟瀏覽器。

瀏覽器支援ES6的最優解決方案

完美。

在使用type=‘module’和和type=‘nomodule’的時候,一定要把SystemJS的type設定成nomodule,與ES5語法統一。


原文發表於瀏覽器支援ES6的最優解決方案

相關文章