1. Start from the entrance

1. package.json

// Use the bin field in package.json to find vite
"bin": {
  "vite": "bin/vite.js"
},
// bin/ viet. js specifies node for execution
#!/usr/bin/env node
require('.. /dist/node/cli')
Copy the code

2. cli

// Register the command
cli.command('[root]').action(async(root, argv) => {
  const options = await resolveOptions({ argv, defaultMode: 'development' })
  returnrunServe(options) }) cli.. command('build [root]').action(async(root, argv) => {
  const options = await resolveOptions({ argv, defaultMode: 'production' })
  return runBuild(options)
})
Copy the code

2.1 resolveOptions

function resolveOptions() {
  // To read the user's configuration, you can specify the configuration file through -c
  // require('vite.config.js')
  const userConfig = await resolveConfig()
}
Copy the code

2.2 runServe

function runServer() {
  // Vite1 starts a KOA server
  const server = require('./server').createServer(options)
  server.listen(port, () = >{})}Copy the code

2.3 runBuild

function runBuild() {
  // Use rollup again
  await require('./build')[options.ssr ? 'ssrBuild' : 'build'](options)
}
Copy the code

2. build

// Vite packaging is ultimately handled using rollup
function build() {
  // Package with rollup
  const rollup = require("rollup").rollup;
  const bundle = await rollup();
  await bundle.write();
}
Copy the code

3. server

// Start a KOA server
function createServer(config) {
  const { root = process.cwd() } = config;
  const app = new Koa();
  const server = resolveServer(config, app.callback());
  // HMRWatcher listens for file changes
  const watcher = chokidar.watch(root, {});
  // resolveAlias
  // const resolver = createResolver();
  // Build the context object for the middleware
  const context = {
    root,
    app,
    server,
    watcher,
    config,
    port: config.port || 3000};// Extend the CTX property
  app.use((ctx, next) = > {
    Object.assign(ctx, context);
    return next();
  });
  // A list of middleware
  const resolvedPlugins = [
    // Here are the various middleware
    serveStaticPlugin, // 1. Let koA provide static file service koa-static
  ];
  resolvedPlugins.forEach((m) = > m && m(context));
  const listen = server.listen.bind(server);
  server.listen = async(port, ... args) => {// this function overwrites the listen method runOptimize
    // await require(".. /optimizer").optimizeDeps(config);
    returnlisten(port, ... args); };return server;
}
Copy the code

3.1 serveStaticPlugin

// Use koa-static to provide static services so that we can access the index.html file and request/SRC /index.js resources
// Next we need to process the index.js file
function serveStaticPlugin({ root, app, resolver, config }) {
  app.use(async (ctx, next) => {
    await next();
  });
  app.use(require("koa-etag") ()); app.use(require("koa-static")(root));
  app.use(require("koa-static")(path.join(root, "public")));
  // history api fallback
}
Copy the code

3.2 moduleRewritePlugin

// index.js browser doesn't know how to handle this
Import {createApp} from "/@modules/vue"
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");

const moduleRewritePlugin = ({ root, app, watcher, resolver }) = > {
  app.use(async (ctx, next) => {
    await next();
    const publicPath = ctx.path;
    // Handle js type files. Vue files separately
    if (ctx.body && ctx.response.is("js") && !ctx.url.endsWith(".map")) {
      // Reading stream.tostring () will determine if it is a stream
      const content = await readBody(ctx.body);
      / / dynamic import
      // const importer = removeUnRelatedHmrQuery(
      // resolver.normalizePublicPath(ctx.url)
      // );
      const importer = {};
      / / rewrite the importctx.body = rewriteImports(root, content, importer, resolver); }});// watcher.on("change", async (filePath) => {});
};

/ / rewrite the import
function rewriteImports(root, source, importer, resolver) {
  let imports = [];
  // es-module-lexer const [imports, exports] = parse(source, {})
  imports = parse(source)[0];
  if (imports.length) {
    // magic-string Specifies the operation string
    const s = new MagicString(source);
    for (let i = 0; i < imports.length; i++) {
      // Do not consider dynamic
      const { s: start, e: end } = imports[i];
      // if (isExternalUrl(id)) {
      // continue
      // }
      // plus /@modules/ also handles relative paths./foo -> /some/path/foo, etc
      // const resolved = resolveImport(root, importer, id, resolver);
      // s.overwrite(start, end);
      let id = source.substring(start, end);
      if (/ / ^ ^ \ / \].test(id)) {
        id = `/@modules/${id}`;
        // Modify the path to add the /@modules prefixs.overwrite(start, end, id); }}return s.toString();
  } else {
    returnsource; }}Copy the code

3.3 moduleResolvePlugin

// @modules/vue could not be found parsing @modules
const moduleRE = /^\/@modules\//;
const moduleResolvePlugin = ({ root, app, resolver }) = > {
  // Handle vue find the esM module under @vue/
  const vueResolved = resolveVue(root);
  app.use(async (ctx, next) => {
    // Modules that are not @modules are directly next
    if(! moduleRE.test(ctx.path))return next();
    // Remove the @modules prefix
    const id = decodeURIComponent(ctx.path.replace(moduleRE, ""));
    ctx.type = "js";
    const serve = async (id, file) => {
      Moduleidtofilemap. set(id, file) // Cache cache
      // ctx.read = cachedRead.bind(null, ctx) fs.readFile
      await ctx.read(file);
      return next();
    };
    if (id in vueResolved) {
      return serve(id, vueResolved[id]);
    }
    // Third party module processing?
    // const referer = ctx.get("referer");
    // let importer;
    // // map file
    // const isMapFile = ctx.path.endsWith(".map");
    // const importerFilePath = importer ? resolver.requestToFile(importer) : root;
    // const nodeModuleInfo = resolveNodeModule(root, id, resolver);
    // return serve(id, nodeModuleFilePath, "node_modules");
  });
};
// Handle vUE
const resolveVue = (root) = > {
  let vueVersion;
  let vueBasePath;
  let compilerPath;
  // Find package.json file
  const projectPkg = JSON.parse(lookupFile(root, ["package.json") | |` ` {});
  / / find the vue
  letisLocal = !! (projectPkg.dependencies && projectPkg.dependencies.vue);// Find vue's package resolve.sync()
  const userVuePkg = resolveFrom(root, "vue/package.json");
  vueBasePath = path.dirname(userVuePkg);
  vueVersion = fs.readJSONSync(userVuePkg).version;
  // Parse vue single file package
  const compilerPkgPath = resolveFrom(root, "@vue/compiler-sfc/package.json");
  const compilerPkg = require(compilerPkgPath);
  compilerPath = path.join(path.dirname(compilerPkgPath), compilerPkg.main);
  // Various files of vue and modules of vue3
  const resolvePath = (name, from) = >
    resolveFrom(from.`@vue/${name}/dist/${name}.esm-bundler.js`);
  const runtimeDomPath = resolvePath("runtime-dom", vueBasePath);
  const runtimeCorePath = resolvePath("runtime-core", runtimeDomPath);
  const reactivityPath = resolvePath("reactivity", runtimeCorePath);
  const sharedPath = resolvePath("shared", runtimeCorePath);
  return {
    version: vueVersion,
    vue: runtimeDomPath,
    "@vue/runtime-dom": runtimeDomPath,
    "@vue/runtime-core": runtimeCorePath,
    "@vue/reactivity": reactivityPath,
    "@vue/shared": sharedPath,
    compiler: compilerPath,
    isLocal,
  };
};
Copy the code

3.4 vuePlugin

// Handle app.vue vue single file
// 1. Rewrite the content of app.vue
// 2. Process the template file
// 3. Handle style content
const vuePlugin = () = > {
  // Only vue files are processed
  if(! ctx.path.endsWith(".vue") && !ctx.vue) { return next(); }
  // The vue content will be overwritten to introduce template and style to be specified via query
  const query = ctx.query;
  const publicPath = ctx.path;
  const filePath = path.join(root, publicPath);
  // Parse single-file components
  / / {
  // filename: '',
  // source: '',
  // template: {
  // type: 'template',
  // content: '',
  // ast: ''
  / /},
  // script: {
  // type: 'script',
  / /},
  // styles: [
  // { type: 'style', content: ''}
  / /]
  // }
  const descriptor = await parseSFC(root, filePath, ctx.body);
  if(query.type) {
    ctx.type = "js";
    const {code} = await compileSFCMain()
    ctx.body = code
  }
  if (query.type === "template") {
    const templateBlock = descriptor.template;
    ctx.type = "js";
    const { code } = compileSFCTemplate()
    ctx.body = code;
  }
  if(query.type === "style") {
    const styleBlock = descriptor.styles[index];
    const result = await compileSFCStyle()
    ctx.type = "js";
    ctx.body = codegenCss()
  }
  if (query.type === "custom") {}// watcher.on('change', file => {handleVueReload()})
}
Copy the code

3.4.1 track parseSFC

// Read the contents of the file through @vue/ compiler-sFC parsing single file parsing
async function parseSFC(root, filePath, content) {
  // Read the contents of the file
  content = await cachedRead(null, filePath, true /* poll */);
  if (typeofcontent ! = ="string") {
    content = content.toString();
  }
  // @vue/compiler- SFC parses single files
  const { parse } = resolveCompiler(root);
  const { descriptor } = parse(content, {
    filename: filePath,
    sourceMap: true});return descriptor;
}
Copy the code

3.4.2 compileSFCMain

// We will change the contents of the.vue single file
async function compileSFCMain(descriptor, filepath, publicPath, root) {
  // creates unique hashes
  const id = hash_sum(publicPath)
  let code = ` `;
  let content = ` `;
  let script = descriptor.script;
  const compiler = resolveCompiler(root);
  // Parse the script contents
  if ((descriptor.script || descriptor.scriptSetup) && compiler.compileScript) {
    script = compiler.compileScript(descriptor, { id });
  }
  if (script) {
    content = script.content;
    // ts
    // if (script.lang === "ts") {
    //   // esbuild的transform
    // const res = await transform(content, publicPath, {
    // loader: "ts",
    / /});
    // content = res.code;
    // }
  }
  // @vue/compiler-sfc
  code += rewriteDefault(content, "__script");
  // Style scoped and CSS-module
  let hasScoped = false;
  // let hasCSSModules = false;
  if (descriptor.styles) {
    descriptor.styles.forEach((s, i) = > {
      const styleRequest = publicPath + `? type=style&index=${i}`;
      if (s.scoped) hasScoped = true;
      if (s.module) {
      } else {
        // The style file is imported and has the type attribute
        code += `\nimport The ${JSON.stringify(styleRequest)}`;
      }
      if (hasScoped) {
        // import "/src/App.vue? type=style&index=0"
        code += `\n__script.__scopeId = "data-v-${id}"`; }}); }// if(descriptor.customBlocks) {}

  // The template becomes the render function
  if (descriptor.template) {
    const templateRequest = publicPath + `? type=template`;
    // import { render as __render } from "/src/App.vue? type=template"
    code += `\nimport { render as __render } from The ${JSON.stringify(
      templateRequest
    )}`;
    // __script.render = __render
    code += `\n__script.render = __render`;
  }
  code += `\n__script.__hmrId = The ${JSON.stringify(publicPath)}`;
  code += `\ntypeof __VUE_HMR_RUNTIME__ ! == 'undefined' && __VUE_HMR_RUNTIME__.createRecord(__script.__hmrId, __script)`;
  // __script.__file = "/Users/liu/vitejs/hello-vite/src/App.vue"
  code += `\n__script.__file = The ${JSON.stringify(filepath)}`;
  code += `\nexport default __script`;
  return { code };
}
Copy the code

Rule 3.4.3 compileSFCTemplate

// The processing template is @vue/ compiler-sFC
function compileSFCTemplate(root, template, filePath, publicPath, scoped) {
  const { compileTemplate } = resolveCompiler(root);
  const id = hash_sum(publicPath);
  const { code } = compileTemplate({
    source: template.content,
    id,
  });
  return { code };
}
Copy the code

3.4.4 compileSFCStyle

// Handle styles
async function compileSFCStyle(root, style, index, filePath, publicPath) {
  // const { generateCodeFrame } = resolveCompiler(root)
  const resource = filePath + `? type=style&index=${index}`
  // Compile CSS postcss? Again using @vue/ Compiler-postCSS used internally in SFC
  const result = await compileCss(root, publicPath, {
    source: style.content, 
    filename: resource,
    id: ` `.// will be computed in compileCss
    scoped: style.scoped ! =null.modules: style.module ! =null.preprocessLang: style.lang // css less scss stylus
  })
  // recordCssImportChain(result.dependencies, resource)
  // result.code = await rewriteCssUrls(result.code, publicPath)
  return result
}
Copy the code

3.4.5 codegenCss

function codegenCss(id, css, modules) {
  let code =
    // clientPublicPath: /vite/client
    `import { updateStyle } from "${clientPublicPath}"\n` +
    `const css = The ${JSON.stringify(css)}\n` +
    `updateStyle(The ${JSON.stringify(id)}, css)\n`
  if (modules) {
    code += dataToEsm(modules, { namedExports: true})}else {
    code += `export default css`
  }
  return code 
}
Copy the code

3.5 clientPlugin

CodegenCss import {updateStyle} from '/vite/client'
const clientPlugin = ({app, config}) = > {
  // Read the contents of client/client.js
  const clientCode = fs.readFileSync(clientFilePath, 'utf-8')
  app.use(async(ctx, next) => {
    if (ctx.path === clientPublicPath) { // /vite/client
      // if (config.hmr){} // socket
      ctx.type = 'js'
      ctx.status = 200
      ctx.body = clientCode
    } else {
      return next()
    }
  })
}
Copy the code

3.6 htmlRewritePlugin

// We inserted a script script in the htmlRewritePlugin middleware
// Changed the contents of our index. HTML
const htmlRewritePlugin = ({ root, app, watcher, resolver, config}) = > {
  
  const devInjectionCode = `\n<script type="module">import "${clientPublicPath}"</script>\n`
  return injectScriptToHtml(html, devInjectionCode)
  app.use(async(ctx, next) => {
    await next()
    if (ctx.response.is('html') && ctx.body) {
      const importer = ctx.path
      const html = await readBody(ctx.body)
      if(! html)return
      ctx.body = await rewriteHtml(importer, html)
      return
    }
  })
  watcher.on('change'.(file) = >{})}Copy the code

3.7 the client/client. Js

// Handle the process environment variable
const __MODE__ = ' '; (window).process = (window).process || {} ; (window).process.env = (window).process.env || {} ; (window).process.env.NODE_ENV = __MODE__
// Handle styles
function updateStyle(id, content) {
  let style = document.createElement('style')
  style.setAttribute('type'.'text/css')
  style.innerHTML = content
  document.head.appendChild(style)
}
// There are many other functions
Copy the code

3.8 summarize

1. We need a static file server to access our page 2. EsModule cannot recognize import {} from 'vue' we need to rewrite the path 3. When introducing @module/ XXX modules we need to parse them. When accessing app.vue, we need to handle the single file. 4.1 Rewrite the contents of app.vue. <script type="module">import "/vite/client"</script> There is a problem with processing in client.jsCopy the code