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

          不到100行!站在潮流前沿,快速實(shí)現(xiàn)一個(gè)簡(jiǎn)易版 vite

          共 27952字,需瀏覽 56分鐘

           ·

          2021-06-03 15:56

          張宇航,微醫(yī)前端技術(shù)部醫(yī)保支撐組,一個(gè)不文藝的處女座程序員。

          目錄

          標(biāo)題模塊內(nèi)容
          90 行代碼的webpack,你確定不學(xué)嗎?webpack簡(jiǎn)易實(shí)現(xiàn)

          寫在最前面

          本文最終實(shí)現(xiàn)的簡(jiǎn)易版 vite 可通過(guò)github 地址(https://github.com/levelyu/simple-vite)下載,代碼實(shí)現(xiàn)地較為簡(jiǎn)單(不到 100 行)可運(yùn)行后再看此文,閱讀效果可能更佳~

          要解決的問(wèn)題

          首先我們參照官方文檔啟動(dòng)一個(gè) vue 模板的 vite-demo 項(xiàng)目

          yarn create @vitejs/app vite-demo --template vue
          cd vite-demo
          yarn
          yarn dev

          然后打開(kāi)瀏覽器查看網(wǎng)絡(luò)請(qǐng)求,我們不難發(fā)現(xiàn) vite 正如官方文檔所述利用瀏覽器支持原生 ES 模塊的特性,讓瀏覽器解析了 import 語(yǔ)句并發(fā)出網(wǎng)絡(luò)請(qǐng)求避免了本地編譯打包的過(guò)程,因此啟動(dòng)速度非常之快。


          常言道“先知其然,然后知其所以然”,在打開(kāi)了 vite 模板工程的源文件再對(duì)照上述的網(wǎng)絡(luò)請(qǐng)求后,有的同學(xué)可能有以下幾個(gè)疑問(wèn):

          1:main.js 返回的內(nèi)容 其中 impor 語(yǔ)句為什么被改寫成了 import { createApp } from '/node_modules/.vite/vue.js?

          2:查看本地文件也會(huì)發(fā)現(xiàn) node_modules 文件夾下為什么會(huì)多出了一個(gè).vit 文件夾?


          3:.vue 文件的請(qǐng)求是怎么處理并返回能正常運(yùn)行的 js 呢?

          4: 為什么會(huì)多出兩個(gè) js 文件請(qǐng)求 /@vite/client 和 /node_modules/vite/dist/client/env.js 以及一個(gè) websocket 連接?

          對(duì)于問(wèn)題 4,實(shí)際上是 vite devServer 的熱更新相關(guān)的功能,不在本文的研究重點(diǎn),因此本文的目的就是帶著問(wèn)題 1,2,3,參照源碼實(shí)現(xiàn)一個(gè)沒(méi)有熱更新沒(méi)有打包功能的極簡(jiǎn)易的 vite。(注:本文參考的 vite 源碼版本號(hào)為 2.3.0)

          準(zhǔn)備工作

          工欲善其事,必先利其器。既然是從源碼分析問(wèn)題,那就先準(zhǔn)備好調(diào)試工作。參照官方文檔:

          首先克隆 vite 倉(cāng)庫(kù)并創(chuàng)建一個(gè)軟鏈接

          git clone [email protected]:vitejs/vite.git

          cd vite && yarn

          cd packages/vite && yarn build && yarn link

          yarn dev

          進(jìn)入之前初始化好的 vite-demo 項(xiàng)目并鏈接到本地 vite 倉(cāng)庫(kù)地址

          cd vite-demo
          yarn link vite

          從 vite bin 目錄下 vite.js 文件不難發(fā)現(xiàn) vite 命令對(duì)應(yīng)的入口文件在 node_modules/vite/dist/node/cli.js

          微信截圖_20210511230131.png

          因此我們可以在 vite-demo 的 package.json 文件中加入以下腳本命令:

           "debug""node --inspect-brk node_modules/vite/dist/node/cli.js"

          并運(yùn)行命令 yarn debug  后打開(kāi)瀏覽器控制臺(tái)即可看到 node 的圖標(biāo),點(diǎn)擊后,我們就可以開(kāi)始進(jìn)行源碼調(diào)試的工作了:

          微信圖片_20210511231000.png

          源碼分析

          注意:為方便理解,本文對(duì)應(yīng)的源碼均為截取后的偽代碼

          server 的創(chuàng)建過(guò)程

          // src/node/cli.ts
          cli
            .command('[root]')
            .alias('serve')
            .action(async () => {
              const { createServer } = await import('./server')
                const server = await createServer({
                  // ...
                })
                await server.listen()
            })

          不難看出上述入口文件代碼是從 src/node/server/index.ts 引入了一個(gè) createServer 方法并調(diào)用返回了一個(gè) server 對(duì)象,緊接著調(diào)用了 server 的 listen 方法。ok,那就讓我們看看這個(gè) createServer 方法內(nèi)部做了哪些事情:

          // src/node/server/index.ts

          // ....
          import connect from 'connect';

          import { transformMiddleware } from './middlewares/transform'
          //...
          export async function createServer({
              // ...
              const middlewares = connect();
              const httpServer = await resolveHttpServer({}, middlewares)
              // 實(shí)際 server 的配置會(huì)讀取 vite.config.js 以及各種插件中的配置 本文力求通俗簡(jiǎn)易就不再詳細(xì)分析贅述...
              const server = {
                  httpServer,
                  listen() {
                      return startServer(server);
                  },
              };
              // ...
              middlewares.use(transformMiddleware(server))
              // ...
              await runOptimize();
              return server;
          }

          async function startServer(server{
              // ...
              const httpServer = server.httpServer;
              // ...
              const port = 3000
              const hostname = '127.0.0.1';
              return new Promise((resolve, reject) =>{
                  httpServer.listen(port, hostname, () => {
                      resolve(server);
                  });
              });
          };

          // src/node/server/http.ts

          export async function resolveHttpServer(serveroptions, app{
              return require('http').createServer(app)
          }

          通過(guò)上述偽代碼可以發(fā)現(xiàn),vite2 最終是調(diào)用了 http.ts 中的 resolveHttpServer 方法,通過(guò) node 原生的 http 模塊創(chuàng)建的 server。同時(shí)在 createServer 方法內(nèi)部,使用了 connect 框架作為中間件。

          依賴預(yù)構(gòu)建

          細(xì)心的同學(xué)不難發(fā)現(xiàn)在上述 createServer 方法的偽代碼中有個(gè) runOptimize 方法,下面讓我們看看這個(gè)函數(shù)里具體做了哪些事情:

          // src/node/server/index.ts

          const runOptimize = async () => {
              if (config.cacheDir) {
                server._isRunningOptimizer = true
                try {
                  server._optimizeDepsMetadata = await optimizeDeps(config)
                } finally {
                  server._isRunningOptimizer = false
                }
                server._registerMissingImport = createMissingImporterRegisterFn(server)
              }
          }

          實(shí)際該方法最重要的是調(diào)用了依賴預(yù)構(gòu)建的核心方法:optimizeDeps, 其定義在 src/node/optimizer/index.ts 中,并且在 server 啟動(dòng)前就已調(diào)用。

          那么何為依賴預(yù)構(gòu)建呢,vite 不是 No Bundle 嗎?對(duì)此,官方文檔做出了詳細(xì)解釋:點(diǎn)此查看原因,簡(jiǎn)而言之其目的有二:

          1. 兼容 CommonJS 和 AMD 模塊的依賴
          2. 減少模塊間依賴引用導(dǎo)致過(guò)多的請(qǐng)求次數(shù)

          再結(jié)合以下偽代碼分析:


          // src/node/optimizer/index.ts

          import { build } from 'esbuild';
          import { scanImports } from './scan';

          export async function optimizeDeps({
              // cacheDir 的定義在 src/node/config.ts
              const cacheDir =  `node_modules/.vite`;
              // optimizeDeps  函數(shù)依賴預(yù)構(gòu)建的重要函數(shù) 
              const dataPath = path.join(cacheDir, '_metadata.json'); 
              if (fs.existsSync(cacheDir)) {
                  emptyDir(cacheDir)
                } else {
                  // 創(chuàng)建 cacheDir 目錄
                  fs.mkdirSync(cacheDir, { recursive: true })
                }
              ;({ deps, missing } = await scanImports(config))
              // eg: deps = {vue: "C:/code/sourcecode/vite-demo/node_modules/vue/dist/vue.runtime.esm-bundler.js"}
              const result = await build({
                  entryPoints: Object.keys(flatIdDeps),
                  outdir: cacheDir,
              })
              writeFile(dataPath, JSON.stringify(data, null2))
          }

          至此,我們基本可以得到開(kāi)篇問(wèn)題 2(為什么 node_modules 下多出了一個(gè).vite 文件夾)的答案了。那么,可能又有同學(xué)有以下兩個(gè)疑問(wèn):

          1.vite 是如何分析找到哪些模塊是需要預(yù)構(gòu)建的呢?

          2.vite 是如何完成預(yù)構(gòu)建的同時(shí)保證構(gòu)建速度的呢?

          帶著這兩個(gè)問(wèn)題,繼續(xù)一路 debug 下去,不難發(fā)現(xiàn)答案就是esbuild,關(guān)于 esbuild 是什么這里就不再贅述了,這里就貼一張官方文檔的對(duì)比圖感受下,總之就是一個(gè)字:快?。。?/p>


          繼續(xù)回到剛才 src/node/optimizer/index.ts 中的偽代碼,實(shí)際上 scanImports 函數(shù)其實(shí)就是完成對(duì) import 語(yǔ)句的掃描,并返回了需要構(gòu)建的依賴 deps, 下圖則說(shuō)明了這個(gè) deps 其實(shí)就是 main.js 中唯一的依賴 vue 對(duì)應(yīng)的路徑:


          那么這個(gè) scanImports 是如何找到我們的唯一依賴 vue 呢:進(jìn)入 scanImports 函數(shù)有以下偽代碼:

          // src/node/optimizer/scan.ts
          import { Loader, Plugin, build, transform } from 'esbuild'

          export async function scanImports({
              cosnt entry = await globEntries('**/*.html', config)
              const plugin = esbuildScanPlugin()
              build({
                  write: false,
                  entryPoints: [entry],
                  bundle: true,
                  format: 'esm',
                 // ...
                })
              
          }
          function esbuildScanPlugin({
              return {
                  name: 'vite:dep-scan',
                  setup(build) {
                    build.onLoad(
                          { filter: htmlTypesRE, namespace'html' },
                          // 讀取 html 內(nèi)容  正則匹配到 <script> 內(nèi)的內(nèi)容 
                      return {
                              loader: 'js',
                              content: 'import "/src/main.js" export default {}"
                          }
                      )
                     build.onLoad({ filter: JS_TYPES_RE }, ({ path: id } => {
                          // eg: id = '
          C:\\code\\sourcecode\\vite-demo\\src\\main.js'
                  
                          return {
                              loader: '
          js',
                              content: '
          ' // eg: 讀取 main.js 內(nèi)容 內(nèi)有 import vue from 'vue'
                          }
                      }) 
                      build.onResolve( {filter: /^[\w@][^:]/},async ({ path: id, importer }) => {
                          // eg: id = "vue" 
                          // eg: importer = "C:\\code\\sourcecode\\vite-demo\\src\\main.js"
                          // 加入依賴
                          depImports[id] = await resolve(id, importer); // eg: 返回"C:/code/sourcecode/vite-demo/node_modules/vue/dist/vue.runtime.esm-bundler.js"
                          return {
                              path: '
          C:/code/sourcecode/vite-demo/node_modules/vue/dist/vue.runtime.esm-bundler.js'
                          }
                      })
                  }
              };
          }

          對(duì)照代碼注釋并結(jié)合以下流程圖


          至此我們可以得出結(jié)論:vite 主要是通過(guò)一個(gè)內(nèi)置的 vite:dep-scan esbuild 插件分析依賴項(xiàng)并將其寫入一個(gè)_metadata.json 文件中,并通過(guò) esbuild 將依賴的模塊(如將 vue.runtime.esm-bundler.js)打包至.vite 文件中(產(chǎn)生一個(gè) vue.js 和 vue.js.map 文件),這也就是開(kāi)篇問(wèn)題 2(本地多了一個(gè).vite 文件夾)的答案。

          transformMiddleware

          在上節(jié)中我們分析了 vite 預(yù)構(gòu)建的過(guò)程,最終其將打包后的文件寫入在.vite 文件夾內(nèi)解決了開(kāi)篇提出的問(wèn)題 2。那么讓我們繼續(xù)回到開(kāi)篇提到的問(wèn)題 1:main.js 中的 import vue from 'vue'是如何改寫成 import vue from '/node_modules/.vite/vue.js'的:

          還記得我們?cè)?src/node/optimizer/index.ts 中的一段代碼嗎:

          //src/node/optimizer/index.ts

          import { transformMiddleware } from './middlewares/transform'
          const middlewares = connect();
          middlewares.use(transformMiddleware);

          實(shí)際上 transformMiddleware 正是 vite devServer 核心的中間件,簡(jiǎn)而言之它負(fù)責(zé)攔截處理各種文件的請(qǐng)求并將其內(nèi)容轉(zhuǎn)換成瀏覽器能識(shí)別的正確代碼,下面讓我們看下 transformMiddleware 做了哪些事情:

          // src/node/server/middlewares/transform.ts

          import { transformRequest } from '../transformRequest'

          export function transformMiddleware(server{
              // ....
              if (isJSRequest(url) ) {
                  const result = await transformRequest(url)
                  return send(
                      req,
                      res,
                      result.code,
                      type,
                      result.etag,
                      // allow browser to cache npm deps!
                      isDep ? 'max-age=31536000,immutable' : 'no-cache',
                      result.map
                   )
              }
          }

          可以看得出它對(duì) js 的請(qǐng)求,是通過(guò) vite 中間件的一個(gè)核心方法 transformRequest 處理的,并將結(jié)果發(fā)送至瀏覽器

          // src/node/server/transformRequest.ts

          export async function transformRequest(url{
              code = await fs.readFile(url, 'utf-8')
              const transformResult = await pluginContainer.transform(code)
              code = transformResult.code!
              return {
                  code
              }
          }

          transformRequest 中代碼的核心處理是 pluginContainer.transform 方法,而 transform 方法會(huì)遍歷 vite 內(nèi)置的所有插件以及用戶配置的插件處理轉(zhuǎn)換 code,其中內(nèi)置的一個(gè)核心的插件為 import-analysis

          // src/node/plugins/importAnalysis.ts

          import { parse as parseImports } from 'es-module-lexer'
          export function importAnalysisPlugin({
              return {
                  name: 'vite:import-analysis',
                  async transform(source, importer, ssr) {
                      const specifier = parseImports(source); // specifier = vue
                      await normalizeUrl(specifier);
                      const normalizeUrl  = async (specifier)=> {
                          const resolved = await this.resolve(specifier)
                          // eg: resolved = {id: "C:/code/sourcecode/vite-demo/node_modules/.vite/vue.js?v=82c5917e"}
                      }
                  }
              }
          }

          對(duì) importAnalysisPlugin 函數(shù)內(nèi)部做的事情可簡(jiǎn)單歸納如下:

          1. 使用一個(gè)詞法分析利器 es-module-lexer 對(duì)源代碼進(jìn)行詞法分析,并最終能拿到 main.js 中的語(yǔ)句 import vue from 'vue'中的 vue
          2. 調(diào)用 reslove 方法最終其會(huì)先后調(diào)用 vite 內(nèi)置的兩個(gè) plugin:vite:pre-alias 及 vite:resolve
          3. 最終在 vite:resolve 內(nèi)的鉤子函數(shù) resolveId 內(nèi)部調(diào)用 tryOptimizedResolve
          4. tryOptimizedResolve 最終會(huì)通過(guò)讀取依賴構(gòu)建階段的緩存的依賴映射對(duì)象,拿到 vue 對(duì)應(yīng)的路徑

          小結(jié)一下

          至此我們已經(jīng)通過(guò)源碼分析解決了開(kāi)篇所提到的問(wèn)題 1 和問(wèn)題 2,簡(jiǎn)單地總結(jié)下就是:

          1. vite 在啟動(dòng)服務(wù)器之前通過(guò) esbuild 及內(nèi)置的 vite:dep-scan esbuild 插件將 main.js 中的依賴 vue 預(yù)構(gòu)建打包至 /node_modules/.vite/下
          2. 核心中間件 transformMiddleware 攔截 main.js 請(qǐng)求,讀取其內(nèi)容,在 import-analysis 的插件內(nèi)部通過(guò) es-module-lexer 分析 import 語(yǔ)句讀取到依賴 vue,再通過(guò)一系列的內(nèi)置 plugin 最終將 import 語(yǔ)句中的 vue 轉(zhuǎn)換成 vue 對(duì)應(yīng)預(yù)構(gòu)建的真實(shí)路徑

          對(duì)于問(wèn)題 3vite 是如何轉(zhuǎn)換.vue 文件的請(qǐng)求,vite 同樣是通過(guò) transformMiddleware 攔截.vue 請(qǐng)求并調(diào)用外部插件@vitejs/plugin-vue 處理轉(zhuǎn)換的,感興趣的同學(xué)可以查看 plugin-vue 的源碼, 本文就不再贅述了而是通過(guò)下文的實(shí)踐章節(jié)以代碼來(lái)解釋。

          實(shí)踐一下

          ok,在一頓分析之后我們終于來(lái)到了 coding 的環(huán)節(jié)了,廢話不多說(shuō),我們先創(chuàng)建一個(gè) server

          // simple-vite/vit/index.js
          const http = require('http');
          const connect = require('connect');
          const middlewares = connect();
          const createServer = async ()=> {
              // 依賴預(yù)構(gòu)建
              await optimizeDeps();
              http.createServer(middlewares).listen(3000, () => {
                  console.log('simple-vite-dev-server start at localhost: 3000!');
              });
          };
          // 用于返回 html 的中間件
          middlewares.use(indexHtmlMiddleware);
          // 處理 js 和 vue 請(qǐng)求的中間件
          middlewares.use(transformMiddleware);
          createServer();

          接著我們寫下依賴預(yù)構(gòu)建的函數(shù) optimizeDeps

          // simple-vite/vit/index.js
          const fs = require('fs');
          const path = require('path');
          const esbuild = require('esbuild');
          // 因?yàn)槲覀兊?nbsp;vite 目錄和測(cè)試的 src 目錄在同一層,因此加了個(gè)../
          const cacheDir = path.join(__dirname, '../''node_modules/.vite');
          const optimizeDeps = async () => {
              if (fs.existsSync(cacheDir)) return false;
              fs.mkdirSync(cacheDir, { recursivetrue });
              // 在分析依賴的時(shí)候 這里為簡(jiǎn)單實(shí)現(xiàn)就沒(méi)按照源碼使用 esbuild 插件去分析
              // 而是直接簡(jiǎn)單粗暴的讀取了上級(jí) package.json 的 dependencies 字段
              const deps = Object.keys(require('../package.json').dependencies);
              // 關(guān)于 esbuild 的參數(shù)可參考官方文檔
              const result = await esbuild.build({
                  entryPoints: deps,
                  bundletrue,
                  format'esm',
                  logLevel'error',
                  splittingtrue,
                  sourcemaptrue,
                  outdir: cacheDir,
                  treeShaking'ignore-annotations',
                  metafiletrue,
                  define: {'process.env.NODE_ENV'"\"development\""}
                });
              const outputs = Object.keys(result.metafile.outputs);
              const data = {};
              deps.forEach((dep) => {
                  data[dep] = '/' + outputs.find(output => output.endsWith(`${dep}.js`));
              });
              const dataPath = path.join(cacheDir, '_metadata.json');
              fs.writeFileSync(dataPath, JSON.stringify(data, null2));
          };

          至此依賴預(yù)構(gòu)建的函數(shù)已寫完,當(dāng)我們運(yùn)行命令后會(huì)發(fā)現(xiàn)有打包后的依賴包及依賴映射的 json 文件,而且整個(gè)過(guò)程非???/p>

          微信圖片_20210513214012.png

          再然后我們來(lái)實(shí)現(xiàn)下中間件函數(shù),indexHtmlMiddleware 沒(méi)什么好說(shuō)的就是讀取返回根目錄的 index.html

          // simple-vite/vit/index.js
          const indexHtmlMiddleware = (req, res, next) => {
              if (req.url === '/') {
                  const htmlPath = path.join(__dirname, '../index.html');
                  const htmlContent = fs.readFileSync(htmlPath, 'utf-8');
                  res.setHeader('Content-Type''text/html');
                  res.statusCode = 200;
                  return res.end(htmlContent);
              }
              next();
          };

          最核心的當(dāng)屬 transformMiddleware 了,首先讓我們處理下 js 文件

          // simple-vite/vit/index.js
          const transformMiddleware = async (req, res, next) => {
              // 因?yàn)轭A(yù)構(gòu)建我們配置生成了 map 文件所以同樣要處理下 map 文件
              if (req.url.endsWith('.js') || req.url.endsWith('.map')) {
                  const jsPath = path.join(__dirname, '../', req.url);
                  const code = fs.readFileSync(jsPath, 'utf-8');
                  res.setHeader('Content-Type''application/javascript');
                  res.statusCode = 200;
                  // map 文件不需要分析 import 語(yǔ)句
                  const transformCode = req.url.endsWith('.map') ? code : await importAnalysis(code);
                  return res.end(transformCode);
              }
              next();
          };

          transformMiddleware 最關(guān)鍵的就是 importAnalysis 函數(shù)了,正如 vite2 源碼里一樣其正是處理分析源代碼中的 import 語(yǔ)句,并將依賴包替換成預(yù)構(gòu)建包的路徑

          // simple-vite/vit/index.js
          const { init, parse } = require('es-module-lexer');
          const MagicString = require('magic-string');

          const importAnalysis = async (code) => {
              // es-module-lexer 的 init 必須在 parse 前 Resolve
              await init;
              // 通過(guò) es-module-lexer 分析源 code 中所有的 import 語(yǔ)句
              const [imports] = parse(code);
              // 如果沒(méi)有 import 語(yǔ)句我們直接返回源 code
              if (!imports || !imports.length) return code;
              // 定義依賴映射的對(duì)象
              const metaData = require(path.join(cacheDir, '_metadata.json'));
              // magic-string vite2 源碼中使用到的一個(gè)工具 主要適用于將源代碼中的某些輕微修改或者替換
              let transformCode = new MagicString(code);
              imports.forEach((importer) => {
                  // n: 表示模塊的名稱 如 vue
                  // s: 模塊名稱在導(dǎo)入語(yǔ)句中的起始位置
                  // e: 模塊名稱在導(dǎo)入語(yǔ)句中的結(jié)束位置
                  const { n, s, e } = importer;
                  // 得到模塊對(duì)應(yīng)預(yù)構(gòu)建后的真實(shí)路徑  如 
                  const replacePath = metaData[n] || n; 
                  // 將模塊名稱替換成真實(shí)路徑如/node_modules/.vite
                  transformCode = transformCode.overwrite(s, e, replacePath);
              });
              return transformCode.toString();
          };

          至此,對(duì)于 js 請(qǐng)求已處理完畢,其中主要用到的兩個(gè)包 es-module-lexer 和 magic-string 感興趣的同學(xué)可以去對(duì)應(yīng)的 github 地址了解。最后讓我們?cè)偬幚硐?vue 文件吧:

          // simple-vite/vit/index.js
          const compileSFC = require('@vue/compiler-sfc');
          const compileDom = require('@vue/compiler-dom');

          const transformMiddleware = async (req, res, next) => {
              if (req.url.indexOf('.vue')!==-1) {
                  const vuePath = path.join(__dirname, '../', req.url.split('?')[0]);
                  // 拿到 vue 文件中的內(nèi)容
                  const vueContent =  fs.readFileSync(vuePath, 'utf-8');
                  // 通過(guò)@vue/compiler-sfc 將 vue 中的內(nèi)容解析成 AST
                  const vueParseContet = compileSFC.parse(vueContent);
                  // 得到 vue 文件中 script 內(nèi)的 code
                  const scriptContent = vueParseContet.descriptor.script.content;
                  const replaceScript = scriptContent.replace('export default ''const __script = ');
                  // 得到 vue 文件中 template 內(nèi)的內(nèi)容
                  const tpl = vueParseContet.descriptor.template.content;
                  // 通過(guò)@vue/compiler-dom 將其解析成 render 函數(shù)
                  const tplCode = compileDom.compile(tpl, { mode'module' }).code;
                  const tplCodeReplace = tplCode.replace('export function render(_ctx, _cache)''__script.render=(_ctx, _cache)=>');
                  // 最后不要忘了 script 內(nèi)的 code 還要再一次進(jìn)行 import 語(yǔ)句分析替換
                  const code = `
                          ${await importAnalysis(replaceScript)}
                          ${tplCodeReplace}
                          export default __script;
                  `
          ;
                  res.setHeader('Content-Type''application/javascript');
                  res.statusCode = 200;
                  return res.end(await importAnalysis(code));
              }
              next();
          };

          關(guān)于.vue 文件的處理好像也沒(méi)什么好說(shuō)的了,看代碼看注釋就完事了,想深入了解的同學(xué)可查看@vitejs/plugin-vue。然后讓我們看下此代碼實(shí)現(xiàn)的最終效果吧:


          如上圖所示,所有請(qǐng)求的文件最終都轉(zhuǎn)換成了瀏覽器能成功運(yùn)行的 js 代碼。

          最后的總結(jié)

          本文的最終目的是參照 vite2 源碼實(shí)現(xiàn)一個(gè)極其簡(jiǎn)易版的 vite,其主要功能簡(jiǎn)而言之是以下兩點(diǎn):

          1. 利用 esbuild 進(jìn)行預(yù)構(gòu)建工作,其目的是能將我們依賴的瀏覽器不支持運(yùn)行的 CJS 和 AMD 模塊的代碼打包轉(zhuǎn)換為瀏覽器支持的 ES 模塊代碼,同時(shí)避免了過(guò)多的網(wǎng)絡(luò)請(qǐng)求次數(shù)。
          2. 模擬源碼實(shí)現(xiàn)一個(gè) transformMiddleware,其目的是能將源代碼進(jìn)行轉(zhuǎn)換瀏覽器能支持運(yùn)行的代碼,如:分析源代碼的 import 語(yǔ)句并其替換為瀏覽器可執(zhí)行的 import 語(yǔ)句以及將 vue 文件轉(zhuǎn)換為可執(zhí)行的 js 代碼。

          最后感謝您能抽出寶貴的時(shí)間來(lái)看此文章,希望能給您帶來(lái)收獲。


          往期推薦

          Vite 太快了,煩死了,是時(shí)候該小睡一會(huì)了。


          如何實(shí)現(xiàn)比 setTimeout 快 80 倍的定時(shí)器?


          萬(wàn)字長(zhǎng)文!總結(jié)Vue 性能優(yōu)化方式及原理


          90 行代碼的 webpack,你確定不學(xué)嗎?


          最后





          如果你覺(jué)得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:

          1. 點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)

          2. 歡迎加我微信「huab119」拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...

          3. 關(guān)注公眾號(hào)「前端勸退師」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。




          點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了
          瀏覽 74
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  五月天婷婷丁香网 | 欧美成人精品免费 | 欧美XXXXBBBB | 欧美成人免费电影网站 | 久久久久久成人无码 |