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…