The "toy vite" developed by you Yuxi a few years ago has only more than 100 lines of code, but it is very helpful to understand the principle of vite

1. Preface

Hello, I'm Ruokawa . Recently organized Source code co reading activity , you can add me wechat if you are interested ruochuan12 Participation has been carried out for more than two months. We exchange and study together and make common progress.

Want to learn the source code, I highly recommend what I wrote before Learning source code overall architecture series It contains more than ten source code articles, such as jQuery, underscore, lodash, vuex, sentry, axios, redux, koa, Vue devtools, vuex4, koa compose, Vue next release, Vue this, create Vue, etc.

Recently organized Source code co reading activity , let's learn the source code together. So a variety of search is worth learning, and the source code with a small number of lines of code.

stay vuejs organization Next, I found the "toy vite" written by you Yuxi a few years ago
vue-dev-server , found 100 lines of code, it is worth learning. So there is this article.

After reading this article, you will learn:

1. learn vite Simple principle
2. Learn to use VSCode Debugging source code
3. Learn how to compile Vue Single file component
4. Learn how to use recast generate ast Convert file
5. How to load package files
6. wait

2. What is the principle of Vue dev server

vue-dev-server#how-it-works
There are four English sentences in the README document.

find Google translation It's quite accurate. I'll carry it intact.

  • Browser requested import as native ES module - no bundle.
  • The server intercepts requests for *. vue files, compiles them on the fly, and then sends them back as JavaScript.
  • For libraries built with ES modules that work in browsers, simply import them directly from CDN.
  • The npm package (package name only) imported into the. js file is rewritten immediately to point to the locally installed file. Currently, only vue is supported as a special case. Other packages may need to be converted to be exposed as local browser target ES modules.

You can also have a look vitejs document , understand the principle. The drawings in the document are very good.

After reading this article, I believe you will have a deeper understanding.

3. Preparation

3.1 cloning project

In this paper, Vue dev server analysis is used to find a star^_^

# Recommend cloning my warehouse
git clone https://github.com/lxchuan12/vue-dev-server-analysis.git
cd vue-dev-server-analysis/vue-dev-server
# npm i -g yarn
# Installation dependency
yarn

# Or clone the official warehouse
git clone https://github.com/vuejs/vue-dev-server.git
cd vue-dev-server
# npm i -g yarn
# Installation dependency
yarn

Generally speaking, let's start with the package.json file:

// vue-dev-server/package.json
{
  "name": "@vue/dev-server",
  "version": "0.1.1",
  "description": "Instant dev server for Vue single file components",
  "main": "middleware.js",
  // Specify executable commands
  "bin": {
    "vue-dev-server": "./bin/vue-dev-server.js"
  },
  "scripts": {
    // First jump to the test folder, and then execute the Vue dev server file on the Node
    "test": "cd test && node ../bin/vue-dev-server.js"
  }
}

According to the scripts test command. Let's look at the test folder.

3.2 test folder

There are three files in the Vue dev server / test folder. The code is not long.

  • index.html
  • main.js
  • text.vue

As shown in the figure below.

Then we find the Vue dev server / bin / Vue dev server.js file, and the code is not long.

3.3 vue-dev-server.js

// vue-dev-server/bin/vue-dev-server.js
#!/usr/bin/env node

const express = require('express')
const { vueMiddleware } = require('../middleware')

const app = express()
const root = process.cwd();

app.use(vueMiddleware())

app.use(express.static(root))

app.listen(3000, () => {
  console.log('server running at http://localhost:3000')
})

It turns out that express started the service of port 3000. The focus is on vueMiddleware middleware. Then let's debug the middleware.

Since it is estimated that many small partners have not used VSCode debugging, here is a detailed description of how to debug the source code. After learning to debug the source code, the source code is not as difficult as expected.

3.4 commissioning items with VSCode

In the Vue dev server / bin / Vue dev server.js file, the line app.use(vueMiddleware()) is marked with a breakpoint.

Find the scripts of Vue dev server / package.json, move the mouse over the test command, and the run script and debug script commands will appear. As shown in the following figure, select the debug script.

Click the enter function (F11) button to enter the vueMiddleware function. If you find that the breakpoint goes to a file that is not the project and you don't want to read it or don't understand it, you can quit or start again. It can be opened in browser traceless (Privacy) mode (Shortcut Ctrl + Shift + N to prevent plug-in interference) http://localhost:3000 , you can continue debugging the functions returned by the vueMiddleware function.

If your VSCode is not Chinese (not used to English), you can install it Simplified Chinese plugin.

If VSCode does not have this debugging function. It is recommended to update to the latest version of VSCode (current version v1.61.2).

Then let's follow the debugging to learn the source code of vueMiddleware. You can look at the main line first and continue breakpoint debugging where you think it is important.

4. vueMiddleware source code

4.1 comparison with and without vueMiddleware Middleware

When it is not in the debugging state, we can annotate app.use(vueMiddleware()) in the Vue dev server / bin / Vue dev server.js file and execute npm run test to open it http://localhost:3000 .

After enabling the middleware again, see the figure below.

Look at the picture, we can see the difference.

4.2 overview of vuemiddleware Middleware

We can find vue-dev-server/middleware.js to see an overview of this middleware function.

// vue-dev-server/middleware.js

const vueMiddleware = (options = defaultOptions) => {
  // ellipsis
  return async (req, res, next) => {
    // ellipsis
    // Processing files ending in. vue
    if (req.path.endsWith('.vue')) {
    // Processing files ending in. js
    } else if (req.path.endsWith('.js')) {
    // Right/__ modules /
    } else if (req.path.startsWith('/__modules/')) {
    } else {
      next()
    }
  }
}
exports.vueMiddleware = vueMiddleware

vueMiddleware finally returns a function. This function mainly does four things:

  • Processing files ending in. vue
  • Processing files ending in. js
  • Right/__ modules /
  • If not, execute the next method and hand over the control to the next middleware

Then let's see how to deal with it.

We can also view the implementation in these important places. For example:

4.3 processing files at the end of. vue

if (req.path.endsWith('.vue')) {
  const key = parseUrl(req).pathname
  let out = await tryCache(key)

  if (!out) {
    // Bundle Single-File Component
    const result = await bundleSFC(req)
    out = result
    cacheData(key, out, result.updateTime)
  }

  send(res, out.code, 'application/javascript')
}

4.3.1 bundleSFC compilation single file component

This function, according to @vue/component-compiler Convert the single file component and finally return the file recognized by the browser.

const vueCompiler = require('@vue/component-compiler')
async function bundleSFC (req) {
  const { filepath, source, updateTime } = await readSource(req)
  const descriptorResult = compiler.compileToDescriptor(filepath, source)
  const assembledResult = vueCompiler.assemble(compiler, filepath, {
    ...descriptorResult,
    script: injectSourceMapToScript(descriptorResult.script),
    styles: injectSourceMapsToStyles(descriptorResult.styles)
  })
  return { ...assembledResult, updateTime }
}

Next, let's look at the readSource function implementation.

4.3.2 readSource reads file resources

This function is mainly used to obtain file resources according to the request. Returns the file path filepath, resource source, and update time updateTime.

const path = require('path')
const fs = require('fs')
const readFile = require('util').promisify(fs.readFile)
const stat = require('util').promisify(fs.stat)
const parseUrl = require('parseurl')
const root = process.cwd()

async function readSource(req) {
  const { pathname } = parseUrl(req)
  const filepath = path.resolve(root, pathname.replace(/^\//, ''))
  return {
    filepath,
    source: await readFile(filepath, 'utf-8'),
    updateTime: (await stat(filepath)).mtime.getTime()
  }
}

exports.readSource = readSource

Next, let's look at the processing of. js files

4.4 processing files ending in. js

if (req.path.endsWith('.js')) {
  const key = parseUrl(req).pathname
  let out = await tryCache(key)

  if (!out) {
    // transform import statements
    // Convert import statement 
    // import Vue from 'vue'
    // => import Vue from "/__modules/vue"
    const result = await readSource(req)
    out = transformModuleImports(result.source)
    cacheData(key, out, result.updateTime)
  }

  send(res, out, 'application/javascript')
}

For Vue dev server / test / main.js conversion

import Vue from 'vue'
import App from './test.vue'

new Vue({
  render: h => h(App)
}).$mount('#app')

// The official account: the eye of Sichuan
// Add wechat ruochuan12
// Read the source code together and learn the source code together
import Vue from "/__modules/vue"
import App from './test.vue'

new Vue({
  render: h => h(App)
}).$mount('#app')

// The official account: the eye of Sichuan
// Add wechat ruochuan12
// Read the source code together and learn the source code together

4.4.1 transformmodule import s

recast

validate-npm-package-name

const recast = require('recast')
const isPkg = require('validate-npm-package-name')

function transformModuleImports(code) {
  const ast = recast.parse(code)
  recast.types.visit(ast, {
    visitImportDeclaration(path) {
      const source = path.node.source.value
      if (!/^\.\/?/.test(source) && isPkg(source)) {
        path.node.source = recast.types.builders.literal(`/__modules/${source}`)
      }
      this.traverse(path)
    }
  })
  return recast.print(ast).code
}

exports.transformModuleImports = transformModuleImports

That is, for npm packet conversion. Here is "/ _modules/vue"

import Vue from 'vue' => import Vue from "/__modules/vue"

4.5 right/__ modules /

import Vue from "/__modules/vue"

This code finally returns the read path Vue dev server / node_ Files under modules / Vue / dist / vue.esm.browser.js.

if (req.path.startsWith('/__modules/')) {
  // 
  const key = parseUrl(req).pathname
  const pkg = req.path.replace(/^\/__modules\//, '')

  let out = await tryCache(key, false) // Do not outdate modules
  if (!out) {
    out = (await loadPkg(pkg)).toString()
    cacheData(key, out, false) // Do not outdate modules
  }

  send(res, out, 'application/javascript')
}

4.5.1 loadPkg loading package (only Vue files are supported here)

At present, only Vue files are supported, that is, the read path Vue dev server / node_ The file under modules / Vue / dist / vue.esm.browser.js is returned.

// vue-dev-server/loadPkg.js
const fs = require('fs')
const path = require('path')
const readFile = require('util').promisify(fs.readFile)

async function loadPkg(pkg) {
  if (pkg === 'vue') {
    // route
    // vue-dev-server/node_modules/vue/dist
    const dir = path.dirname(require.resolve('vue'))
    const filepath = path.join(dir, 'vue.esm.browser.js')
    return readFile(filepath)
  }
  else {
    // TODO
    // check if the package has a browser es module that can be used
    // otherwise bundle it with rollup on the fly?
    throw new Error('npm imports support are not ready yet.')
  }
}

exports.loadPkg = loadPkg

So far, we have basically analyzed the main file and some imported files. Have an understanding of the main process.

5. Summary

Finally, let's take a look at the two diagrams above to summarize whether there is vueMiddleware middleware:

After enabling the middleware, see the following figure.

The browser supports native type=module module request loading. Vue dev server intercepts it and returns the content supported by the browser. Because there is no need to package and build, it is very fast.

<script type="module">
    import './main.js'
</script>

5.1 import Vue from 'vue' conversion

// vue-dev-server/test/main.js
import Vue from 'vue'
import App from './test.vue'

new Vue({
  render: h => h(App)
}).$mount('#app')

import statement in main.js
import Vue from 'vue'
adopt recast Generate ast and convert it to import Vue from "/_modules / Vue"
The final return to the browser is Vue dev server / node_ modules/vue/dist/vue.esm.browser.js

5.2 import App from './test.vue' conversion

import App from './test.vue' in main.js
Then use @vue/component-compiler Convert to browser supported files.

5.3 what else can we do in the future?

Due to the limited space of this article, the tryCache part of the cache has not been analyzed at present. In short, it is used node-lru-cache The least recently used for caching (this algorithm is often tested). The source code of this warehouse should be analyzed later. You are welcome to continue to pay attention to me @ Ruochuan.

It is highly recommended that readers use VSCode to debug Vue dev server source code according to the method in this article. There are many details in the source code, which are not fully described due to the limited space in the article.

It is worth mentioning that The master branch of this warehouse , was written by you Yuxi two years ago. Compared with this article, it will be more complex. Spare readers can learn from it.

You can also see it directly vite Source code.

After reading this article, you may find that there are more and more things that the front end can do. You can't help feeling: the water depth at the front end is unpredictable, and only continuous learning is available.

Finally, welcome to wechat ruochuan12 Communication and participation Source code co reading Activities, we learn the source code together and make common progress.

Keywords: Javascript node.js Front-end Vue.js

Added by $Three3 on Mon, 01 Nov 2021 07:57:39 +0200