preface

Since the company is currently on the React stack, it has not been paying attention to Vue for a long time. Recently, I watched the official live stream of Yubrook – Talk about Vue. Js 3.0 Beta [1], and at the end of the stream, the big guy recommended a “gadget” he wrote himself: “Vite”.

“Vite” : French for “fast”, it is an “HTTP” server that allows applications to be developed without the need for “webpack” packaging and supports super fast “hot updates” (since “webpack” hot updates need to be repackaged).

I was suddenly very interested in this, and it might be a trend in the future, so it’s easier to understand the inner workings of a project when it’s not too big. And “by reading the source code of other people’s good open source projects, you can learn a lot of things that you can’t even see at work.”

Ps: This article is just as detailed as possible on how to develop projects without “Webpack”, hot updates will be covered in the next article, “Vite” version is “1.0.0-RC.4”

Create a project

First, follow the following steps to prepare for commissioning:

  1. Open Vite GitHub[2] and copy the project locally.
  2. Create the ‘example’ folder from the root directory and run ‘YARN create vite-app ‘ to create a base scaffolding ‘and install dependencies’
  3. Add the following command in “example/package.json”
{

  "scripts": {

    "dev""node .. /dist/node/cli.js".

    "dev:debug""set DEBUG=* & node .. /dist/node/cli.js" / / start the debug

  }

}

Copy the code
  1. Run yarn dev in root and example respectively. Open http://localhost:3000 in the browser. The following page is displayed

Front knowledge

“Vite” relies heavily on the features of “Module sciprt”, so do your homework in advance, see: JavaScript modules – MDN. [3]

“Module SciPRt” allows native support modules to run directly in the browser

<script type="module">

    // index.js can be exported as a module, or it can continue to use import to load other dependencies

    import App from './index.js'

</script>

Copy the code

Import {createApp} from ‘vue’ references are not supported. Import {createApp} from ‘vue’ references are not supported

start

Start the server

When you open the project “Vite” is using “Koa” to create an “HTTP” server, starting with “SRC /node/cli.js”

const server = require('./server').createServer(options)

Copy the code

Instead, go to “server/index.js” and add additional processing to it in the form of “middleware.” Here are some of the middleware that are relevant to this article

  const context = { app };



  const resolvedPlugins = [

    moduleRewritePlugin,  //解析js

    htmlRewritePlugin, / / parse HTML

    moduleResolvePlugin, // Get the dependency load content

    vuePlugin, // Parse the vue file

    cssPlugin, / / CSS

    serveStaticPlugin // Static configuration

  ];

  resolvedPlugins.forEach((m) = > m && m(context))

Copy the code

Static configuration

Location to the server/serverPluginServeStatic ts “file, in order to the default resolution” index.html “and other static file content, add some configuration, simple, direct stick code:

const send = require('koa-send')

.

app.use(require('koa-static')(root))

app.use(require('koa-static')(path.join(root, 'public')))

.

// root points to the example directory

await send(ctx, `index.html`, { root })

Copy the code

Overwrite the import library name

Module sciprt does not support “import {createApp} from ‘vue'”

The original entry file looks like this

import { createApp } from 'vue'

import App from './App.vue'

import './index.css'



createApp(App).mount('#app')

Copy the code

After processing, it converts to:

You can see that vue is changed to “/@modules/vue.js”. Here’s how it goes:

  1. First of all, according to the index return above. The HTML file, go to the “server/serverPluginHtml. Ts” (only keep important code)
app.use(async (ctx, next) => {

.

  // return.html

  if (ctx.response.is('html') && ctx.body) {

    const importer = ctx.path

    // Get HTML content

    const html = await readBody(ctx.body)

    // Whether there is a cache

    if (rewriteHtmlPluginCache.has(html)) {

.

    } else {

      if(! html)return

      // Important: rewrite the.html file contents

      ctx.body = await rewriteHtml(importer, html)

    }

    return

  }

})



const scriptRE = /( \b[^>

async function rewriteHtml(importer: string, html: string{

html = html! .replace(scriptRE,(matched, openTag, script) = > {

    // If script is not imported by SRC, replace js imports directly: rewriteImports

    if (script) {

      return `${openTag}${rewriteImports(

        root,

        script,

        importer,

        resolver

      )}
</script>`


    }

  })

  return injectScriptToHtml(html, devInjectionCode)

}

Copy the code
  1. According to the above code, if “script module” is to use the “SRC” introduction, is to “serverPluginModuleRewrite. Ts”, this is very simple, is also called directly rewrite “rewriteImports” method
 app.use(async (ctx, next) => {

.

    const publicPath = ctx.path

    if (

      ctx.body &&

      ctx.response.is('js') &&...

    ) {

      ctx.body = rewriteImports(

        root,

content! .

        importer,

        resolver,

        ctx.query.t

      )

    }

}

Copy the code

rewriteImports

Here is the focus of the whole logic, mainly rewrite the import of “import”, the principle is to use “es-module-lexer” parsing “import”, and then through the re match matching conditions of the import, and then rewrite

import MagicString from 'magic-string'

import { init as initLexer, parse as parseImports } from 'es-module-lexer'



export function rewriteImports(.{

   / / parsing import

   imports = parseImports(source)[0]

.

   const s = new MagicString(source)

   for (let i = 0; i < imports.length; i++) {

      const { s: start, e: end, d: dynamicIndex } = imports[i]

      // id is the name of the imported library, which can be regarded as vue

      let id = source.substring(start, end)

.

      // resolveImport adds /@modules/

      const resolved = resolveImport(...)

  }

  / / rewrite. Js

  s.overwrite(

    start,

    end,

    hasLiteralDynamicId ? ` '${resolved}'` : resolved

  )

}

Copy the code

resolveImport

Here’s a little detail about “resolveImport” : “This can be done in the application without installing vue dependencies”, since “Vite” already has “vue” installed by default, there is a special handling

const bareImportRE = / / ^ ^ \ / \]

// where id == vue

if (bareImportRE.test(id)) {

  id = `/@modules/${resolveBareModuleRequest(root, id, importer, resolver)}`

}



export function resolveBareModuleRequest(.) :string {

  const optimized = resolveOptimizedModule(root, id)

  if (optimized) {

    return path.extname(id) === '.js' ? id : id + '.js'

  }

}

Copy the code
  • The resolveOptimizedModule looks for dependencies in package.json and caches them. Optimized returns true
  • If not, look for the entry file in node_modules of vite and cache & return

Parsing code

Introduced in this paper, we modify the js file will initiate the request, again after this time will turn to “serverPluginModuleResolve” middleware, main is to find relevant code file location, read and return

const moduleRE = /^\/@modules\//

// Get the file path corresponding to each vUE module code

const vueResolved = resolveVue(root)

app.use(async (ctx, next) => {

  if(! moduleRE.test(ctx.path)) {

    return next()

  }

  / / remove / @ modules

  const id = decodeURIComponent(ctx.path.replace(moduleRE, ' '))

  // Read the file and return

  const serve = async (id: string, file: string.typestring) = > {

.

    await ctx.read(file)

    return next()

  }



  // isLocal indicates whether vUE is installed in the project

  if(! vueResolved.isLocal && idin vueResolved) {

    return serve(id, (vueResolved as any)[id], 'non-local vue')

  }

.

}

Copy the code

Process Vue files

How to deal with the “. Vue “file is also the focus of this project, this time switch to the” serverPluginVue “middleware, first take a look at the”. Vue “processing appearance

import HelloWorld from '/src/components/HelloWorld.vue'



const __script = {

    name'App'.

    components: {

        HelloWorld

    }

}



import "/src/App.vue? type=style&index=0"

import {render as __render} from "/src/App.vue? type=template"

__script.render = __render

__script.__hmrId = "/src/App.vue"

__script.__file = "XXX\\vite\\example\\src\\App.vue"

export default __script

Copy the code

As you can see, if you split a “. Vue “file into three requests (script, style, and template), the browser will first receive” app. vue “containing” script “. After being parsed to the path of Template and Style, it sends an HTTP request to request the corresponding resource. Vite intercepts and processes the request again and returns the corresponding content.


Query = query; query = query; query = query;

const query = ctx.query

// If there is no query type, such as/app.vue for direct request

if(! query.type) {

  // The script is processed first, then the template and stytle links are generated

  const { code, map } = await compileSFCMain(

    descriptor,

    filePath,

    publicPath,

    root

  )

  ctx.body = code

  ctx.map = map

  return etagCacheCheck(ctx)

}



if (query.type === 'template') {

  // Generate render function and return

  const { code, map } = compileSFCTemplate({ ... })

  ctx.body = code

  ctx.map = map

  return etagCacheCheck(ctx)

}



if (query.type === 'style') {

  // Generate a CSS file

  const result = await compileSFCStyle(...)

  ctx.type = 'js'

  ctx.body = codegenCss(`${id}-${index}`, result.code, result.modules)

  return etagCacheCheck(ctx)

}

Copy the code

summary

Overall, the main logic is not complicated to understand, but there may be a lot of detailed operations need to be carefully considered, mainly divided into the following steps

  • Script module is introduced into
  • Imports to replace
  • Parse the import path and ask again -> return

Although “Vite” can not be promoted in large-scale production environment at present, it has been iterating at an amazing speed and is likely to be a big trend in the future. After all, it’s a huge step to read the code quickly and understand the author’s design ideas and intentions without getting overly complicated. Right


The resources

[1]

Especially the rain creek – talk about Vue. Js 3.0 Beta official live: https://m.bilibili.com/video/BV1Tg4y1z7FH


[2]

Vite GitHub: https://github.com/vitejs/vite


[3]

JavaScript modules modules: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules