This is the first day of my participation in Gwen Challenge

preface

The project as the name, like Sister-in-law? A nanny? Take care of your children generally explain Vite source code for you.

  • NPM dependency resolution and prebuild: improved page reloading speed and strong cache dependencies across the board.

  • Plugins: Leverage the powerful ecosystem of Rollup plug-ins while also extending development server and SSR functionality as needed.

  • Dynamic Module thermal Overloading (HMR) : Vite provides a set of NATIVE ESM HMR apis. Frameworks with HMR capabilities can leverage this API to provide instant, accurate updates without reloading a page or deleting application state.

The dependency parsing and pre-build section has been completed, and the source code parsing for the plugin and HMR will be available in two weeks.

Vite is 2.3.3.

Better online document viewing experience

After reading helpful can enter github give me a 🌟 small star thank you!

NPM relies on parsing and prebuild

directory

  1. Code entry
  2. Pre-build objects and pre-prepare
  3. Builds and plug-ins
  4. The last

1. Code entry

In the cli.ts file, receive the command line operation parameters.

// Start vite by entering the command line
npm run dev
// Call vite from package and get command arguments such as --force build...
vite xxxx xxx xxx
Copy the code

The first step of running vite is to get command parameters, and finally to create a server and run listen.

//cli.ts

.action(async (root: string.options: ServerOptions & GlobalCLIOptions) => {
        const { createServer } = await import('./server')
        try {
                const server = await createServer({
                ...
                })
                await server.listen()
        } catch(e) { ... }})Copy the code

The runOptimize function in listen is the pre-built core code.

// server/index.ts => listen
if(! middlewareMode && httpServer) {// overwrite listen to run optimizer before server start
  const listen = httpServer.listen.bind(httpServer);
  httpServer.listen = (async (port: number. args:any[]) = > {try {
      await container.buildStart({});
      await runOptimize();
    } catch (e) {
      httpServer.emit('error', e);
      return;
    }
    returnlisten(port, ... args); })as any; . }else {
  await container.buildStart({});
  await runOptimize();
}

// server/index.ts
import { DepOptimizationMetadata, optimizeDeps } from '.. /optimizer'

const runOptimize = async() = > {if (config.cacheDir) {
    server._isRunningOptimizer = true;
    try {
      server._optimizeDepsMetadata = await optimizeDeps(config);
    } finally {
      server._isRunningOptimizer = false; } server._registerMissingImport = createMissingImporterRegisterFn(server); }};Copy the code
// server/index.ts
import { DepOptimizationMetadata, optimizeDeps } from '.. /optimizer'

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

The entry code is simple: after obtaining the Vite command-line arguments, create the internal server and trigger the build of the various functions.

Next comes the section on optimizeDeps.

Pre-build objects and pre-prepare

The path to the pre-cached (metadata.json) and the pre-built hash value are first obtained for subsequent comparison.

This JSON file is the exported data information processed by Vite. When the file exists, it compares the hash values. If they are the same, it directly reads the dependencies in the file.

// /optimizer.ts
async function optimizeDeps(
  config: ResolvedConfig,
  force = config.server.force,
  asCommand = false, newDeps? : Record<string.string>,
) {
  const { root, logger, cacheDir } = config
   // The third args is asCommand
   // For the sake of smoothness, the optimizeDeps function is run directly after vite --force, so we need to distinguish the log output mode
   // vite --force => await optimizeDeps(config, options.force, true)
  const log = asCommand ? logger.info : debug

  if(! cacheDir) { log(`No cache directory. Skipping.`)
    return null

  // Get the prebuilt module path first
  const dataPath = path.join(cacheDir, '_metadata.json'); // Precache path
  / / /... /my-vue-app/node_modules/.vite/_metadata.json
  const mainHash = getDepHash(root, config);
  // Create a data object, which will be used later
  const data: DepOptimizationMetadata = {
    hash: mainHash,
    browserHash: mainHash,
    optimized: {}};Copy the code

How do I get the hash value?

We first get the path to the prebuilt module, which is node_modules/.vite by default.

The following data structure is metadata.json, which will be covered later.

// node_modules/.vite/_metadata.json
{
  "hash": "9a4fa980"."browserHash": "6f00d484"."optimized": {
    "vue": {
      "file": "/... /my-vue-app/node_modules/.vite/vue.js"."src": "/... /my-vue-app/node_modules/vue/dist/vue.runtime.esm-bundler.js"."needsInterop": false
    },
    "axios": {
      "file": "/... /new/my-vue-app/node_modules/.vite/axios.js"."src": "/... /new/my-vue-app/node_modules/axios/index.js"."needsInterop": true}}}Copy the code

Now let’s look at the getDepHash function. As described in the official documentation, Vite uses the following sources to determine whether to rerun a prebuild before it is prebuilt.

  • List of Dependencies in package.json
  • The lockfile of the package manager, such as package-lock.json, yarn.lock, or pnpm-lock.yaml
  • It may have been configured in the relevant fields of viet.config.js

In the following code, the variable lockfileFormats is the locakfile of the package manager.

// /optimizer.ts 
const lockfileFormats = ['package-lock.json'.'yarn.lock'.'pnpm-lock.yaml'];

// /optimizer.ts => getDepHash
let cachedHash: string | undefined;

function getDepHash(root: string, config: ResolvedConfig) :string {
  if (cachedHash) {
    return cachedHash;
  }
  let content = lookupFile(root, lockfileFormats) || ' '; // Slide down to the explanation of lookupFile function.
  // Select * from local file array

  // also take config into account
  // only a subset of config options that can affect dep optimization

  content += JSON.stringify(
    {
      mode: config.mode,
      root: config.root,
      resolve: config.resolve,
      assetsInclude: config.assetsInclude,
      plugins: config.plugins.map((p) = > p.name),
      optimizeDeps: {
        include: config.optimizeDeps? .include,// null
        exclude: config.optimizeDeps? .exclude,//null}},(_, value) = > {
      if (typeof value === 'function' || value instanceof RegExp) {
        return value.toString();
      }
      returnvalue; });// Finally return "9a4fa980" eight-digit hash.
  return createHash('sha256').update(content).digest('hex').substr(0.8);
}

// /optimizer.ts => lookupFile
function lookupFile(
  dir: string,
  formats: string[],
  pathOnly = false.) :string | undefined {
  for (const format of formats) {
    const fullPath = path.join(dir, format); // Obtain the root + format path
    // The path object exists and is a file
    // If pathOnly is true, only the path is returned. Otherwise, utF-8 file contents are returned by default
    if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
      return pathOnly ? fullPath : fs.readFileSync(fullPath, 'utf-8'); }}const parentDir = path.dirname(dir);
  if(parentDir ! == dir) {returnlookupFile(parentDir, formats, pathOnly); }}Copy the code

Whether to force optimization and process the. Vite folder

With the pre-built hash value in hand, let me step back into the optimizeDeps function and continue.

Use the force parameter to determine whether to force optimization. If not, compare the old hash values. If they are equal, return the old metadata.json file contents.

Finally, the.vite folder is processed for subsequent preparation.

// /optimizer.ts.const data: DepOptimizationMetadata = {
    hash: mainHash, //"9a4fa980"
    browserHash: mainHash, //"9a4fa980"
    optimized: {}};// Whether to force pre-tuning regardless of whether it has been changed.
// force = config.server.force Is obtained from cli.ts. Check whether --force is included in the command line parameter
if(! force) {let prevData;
  try {
    // Try parsing existing metadata data to obtain the contents of /.vite/metadata.json
    prevData = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
  } catch (e) {}
  // hash is consistent, no need to re-bundle
  // If the hash of the pre-dep data is the same, skip it directly, and if overwriting is required, use --force
  if (prevData && prevData.hash === data.hash) {
    log('Hash is consistent. Skipping. Use --force to override.');
    returnprevData; }}// If node_modules/. Vite exists, clear it.
if (fs.existsSync(cacheDir)) {
  emptyDir(cacheDir);
} else {
  // Otherwise create the folder and return recursive: true to create the folder path
  fs.mkdirSync(cacheDir, { recursive: true });
}
Copy the code

Gets the module path to which the dependency needs to be compiled

After solving the.vite folder, we follow the code to deal with the contents of the.vite file.

Two variables deps and missing are created here.

Deps: Path objects that need to handle dependencies.

Missing: An array object that needs to handle dependencies but has no source found in node_modules.

//deps
{
  "vue": "/... /my-vue-app/node_modules/vue/dist/vue.runtime.esm-bundler.js"."axios": "/... /my-vue-app/node_modules/axios/index.js"
}
Copy the code

It is important to know in advance that the newDeps args parameter distinguishes between a first build and a build that encounters a new dependency import rewrite run after it has been started.

// /optimizer.ts

let deps: Record<string.string>, missing: Record<string.string>;
// If a new dependency import is encountered after the server has started,
Vite will rerun the dependency build process and reload the page while the dependency is not in the cache.
// As mentioned in the official document above, deps and Missing are the final results
if(! newDeps) {// scanImports is used to retrieve import sources, use re detection, and compile all entry dependencies using esbuild
  ({ deps, missing } = await scanImports(config));
} else {
  deps = newDeps;
  missing = {};
}
// Rewrite updated the browser hash
// update browser hash
data.browserHash = createHash('sha256')
  .update(data.hash + JSON.stringify(deps))
  .digest('hex')
  .substr(0.8);
Copy the code

Module processing without source found (missing)

The following code is simple, dealing with modules whose source is not found in node_modules.

// /optimizer.ts

// Missing is an array object that stores dependencies that need to be handled but can't be found in node_modules.
const missingIds = Object.keys(missing);
if (missingIds.length) {
  throw new Error(
    `The following dependencies are imported but could not be resolved:\n\n  ${missingIds
      .map(
        (id) =>
          `${chalk.cyan(id)} ${chalk.white.dim(
            `(imported by ${missing[id]}) `)},`,
      )
      .join(`\n `)}\n\nAre they installed? `,); }Copy the code

Get and import custom mandatory prebuild (include)

Then process optimizedeps.include in viet.config.js.

As stated in the official documentation API,

Optimizedeps.include: By default, linked packages that are not in node_modules are not prebuilt. Use this option to force the prebuild of linked packages

// /optimizer.ts

// Check whether there are dependencies that need to be forced to build in config. Add them in deps after processing
constinclude = 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)}`,); }}}}Copy the code

The command line prints the information needed to build the module

// /optimizer.ts

const qualifiedIds = Object.keys(deps);
// Needless to say, it is easy to skip without deP dependencies
if(! qualifiedIds.length) { writeFile(dataPath,JSON.stringify(data, null.2));
  log(`No dependencies to bundle. Skipping.\n\n\n`);
  return data;
}

// There is no need to explain too much here, basically just print out the logic of the information, and then the green highlights to tell you to pre-cache blah blah blah
const total = qualifiedIds.length;
const maxListed = 5;
const listed = Math.min(total, maxListed);
const extra = Math.max(0, total - maxListed);
const depsString = chalk.yellow(
  qualifiedIds.slice(0, listed).join(`\n `) +
    (extra > 0 ? `\n (... and${extra} more)` : ` `));if(! asCommand) {if(! newDeps) {// This is auto run on server start - let the user know that we are
    // pre-optimizing deps
    logger.info(
      chalk.greenBright(`Pre-bundling dependencies:\n  ${depsString}`)); logger.info(`(this will be run only when your dependencies or config have changed)`,); }}else {
  logger.info(chalk.greenBright(`Optimizing dependencies:\n  ${depsString}`));
}
Copy the code

Create a pre-built object

The es-module-Lexer module is used to get the pre-built module files in each DEPS, output and save the imported and exported data.

// /optimizer.ts

import { ImportSpecifier, init, parse } from 'es-module-lexer';

// esbuild generates nested directory output with lowest common ancestor base
// this is unpredictable and makes it difficult to analyze entry / output
// mapping. So what we do here is:
// 1. flatten all ids to eliminate slash
// 2. in the plugin, read the entry ourselves as virtual files to retain the
// path.
const flatIdDeps: Record<string.string> = {};
const idToExports: Record<string, ExportsData> = {};
const flatIdToExports: Record<string, ExportsData> = {};
// Run the es-module-lexer initialization function, which will be used later
await init;

for (const id in deps) {
  // Replace the slash in the ID with the underscore node/ ABC => node_abc
  const flatId = flattenId(id);
  flatIdDeps[flatId] = deps[id];
  // Get the file contents for each dependent source
  //{ vue: '/... /my-vue-app/node_modules/vue/dist/vue.runtime.esm-bundler.js',
  // 'element-plus': '/... /my-vue-app/node_modules/element-plus/lib/index.esm.js',
  //  axios: '/.../my-vue-app/node_modules/axios/index.js' }
  const entryContent = fs.readFileSync(deps[id], 'utf-8');
  // Parse comes from es-module-lexer. This package is a very small JS module parser
  // ExportsData is an array, [0] imports, [1] exports
  const exportsData = parse(entryContent) as ExportsData;

  /* ss/se => statement start/end {number} import Parse returns values => ss = 0 se = 60 entryContent.slice(0, 60) => "import {initCustomFormatter, warn } from '@vue/runtime-dom'" entryContent.slice(62, 95) => "export * from '@vue/runtime-dom '
  for (const { ss, se } of exportsData[0]) {
    const exp = entryContent.slice(ss, se);
    if (/export\s+\*\s+from/.test(exp)) {
      exportsData.hasReExports = true; / / to be determined}}// Record exportsData with id flatId respectively
  // exportsData contains import and export data for each build module.
  idToExports[id] = exportsData;
  flatIdToExports[flatId] = exportsData;

}
Copy the code

conclusion

In the code described above, let’s take stock of the current logic.

  1. Gets the contents of the pre-built module (hash values, optimized objects, and so on).
  2. Get the hash value of the package manager’s lockfile transformation to determine if the prebuild needs to be rerun.
  3. Get module paths for dependencies that need to be compiled (DEPS) and modules that need to be compiled but whose source was not found (missing).
  4. Process the missing array and print an error message indicating whether the source has been installed.
  5. Get the custom mandatory pre-built module path (include) from vite.config.js and add it to the DEps object.
  6. The command line prints the information needed to build the module.
  7. Create a pre-built object, get the imported export data in the pre-built object and record it.

Now that we have the dePS objects we need to build, we move on to the next chapter to parse the DEPS objects.

3. Builds and plug-ins

This section prepares you for building and customizing plug-ins for Vite.

Build (build)

A few parameters to note:

  1. Format is set to ESM, which is one of the purposes of Vite to treat all code as native ES modules.

    CommonJS and UMD compatibility: During development, Vite’s development server treats all code as native ES modules. Therefore, Vite must first convert dependencies published as CommonJS or UMD to ESM.

  2. Splitting is set to true, which is only applicable to splitting modules introduced by multiple files into separate files under THE output of ESM. When browsing page A, AXIos is loaded, and when entering page B, axios is directly called, saving the operation of requesting AXIos again.

    Code shared between multiple entry points is split off into a separate shared file that both entry points import. That way if the user first browses to one page and then to another page, they don’t have to download all of the JavaScript for the second page from scratch if the shared part has already been downloaded and cached by their browser.

    Code referenced through an asynchronous import() expression will be split off into a separate file and only loaded when that expression is evaluated. This allows you to improve the initial download time of your app by only downloading the code you need at startup, and then lazily downloading additional code if needed later.

  3. Plugins contain the Vite plugin esbuildDepPlugin: this plugin is explained in more detail below.

  4. TreeShaking is set to ignore-Annotations, a notation mentioned in the documentation for ignoring useless code to reduce the size of the module.

// /optimizer/index.ts

// The core is packaged with esBuild
const result = await build({
  entryPoints: Object.keys(flatIdDeps),
  bundle: true.// Any imported dependencies are packaged together
  format: 'esm'.// Comply with vITE conversion to ESM
  external: config.optimizeDeps? .exclude,// Modules that do not need to be processed
  logLevel: 'error'.// Log level. Only errors are displayed
  // Split the code, which is simply split the shared import file in the entry, loading axios when accessing the a page,
  // Go to page B and use axios loaded directly from page A to save the process of requesting again.
  splitting: true.sourcemap: true.// This needs no further explanation
  outdir: cacheDir, //vite specifies the default cache folder, node_modules/.vite
  // Trim the branches? Annotations By default, delete code that is not useful, and ignore-annotations refer to ignoring code that would damage the package if deleted
  treeShaking: 'ignore-annotations'.metafile: true.// Generate meta JSON
  define, // Replace the identifier
  plugins: [...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config)], ... esbuildOptions, });Copy the code

Esbuild plug-in

Those who know about plug-ins for ESBuild can skip this section, which briefly explains the structure of plug-ins:

(1) The esbuild Plugin is an object structure containing name and setup. Name is the plug-in name, and setup is a function that receives builds.

(2) The main logic is in the setup function, build.onResolve and build.onLoad respectively.

Build. onResolve: This function intercepts the corresponding import path, modifies the path, and marks the specific namespace.

Build. onLoad: This function accepts and filters all incoming items marked with the enV-ns namespace, telling ESBuild what to do with them.

let envPlugin = {
  name: 'env'.setup(build) {
    // The first argument is the interception rule. In the following example, a path named 'env' is intercepted with a re.
    // The second argument is a function that returns the object containing the path (which can be modified and returned here) and the namespace marked 'env-ns'.
    build.onResolve({ filter: /^env$/ }, (args) = > ({
      path: args.path,
      namespace: 'env-ns',}));// The first argument is to receive a path with the env-ns namespace and filter it.
    // The second argument is a function that tells esbuild to return jSON-formatted environment variables in the env-ns namespace.
    build.onLoad({ filter: /. * /.namespace: 'env-ns' }, () => ({
      contents: JSON.stringify(process.env),
      loader: 'json',})); }};require('esbuild')
  .build({
    entryPoints: ['app.js'].bundle: true.outfile: 'out.js'.plugins: [envPlugin],
  })
  .catch(() = > process.exit(1));
Copy the code

esbuildDepPlugin

First, let’s look at some of the functions used by the Vite plugin:

// /optimizer/esbuildDepPlugin.ts

export function esbuildDepPlugin(
  qualified: Record<string.string>,
  exportsData: Record<string, ExportsData>,
  config: ResolvedConfig,
) :Plugin;
Copy the code

(1) Two parsers are created, corresponding respectivelyesmcommonjs.

// /optimizer/esbuildDepPlugin.ts

// default resolver which prefers ESM
const _resolve = config.createResolver({ asSrc: false });

// cjs resolver that prefers Node
const _resolveRequire = config.createResolver({
  asSrc: false.isRequire: true});Copy the code

(2) to createresolveFunction to determine what type of module it is and return the corresponding parser result.

// /optimizer/esbuildDepPlugin.ts

const resolve = (
  id: string.importer: string.kind: ImportKind, resolveDir? :string,
): Promise<string | undefined> = > {let _importer;
  // explicit resolveDir - this is passed only during yarn pnp resolve for
  // entries
  If the folder is passed in, get the folder path of the absolute path
  if (resolveDir) {
    _importer = normalizePath(path.join(resolveDir, The '*'));
  } else {
    // map importer ids to file paths for correct resolution
    /** * if mporter is in external flatIdDeps, * {* vue: '/Users/kev1nzh/Desktop/new/my-vue-app/node_modules/vue/dist/vue.runtime.esm-bundler.js', * axios: '/ Users/kev1nzh/Desktop/new/my - vue - app/node_modules/axios/index, js' *} * if access to the path of the value * /
    _importer = importer in qualified ? qualified[importer] : importer;
  }
  // Call resolveRequire to filter out modules whose kind is require-resolve, require-call
  const resolver = kind.startsWith('require')? _resolveRequire : _resolve;// Return the resolved path. The code for this function will be described in a later section
  return resolver(id, _importer);
};
Copy the code

(3) to createresolveEntryFunction that returns a namespace based on the incoming type.

function resolveEntry(id: string, isEntry: boolean, resolveDir: string) {
  const flatId = flattenId(id);
  if (flatId in qualified) {
    return isEntry
      ? {
          path: flatId,
          namespace: 'dep',}, {path: require.resolve(qualified[flatId], {
            paths: [resolveDir], }), }; }}Copy the code

(4) ViteonResolve

Vite creates two onResolve, one to handle JS files and one to handle non-JS files.

Handling non-js:

// /optimizer/esbuildDepPlugin.ts

// This onResolve will handle non-JS files

// An array of non-js files
const externalTypes = [
  'css'.'less'.'sass'. ] ; build.onResolve( {// Regex matches files formatted in the externalTypes array
    filter: new RegExp(` \ \. (` + externalTypes.join('|') + `) (\ \? . *)? $`),},async ({ path: id, importer, kind }) => {
    Importer {string} Specifies the path to the importer module to be packaged
    / / kind {string} import rules | 'entry - point' | 'import - the statement' | 'the require - call' | 'dynamic - import' | 'the require - resolve' | 'import-rule'| 'url-token'
    const resolved = await resolve(id, importer, kind);
    if (resolved) {
      // returns the mark special processing and returns the path to the imported file
      return {
        path: resolved,
        external: true}; }});Copy the code

Handling js files:

The following code is the most exciting part of Vite, and I’ll probably create a new section to explain it.

// /optimizer/esbuildDepPlugin.ts

// This onResolve will handle js files

build.onResolve(
  { filter: /^[\w@][^:]/ },
  async ({ path: id, importer, kind, resolveDir }) => {
    /**
      id:  vue
      importer:
      kind:  entry-point

      id:  @vue/runtime-dom importer: /Users/kev1nzh/Desktop/new/my-vue-app/node_modules/vue/dist/vue.runtime.esm-bundler.js kind: As shown above, Vite divides pre-packaged modules into entry modules and dependent modules, such as axios Vue, which we import in the project. The run-time dom module is a dependency module of the entry module in the project in package-lock.json, which is distinguished and processed by the following code. * /
    constisEntry = ! importer;// ensure esbuild uses our resolved entries
    let entry;
    // if this is an entry, return entry namespace resolve result
    // If it is the entry, return the namespace named dep to do the next operation
    if ((entry = resolveEntry(id, isEntry, resolveDir))) return entry;

    // check if this is aliased to an entry - also return entry namespace
    const aliased = await _resolve(id, undefined.true);
    if (aliased && (entry = resolveEntry(aliased, isEntry, resolveDir))) {
      return entry;
    }

    // use vite's own resolver
    // ok starts the dependent module process, resolve
    const resolved = await resolve(id, importer, kind);
    if (resolved) {
      // vite custom id const browserExternalId = '__ite -browser-external'
      // Returns the namespace and ID, ignored modules that cannot be handled because of browser compatibility issues
      if (resolved.startsWith(browserExternalId)) {
        // Return to browser-external namespace processing and return id
        return {
          path: id,
          namespace: 'browser-external'}; }// Whether it is a non-JS or external file, returns the same processing as onResolve
      if (isExternalUrl(resolved)) {
        return {
          path: resolved,
          external: true}; }return {
        path: path.resolve(resolved), }; }});Copy the code

(5) ViteonLoad

Dep namespace processing, the following code is a bit complex, simple logic.

The first step is to get the entry path for each entry module, for example axios’ entryFile is /… /my-vue-app/node_modules/axios/index.js,

Convert to a relativePath and add the prefix node_modules/axios/index.js.

The second step is to determine the commonJS, default, and export from types based on exportsData (exported and imported from parse).

Contents => export default require(“./node_modules/axios/index.js”).

Third, obtain the suffix ext based on the path of the entry module.

Finally, the object is returned.

/** * loader {string} tells esbuild to parse to js/ CSS /.... Contents: {string} Load contents */
return {
  loader: ext as Loader,
  contents,
  resolveDir: root,
};
Copy the code
// Get the path to the project
const root = path.resolve(config.root);
build.onLoad({ filter: /. * /.namespace: 'dep' }, ({ path: id }) => {
  // import file vue => /... /my-vue-app/node_modules/vue/dist/vue.runtime.esm-bundler.js
  const entryFile = qualified[id];
  // Get the original path
  let relativePath = normalizePath(path.relative(root, entryFile));
  // here to handle.abc.js =>./abc.js
  if(! relativePath.startsWith('. ')) {
    relativePath = `. /${relativePath}`;
  }

  let contents = ' ';
  const data = exportsData[id];
  const [imports, exports] = data;
  // The following are the processes for handling different modules
  if(! imports.length && !exports.length) {
    // cjs
    // export default require("./node_modules/axios/index.js");
    contents += `export default require("${relativePath}"); `;
  } else {
    if (exports.includes('default')) {
      // default
      // import d from "./node_modules/element-plus/lib/index.esm.js"; export default d;
      contents += `import d from "${relativePath}"; export default d; `;
    }
    if (data.hasReExports || exports.length > 1 || exports[0]! = ='default') {
      // hasReExports
      // export * from "./node_modules/vue/dist/vue.runtime.esm-bundler.js"
      contents += `\nexport * from "${relativePath}"`; }}// Get the suffix of the entry file
  let ext = path.extname(entryFile).slice(1);
  if (ext === 'mjs') ext = 'js';
  /** * loader {string} tells esbuild to parse to js/ CSS /.... The following is an example of how to handle the runtime dom: * {* ext: 'js', * contents: "export * from "./node_modules/vue/dist/vue.runtime.esm-bundler.js", * resolveDir: '.... /node_modules/vue/dist' * } */
  return {
    loader: ext as Loader,
    contents,
    resolveDir: root,
  };
});
Copy the code

conclusion

  1. After obtaining the DEPS object from the pre-built object and pre-preparation section in the previous chapter, the packaging function of ESBuild is called.

  2. Pass it into a Vite custom plug-in, sorted by file type.

  3. Tell esbuild to split it into entry modules and dependencies and then write the final package file to /node_modules/.vite.

4. The last

After all dependencies are built, write to /node_modules/. Vite. If dependencies are added or changed, the.vite will be overwritten. Each time you start a project, if you have a prebuild file, you can start it directly without rewriting the packaged dependencies each time.

ECMA Script Modules(ESM), although 2021, many front-end has been using the latest technology and code to do the project, but there are many, many, many very useful Modules were created several years ago, those module export mechanism is various, by Vite unified conversion into ESM way, Just provide the source code, and let the browser take over the packaging. When a page needs a module, Vite simply transforms and returns the ESM-style source code.

After reading this chapter has a harvest of friends, can go togithubClick a like, the follow-up has the corresponding source code analysis or share, thank you.