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

          三大前端構(gòu)建工具橫評(píng),誰(shuí)是性能之王!

          共 17450字,需瀏覽 35分鐘

           ·

          2021-03-15 06:57

          Vite一經(jīng)發(fā)布就吸引了很多人的關(guān)注,NPM下載量一路攀升:

          而在Vite之前,還有Snowpack也同樣采用了No-Bundler構(gòu)建方案。那么No-Bundler模式與傳統(tǒng)老牌構(gòu)建工具Webpack孰優(yōu)孰劣呢?能否實(shí)現(xiàn)平滑遷移和完美取代?

          下面就帶著問(wèn)題一起分析一下 Vite2、Snowpack3 和 Webpack5 吧!

          Webpack

          Webpack是近年來(lái)使用量最大,同時(shí)社區(qū)最完善的前端打包構(gòu)建工具,5.x版本對(duì)構(gòu)建細(xì)節(jié)進(jìn)行了優(yōu)化,某些場(chǎng)景下打包速度提升明顯,但也沒(méi)能解決之前一直被人詬病的大項(xiàng)目編譯慢的問(wèn)題,這也和Webpack本身的機(jī)制相關(guān)。

          已經(jīng)有很多文章講解Webpack的運(yùn)行原理了,本文就不再贅述,我們著重分析一下后起之秀。

          Snowpack

          什么是Snowpack?

          首次提出利用瀏覽器原生ESM能力的工具并非是Vite,而是一個(gè)叫做Snowpack的工具。前身是@pika/web,從1.x版本開(kāi)始更名為Snowpack。

          Snowpack在其官網(wǎng)是這樣進(jìn)行自我介紹的:“Snowpack是一種閃電般快速的前端構(gòu)建工具,專為現(xiàn)代Web設(shè)計(jì)。 它是開(kāi)發(fā)工作流程較重,較復(fù)雜的打包工具(如Webpack或Parcel)的替代方案。Snowpack利用JavaScript的本機(jī)模塊系統(tǒng)(稱為ESM)來(lái)避免不必要的工作并保持流暢的開(kāi)發(fā)體驗(yàn)”。

          Snowpack的理念是減少或避免整個(gè)bundle的打包,每次保存單個(gè)文件時(shí),傳統(tǒng)的JavaScript構(gòu)建工具(例如Webpack和Parcel)都需要重新構(gòu)建和重新打包應(yīng)用程序的整個(gè)bundle。重新打包時(shí)增加了在保存更改和看到更改反映在瀏覽器之間的時(shí)間間隔。在開(kāi)發(fā)過(guò)程中,Snowpack為你的應(yīng)用程序提供unbundled server。每個(gè)文件只需要構(gòu)建一次,就可以永久緩存。文件更改時(shí),Snowpack會(huì)重新構(gòu)建該單個(gè)文件。在重新構(gòu)建每次變更時(shí)沒(méi)有任何的時(shí)間浪費(fèi),只需要在瀏覽器中進(jìn)行HMR更新。

          再了解一下發(fā)明Snowpack的團(tuán)隊(duì)Pika,Pika團(tuán)隊(duì)有一個(gè)宏偉的使命:讓W(xué)eb應(yīng)用提速90%:

          為此,Pika團(tuán)隊(duì)開(kāi)發(fā)并維護(hù)了兩個(gè)技術(shù)體系:構(gòu)建相關(guān)的Snowpack和造福大眾的Skypack。

          在這里我們簡(jiǎn)單聊一下Skypack的初衷,當(dāng)前許多Web應(yīng)用都是在不同NPM包的基礎(chǔ)上進(jìn)行構(gòu)建的,而這些NPM包都被Webpack之類的打包工具打成了一個(gè)bundle,如果這些NPM包都來(lái)源于同一個(gè)CDN地址,且支持跨域緩存,那么這些NPM包在緩存生效期內(nèi)都只需要加載一次,其他網(wǎng)站用到了同樣的NPM包,就不需要重新下載,而是直接讀取本地緩存。

          例如,智聯(lián)的官網(wǎng)與B端都是基于vue+vuex開(kāi)發(fā)的,當(dāng)HR在B端發(fā)完職位后,進(jìn)入官網(wǎng)預(yù)覽自己的公司對(duì)外主頁(yè)都不用重新下載,只需要下載智聯(lián)官網(wǎng)相關(guān)的一些業(yè)務(wù)代碼即可。為此,Pika專門建立了一個(gè)CDN(Skypack)用來(lái)下載NPM上的ESM模塊。

          后來(lái)Snowpack發(fā)布的時(shí)候,Pika團(tuán)隊(duì)順便發(fā)表了一篇名為《A Future Without Webpack》 的文章,告訴大家可以嘗試拋棄Webpack,采用全新的打包構(gòu)建方案,下圖取自其官網(wǎng),展示了bundled與unbundled之間的區(qū)別。

          在HTTP/2和5G網(wǎng)絡(luò)的加持下,我們可以預(yù)見(jiàn)到HTTP請(qǐng)求數(shù)量不再成為問(wèn)題,而隨著Web領(lǐng)域新標(biāo)準(zhǔn)的普及,瀏覽器也在逐步支持ESM。

          源碼分析

          啟動(dòng)構(gòu)建時(shí)會(huì)調(diào)用源碼src/index.ts中的cli方法,該方法的代碼刪減版如下:

          import {command as buildCommand} from'./commands/build';

          exportasyncfunction cli(args: string[]) {
          const cliFlags = yargs(args, {
          array: ['install', 'env', 'exclude', 'external']
          }) as CLIFlags;

          if (cmd === 'build') {
          await buildCommand(commandOptions);
          return process.exit(0);
          }
          }

          進(jìn)入commands/build文件,執(zhí)行大致邏輯如下:

          exportasyncfunction build(commandOptions: CommandOptions): Promise<SnowpackBuildResult> {
          // 讀取config代碼
          // ...
          for (const runPlugin of config.plugins) {
          if (runPlugin.run) {
          // 執(zhí)行插件
          }
          }

          // 將 `import.meta.env` 的內(nèi)容寫入文件
          await fs.writeFile(
          path.join(internalFilESbuildLoc, 'env.js'),
          generateEnvModule({mode: 'production', isSSR}),
          );

          // 如果 HMR,則加載 hmr 工具文件
          if (getIsHmrEnabled(config)) {
          await fs.writeFile(path.resolve(internalFilESbuildLoc, 'hmr-client.js'), HMR_CLIENT_CODE);
          await fs.writeFile(
          path.resolve(internalFilESbuildLoc, 'hmr-error-overlay.js'),
          HMR_OVERLAY_CODE,
          );
          hmrEngine = new EsmHmrEngine({port: config.devOptions.hmrPort});
          }

          // 開(kāi)始構(gòu)建源文件
          logger.info(colors.yellow('! building source files...'));
          const buildStart = performance.now();
          const buildPipelineFiles: Record<string, FileBuilder> = {};

          // 根據(jù)主 buildPipelineFiles 列表安裝所有需要的依賴項(xiàng),對(duì)應(yīng)下面第三部
          asyncfunction installDependencies() {
          const scannedFiles = Object.values(buildPipelineFiles)
          .map((f) =>Object.values(f.filesToResolve))
          .reduce((flat, item) => flat.concat(item), []);

          // 指定安裝的目標(biāo)文件夾
          const installDest = path.join(buildDirectoryLoc, config.buildOptions.metaUrlPath, 'pkg');

          // installOptimizedDependencies 方法調(diào)用了 esinstall 包,包內(nèi)部調(diào)用了 rollup 進(jìn)行模塊分析及 commonjs 轉(zhuǎn) esm
          const installResult = await installOptimizedDependencies(
          scannedFiles,
          installDest,
          commandOptions,
          );

          return installResult
          }

          // 下面因代碼繁多,僅展示源碼中的注釋
          // 0. Find all source files.
          // 1. Build all files for the first time, from source.
          // 2. Install all dependencies. This gets us the import map we need to resolve imports.
          // 3. Resolve all built file imports.
          // 4. Write files to disk.
          // 5. Optimize the build.

          // "--watch" mode - Start watching the file system.
          // Defer "chokidar" loading to here, to reduce impact on overall startup time
          logger.info(colors.cyan('watching for changes...'));
          const chokidar = awaitimport('chokidar');

          // 本地文件刪除時(shí)清除 buildPipelineFiles 對(duì)應(yīng)的文件
          function onDeleteEvent(fileLoc: string) {
          delete buildPipelineFiles[fileLoc];
          }

          // 本地文件創(chuàng)建及修改時(shí)觸發(fā)
          asyncfunction onWatchEvent(fileLoc: string) {
          // 1. Build the file.
          // 2. Resolve any ESM imports. Handle new imports by triggering a re-install.
          // 3. Write to disk. If any proxy imports are needed, write those as well.

          // 觸發(fā) HMR
          if (hmrEngine) {
          hmrEngine.broadcastMessage({type: 'reload'});
          }
          }

          // 創(chuàng)建文件監(jiān)聽(tīng)器
          const watcher = chokidar.watch(Object.keys(config.mount), {
          ignored: config.exclude,
          ignoreInitial: true,
          persistent: true,
          disableGlobbing: false,
          useFsEvents: isFsEventsEnabled(),
          });
          watcher.on('add', (fileLoc) => onWatchEvent(fileLoc));
          watcher.on('change', (fileLoc) => onWatchEvent(fileLoc));
          watcher.on('unlink', (fileLoc) => onDeleteEvent(fileLoc));

          // 返回一些方法給 plugin 使用
          return {
          result: buildResultManifest,
          onFileChange: (callback) => (onFileChangeCallback = callback),
          async shutdown() {
          await watcher.close();
          },
          };
          }

          exportasyncfunction command(commandOptions: CommandOptions) {
          try {
          await build(commandOptions);
          } catch (err) {
          logger.error(err.message);
          logger.debug(err.stack);
          process.exit(1);
          }

          if (commandOptions.config.buildOptions.watch) {
          // We intentionally never want to exit in watch mode!
          returnnewPromise(() => {});
          }
          }

          所有的模塊會(huì)經(jīng)過(guò)install進(jìn)行安裝,此處的安裝是將模塊轉(zhuǎn)換成ESM放在pkg目錄下,并不是npm包安裝的概念。

          在Snowpack3中增加了一些老版本不支持的能力,如:內(nèi)部默認(rèn)集成Node服務(wù)、支持CSS Modules、支持HMR等。

          Vite

          什么是Vite?

          Vite(法語(yǔ)單詞“ fast”,發(fā)音為/vit/)是一種構(gòu)建工具,旨在為現(xiàn)代Web項(xiàng)目提供更快,更精簡(jiǎn)的開(kāi)發(fā)體驗(yàn)。它包括兩個(gè)主要部分:

          1. 開(kāi)發(fā)服務(wù)器,它在本機(jī)ESM上提供了豐富的功能增強(qiáng),例如,極快的Hot Module Replacement(HMR)。
          2. 構(gòu)建命令,它將代碼使用Rollup進(jìn)行構(gòu)建。

          隨著vue3的推出,Vite也隨之成名,起初是一個(gè)針對(duì)Vue3的打包編譯工具,目前2.x版本發(fā)布面向了任何前端框架,不局限于Vue,在Vite的README中也提到了在某些想法上參考了Snowpack。

          在已有方案上Vue本可以拋棄Webpack選擇Snowpack,但選擇開(kāi)發(fā)Vite來(lái)造一個(gè)新的輪子也有Vue團(tuán)隊(duì)自己的考量。

          在Vite官方文檔列舉了Vite與Snowpack的異同,其實(shí)本質(zhì)是說(shuō)明Vite相較于Snowpack的優(yōu)勢(shì)。

          相同點(diǎn),引用Vite官方的話:

          Snowpack is also a no-bundle native ESM dev server that is very similar in scope to Vite。

          不同點(diǎn):

          1. Snowpack的build默認(rèn)是不打包的,好處是可以靈活選擇Rollup、Webpack等打包工具,壞處就是不同打包工具帶來(lái)了不同的體驗(yàn),當(dāng)前ESbuild作為生產(chǎn)環(huán)境打包尚不穩(wěn)定,Rollup也沒(méi)有官方支持Snowpack,不同的工具會(huì)產(chǎn)生不同的配置文件;
          2. Vite支持多page打包;
          3. Vite支持Library Mode;
          4. Vite支持CSS代碼拆分,Snowpack默認(rèn)是CSS in JS;
          5. Vite優(yōu)化了異步代碼加載;
          6. Vite支持動(dòng)態(tài)引入 polyfill;
          7. Vite官方的 legacy mode plugin,可以同時(shí)生成 ESM 和 NO ESM;
          8. First Class Vue Support。

          第5點(diǎn)Vite官網(wǎng)有詳細(xì)介紹,在非優(yōu)化方案中,當(dāng)A導(dǎo)入異步塊時(shí),瀏覽器必須先請(qǐng)求并解析,A然后才能確定它也需要公共塊C。這會(huì)導(dǎo)致額外的網(wǎng)絡(luò)往返:

          Entry ---> A ---> C

          Vite通過(guò)預(yù)加載步驟自動(dòng)重寫代碼拆分的動(dòng)態(tài)導(dǎo)入調(diào)用,以便在A請(qǐng)求時(shí)并行C獲?。?/p>

          Entry ---> (A + C)

          可能C會(huì)多次導(dǎo)入,這將導(dǎo)致在未優(yōu)化的情況下發(fā)出多次請(qǐng)求。Vite的優(yōu)化將跟蹤所有import,以完全消除重復(fù)請(qǐng)求,示意圖如下:

          第8點(diǎn)的First Class Vue Support,雖然在列表的最后一項(xiàng),實(shí)則是點(diǎn)睛之筆。

          源碼分析

          Vite在啟動(dòng)時(shí),如果不是中間件模式,內(nèi)部默認(rèn)會(huì)啟一個(gè)http server。

          exportasyncfunction createServer(
          inlineConfig: InlineConfig = {}
          ): Promise<ViteDevServer>
          {
          // 獲取 config
          const config = await resolveConfig(inlineConfig, 'serve', 'development')
          const root = config.root
          const serverConfig = config.server || {}

          // 判斷是否是中間件模式
          const middlewareMode = !!serverConfig.middlewareMode
          const middlewares = connect() as Connect.Server

          // 中間件模式不創(chuàng)建 http 服務(wù),允許外部以中間件形式調(diào)用:https://Vitejs.dev/guide/api-javascript.html#using-the-Vite-server-as-a-middleware
          const httpServer = middlewareMode
          ? null
          : await resolveHttpServer(serverConfig, middlewares)

          // 創(chuàng)建 websocket 服務(wù)
          const ws = createWebSocketServer(httpServer, config)

          // 創(chuàng)建文件監(jiān)聽(tīng)器
          const { ignored = [], ...watchOptions } = serverConfig.watch || {}
          const watcher = chokidar.watch(path.resolve(root), {
          ignored: ['**/node_modules/**', '**/.git/**', ...ignored],
          ignoreInitial: true,
          ignorePermissionErrors: true,
          ...watchOptions
          }) as FSWatcher


          const plugins = config.plugins
          const container = await createPluginContainer(config, watcher)
          const moduleGraph = new ModuleGraph(container)
          const closeHttpServer = createSeverCloseFn(httpServer)

          const server: ViteDevServer = {
          // 前面定義的常量,包含:config、中間件、websocket、文件監(jiān)聽(tīng)器、ESbuild 等
          }

          // 監(jiān)聽(tīng)進(jìn)程關(guān)閉
          process.once('SIGTERM', async () => {
          try {
          await server.close()
          } finally {
          process.exit(0)
          }
          })

          watcher.on('change', async (file) => {
          file = normalizePath(file)

          // 文件更改時(shí)使模塊圖緩存無(wú)效
          moduleGraph.onFileChange(file)

          if (serverConfig.hmr !== false) {
          try {
          // 大致邏輯是修改 env 文件時(shí)直接重啟 server,根據(jù) moduleGraph 精準(zhǔn)刷新,必要時(shí)全部刷新
          await handleHMRUpdate(file, server)
          } catch (err) {
          ws.send({
          type: 'error',
          err: prepareError(err)
          })
          }
          }
          })

          // 監(jiān)聽(tīng)文件創(chuàng)建
          watcher.on('add', (file) => {
          handleFileAddUnlink(normalizePath(file), server)
          })

          // 監(jiān)聽(tīng)文件刪除
          watcher.on('unlink', (file) => {
          handleFileAddUnlink(normalizePath(file), server, true)
          })

          // 掛載插件的服務(wù)配置鉤子
          const postHooks: ((() => void) | void)[] = []
          for (const plugin of plugins) {
          if (plugin.configureServer) {
          postHooks.push(await plugin.configureServer(server))
          }
          }

          // 加載多個(gè)中間件,包含 cors、proxyopen-in-editor、靜態(tài)文件服務(wù)等

          // 運(yùn)行post鉤子,在html中間件之前應(yīng)用的,這樣外部中間件就可以提供自定義內(nèi)容取代 index.html
          postHooks.forEach((fn) => fn && fn())

          if (!middlewareMode) {
          // 轉(zhuǎn)換 html
          middlewares.use(indexHtmlMiddleware(server, plugins))
          // 處理 404
          middlewares.use((_, res) => {
          res.statusCode = 404
          res.end()
          }
          )
          }

          // errorHandler 中間件
          middlewares.use(errorMiddleware(server, middlewareMode))

          // 執(zhí)行優(yōu)化邏輯
          const runOptimize = async () =>
          {
          if (config.optimizeCacheDir) {
          // 將使用 ESbuild 將依賴打包并寫入 node_modules/.Vite/xxx
          await optimizeDeps(config)
          // 更新 metadata 文件
          const dataPath = path.resolve(config.optimizeCacheDir, 'metadata.json')
          if (fs.existsSync(dataPath)) {
          server._optimizeDepsMetadata = JSON.parse(
          fs.readFileSync(dataPath, 'utf-8')
          )
          }
          }
          }

          if (!middlewareMode && httpServer) {
          // 在服務(wù)器啟動(dòng)前覆蓋listen方法并運(yùn)行優(yōu)化器
          const listen = httpServer.listen.bind(httpServer)
          httpServer.listen = (async (port: number, ...args: any[]) => {
          await container.buildStart({})
          await runOptimize()
          return listen(port, ...args)
          }
          ) as any

          httpServer.once('listening', () => {
          // 更新實(shí)際端口,因?yàn)檫@可能與初始端口不同
          serverConfig.port = (httpServer.address() as AddressInfo).port
          }
          )
          } else {
          await runOptimize()
          }

          // 最后返回服務(wù)
          return server
          }

          訪問(wèn)Vite服務(wù)的時(shí)候,默認(rèn)會(huì)返回index.html:

          <!DOCTYPE html>
          <html lang="en">
          <head>
          <script type="module" src="/@Vite/client"></script>
          <meta charset="UTF-8" />
          <link rel="icon" href="/favicon.ico" />
          <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          <title>Vite App</title>
          </head>
          <body>
          <div id="app"></div>
          <script type="module" src="/src/main.js"></script>
          </body>
          </html>

          處理 import 文件邏輯,在 node/plugins/importAnalysis.ts 文件內(nèi):

          exportfunction importAnalysisPlugin(config: ResolvedConfig): Plugin {
          const clientPublicPath = path.posix.join(config.base, CLIENT_PUBLIC_PATH)
          let server: ViteDevServer
          return {
          name: 'Vite:import-analysis',
          configureServer(_server) {
          server = _server
          },
          async transform(source, importer, ssr) {
          const rewriteStart = Date.now()
          // 使用 es-module-lexer 進(jìn)行語(yǔ)法解析

          await init
          let imports: ImportSpecifier[] = []
          try {
          imports = parseImports(source)[0]
          } catch (e) {
          const isVue = importer.endsWith('.vue')
          const maybeJSX = !isVue && isJSRequest(importer)


          // 判斷文件后綴給不同的提示信息
          const msg = isVue
          ? `Install @Vitejs/plugin-vue to handle .vue files.`
          : maybeJSX
          ? `If you are using JSX, make sure to name the file with the .jsx or .tsx extension.`
          : `You may need to install appropriate plugins to handle the ${path.extname(
          importer
          )}
          file format.`

          this.error(
          `Failed to parse source for import analysis because the content ` +
          `contains invalid JS syntax. ` +
          msg,
          e.idx
          )
          }

          // 將代碼字符串取出
          let s: MagicString | undefined
          const str = () => s || (s = new MagicString(source))

          // 解析 env、glob 等并處理
          // 轉(zhuǎn)換 cjs 成 esm
          }
          }
          }

          拿Vue的NPM包舉例經(jīng)優(yōu)化器處理后的路徑如下:

          // before
          - import { createApp } from'vue'
          + import { createApp } from'/node_modules/.Vite/vue.runtime.esm-bundler.js?v=d17c1aa4'
          import App from'/src/App.vue'

          createApp(App).mount('#app')

          截圖中的/src/App.vue路徑經(jīng)過(guò)Vite處理發(fā)生了什么?

          首先需要引用 @Vitejs/plugin-vue 來(lái)處理,內(nèi)部使用Vue官方的編譯器@vue/compiler-sfc,plugin處理邏輯同rollup的plugin,Vite在Rollup的插件機(jī)制上進(jìn)行了擴(kuò)展。

          詳細(xì)可參考:https://Vitejs.dev/guide/api-plugin.html,這里不做展開(kāi)。

          編譯后的App.vue文件如下:

          import { createHotContext as __Vite__createHotContext } from"/@Vite/client";
          import.meta.hot = __Vite__createHotContext("/src/App.vue");
          import HelloWorld from'/src/components/HelloWorld.vue'

          const _sfc_main = {
          expose: [],
          setup(__props) {
          return { HelloWorld }
          }
          }

          import {
          createVNode as _createVNode,
          Fragment as _Fragment,
          openBlock as _openBlock,
          createBlock as _createBlock
          } from"/node_modules/.Vite/vue.runtime.esm-bundler.js?v=d17c1aa4"

          const _hoisted_1 = /*#__PURE__*/_createVNode("img", {
          alt: "Vue logo",
          src: "/src/assets/logo.png"
          }, null, -1/* HOISTED */)

          function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
          return (_openBlock(), _createBlock(_Fragment, null, [
          _hoisted_1,
          _createVNode($setup["HelloWorld"], { msg: "Hello Vue 3 + Vite" })
          ], 64/* STABLE_FRAGMENT */))
          }

          import"/src/App.vue?vue&type=style&index=0&lang.css"

          _sfc_main.render = _sfc_render
          _sfc_main.__file = "/Users/orange/build/Vite-vue3/src/App.vue"
          exportdefault _sfc_main
          _sfc_main.__hmrId = "7ba5bd90"
          typeof __VUE_HMR_RUNTIME__ !== 'undefined' && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)
          import.meta.hot.accept(({ default: updated, _rerender_only }) => {
          if (_rerender_only) {
          __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)
          } else {
          __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)
          }
          })

          可以發(fā)現(xiàn),Vite本身并不會(huì)遞歸編譯,這個(gè)過(guò)程交給了瀏覽器,當(dāng)瀏覽器運(yùn)行到import HelloWorld from '/src/components/HelloWorld.vue' 時(shí),又會(huì)發(fā)起新的請(qǐng)求,通過(guò)中間件來(lái)編譯 vue,以此類推,為了證明我們的結(jié)論,可以看到 HelloWorld.vue 的請(qǐng)求信息:

          經(jīng)過(guò)分析源碼后,能斷定的是,Snowpack與Vite在啟動(dòng)服務(wù)的時(shí)間會(huì)遠(yuǎn)超Webpack,但復(fù)雜工程的首次編譯到完全可運(yùn)行的時(shí)間需要進(jìn)一步測(cè)試,不同場(chǎng)景下可能產(chǎn)生截然不同的結(jié)果。

          功能對(duì)比


          [email protected][email protected][email protected]
          支持Vue2非官方支持: https://github.com/underfin/vite-plugin-vue2支持:vue-loader@^15.0.0非官方支持: https://www.npmjs.com/package/@lepzulnag/Snowpack-plugin-vue-2
          支持Vue3支持支持:vue-loader@^16.0.0(https://github.com/Jamie-Yang/vue3-boilerplate)支持: https://www.npmjs.com/package/@Snowpack/plugin-vue
          支持Typescript支持:ESbuild (默認(rèn)無(wú)類型檢查)支持:ts-loader支持: https://github.com/Snowpackjs/Snowpack/tree/main/create-Snowpack-app/app-template-vue-typescript
          支持CSS預(yù)處理器支持:https://vitejs.dev/guide/features.html#css-pre-processors支持:https://vue-loader.vuejs.org/guide/pre-processors.html部分支持:官方僅提供了Sass和Postcss,且存在未解決BUG
          支持CSS Modules支持:https://vitejs.dev/guide/features.html#css-modules支持:https://vue-loader.vuejs.org/guide/css-modules.html支持
          支持靜態(tài)文件支持支持支持
          開(kāi)發(fā)環(huán)境no-bundle native ESM(CJS → ESM)bundle(CJS/UMD/ESM)no-bundle native ESM(CJS → ESM)
          HMR支持支持支持
          生產(chǎn)環(huán)境RollupWebpackWebpack, Rollup, or even ESbuild
          Node API 調(diào)用能力支持支持支持

          啟動(dòng)時(shí)編譯速度對(duì)比

          下面一組測(cè)試的代碼完全相同,都是 Hello World 工程,沒(méi)有任何復(fù)雜邏輯,Webpack 與 Snowpack 分別引入了對(duì)應(yīng)的 Vue plugin,Vite 無(wú)需任何插件。

          Webpack5 + vue3(1.62s)

          工程目錄:

          控制臺(tái)輸出:

          Snowpack3 + vue3(2.51s)

          工程目錄:

          控制臺(tái)輸出:

          Vite2 + vue3(0.99s)

          工程目錄:

          控制臺(tái)輸出:

          真實(shí)項(xiàng)目遷移

          測(cè)試案例:已存在的復(fù)雜邏輯vue工程

          經(jīng)過(guò)簡(jiǎn)單的測(cè)試及調(diào)研結(jié)果,首先從生態(tài)和性能上排除了Snowpack,下面將測(cè)試Webpack5與Vite2。

          遷移Vite2遇到的問(wèn)題:

          1.不支持省略.vue后綴,因?yàn)榇寺酚蓹C(jī)制與編譯處理強(qiáng)關(guān)聯(lián);

          2.不支持.vue后綴文件內(nèi)寫jsx,若寫jsx,需要改文件后綴為.jsx;

          3.不建議import { ... } from "dayjs"與import duration from 'dayjs/plugin/duration'同時(shí)使用,從源碼會(huì)發(fā)現(xiàn)在optimizeDeps階段已經(jīng)把ESM編譯到了緩存文件夾,若同時(shí)使用會(huì)報(bào)錯(cuò):

          4.當(dāng)optimizeDeps忽略后文件路徑錯(cuò)誤,node_modules/dayjs/dayjs.main.js?version=xxxxx,此處不應(yīng)該在query中添加version;

          5.組件庫(kù)中window.$方法找不到,不能強(qiáng)依賴關(guān)聯(lián)順序,跟請(qǐng)求返回順序有關(guān);

          6.當(dāng)dependencies首次未被寫入緩存時(shí),補(bǔ)充寫入會(huì)報(bào)錯(cuò),需要二次重啟;

          7.在依賴關(guān)系復(fù)雜場(chǎng)景,Vue被多次cache,會(huì)出現(xiàn)ESM二次封裝的情況,也就是ESM里面嵌套ESM的情況;

          種種原因,調(diào)試到這里終結(jié)了,結(jié)論就是Vite2目前處于初期,尚不穩(wěn)定,處理深層次依賴就目前的moduleGraph機(jī)制還有所欠缺,有待完善。

          Webpack5

          效果和我們之前測(cè)試相同代碼在Webpack4下50+秒相比提升明顯,實(shí)際場(chǎng)景可能存在誤差,但WebpackConfig配置細(xì)節(jié)基本一致。

          編譯壓縮提速

          不知大家是否有遇到這個(gè)問(wèn)題:

          <--- Last few GCs --->
          [59757:0x103000000] 32063 ms: Mark-sweep 1393.5 (1477.7) -> 1393.5 (1477.7) MB, 109.0 / 0.0 ms allocation failure GC in old space requested
          <--- JS stacktrace --->
          ==== JS stack trace =========================================
          Security context: 0x24d9482a5ec1 <JSObject>
          ...

          或者在 92% 的進(jìn)度里卡很久:

          Webpack chunk asset optimization (92%) TerserPlugin

          隨著產(chǎn)物越來(lái)越大,編譯上線和CI的時(shí)間都越來(lái)越長(zhǎng),而其中1/3及更多的時(shí)間則是在做壓縮的部分。OOM的問(wèn)題也通常來(lái)源于壓縮。

          如何解決壓縮慢和占內(nèi)存的問(wèn)題,一直是逃避不開(kāi)的話題,Vite采用了ESbuild,接下來(lái)分析一下ESbuild。

          ESbuild

          下面是官方的構(gòu)建時(shí)間對(duì)比圖,并沒(méi)有說(shuō)明場(chǎng)景,文件大小等,所以不具備實(shí)際參考價(jià)值。

          之所以快,其中最主要的應(yīng)該是用go寫,然后編譯為Native代碼。然后npm安裝時(shí)動(dòng)態(tài)去下對(duì)應(yīng)平臺(tái)的二進(jìn)制包,支持Mac、Linux和Windows,比如esbuild-darwin-64。

          相同思路的還有es-module-lexer、swc等,都是用編譯成Native代碼的方式進(jìn)行提速,用來(lái)彌補(bǔ)Node在密集CPU計(jì)算場(chǎng)景的短板。

          ESbuild有兩個(gè)功能,bundler和minifier。bundler的功能和babel以及Webpack相比差異很大,直接使用對(duì)現(xiàn)有業(yè)務(wù)的風(fēng)險(xiǎn)較大;而minifier可以嘗試,在Webpack和babel產(chǎn)物的基礎(chǔ)上做一次生產(chǎn)環(huán)境壓縮,可以節(jié)省terser plugin的壓縮時(shí)間。

          同時(shí)針對(duì)Webpack提供了 esbuild-webpack-plugin,可以在 Webpack 內(nèi)直接使用 ESbuild。

          優(yōu)缺點(diǎn)及總結(jié)

          Snowpack

          缺點(diǎn):

          1. 社區(qū)不夠完善,無(wú)法支撐我們后續(xù)的業(yè)務(wù)演進(jìn);
          2. 編譯速度提效不明顯。

          Vite

          優(yōu)點(diǎn):

          1. 因其與rollup聯(lián)合,社區(qū)里rolllup的插件基本都可以直接使用,社區(qū)相對(duì)完善;
          2. 編譯速度快。

          缺點(diǎn):

          1. 目前Vite處于2.0初期,BUG比較多;
          2. 本地的 ESbuild 與生產(chǎn)環(huán)境的babel編譯結(jié)果差距較大,可能會(huì)導(dǎo)致功能差異。

          Webpack5

          優(yōu)點(diǎn):

          1. 從實(shí)際測(cè)試要比Webpack4快許多;
          2. 可借助ESbuild的代碼壓縮機(jī)制。

          缺點(diǎn):

          1. 相較 Vite 的本地開(kāi)發(fā)編譯速度有寫不足(其實(shí)算不上缺點(diǎn),因?yàn)榻鉀Q了生產(chǎn)環(huán)境差異)。

          回到我們文章開(kāi)始的問(wèn)題,經(jīng)過(guò)上文的遷移測(cè)試環(huán)節(jié),我們需要調(diào)整大量代碼進(jìn)行Vite遷移適配,由于原有Vue工程未遵循Vite的開(kāi)發(fā)范式,遷移過(guò)程比較坎坷,新工程只要遵循Vite官方的開(kāi)發(fā)文檔,會(huì)規(guī)避上文提到的大部分問(wèn)題。

          所以已有Webpack工程遷移成本還是較大的,另一方面也是需要重點(diǎn)關(guān)注的就是本地開(kāi)發(fā)與生產(chǎn)環(huán)境的差異問(wèn)題,如果本機(jī)開(kāi)發(fā)環(huán)境采用No-Bundle機(jī)制,而生產(chǎn)發(fā)布環(huán)境采用Bundle機(jī)制,這種不一致性會(huì)給測(cè)試和排查問(wèn)題帶來(lái)困擾和風(fēng)險(xiǎn),在生態(tài)尚未齊備之前,建議審慎嘗試。

          最后



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

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

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

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


          點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了



          瀏覽 46
          點(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>
                  日韩精品十区 | 精品人妻网站 | 亚洲欧美婷婷五月色综合 | 国产精品免费视频观看 | 三级黄色天天天天 |