<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端應該掌握的編譯基礎(基于 babel)

          共 22648字,需瀏覽 46分鐘

           ·

          2021-07-10 14:12

          • 作者:陳大魚頭
          • github: KRISACHAN

          開發(fā)息息相關

          雖然 Babel 團隊在各種哭窮,但是 Babel 始終是我們前端在開發(fā)中不可或缺的重要工具。 雖然我們只是 API 調用工,但是多了解一些總是會有好處的嘛 ??????

          什么是編譯器?

          編譯器(compiler)是一種計算機程序,它會將某種編程語言寫成的源代碼(原始語言)轉換成另一種編程語言(目標語言)。

          源代碼(source code)→ 預處理器(preprocessor)→ 編譯器(compiler)→ 匯編程序(assembler)→ 目標代碼(object code)→ 鏈接器(linker)→ 可執(zhí)行文件(executables),最后打包好的文件就可以給電腦去判讀運行了。

          什么是解釋器?

          解釋器(英語:interpreter),是一種計算機程序,能夠把解釋型語言解釋執(zhí)行。解釋器就像一位“中間人”。解釋器邊解釋邊執(zhí)行,因此依賴于解釋器的程序運行速度比較緩慢。解釋器的好處是它不需要重新編譯整個程序,從而減輕了每次程序更新后編譯的負擔。相對的編譯器一次性將所有源代碼編譯成二進制文件,執(zhí)行時無需依賴編譯器或其他額外的程序。

          跟編譯器的區(qū)別就是一個是邊編譯邊執(zhí)行,一個是編譯完才執(zhí)行。

          高級語言編譯器步驟

          1. 輸入源程序字符流
          2. 詞法分析
          3. 語法分析
          4. 語義分析
          5. 中間代碼生成
          6. 機器無關代碼優(yōu)化
          7. 代碼生成
          8. 機器相關代碼優(yōu)化
          9. 目標代碼生成

          V8 編譯 JS 代碼的過程

          1. 生成抽象語法樹(AST)和執(zhí)行上下文
          2. 第一階段是分詞(tokenize),又稱為詞法分析
          3. 第二階段是解析(parse),又稱為語法分析
          4. 生成字節(jié)碼
          5. 字節(jié)碼就是介于 AST 和機器碼之間的一種代碼。但是與特定類型的機器碼無關,字節(jié)碼需要通過解釋器將其轉換為機器碼后才能執(zhí)行。
          6. 執(zhí)行代碼

          JS 執(zhí)行代碼的過程

          • 執(zhí)行全局代碼時,創(chuàng)建全局上下文
          • 調用函數時,創(chuàng)建函數上下文
          • 使用 eval 函數時,創(chuàng)建 eval 上下文
          • 執(zhí)行局部代碼時,創(chuàng)建局部上下文

          關于 Babel

          Babel ,又名 Babel.js。 是一個用于 web 開發(fā),且自由開源的 JavaScript 編譯器、轉譯器。

          Babel 的編譯流程:

          圖片來源:透過製作 Babel-plugin 初訪 AST

          Parse

          Babel 的第一步就是將源碼轉換為抽象語法樹(AST)

          const babel = require('@babel/core');
          const { parseAsync } = babel;
          const parseCode = async (code = '', options = {}) => {
            const res = await parseAsync(code, options);
          };
          parseCode(`
            const a = 1;
          `
          )

          可通過 https://astexplorer.net/ 在線查看具體結果

          這一步會將收集到的的代碼,通過 詞法分析(Lexical analysis) 跟 語法分析(Parsing) 兩個階段將代碼轉換成 AST

          詞法分析(Lexical analysis)

          詞法分析會將代碼轉為 token ,可以理解為是對每個不可分割單詞元的描述,例如 const 就會轉換成下面這樣:

          Token {
              type: 
                  TokenType {
                  label: 'const',
                  keyword: 'const',
                  beforeExpr: false,
                  startsExpr: false,
                  rightAssociative: false,
                  isLoop: false,
                  isAssign: false,
                  prefix: false,
                  postfix: false,
                  binop: null,
                  updateContext: null
              },
              value: 'const',
              start: 5,
              end: 10,
              loc: 
              SourceLocation {
                  start: Position { line: 2, column: 4 },
                  end: Position { line: 2, column: 9 },
                  filename: undefined,
                  identifierName: undefined
              }
          }

          type 就是 對 token 的描述,如果想要查看 bebal 生成的 token,我們可以在 options 里寫入:

          parserOpts: {
            tokenstrue
          }

          關于 @babel/parser  更多配置,可查看:https://babeljs.io/docs/en/babel-parser#options

          語法分析(Parsing)

          語法分析則是將上述的 token 轉換成對應的 ast 結構

          所以我們就可以看到這樣的一段樹狀結構(過濾部分信息)

          {
              "type""VariableDeclaration",
              "start"0,
              "end"14,
              "loc": {
                  "start": {
                      "line"1,
                      "column"0
                  },
                  "end": {
                      "line"1,
                      "column"14
                  }
              },
              "declarations": [
                  {
                      "type""VariableDeclarator",
                      "start"6,
                      "end"13,
                      "loc": {
                          "start": {
                              "line"1,
                              "column"6
                          },
                          "end": {
                              "line"1,
                              "column"13
                          }
                      },
                      "id": {
                          "type""Identifier",
                          "start"6,
                          "end"9,
                          "loc": {
                              "start": {
                                  "line"1,
                                  "column"6
                              },
                              "end": {
                                  "line"1,
                                  "column"9
                              },
                              "identifierName""abc"
                          },
                          "name""abc"
                      },
                      "init": {
                          "type""NumericLiteral",
                          "start"12,
                          "end"13,
                          "loc": {
                              "start": {
                                  "line"1,
                                  "column"12
                              },
                              "end": {
                                  "line"1,
                                  "column"13
                              }
                          },
                          "extra": {
                              "rawValue"1,
                              "raw""1"
                          },
                          "value"1
                      }
                  }
              ],
              "kind""const"
          }

          這樣與 type 同級的結構就叫  節(jié)點(Node) , loc ,start ,end 則是位置信息

          Transform

          Babel 的第二步就是遍歷 AST,并調用 transform 以訪問者模式進行修改

          export default function (babel{
            const { types: t } = babel;
            
            return {
              name"ast-transform"// not required
              visitor: {
                Identifier(path) {
                  path.node.name = path.node.name.split('').reverse().join('');
                }
              }
            };
          }

          通過執(zhí)行上述的 transform ,我們可以有:

          上述功能也可通過 https://astexplorer.net/ 在線查看

          Generate

          Babel 的第三步就是把轉換后的 AST 打印成目標代碼,并生成 sourcemap

          開發(fā)一個 babel 插件

          前置知識 - 訪問者模式

          訪問者模式: 在訪問者模式(Visitor Pattern)中,我們使用了一個訪問者類,它改變了元素類的執(zhí)行算法。通過這種方式,元素的執(zhí)行算法可以隨著訪問者改變而改變。這種類型的設計模式屬于行為型模式。根據模式,元素對象已接受訪問者對象,這樣訪問者對象就可以處理元素對象上的操作。

          知道你們不想看文字描述,所以直接上代碼!

          class 漢堡包 {
              accept(fatBoyVisitor) {
                  fatBoyVisitor.visit(this);
              }
          };

          class 薯條 {
              accept(fatBoyVisitor) {
                  fatBoyVisitor.visit(this);
              }
          };

          class 炸雞 {
              accept(fatBoyVisitor) {
                  fatBoyVisitor.visit(this);
              }
          };

          class FatBoy {
              constructor(foods) {
                  this.foods = foods;
              }

              accept(fatBoyFoodVisitor) {
                  this.foods.forEach(food => {
                      food.accept(fatBoyFoodVisitor);
                  });
              }
          };

          class FatBoyFoodVisitor {
              visit(food) {
                  console.log(`肥宅吃了${food.constructor.name}`);
              }
          };

          const fatBoy = new FatBoy([new 漢堡包(), new 薯條(), new 炸雞()]);
          fatBoy.accept(new FatBoyFoodVisitor());

          最終輸出結果是:

          肥宅吃了漢堡包
          肥宅吃了薯條
          肥宅吃了炸雞

          babel-plugin-transform-object-assign 源碼

          import { declare } from "@babel/helper-plugin-utils";

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

            return {
              name"transform-object-assign",

              visitor: {
                CallExpressionfunction(path, file{
                  if (path.get("callee").matchesPattern("Object.assign")) {
                    path.node.callee = file.addHelper("extends");
                  }
                },
              },
            };
          });

          上面的就是 babel-plugin-transform-object-assign 的源碼。

          • declare:是一個用于簡化創(chuàng)建 transformer 的工具函數
          • assertVersion:檢查當前 babel 的大版本
          • name:當前插件的名字
          • visitor:對外提供修改內容的訪問者
          • CallExpression:函數調用的 type,每一句代碼都會生成對應的 type,例如最上面的函數名 abc 則對應的是一個 Identifier 類型,如果需要修改某一個 type 的代碼,則在里面創(chuàng)建對應的 type 訪問者進行修改即可。

          具體生成的代碼如下:

          // input
          const a = Object.assign({ a1 }, { b2 });

          // output
          "use strict";

          function _extends({ _extends = Object.assign || function (targetfor (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(thisarguments); }

          const a = _extends({
            a1
          }, {
            b2
          });

          Babel 插件實戰(zhàn) - 清除 console 源碼

          先上代碼:

          const babel = require('@babel/core');
          const get = require('lodash/get');
          const eq = require('lodash/eq');

          const { transformAsync } = babel;

          const removeConsole = rootPath => ({
              visitor: {
                  ExpressionStatementpath => {
                      const name = get(path, 'node.expression.callee.object.name');
                      const CONSOLE_PREFIX = 'console';
                      if (!eq(name, CONSOLE_PREFIX)) {
                          return;
                      };
                      path.remove();
                  },
              }
          });

          const transformCode = async (code = '') => {
              const res = await transformAsync(code, {
                  plugins: [
                      removeConsole,
                  ],
              });

              console.log(res.code);
          };

          transformCode(`
              const a = 10;
              console.group('嚶嚶嚶');
              console.log(a);
              console.groupEnd();
          `
          );

          輸出結果:

          const a = 10;

          上面的功能就是我們在聲明語句類型 ExpressionStatement 中實現的。

          node.expression 對應的是當前類型里的子表達式,在這個場景里,它的 type === 'CallExpression'。

          callee 對應的就是一個調用函數類型,在這個場景里,它的 type === 'MemberExpression'。

          object 對應的就是當前調用函數的前置對象,它的 type === 'Identifier',name 則是 console。

          所以我們的實現就很簡單了,只要 name === 'console' ,我們就可以通過內部暴露的 remove 方法直接刪除當前代碼。

          Babel 插件實戰(zhàn) - 新的語法

          總所周知,JS 不能這么寫

          # python
          arr = [123]
          print(arr[-1]) # 3
          print(arr[len(arr) - 1]) # 3

          但是我們可以用魔法打敗魔法

          作為一個兇起來連自己都可以編譯的語言,這有多難呢~

          具體實現如下:

          const babel = require('@babel/core');
          const get = require('lodash/get');
          const tailIndex = rootPath => ({
              visitor: {
                  MemberExpressionpath => {
                      const {
                          object: obj,
                          property: prop,
                      } = get(path, 'node', {});

                      const isNotMatch = codeNotMatch(obj, prop);
                      if (isNotMatch) {
                          return;
                      };

                      const {
                          index,
                          operator,
                          name,
                      } = createMatchedKeys(obj, prop);

                      if (!index || !name) {
                          return;
                      };

                      const res = genHeadIndex(index, name, operator);

                      path.replaceWithSourceString(res);
                  },
              },
          });

          MemberExpression 就是當前要處理的語句類型。

          codeNotMatch 是我們自己實現的函數,用于判斷 node.objectnode.property 是否合法,具體實現如下:

          const t = require('@babel/types');

          const codeNotMatch = (obj, prop) => {
              const objIsIdentifier = t.isIdentifier(obj);
              const propIsUnaryExpression = t.isUnaryExpression(prop);

              const objNotMatch = !obj || !objIsIdentifier;
              const propNotMatch = !prop || !propIsUnaryExpression;

              return objNotMatch || propNotMatch;
          };

          這里的 require('@babel/types') 是 babel 的一個工具包,這里面我們運用了它的語句判斷能力。這種 isXXX 的大體實現如下:

          function isIdentifier(node, opts{
            if (!node) return false;
            const nodeType = node.type;
            if (nodeType === 具體類型) {
              if (typeof opts === "undefined") {
                return true;
              } else {
                return shallowEqual(node, opts);
              }
            }
            return false;
          }

          上面的 shallowEqual 實現如下:

          function shallowEqual(actual, expected{
            const keys = Object.keys(expected);

            for (const key of keys) {
              if (actual[key] !== expected[key]) {
                return false;
              }
            }

            return true;
          }

          createMatchedKeys 用于創(chuàng)建最終匹配的字符,即需要將 -1 改為 .length - 1 的形式,所以具體實現如下:

          const createMatchedKeys = (obj, prop) => {
              const {
                  prefix,
                  operator,
                  argument: arg
              } = prop;

              let index;
              let name;

              const propIsArrayExpression = !!prefix && !!operator && !!arg;
              const argIsNumericLiteral = t.isNumericLiteral(arg);

              if (propIsArrayExpression && argIsNumericLiteral) {
                  index = get(arg, 'value');
                  name = get(obj, 'name');
              };

              return {
                  index,
                  operator,
                  name,
              };
          };

          這里面一路判斷,匹配即可。

          所以當我們拿到下標 ,操作符 跟 數組名 之后,直接組合成最終要生成的代碼即可,即有:

          const genHeadIndex = (index, name, operator) => `${name}[${name}.length ${operator} ${index}]`;

          最后我們直接替換源碼即可,怎么替換呢,babel 有通過訪問者模式返回 replaceWithSourceString 方法進行硬編碼替換。。。

          替換的邏輯就是先通過  babel.parse  將要替換的代碼生成 ast,然后從 loc 到具體的 node 進行替換。

          一個新語法,就這么完成啦~

          參考資料

          1. 透過製作 Babel-plugin 初訪 AST
          2. 詞法分析(Lexical analysis)
          3. 語法分析(Parsing)
          4. https://babeljs.io/docs/en/babel-parser#options
          5. https://astexplorer.net/
          6. https://github.com/babel/babel
          7. https://github.com/babel/minify
          8. 『1W7字中高級前端面試必知必會』終極版
          9. Babel 插件手冊
          瀏覽 52
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲欧洲高清无码在线视频 | 色综合999 | 免费看一级黄色录像 | 欧美乱码人妻蜜桃视频 | 免费在线观看内射 |