Initialize a new project

npm init -y
Copy the code

Installation of Koa

yarn add koa
Copy the code

Build a KOA server

Build a KOA server by creating a mini-viet.js file in the root directory

const Koa = require('koa')
const app = new Koa()
const fs = require('fs')

// Return to user home page
app.use(async ctx => {
    ctx.body = 'mini vite ~'
})

app.listen(9527.() = > {
    console.log("mini vite start ~")})Copy the code

We need to read the index.html file and return it to the user’s home page using the FS module

const fs = require('fs')
Copy the code

Get the URL that the user is currently requesting. If it is the home page, read the index.html content and return it to the front end

const { url } = ctx.request
if(url === '/') {
    ctx.type = "text/html"
    ctx.body = fs.readFileSync('./index.html'.'utf8')}Copy the code

At this point we find that the front end sends a request for a main.js file to the back end

So the back end responds to the request

if(url === '/') {
    // ...
} else if(url.endsWith('.js')) {
    // Respond to the JS request
    const jsPath = path.join(__dirname, url) // Convert to absolute address for loading
    ctx.type = "text/javascript" // Tell the browser that this is a JavaScript file
    ctx.body = fs.readFileSync(jsPath, 'utf8')}Copy the code

We found that the front-end main.js request was successful

Write the vue3 application in main.js

Next, let’s install Vue3 and write the Vue3 application in main.js

yarn add vue@next
Copy the code

To write a VUe3 application, let’s not write a tamplate, because writing a tamplate requires compilation, so let’s take a quick step and write a rendering function first.

import { createApp, h } from 'vue'
createApp({
    render: () = > h("h1"."hello mini vite !")
}).mount("#app")
Copy the code

Vite loading module address processing

We wrote a Vue3 application like this and found that the browser had an error

The address of the loaded module needs to be a relative address.

At this point we need to rewrite the ‘vue’ address in the Vite server, also known as precompiled, otherwise the server will not load. We need to handle the ‘vue’ as a relative address, such as ‘/@modules/vue’, so that the browser can request the Vite server to load the vue file from the node_modules module.

In the Vite server, when encountered JS files, modules loaded with non-relative addresses are processed as relative addresses.

Write a function to handle the module address

/** * reimport to the relative address */
function rewriteImport(content) {
    return content.replace(/ from ['|"](.*)['|"]/g.function(s0, s1) {
        // s0 matches the character string, s1 groups the content
        // check the relative address
        if(s1.startsWith('. ') || s1.startsWith('/') || s1.startsWith('.. / ')) {
            // return unchanged
            return s0
        } else {
            return ` from '/@modules/${s1}'`}})}Copy the code

We found that the address of the vue module in main.js was already handled as ‘/@modules/vue’.

A request was also made to the Vite server

Next we need to respond to the request

How do I load packages in node_module

Json file. This file will have a module field that records the address of the output file of the package. For example, the module field in the root directory of the vue package we want to request looks like this:

Dependent loading code

if(url.startsWith('/@modules/')) {
    // Get the part after @modules, the module name
    const moduleName = url.replace('/@modules/'.' ')
    const prefix = path.join(__dirname, './node_modules', moduleName)
    // The address of the file to load
    const module = require(prefix + '/package.json').module
    const filePath = path.join(prefix, module)
    const res = fs.readFileSync(filePath, 'utf8')
    ctx.type = "text/javascript" 
    ctx.body = rewriteImport(res) // There may be import code inside it, so it needs to be rewritten as well
 }
Copy the code

At this point we find that there are many other packages loaded besides the Vue file

Simulate a Node server variable

At this point, you need to simulate a Node server variable in index.html

<script>
  window.process = { env: { NODE_ENV: 'dev'}}</script>
<script type="module" src="./src/main.js"></script>
Copy the code

Otherwise an error will be reported

Once the Node server variable is simulated, it is rendered successfully

Parse vUE files

Create a new app.vue file

<template>
    <div>
        {{title}}
    </div>
</template>
<script>
import { ref } from 'vue'
export default {
    setup () {
        const title = ref('hello, coboy ~')
        return { title }
    }
}
</script>
Copy the code

And then import it in main.js

import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount("#app")
Copy the code

Parses SFC files

Error found because we haven’t processed the vue file request yet

To parse VUE files, use the @vue/ Compiler-SFC module

const compilerSfc = require('@vue/compiler-sfc')
Copy the code

Read the requested vue file and then use the compilersfc.parse method to parse the vue file contents to get an AST

if(url.indexOf('.vue') > -1) {
        // Read vue file contents
        const vuePath = path.join(__dirname, url.split('? ') [0])
        // compilerSfc parses the SFC to get an AST
        const res = compilerSfc.parse(fs.readFileSync(vuePath, 'utf8'))
        console.log('ast', res)
}
Copy the code

Print compilersfc.parse to parse the SFC file:

We see that the printed result describes the structure of app. vue. The content of tamplate module is placed on tamplate field, and the content of script tag module is placed on Script field, and the tamplate field module needs to be further compiled to obtain a render function. It then assigns the render function in the script field to compile the SFC component into the JSX component. Now that we know how it works, let’s move on.

The content of the script part should be returned first, and the default export of the script should be converted into a variable first. Moreover, there may be import modules inside the script, so it is necessary to rewrite the import of the script content. The contents of the Tamplate section are rewritten by building an import to become a new request

// Get the script content
const scriptConent = res.descriptor.script.content
// Transform exports configuration objects as variables by default
const script = scriptConent.replace('export default '.'const __script = ')
ctx.type = 'text/javascript'
ctx.body = `
    ${rewriteImport(script)}Import {render as __render} from 'import {render as __render} from'${url}? type=template' __script.render = __render export default __script `
Copy the code

Tamplate template compilation

Next we need to deal with the compilation of the template and a new role appears @vue/compiler-dom

const compilerDom = require('@vue/compiler-dom')
Copy the code
if(query.type === 'template') {
    const tpl = res.descriptor.template.content
    // compile to a file containing the render module
    const render = compilerDom.compile(tpl, { mode: 'module' }).code
    ctx.type = 'text/javascript'
    ctx.body = rewriteImport(render)
}
Copy the code

Style module processing

Let’s add a style CSS module to app. vue

<style lang="less">
.container{
    background-color: green;
}
</style>
Copy the code

Then we can reprint the file and see that compilersfc. parse parse the SFC file and find that there is an Object in the Styles array field.

Let’s print styles further

console.log(res.descriptor.styles)
Copy the code

We can see the contents of styles very clearly, and then we can simulate the style request, because styles is an array that can have multiple requests and its language tag

// Get the styles content
const styles = res.descriptor.styles
let importCss = ' '
if(styles.length > 0) {
    styles.forEach((o, i) = > {
        importCss += `import '${url}? type=style&index=${i}&lang=${o.lang}'\n`
    })
}
ctx.type = 'text/javascript'
ctx.body = `
    ${rewriteImport(script)}Import {render as __render} from 'import {render as __render} from'${url}? type=template'${importCss}
    __script.render = __render
    export default __script
`
Copy the code

Then we see that the browser has an extra request, and we can do something with that request parameter

if(query.type === 'style') {
    // Get the styles content
    const styles = res.descriptor.styles
    const index = query.index
    // Depending on whether the lang is less or SCSS, it can be processed with the appropriate processor
    const lang = query.lang 
    const content = `
    const css = "${styles[index].content.replace(/[\n\r]/g."")}"
    let link = document.createElement('style')
    link.setAttribute('type', 'text/css')
    document.head.appendChild(link)
    link.innerHTML = css
    export default css
    `
    ctx.type = 'application/javascript'
    ctx.body = content
}
Copy the code

Next we see that the styling has succeeded and the background color has been changed

Github address: github.com/amebyte/min…