Angular CLI 7.3 使用 ES2015 的 nomodule 屬性載入 Polyfills 函式庫 | The Will Will Web

The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

Angular CLI 7.3 使用 ES2015 的 nomodule 屬性載入 Polyfills 函式庫

最近推出的 Angular CLI 7.3 版本,新增了一個相當亮眼的特性。當你的 Angular 應用程式同時要符合 IE 或舊版瀏覽器時,以往都會用 Polyfill 來填充缺少的 HTML5/JS APIs,但這件事到了 Angular CLI 7.3 又變得更貼心了。不但如此,這個新特性一樣可以用在各種 SPA 框架中,像是 Vue, React 或其他函式庫也都可以套用相同技巧。欲知詳情請繼續看下去。

關於 polyfills.ts 檔案

在 Angular CLI 所建立的專案下,都有個 src/polyfills.ts 檔案,裡面可以讓你設定需要載入哪些必要的 Polyfills 函式庫,好讓你的網站也可以支援舊版瀏覽器執行。

目前 Angular 支援的 Polyfills APIs 最低可支援到 IE9 瀏覽器!

Angular CLI 內建的 Polyfills 定義一直在進化,幾乎每一個大版都會有些更貼心的設計出現,我們來看看最近幾版的變化與差異。

關於 Angular CLI 6 的 polyfills.ts 檔案

如果你用 Angular CLI 6 建立的 Angular 專案,其預設的 src/polyfills.ts 檔案內容如下。註解中詳細說明了使用的時機點,想支援哪個瀏覽器就要匯入必要的 Polyfill 模組,有些模組需要額外透過 npm 進行安裝,記得要安裝之後才能使用。

/**
 * This file includes polyfills needed by Angular and is loaded before the app.
 * You can add your own extra polyfills to this file.
 *
 * This file is divided into 2 sections:
 *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
 *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
 *      file.
 *
 * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
 * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
 * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
 *
 * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
 */

/***************************************************************************************************
 * BROWSER POLYFILLS
 */

/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';

/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js';  // Run `npm install --save classlist.js`.

/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';

/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import 'core-js/es7/reflect';

/**
 * Web Animations `@angular/platform-browser/animations`
 * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
 * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
 **/
// import 'web-animations-js';  // Run `npm install --save web-animations-js`.

/**
 * By default, zone.js will patch all possible macroTask and DomEvents
 * user can disable parts of macroTask/DomEvents patch by setting following flags
 */

// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames

/*
 * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
 * with the following flag, it will bypass `zone.js` patch for IE/Edge
 */
// (window as any).__Zone_enable_cross_context_check = true;

/***************************************************************************************************
 * Zone JS is required by default for Angular itself.
 */
import 'zone.js/dist/zone'; // Included with Angular CLI.

/***************************************************************************************************
 * APPLICATION IMPORTS
 */

關於 Angular CLI 7 的 polyfills.ts 檔案

我在 Angular Taiwan 2018 技術大會的【Angular 7 全新功能探索】演講中曾經分享過關於 Polyfills 的改進。但我們先來看一下這個版本預設的 src/polyfills.ts 檔案內容:

/**
 * This file includes polyfills needed by Angular and is loaded before the app.
 * You can add your own extra polyfills to this file.
 *
 * This file is divided into 2 sections:
 *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
 *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
 *      file.
 *
 * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
 * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
 * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
 *
 * Learn more in https://angular.io/guide/browser-support
 */

/***************************************************************************************************
 * BROWSER POLYFILLS
 */

/** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills.
 *  This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot
 */

// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';

/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js';  // Run `npm install --save classlist.js`.

/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';

/**
 * Web Animations `@angular/platform-browser/animations`
 * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
 * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
 */
// import 'web-animations-js';  // Run `npm install --save web-animations-js`.

/**
 * By default, zone.js will patch all possible macroTask and DomEvents
 * user can disable parts of macroTask/DomEvents patch by setting following flags
 * because those flags need to be set before `zone.js` being loaded, and webpack
 * will put import in the top of bundle, so user need to create a separate file
 * in this directory (for example: zone-flags.ts), and put the following flags
 * into that file, and then add the following code before importing zone.js.
 * import './zone-flags.ts';
 *
 * The flags allowed in zone-flags.ts are listed here.
 *
 * The following flags will work for all browsers.
 *
 * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
 * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
 * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
 *
 *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
 *  with the following flag, it will bypass `zone.js` patch for IE/Edge
 *
 *  (window as any).__Zone_enable_cross_context_check = true;
 *
 */

/***************************************************************************************************
 * Zone JS is required by default for Angular itself.
 */
import 'zone.js/dist/zone'; // Included with Angular CLI.

/***************************************************************************************************
 * APPLICATION IMPORTS
 */

大部分內容都是一樣的,除了有些註解說明有調整外,主要是有一行不見了!

/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import 'core-js/es7/reflect';

原本在 Angular CLI 6 之前有一個 reflect-metadata 套件預設被載入,但此套件只有執行在 JIT 模式的時候才需要,一般來說我們都會用 AOT 模式建置專案,因此這個檔案經常需要「手動」註解掉,但很多人不知道這個細節,導致這個檔案一直被額外載入,浪費了 20.7KB 左右的下載流量。

從 Angular CLI 7.0 開始,這項設定已經被拿掉,改由 ng build 的過程透過 webpack 全自動判斷是否加入,相當貼心的設計!

關於 Angular CLI 7.3 的 polyfills.ts 檔案

從 Angular CLI 7.3 開始,其預設的 src/polyfills.ts 檔案內容如下:

/**
 * This file includes polyfills needed by Angular and is loaded before the app.
 * You can add your own extra polyfills to this file.
 *
 * This file is divided into 2 sections:
 *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
 *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
 *      file.
 *
 * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
 * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
 * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
 *
 * Learn more in https://angular.io/guide/browser-support
 */

/***************************************************************************************************
 * BROWSER POLYFILLS
 */

/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js';  // Run `npm install --save classlist.js`.

/**
 * Web Animations `@angular/platform-browser/animations`
 * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
 * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
 */
// import 'web-animations-js';  // Run `npm install --save web-animations-js`.

/**
 * By default, zone.js will patch all possible macroTask and DomEvents
 * user can disable parts of macroTask/DomEvents patch by setting following flags
 * because those flags need to be set before `zone.js` being loaded, and webpack
 * will put import in the top of bundle, so user need to create a separate file
 * in this directory (for example: zone-flags.ts), and put the following flags
 * into that file, and then add the following code before importing zone.js.
 * import './zone-flags.ts';
 *
 * The flags allowed in zone-flags.ts are listed here.
 *
 * The following flags will work for all browsers.
 *
 * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
 * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
 * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
 *
 *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
 *  with the following flag, it will bypass `zone.js` patch for IE/Edge
 *
 *  (window as any).__Zone_enable_cross_context_check = true;
 *
 */

/***************************************************************************************************
 * Zone JS is required by default for Angular itself.
 */
import 'zone.js/dist/zone'; // Included with Angular CLI.

/***************************************************************************************************
 * APPLICATION IMPORTS
 */

這個版本就更激進了,直接拿掉以下內容:

/** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills.
 *  This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot
 */

// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';

/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';

不過各位不用擔心,並不是說從此不支援 IE9 ~ IE11,而是有新的設計出現,叫做 Conditional ES5 Browser Polyfill Loading

條件式 ES5 瀏覽器相容套件載入機制

全新的 Angular CLI 7.3 在 angular.json 設定檔中增加了一條選項設定 "es5BrowserSupport": true

"build": {
  "builder": "@angular-devkit/build-angular:browser",
  "options": {
    "outputPath": "dist/demo1",
    "index": "src/index.html",
    "main": "src/main.ts",
    "polyfills": "src/polyfills.ts",
    "tsConfig": "src/tsconfig.app.json",
    "assets": [
      "src/favicon.ico",
      "src/assets"
    ],
    "styles": [
      "src/styles.css"
    ],
    "scripts": [],
    "es5BrowserSupport": true
  },
  "configurations": {
    "production": {
      "fileReplacements": [
        {
          "replace": "src/environments/environment.ts",
          "with": "src/environments/environment.prod.ts"
        }
      ],
      "optimization": true,
      "outputHashing": "all",
      "sourceMap": false,
      "extractCss": true,
      "namedChunks": false,
      "aot": true,
      "extractLicenses": true,
      "vendorChunk": false,
      "buildOptimizer": true,
      "budgets": [
        {
          "type": "initial",
          "maximumWarning": "2mb",
          "maximumError": "5mb"
        }
      ]
    }
  }
},

如果從舊版升級上來,記得要手動調整 src/polyfills.tsangular.json 的選項設定!(schema.json)

這個 "es5BrowserSupport" 選項如果設定為 true 的話,當你使用 ng buildng build --prod 的時候就會自動自動幫你打包上述這幾個 Polyfills 到一個獨立的 JS 檔案中,檔名為 es2015-polyfills.*****.js

但真正的重點在於這個檔案的載入方式,請看一下 ng build --prod 完後後建置出來的 index.html 檔案內容:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Demo1</title>
    <base href="/" />

    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="icon" type="image/x-icon" href="favicon.ico" />
    <link rel="stylesheet" href="styles.3ff695c00d717f2d2a11.css" />
  </head>

  <body>
    <app-root></app-root>

    <script
      type="text/javascript"
      src="runtime.b57bf819d5bdce77f1c7.js"
    ></script>
    <script
      type="text/javascript"
      src="es2015-polyfills.41976a8133a2445ac0d9.js"
      nomodule
    ></script>
    <script
      type="text/javascript"
      src="polyfills.fb4ac03bdf7e23477d5b.js"
    ></script>
    <script type="text/javascript" src="main.e42ec0deb336589a6946.js"></script>
  </body>
</html>

看到亮點了嗎?

這裡的 es2015-polyfills.*****.js 檔案被載入時,被加上了 nomodule 屬性。這個屬性只有在支援 ES2015 的瀏覽器才認得,這些現代化的瀏覽器只要看見 <script> 標籤使用了 nomodule 屬性,就會自動忽略這個 JS 檔案載入,並不是載入後不執行,而是連載入動作都不會發生,大幅節省頻寬!(約 56K 左右)

但是 IE 或其他舊版瀏覽器,並不認得 nomodule 屬性,所以檔案就還是會載入並執行。如此一來,就徹底解決了跨瀏覽器相容的問題,是不是相當漂亮!

Safari 10.1 不支援 nomodule 的問題

目前已知支援 ES2015 模組化技術的瀏覽器版本如下:

  • Safari 10.1+
  • Chrome 61+
  • Firefox 60+
  • Edge 16+

不過 Safari 10.1 卻不支援 nomodule 屬性,解決方法可以參考 Safari 10.1 nomodule support 這個 Polyfills 來解決。這份 Polyfills 有詳細註解說明方式,請看仔細再加入到你的現有專案中!

相關連結