directory

  • A demo
  • Use the default vite configuration
  • Defined as NPM packages via link to node_modules
  • Custom Vite plugin solution

What really stands out about Vite is the pre-build and responsive development experience during development. However, when using Vite in one’s own project, one of the most important problems is that the optimization does not work, mainly because the first screen of our project, i.e. index.html, relies on too many business development modules, and requesting these modules requires network time, thus blocking the fast first screen output.

In addition, our projects generally use Vite as local development to speed up, and production builds still use stable Webpack, so we don’t want to need to modify the structure of business code and import other modules for Vite. This is a big pain point.

A demo

We have the following project

├── SRC │ ├─ SRC │ ├─ app.jsX │ ├── ─ ├.tsX │ ├── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── Component2. Benchmark... │ │ ├ ─ ─ Component9. TSX │ │ ├ ─ ─ Component10. The TSX │ │ └ ─ ─ but ts │ ├ ─ ─ the main, js │ ├ ─ ─ pages │ │ ├ ─ ─ home │ │ │ ├ ─ ─ Index. The TSX │ │ │ └ ─ ─ the main, js │ │ └ ─ ─ the login │ │ ├ ─ ─ Index. The TSX │ │ └ ─ ─ the main, js │ └ ─ ─ utils │ ├ ─ ─ Index. The ts │ ├ ─ ─ util1. Ts...  │ ├─ util.exercises ─ util.exercises ─ util.exercises ─ util.exercisesCopy the code

For components/index all components are exported:

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

export default{Comp1 Comp2,... Comp9, Comp10 }Copy the code

All utils are imported for utils/index

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

export default{util1, util2,... util20, }Copy the code

For index.html and login.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>
Copy the code
// 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>; }; }});Copy the code

Use the default vite configuration

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

export default defineConfig({
  plugins: [vue(), vueJsx()],
});
Copy the code

This is what we get when we start the project:

TSX refers to components and utils, so these files are loaded, and all dependent modules in the component and utils are loaded. But only comp1 and utils1 are used in the project, and other modules are loaded because Vite does not guarantee that they are free of side effects. The output time of the first screen is affected.

In addition, if jump to other pages such as login.html, although the component and util so many files are not modified, but for the user module will not be browser strong cache, see vite2 source analysis (2) – request resources. Cache-control no-cache will go to the Vite server and request the module again. If the module has not changed (the eTAG is the same), 304 will be returned. Otherwise, 200 will be returned. So we need to package the business modules that don’t change very often. For third-party modules such as Vue Lodash-es vite will be pre-packed in the pre-build process by default, but will not be pre-built for user-defined modules by default, pre-built content can refer to vite2 source analysis (a) – Start Vite.

Defined as NPM packages via link to 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"
  },

// Add package.json to Componetns utils
{
  "name": "my-components"."version": "0.0.1"."main": "./index.ts"."module": "./index.ts"."dependencies": {
    "vue": "^ 3.2.31"}}/ / modify pages/home/index. The ts

// import comps from ".. /.. /components/index";
// import util from '.. /.. /utils/index';
import comps from 'my-components';
import util from 'my-utils';
Copy the code

That is, wrap components Utils as a third-party dependency, then link to node_modules, and then use the package name instead of the relative path in the file.

Components merged into my-component utils merged into my-utils, looks like it’s using memory cache.

This approach is therefore suitable for packages with no side effects such as Utils, otherwise it is not recommended, and it can be fatal for modifying workload on a large project.

Custom Vite plugin solution

A reasonable design should not appear in the business code for the purpose of engineering optimization and some code intrusion, so the above in order to improve the efficiency of development and direct change of the source code should be wrong. So let’s solve this problem by writing our own project-related plug-in.

//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()],
});


Copy the code

OptimizeDeps configures the inclues field in the Vite configuration file. This field is an array, and each item must be an absolute path. These files will only be built into the cache after pre-build time, the specific source code is:

 / / vite/against 2.4.1 / packages/vite/SRC/node/optimizer index. The ts 187 lines
 constinclude = config.optimizeDeps? .includeif (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 = awaitcreatePluginContainer({ ... resolved,plugins: [aliasPlugin({ entries: resolved.resolve.alias })]
        }))
    } else {
      container =
        resolverContainer ||
        (resolverContainer = awaitcreatePluginContainer({ ... 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 } }Copy the code

After scanImports is over, the user passes in additional include processing, mainly by calling Vite’s resolve method, config.createResolver, which does not execute the user’s custom plugin. Only two are executed: the Alias and resolve plugin. So you can only generate a pre-built cache by passing in an absolute path. We can see the metadata pre-built. Json. (/ Users/mt/Documents/storehouse – vite vite/demo – 0 – vue3 to the root directory of the project root)

{
  "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}}}Copy the code

ImportDepsPlugin is a custom plugin that can be used to load resources in the SRC directory. If the code contains the import component, the import of the URL module, You need to convert this import to the path of the pre-built cache.

So what we get when we request the index.tsx file is:

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)
}
)
Copy the code

You’ll notice that for comps util both of the imported addresses are now cached. At this point we were able to speed up the Vite build without changing the business code.