Optimización del rendimiento de Vite: aumente la preconstrucción del código comercial, acelere la salida de la primera pantalla

contenido

Lo que hace que vite se destaque es la experiencia de desarrollo receptiva y preconstruida durante la fase de desarrollo. Sin embargo, cuando usa vite en su propio proyecto, puede experimentar aclimatación.Un problema muy importante es que la optimización no tiene ningún efecto, principalmente porque la primera pantalla de nuestro proyecto, es decir, index.html, se basa en demasiados módulos de desarrollo comercial. y solicitudes Estos módulos requieren mucho tiempo de red, lo que bloquea la salida rápida de la primera pantalla.

Además, nuestros proyectos generalmente usan vite para acelerar el desarrollo local y usan un paquete web estable para compilaciones de producción, por lo que no queremos modificar la estructura del código comercial y la forma de importar otros módulos para vite. Este es un gran punto de dolor.

una demostración

tenemos el siguiente proyecto

├── index.html
├── login.html
├── package.json
├── src
│   ├── App.jsx
│   ├── components
│   │   ├── Component1.tsx
│   │   ├── Component2.tsx
……
│   │   ├── Component9.tsx
│   │   ├── Component10.tsx
│   │   └── index.ts
│   ├── main.js
│   ├── pages
│   │   ├── home
│   │   │   ├── Index.tsx
│   │   │   └── main.js
│   │   └── login
│   │       ├── Index.tsx
│   │       └── main.js
│   └── utils
│       ├── index.ts
│       ├── util1.ts
……
│       ├── util19.ts
│       ├── util20.ts

├── vite.config.js
└── yarn.lock
复制代码

Para componentes/índice, se exportarán todos los componentes:

import Comp1 from './Component1'; 
import Comp2 from './Component2'; 
……
import Comp9 from './Component9';
import Comp10 from './Component10';

export default {
  Comp1,
  Comp2,
  ……
  Comp9,
  Comp10
}
复制代码

Para utils/index importará todos los utils

import util1 from './util1';
import util2 from './util2';
……
import util20 from './util20';

export default {
util1,
util2,
……
util20,
}
复制代码

Para index.html, iniciar sesión.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
</head>

<body>
  <div>home</div>
  <div id="app">home</div>
  <script type="module" src="/src/pages/home/main.js"></script>
</body>

</html>
复制代码
// home/main.js
import { createApp } from "vue";
import App from "./Index";
import { toString, toArray } from "lodash-es";

console.log(toString(123));
console.log(toArray([]));

createApp(App).mount("#app");

// home/index.tsx
import { defineComponent } from "vue";
import comps from "../../components/index"; 
import util from '../../utils/index';

console.log(util.util1);


export default defineComponent({
  setup() {
    return () => {
      return <div>
        <comps.Comp1>Comp1</comps.Comp1>
        <a href="/index.html">home</a> &nbsp;&nbsp;
        <a href="/login.html">login</a>
        </div>;
    };
  },
});

复制代码

Usar la configuración predeterminada de vite

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";

export default defineConfig({
  plugins: [vue(), vueJsx()],
});
复制代码

Iniciando el proyecto obtendremos este efecto:

ezgif.com-gif-maker.gif

Verá que cuando solicita home/main.js, debido a que se hace referencia a index.tsx e index.tsx hace referencia a componentes y utilidades, estos archivos se cargarán y todos los módulos dependientes en componentes y utilidades también se cargarán, pero en el proyecto real, solo se utilizan comp1 y utils1 Para otros módulos, vite no garantiza que otros módulos no tengan efectos secundarios, por lo que se cargarán en ellos. Afecta el tiempo de salida de la primera pantalla.

再者, 如果跳转到其他页面比如 login.html 时候, 虽然 组件与 util 那么多文件都没有修改, 但是对于 用户模块来说不会进行浏览器强缓存, 具体可以看 vite2 源码分析(二) — 请求资源。 也就是 cache-control no-cache, 会去 vite 服务端再一次请求该模块, 如果该模块没有修改(etag 相同)则返回 304, 否则返回 200, 但是对于这么文件请求,哪怕是 304 也是很消耗时间的。 所以我们需要将那些不经常变动的业务模块进行打包。对于第三方模块 比如 vue lodash-es vite 默认在预构建的过程中会提前打包好的, 但是默认情况下并不会对用户自定义的模块进行预构建处理, 预构建的内容可以参考 vite2 源码分析(一) — 启动 vite

定义成 npm 包通过 link 到 node_modules

// vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";

export default defineConfig({
  +optimizeDeps: {
  +  include: ["my-components", "my-utils"],
  +},
  plugins: [vue(), vueJsx()],
});

// package.json
  "dependencies": {
    "lodash": "^4.17.21",
    "lodash-es": "^4.17.21",
    "vue": "^3.0.5",
    +"my-components": "link:src/components",
    +"my-utils": "link:src/utils"
  },

// 在 componetns utils 中增加 package.json
{
  "name": "my-components",
  "version": "0.0.1",
  "main": "./index.ts",
  "module": "./index.ts",
  "dependencies": {
    "vue": "^3.2.31"
  }
}
// 修改 pages/home/index.ts

// import comps from "../../components/index";
// import util from '../../utils/index';
import comps from 'my-components';
import util from 'my-utils';
复制代码

也就是说将 components utils 封装成第三方依赖, 然后将 link 到 node_modules中,然后文件中使用的不是相对路径而是包名。

image_ieMAv16116qKLwTr4mC9EH.png

运行以后感觉还不错,components 合并到了 my-component utils 合并到了 my-utils 中, 看起来都是用了内存缓存了。

所以这种方法适用于比如 utils 的没有副作用的包, 否则不建议使用这种方式, 并且这种方式对于一个大型项目来说修改工作量也是致命的。

自定义 vite plugin 解决

一个合理的设计是不应该在业务代码中出现一些为了工程化的优化而带来的一些代码侵入的, 所以上述为了提高开发效率而直接改动源代码的思路应该是错误的。那么我们来从自己写一个项目相关的插件的方式来解决掉这个问题。

//vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
const { resolve, dirname, extname } = require("path");

const getPathNoExt = (resolveId) => {
  const ext = extname(resolveId);
  return ext ? resolveId.replace(ext, "") : resolveId;
};

const myDeps = [resolve(__dirname, "./src/components/index.ts"), resolve(__dirname, "./src/utils/index.ts")];

const importDepsPlugin = () => {
  let server = null;
  return {
    name: "my-import-deps-plugin",
    enforce: "pre",

    configureServer(_server) {
      server = _server;
    },

    resolveId(id, importer) {
      const resolvePath = getPathNoExt(resolve(dirname(importer), id));
      const index = myDeps.findIndex((v) => getPathNoExt(v) === resolvePath);
      if (index >= 0) {
        const cacheDir = server.config.cacheDir;
        const depData = server._optimizeDepsMetadata;
        if (cacheDir && depData) {
          const isOptimized = depData.optimized[myDeps[index]];
          if (isOptimized) {
            return isOptimized.file + `?v=${depData.browserHash}${isOptimized.needsInterop ? `&es-interop` : ``}`;
          }
        }
      }
    },
  };
};


export default defineConfig({
  optimizeDeps: {
    include: myDeps,
  },
  plugins: [importDepsPlugin(), vue(), vueJsx()],
});


复制代码

image_hrsP9Hp2FbcaPdophmAmqZ.png

Configure el campo de inclusión de configuración deOptimizeDeps en el archivo de configuración de vite, este campo es una matriz, ** cada elemento debe ser una ruta absoluta**. Estos archivos se integrarán en la memoria caché solo después de la compilación previa. El código fuente específico es:

 // vite/2.4.1/packages/vite/src/node/optimizer/index.ts 187行
 const include = config.optimizeDeps?.include
  if (include) {
    const resolve = config.createResolver({ asSrc: false })
    for (const id of include) {
      if (!deps[id]) {
        const entry = await resolve(id)
        if (entry) {
          deps[id] = entry
        } else {
          throw new Error(
            `Failed to resolve force included dependency: ${chalk.cyan(id)}`
          )
        }
      }
    }
  }
  
  const createResolver: ResolvedConfig['createResolver'] = (options) => {
  let aliasContainer: PluginContainer | undefined
  let resolverContainer: PluginContainer | undefined
  return async (id, importer, aliasOnly, ssr) => {
    let container: PluginContainer
    if (aliasOnly) {
      container =
        aliasContainer ||
        (aliasContainer = await createPluginContainer({
          ...resolved,
          plugins: [aliasPlugin({ entries: resolved.resolve.alias })]
        }))
    } else {
      container =
        resolverContainer ||
        (resolverContainer = await createPluginContainer({
          ...resolved,
          plugins: [
            aliasPlugin({ entries: resolved.resolve.alias }),
            resolvePlugin({
              ...resolved.resolve,
              root: resolvedRoot,
              isProduction,
              isBuild: command === 'build',
              ssrTarget: resolved.ssr?.target,
              asSrc: true,
              preferRelative: false,
              tryIndex: true,
              ...options
            })
          ]
        }))
    }
    return (await container.resolveId(id, importer, undefined, ssr))?.id
  }
}
复制代码

Una vez que finaliza scanImports, la inclusión pasada por el usuario debe procesarse aún más, principalmente llamando al método de resolución definido por el propio vite, es decir, config.createResolver.Este método no ejecuta el complemento definido por el usuario, sino que solo ejecuta dos alias y resolver el complemento. . Por lo tanto, el caché preconstruido solo se puede generar al pasar la ruta absoluta. Podemos mirar el metadata.json preconstruido (/Users/mt/Documents/storehouse/vite/demo-0-vite-vue3 es la raíz del proyecto)

{
  "hash": "5fe06ea4",
  "browserHash": "c0cdb122",
  "optimized": {
    "vue": {
      "file": "/Users/mt/Documents/storehouse/vite/demo-0-vite-vue3/node_modules/.vite/vue.js",
      "src": "/Users/mt/Documents/storehouse/vite/demo-0-vite-vue3/node_modules/vue/dist/vue.runtime.esm-bundler.js",
      "needsInterop": false
    },
    "lodash-es": {
      "file": "/Users/mt/Documents/storehouse/vite/demo-0-vite-vue3/node_modules/.vite/lodash-es.js",
      "src": "/Users/mt/Documents/storehouse/vite/demo-0-vite-vue3/node_modules/lodash-es/lodash.js",
      "needsInterop": false
    },
    "/Users/mt/Documents/storehouse/vite/demo-0-vite-vue3/src/components/index.ts": {
      "file": "/Users/mt/Documents/storehouse/vite/demo-0-vite-vue3/node_modules/.vite/_Users_mt_Documents_storehouse_vite_demo-0-vite-vue3_src_components_index_ts.js",
      "src": "/Users/mt/Documents/storehouse/vite/demo-0-vite-vue3/src/components/index.ts",
      "needsInterop": false
    },
    "/Users/mt/Documents/storehouse/vite/demo-0-vite-vue3/src/utils/index.ts": {
      "file": "/Users/mt/Documents/storehouse/vite/demo-0-vite-vue3/node_modules/.vite/_Users_mt_Documents_storehouse_vite_demo-0-vite-vue3_src_utils_index_ts.js",
      "src": "/Users/mt/Documents/storehouse/vite/demo-0-vite-vue3/src/utils/index.ts",
      "needsInterop": false
    }
  }
}
复制代码

Para poder acceder al contenido en caché cuando lo solicite, debemos agregar un complemento personalizado importDepsPlugin, el objetivo principal de este complemento es cargar los recursos en el directorio src, si el código cargado contiene la importación del componente de importación y el módulo url, debe convertir esta importación en una ruta a un caché preconstruido.

Entonces, lo que obtenemos cuando solicitamos el archivo Index.tsx es:

import {createHotContext as __vite__createHotContext} from "/@vite/client";
import.meta.hot = __vite__createHotContext("/src/pages/home/Index.tsx");
import {createTextVNode as _createTextVNode, createVNode as _createVNode} from "/node_modules/.vite/vue.js?v=c0cdb122";
import {defineComponent} from "/node_modules/.vite/vue.js?v=c0cdb122";
import comps from "/node_modules/.vite/_Users_mt_Documents_storehouse_vite_demo-0-vite-vue3_src_components_index_ts.js?v=c0cdb122";
import util from '/node_modules/.vite/_Users_mt_Documents_storehouse_vite_demo-0-vite-vue3_src_utils_index_ts.js?v=c0cdb122';
console.log(util.util1);
const __default__ = defineComponent({
    setup() {
        return ()=>{
            return _createVNode("div", null, [_createVNode(comps.Comp1, null, null), _createVNode("a", {
                "href": "/index.html"
            }, [_createTextVNode("home")]), _createTextVNode(" \xA0\xA0"), _createVNode("a", {
                "href": "/login.html"
            }, [_createTextVNode("login")])]);
        }
        ;
    }

});
export default __default__
__default__.__hmrId = "ad9a0a10"
__VUE_HMR_RUNTIME__.createRecord("ad9a0a10", __default__)
import.meta.hot.accept(({default: __default})=>{
    __VUE_HMR_RUNTIME__.reload("ad9a0a10", __default)
}
)
复制代码

Encontrará que las dos direcciones importadas para comps util se han convertido en direcciones en caché. En este punto, nos hemos dado cuenta de que la velocidad de construcción de vite se puede mejorar sin cambiar el código comercial.

Supongo que te gusta

Origin juejin.im/post/7085424254702845960
Recomendado
Clasificación