成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

最近很火的Vue Vine是如何實現一個文件中寫多個組件

開發 前端
Vue Vine是一個vite插件,vite解析每個模塊時都會觸發插件的transform?鉤子函數。在鉤子函數中會去判斷當前文件是否以.vine.ts結尾的,如果不是則return。

前言

在今年的Vue Conf 2024大會上,沈青川大佬(維護Vue/Vite 中文文檔)在會上介紹了他的新項目Vue Vine。Vue Vine提供了全新Vue組件書寫方式,主要的賣點是可以在一個文件里面寫多個vue組件。相信你最近應該看到了不少介紹Vue Vine的文章,這篇文章我們另辟蹊徑來講講Vue Vine是如何實現在一個文件里面寫多個vue組件。

看個demo

我們先來看普通的vue組件,about.vue代碼如下:

<template>
  <h3>i am about page</h3>
</template>

<script lang="ts" setup></script>

我們在瀏覽器中來看看編譯后的js代碼,代碼如下:

const _sfc_main = {};

function _sfc_render(_ctx, _cache) {
  return _openBlock(), _createElementBlock("h3", null, "i am about page");
}

_sfc_main.render = _sfc_render;
export default _sfc_main;

從上面的代碼可以看到普通的vue組件編譯后生成的js文件會export default導出一個_sfc_main組件對象,并且這個組件對象上面有個大名鼎鼎的render函數。父組件只需要import導入子組件里面export default導出的_sfc_main組件對象就可以啦。

搞清楚普通的vue組件編譯后是什么樣的,我們接著來看一個Vue Vine的demo,Vue Vine的組件必須以.vine.ts 結尾,home.vine.ts代碼如下:

async function ChildComp() {
  return vine`
    <h3>我是子組件</h3>
  `;
}

export async function Home() {
  return vine`
  <h3>我是父組件</h3>
    <ChildComp  />
  `;
}

如果你熟悉react,你會發現Vine 組件函數和react比較相似,不同的是return的時候需要在其返回值上顯式使用 vine 標記的模板字符串。

在瀏覽器中來看看home.vine.ts編譯后的代碼,代碼如下:

export const ChildComp = (() => {
  const __vine = _defineComponent({
    name: "ChildComp",
    setup(__props, { expose: __expose }) {
      // ...省略
    },
  });

  function __sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
    return _openBlock(), _createElementBlock("h3", null, "我是子組件");
  }

  __vine.render = __sfc_render;
  return __vine;
})();

export const Home = (() => {
  const __vine = _defineComponent({
    name: "Home",
    setup(__props, { expose: __expose }) {
      // ...省略
    },
  });

  function __sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
    return (
      _openBlock(),
      _createElementBlock(
        _Fragment,
        null,
        [_hoisted_1, _createVNode($setup["ChildComp"])],
        64,
      )
    );
  }

  __vine.render = __sfc_render;
  return __vine;
})();

從上面的代碼可以看到組件ChildComp和Home編譯后是一個立即調用函數,在函數中return了__vine組件對象,并且這個組件對象上面也有render函數。想必細心的你已經發現了在同一個文件里面定義的多個組件經過編譯后,從常規的export default導出一個默認的vue組件對象變成了export導出多個具名的vue組件對象。

接下來我們將通過debug的方式帶你搞清楚Vue Vine是如何實現一個文件內導出多個vue組件對象。

createVinePlugin函數

我們遇見的第一個問題是需要找到從哪里開始著手debug?

來看一下官方文檔是接入vue vine的,如下圖:

圖片圖片

從上圖中可以看到vine是一個vite插件,以插件的形式起作用的。

現在我們找到了一切起源就是這個VineVitePlugin函數,所以我們需要給vite.config.ts文件中的VineVitePlugin函數打個斷點。如下圖:

圖片圖片

接下來我們需要啟動一個debug終端。這里以vscode舉例,打開終端然后點擊終端中的+號旁邊的下拉箭頭,在下拉中點擊Javascript Debug Terminal就可以啟動一個debug終端。

圖片圖片

在debug終端執行yarn dev,在瀏覽器中打開對應的頁面,比如:http://localhost:3333/ 。此時代碼將會停留在我們打的斷點VineVitePlugin函數調用處,讓代碼走進VineVitePlugin函數,發現這個函數實際定義的名字叫createVinePlugin,在我們這個場景中簡化后的createVinePlugin函數代碼如下:

function createVinePlugin() {
  return {
    name: "vue-vine-plugin",
    async resolveId(id) {
      // ...省略
    },
    async load(id) {
      // ...省略
    },
    async transform(code, id) {
      const { fileId, query } = parseQuery(id);
      if (!fileId.endsWith(".vine.ts") || query.type === QUERY_TYPE_STYLE) {
        return;
      }
      return runCompileScript(code, id);
    },
    async handleHotUpdate(ctx) {
      // ...省略
    }
  };
}

從上面的代碼可以看到插件中有不少鉤子函數,vite會在對應的時候調用這些插件的鉤子函數,比如當vite解析每個模塊時就會調用transform等函數。

transform鉤子函數的接收的第一個參數為code,是當前文件的code代碼字符串。第二個參數為id,是當前文件路徑,這個路徑可能帶有query。

在transform鉤子函數中先調用parseQuery函數根據當前文件路徑拿到去除query的文件路徑,以及query對象。

!fileId.endsWith(".vine.ts") 的意思是判斷當前文件是不是.vine.ts結尾的文件,如果不是則不進行任何處理,這也就是為什么文檔中會寫Vue Vine只支持.vine.ts結尾的文件。

query.type === QUERY_TYPE_STYLE的意思是判斷當前文件是不是css文件,因為同一個vue文件會被處理兩次,第一次處理時只會處理template和script這兩個模塊,第二次再去單獨處理style模塊。

在transform鉤子函數的最后就是調用runCompileScript(code, id)函數,并且將其執行結果進行返回。

runCompileScript函數

接著將斷點走進runCompileScript函數,在我們這個場景中簡化后的runCompileScript函數代碼如下:

const runCompileScript = (code, fileId) => {
  const vineFileCtx = compileVineTypeScriptFile(
    code,
    fileId,
    compilerHooks,
    fileCtxMap,
  );

  return {
    code: vineFileCtx.fileMagicCode.toString(),
  };
};

從上面的代碼可以看到首先會以code(當前文件的code代碼字符串)為參數去執行compileVineTypeScriptFile函數,這個函數會返回一個vineFileCtx上下文對象。這個上下文對象的fileMagicCode.toString(),就是前面我們在瀏覽器中看到的最終編譯好的js代碼。

compileVineTypeScriptFile函數

接著將斷點走進compileVineTypeScriptFile函數,在我們這個場景中簡化后的compileVineTypeScriptFile函數代碼如下:

function compileVineTypeScriptFile(
  code: string,
  fileId: string,
  compilerHooks: VineCompilerHooks,
  fileCtxCache?: VineFileCtx,
) {
  const vineFileCtx: VineFileCtx = createVineFileCtx(
    code,
    fileId,
    fileCtxCache,
  );
  const vineCompFnDecls = findVineCompFnDecls(vineFileCtx.root);
  doAnalyzeVine(compilerHooks, vineFileCtx, vineCompFnDecls);
  transformFile(
    vineFileCtx,
    compilerHooks,
    compilerOptions?.inlineTemplate ?? true,
  );

  return vineFileCtx;
}

在執行compileVineTypeScriptFile函數之前,我們在debug終端來看看接收的第一個參數code,如下圖:

圖片圖片

從上圖中可以看到第一個參數code就是我們寫的home.vine.ts文件中的源代碼。

createVineFileCtx函數

接下來看第一個函數調用createVineFileCtx,這個函數返回一個vineFileCtx上下文對象。將斷點走進createVineFileCtx函數,在我們這個場景中簡化后的createVineFileCtx函數代碼如下:

import MagicString from 'magic-string'

function createVineFileCtx(code: string, fileId: string) {
  const root = babelParse(code);
  const vineFileCtx: VineFileCtx = {
    root,
    fileMagicCode: new MagicString(code),
    vineCompFns: [],
    // ...省略
  };
  return vineFileCtx;
}

由于Vue Vine中的組件和react相似是組件函數,組件函數中當然全部都是js代碼。既然是js代碼那么就可以使用babel的parser函數將組件函數的js代碼編譯成AST抽象語法樹,所以第一步就是使用code去調用babel的parser函數生成AST抽象語法樹,然后賦值給root變量。

我們在debug終端來看看得到的AST抽象語法樹是什么樣的,如下圖:

圖片圖片

從上圖中可以看到在body數組中有兩項,分別對應的就是ChildComp組件函數和Home組件函數。

接下來就是return返回一個vineFileCtx上下文對象,對象上面的幾個屬性我們需要講一下。

  • root:由.vine.ts文件轉換后的AST抽象語法樹。
  • vineCompFns:數組中存了文件中定義的多個vue組件,初始化時為空數組。
  • fileMagicCode:是一個由magic-string庫new的一個對象,對象中存了在編譯時生成的js代碼字符串。magic-string是由svelte的作者寫的一個庫,用于處理字符串的JavaScript庫。它可以讓你在字符串中進行插入、刪除、替換等操作,在編譯時就是利用這個庫生成編譯后的js代碼。toString方法返回經過處理后的字符串,前面的runCompileScript函數中就是最終調用vineFileCtx.fileMagicCode.toString()方法返回經過編譯階段處理得到的js代碼。

findVineCompFnDecls函數

我們接著來看compileVineTypeScriptFile函數中的第二個函數調用findVineCompFnDecls:

function compileVineTypeScriptFile(
  code: string,
  fileId: string,
  compilerHooks: VineCompilerHooks,
  fileCtxCache?: VineFileCtx,
) {
  // ...省略
  const vineCompFnDecls = findVineCompFnDecls(vineFileCtx.root);
  // ...省略
}

通過前一步我們拿到了一個vineFileCtx上下文對象,vineFileCtx.root中存的是編譯后的AST抽象語法樹。

所以這一步就是調用findVineCompFnDecls函數從AST抽象語法樹中提取出在.vine.ts文件中定義的多個vue組件對象對應的Node節點。我們在debug終端來看看組件對象對應的Node節點組成的數組vineCompFnDecls,如下圖:

圖片圖片

從上圖中可以看到數組由兩個Node節點組成,分別對應的是ChildComp組件函數和Home組件函數。

doAnalyzeVine函數

我們接著來看compileVineTypeScriptFile函數中的第三個函數調用doAnalyzeVine:

function compileVineTypeScriptFile(
  code: string,
  fileId: string,
  compilerHooks: VineCompilerHooks,
  fileCtxCache?: VineFileCtx,
) {
  // ...省略
  doAnalyzeVine(compilerHooks, vineFileCtx, vineCompFnDecls);
  // ...省略
}

經過上一步的處理我們拿到了兩個組件對象的Node節點,并且將這兩個Node節點存到了vineCompFnDecls數組中。

由于組件對象的Node節點是一個標準的AST抽象語法樹的Node節點,并不能清晰的描述一個vue組件對象。所以接下來就是調用doAnalyzeVine函數遍歷組件對象的Node節點,將其轉換為能夠清晰的描述一個vue組件的對象,將這些vue組件對象組成數組塞到vineFileCtx上下文對象的vineCompFns屬性上。

我們在debug終端來看看經過doAnalyzeVine函數處理后生成的vineFileCtx.vineCompFns屬性是什么樣的,如下圖:

圖片圖片

從上圖中可以看到vineCompFns屬性中存的組件對象已經能夠清晰的描述一個vue組件,上面有一些我們熟悉的屬性props、slots等。

transformFile函數

我們接著來看compileVineTypeScriptFile函數中的第四個函數調用transformFile:

function compileVineTypeScriptFile(
  code: string,
  fileId: string,
  compilerHooks: VineCompilerHooks,
  fileCtxCache?: VineFileCtx,
) {
  // ...省略
  transformFile(
    vineFileCtx,
    compilerHooks,
    compilerOptions?.inlineTemplate ?? true,
  );
  // ...省略
}

經過上一步的處理后在vineFileCtx上下文對象的vineCompFns屬性數組中已經存了一系列能夠清晰描述vue組件的對象。

在前面我們講過了vineFileCtx.vineCompFns數組中存的對象能夠清晰的描述一個vue組件,但是對象中并沒有我們期望的render函數、setup函數等。

所以接下來就需要調用transformFile函數,遍歷上一步拿到的vineFileCtx.vineCompFns數組,將所有的vue組件轉換成對應的立即調用函數。在每個立即調用函數中都會return一個__vine組件對象,并且這個__vine組件對象上都有一個render屬性。

之所以包裝成一個立即調用函數,是因為每個組件都會生成一個名為__vine組件對象,所以才需要立即調用函數將作用域進行隔離。

我們在debug終端來看看經過transformFile函數處理后拿到的js code代碼字符串,如下圖:

圖片圖片

從上圖中可以看到此時的js code代碼字符串已經和我們之前在瀏覽器中看到的編譯后的代碼一模一樣了。

總結

Vue Vine是一個vite插件,vite解析每個模塊時都會觸發插件的transform鉤子函數。在鉤子函數中會去判斷當前文件是否以.vine.ts結尾的,如果不是則return。

在transform鉤子函數中會去調用runCompileScript函數,runCompileScript函數并不是實際干活的地方,而是去調用compileVineTypeScriptFile函數。

在compileVineTypeScriptFile函數中先new一個vineFileCtx上下文對象,對象中的root屬性存了由.vine.ts文件轉換成的AST抽象語法樹。

接著就是調用findVineCompFnDecls函數從AST抽象語法樹中找到組件對象對應的Node節點。

由于Node節點并不能清晰的描述一個vue組件,所以需要調用doAnalyzeVine函數將這些Node節點轉換成能夠清晰描述vue組件的對象。

最后就是遍歷這些vue組件對象將其轉換成立即調用函數。在每個立即調用函數中都會return一個__vine組件對象,并且這個__vine組件對象上都有一個render屬性。

責任編輯:武曉燕 來源: 前端歐陽
相關推薦

2025-04-29 00:00:00

超節點SuperPod大模型

2024-05-22 08:47:41

2021-07-22 09:40:10

GitHub代碼開發者

2025-04-29 08:56:36

2015-12-15 13:43:24

volte

2021-08-11 07:53:22

Git rejecthint

2024-11-15 10:03:43

應用模板Vue

2020-11-12 09:15:16

GitHubPython開發

2023-09-07 23:06:07

2024-04-01 11:52:46

2022-10-24 00:04:57

飛天茅臺架構搶購

2016-09-06 19:45:18

javascriptVue前端

2023-09-05 20:17:18

typescriptPropTypesreact

2023-04-28 09:30:40

vuereact

2018-01-31 15:45:07

前端Vue.js組件

2010-04-19 17:21:36

Oracle寫文件

2022-08-29 07:48:27

文件數據參數類型

2018-09-18 10:11:21

前端vue.jsjavascript

2020-05-06 16:47:59

ServerlessMVC架構

2022-10-17 08:03:47

封裝vue組件
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚州国产 | 亚洲成人免费av | 久草在线| 天天草天天射 | 久综合| 国产精品96久久久久久 | 久草在线视频中文 | 喷水毛片| 丁香久久 | 国产精品片aa在线观看 | 亚洲午夜视频在线观看 | 免费久久99精品国产婷婷六月 | 91精品国产高清久久久久久久久 | av中文天堂 | 亚洲美女av网站 | 一级做a爰片性色毛片16 | 日韩欧美一区二区三区免费观看 | 久久久久久久综合 | 亚洲精品乱码久久久久久按摩观 | 精品久 | 国产9999精品 | 中文字幕乱码亚洲精品一区 | 欧美精品一区二区三区在线 | 高清久久 | 免费大黄视频 | 久久久久久影院 | 亚洲高清在线 | 91免费在线视频 | 欧美h视频 | 龙珠z国语版在线观看 | 男女羞羞在线观看 | 久久久高清| 99re视频在线观看 | 国产精品一区二区在线播放 | 日本在线免费视频 | 日本偷偷操| 成人在线一区二区 | 狠狠狠 | 精品福利一区 | 91伊人网 | 国产日本精品视频 |