1. Introduction

Hi, I’m Wakawa. Welcome to follow my official account Ruochuan Vision. Recently, I organized an activity to read the source code together. If you are interested, you can join in with ruoChuan12 on wechat.

If you want to learn source code, Highly recommend before I wrote “learning source code overall architecture series” Includes jQuery, underscore, Lodash, vuex, Sentry, AXIos, Redux, KOA, vue-devtools, vuex4, koa-compose, VUe-next-release, vue-this, creation-V Ue and more than 10 source code articles.

Recently, we organized a source code reading activity to learn source code together. So all kinds of search worth our study, and code lines are not many source code.

Organized by Vuejs, I found the “Toy Vite” Vue-Dev-Server that Yu Had written a few years earlier and found about 100 lines of code that was worth learning. Hence the article.

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 components 4. Learn how to use Recast to generate AST conversion files 5. How to load package file 6. EtcCopy the code

2. Vue-dev-server how does it work

Vue-dev-server #how-it-works README

Found that Google translation is still more accurate, I intact to carry over.

  • Browser requests import as native ES module import – no binding.
  • The server intercepts requests for *.vue files, compiles them on the fly, and sends them back as JavaScript.
  • For libraries that provide ES module builds that work in the browser, simply import them directly from the CDN.
  • NPM packages (package name only) imported into.js files are rewritten immediately to point to locally installed files. Currently, only VUE is supported as a special case. Other packages may need to be transformed to be exposed as the local browser target ES module.

3. Preparation

3.1 Cloning Project

Vue -dev-server-analysis (*

# Recommend clone 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
# install dependencies
yarn

# or clone the official repository
git clone https://github.com/vuejs/vue-dev-server.git
cd vue-dev-server
# npm i -g yarn
# install dependencies
yarn
Copy the code

In general, we’ll 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 an executable command
  "bin": {
    "vue-dev-server": "./bin/vue-dev-server.js"
  },
  "scripts": {
    // Go to the test folder and execute the vue-dev-server file with Node
    "test": "cd test && node .. /bin/vue-dev-server.js"}}Copy the code

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

3.2 Test Folder

Vue-dev-server /test folder has three files, the code is not long.

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

As shown in the figure below.

Vue -dev-server/bin/vue-dev-server.js file 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')})Copy the code

It was Express that started the port 3000 service. The emphasis is on the vueMiddleware middleware. Then we debug the middleware.

In view of the estimated many partners have not used VSCode debugging, here is a detailed description of how to debug the source code. After learning to debug source code, source code is not as difficult as imagined.

3.4 debug the project with VSCode

Vue-dev-server /bin/vue-dev-server.js line app.use(vueMiddleware()) break point.

Locate the scripts for vue-dev-server/package.json and move the mouse over the test command to bring up the run script and debug script commands. Select the debug script as shown below.

Click the Enter Function (F11) button to enter the vueMiddleware function. If you find a breakpoint in a file that is not part of the project, you can exit or start again. You can continue debugging the functions returned by the vueMiddleware function by opening http://localhost:3000 with the browser’s traceless (privacy) mode (Ctrl + Shift + N to prevent plugin interference).

If your VSCode is not in Chinese (not used to English), you can install the simplified Chinese plug-in. If VSCode doesn’t have this debugging feature. It is recommended to update to the latest version of VSCode (currently the latest version is v1.61.2).

Then we follow the debugging to learn vueMiddleware source code. You can look at the main line first and continue breakpoint debugging where you feel it is important.

4. VueMiddleware source code

4.1 Comparison of vueMiddleware middleware

Use (vueMiddleware()) in vue-dev-server/bin/vue-dev-server.js Run NPM run test to open http://localhost:3000.

After enabling the middleware, the following figure is shown.

We get an idea of what the differences are.

4.2 Overview of vueMiddleware Middleware

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

// vue-dev-server/middleware.js

const vueMiddleware = (options = defaultOptions) = > {
  / / to omit
  return async (req, res, next) => {
    / / to omit
    // Process files ending in.vue
    if (req.path.endsWith('.vue')) {
    // process files at the end of.js
    } else if (req.path.endsWith('.js')) {
    // Processes files starting with /__modules/
    } else if (req.path.startsWith('/__modules/')) {}else {
      next()
    }
  }
}
exports.vueMiddleware = vueMiddleware
Copy the code

VueMiddleware eventually returns a function. There are four main things that this function does:

  • right.vueEnd the file for processing
  • right.jsEnd the file for processing
  • right/__modules/Start the file for processing
  • If not, perform the operationnextMethod to hand control to the next middleware

And then let’s see how we do that.

We can also break through these important points to see the implementation. Such as:

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

4.3.1 bundleSFC Compiles single-file components

This function converts the single-file component according to @vue/ component-Compiler and returns a file that the browser recognizes.

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

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

4.3.2 readSource Reads file resources

This function retrieves a file resource on request. Return filepath filepath, source, and updateTime 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
Copy the code

Next, let’s look at handling.js files

4.4 Process files at the end of.js

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

  if(! out) {// transform import statements
    // Convert the 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')}Copy the code

Convert to vue-dev-server/test/main.js

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

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

// Public account: Ruochuan Vision
// Add wechat ruochuan12
// Read the source code together
Copy the code
import Vue from "/__modules/vue"
import App from './test.vue'

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

// Public account: Ruochuan Vision
// Add wechat ruochuan12
// Read the source code together
Copy the code

4.4.1 transformModuleImports Import is imported

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 (!1 / ^ \ \ /? /.test(source) && isPkg(source)) {
        path.node.source = recast.types.builders.literal(`/__modules/${source}`)}this.traverse(path)
    }
  })
  return recast.print(ast).code
}

exports.transformModuleImports = transformModuleImports
Copy the code

That is, conversion for NPM packages. Here is “/__modules/vue”

import Vue from 'vue'= >import Vue from "/__modules/vue"
Copy the code

4.5 Process files starting with /__modules/

import Vue from "/__modules/vue"
Copy the code

This code eventually return path is to read the vue – dev – server/node_modules/vue/dist/vue. Esm. The js file.

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

4.5.1 loadPkg Loading packages (Only Vue files are supported)

Currently only supports the Vue file, that is, to read path Vue – dev – server/node_modules/Vue/dist/Vue. Esm. The js file returns.

// 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') {
    / / path
    // 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
Copy the code

At this point, we have basically analyzed the main file and some imported files. Get an idea of the main process.

5. To summarize

Finally, let’s look at the above two pictures of vueMiddleware middleware and sum up:

After enabling the middleware, the following figure is shown.

Browsers support native type=module request loading. Vue-dev-server intercepts this and returns browser-supported content, which is fast because you don’t need to package builds.

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

5.1 Import Vue from ‘Vue

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

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

The import statement import Vue from ‘Vue’ in main.js is converted from recast to import Vue from “/__modules/ Vue “and returned to the browser vue-dev-server/node_modules/vue/dist/vue.esm.browser.js

5.2 Import App from ‘./test.vue

Import App from ‘./test.vue’ is converted to a browser-supported file using @vue/component-compiler.

5.3 What else can I Do?

Due to the limited space of this article, the tryCache section of the cache is not currently analyzed. Node-lru-cache is the least recently used cache. The source code of this warehouse should be analyzed in the future. Welcome to continue to follow me @Ruochuan.

Use VSCode to debug vue-dev-server source code. There are many details in the source code due to space constraints, not fully developed.

It is worth mentioning that the master branch of this warehouse was written by Yu Xi two years ago.

You can also go directly to the vite source code.

After reading this article, maybe you can find that the front end can do more and more things, not by feeling: the front end of the water is unfathomable, only to continue learning.

Finally, you are welcome to join us in ruochuan12 to learn source code and make progress together.


About && communication groups

Recently, I organized a reading activity for source code. If you are interested, you can join me in wechat ruochuan12 for long-term communication and learning.

Author: Often in the name of ruochuan mixed traces in rivers and lakes. Welcome to add my wechat account ruochuan12. Front road | know very little, only good study. Concern about the public number if chuan vision, every week to learn the source code, learn to see the source code, advanced advanced front end. Wakawa’s blog segmentfault wakawa’s view column, has opened a column on wakawa’s view, welcome to follow ~ dig gold column, welcome to follow ~ github blog, beg a star^_^~