Implementation principle of Vite

Vite uses ES6 export and import to import and export modules in the browser, and implements on-demand loading. Vite relies heavily on the Module Script feature.

The processing steps are as follows:

  • usekoaThe middleware gets the request body data and sends it back to the browser
  • throughes-module-lexerParse the source file resources and generateASTTo obtainimportThe content of the
  • judgeimportIs the resource ofnpmThe module
  • The processing resource path after processing is returned as:"xxx" => "/@modules/xxx"Such as:import { createApp } from '/@modules/vue'

The dependencies required by template, script, style and other contents are processed in the form of HTTP requests, and the contents of each module of SFC file are distinguished and loaded through the form of query parameters.

Simple implementation of Vite

  1. Install dependencies
npm install es-module-lexer koa koa-static magic-string
Copy the code
  • koa,koa-staticviteDo service development internally
  • es-module-lexerAnalysis of the ES6importgrammar
  • magic-stringImplement rewriting string content

Entrance to the file

// vite/src/server.js

const Koa = require('koa');

function createServer() {
    const app = new Koa();
    const root = process.cwd(); // Path where the current command is executed

    // Build the context object
    const context = { app, root };
    // Plug-in collection
    const resolvedPlugins = [];

    app.use((ctx, next) = > {
        // Extend the CTX property
        Object.assign(ctx, context);
        return next();
    });

    // Register all plug-ins in turn
    resolvedPlugins.forEach(plugin= > plugin(context));
    return app;
}

createServer().listen(9999.() = > {
    console.log('Vite Serve Start Port: 9999');
});
Copy the code

Static service configuration

  • Introducing intermediate plug-ins
// vite/src/server.js

const serveStaticPlugin = require('./serverPluginServeStatic');
// Plug-in collection
const resolvedPlugins = [
    serveStaticPlugin
];
Copy the code

Specifies that files in the current directory and files in the public directory can be accessed directly

// vite/src/serverPluginServeStatic.js

const static = require('koa-static');
const path = require('path');

function serveStaticPlugin ({app, root}) {
    // Use the current root directory as a static directory
    app.use(static(root));
    // Use public as the root directory
    app.use(static(path.resolve(root, 'public')))}module.exports = serveStaticPlugin;
Copy the code

Rewrite module path

The ES6 module automatically sends requests to find response files, such as: import App from ‘/ app.vue ‘, Import App from ‘./ app.vue ‘, import App from ‘.. /App.vue’

Error: import {createApp} from ‘vue’

Uncaught TypeError: Failed to resolve module specifier “vue”. Relative references must start with either “/”, “./”, or “.. / “.

The vite solution is: /@modules/ XXX

Import {createApp} from ‘/@modules/vue’

  • Introducing intermediate plug-ins
// vite/src/server.js

const serveStaticPlugin = require('./serverPluginServeStatic');
const moduleRewritePlugin = require('./serverPluginModuleRewrite');
// Plug-in collection
const resolvedPlugins = [
    moduleRewritePlugin,
    serveStaticPlugin
];
Copy the code
  • rightjsIn the fileimportThe syntax overwrites the path, which again intercepts the request to the server
// vite/src/serverPluginModuleRewrite.js

const { parse } = require('es-module-lexer');
const MagicString = require('magic-string');

const { readBody } = require("./utils");

function serverPluginModuleRewrite({ app, root }) {
    app.use(async (ctx, next) => {

        await next();

        // Intercepts files of type JS
        if (ctx.body && ctx.response.is('js')) {
            // Read the contents of the file
            const content = await readBody(ctx.body);
            // Overwrite the unrecognized path in import to return the processed file contents
            const rc = rewriteImports(content);

            + import {createApp} from '/@modules/vue' //.... * /ctx.body = rc; }})}// Override the request path /@modules/ XXX
function rewriteImports(source) {
    const imports = parse(source)[0];
    const magicString = new MagicString(source);

    if (imports.length) {
        for (let i = 0; i < imports.length; i++) {
            const { s, e } = imports[i];
            let id = source.substring(s, e);
            if (/ / ^ ^ \ / \].test(id)) {
                id = `/@modules/${id}`;
                // Modify the path to add the /@modules prefixmagicString.overwrite(s, e, id); }}}return magicString.toString();
}

module.exports = serverPluginModuleRewrite;
Copy the code

utils.js

// vite/src/utils.js

const { Readable } = require('stream');

function readBody(stream) {
    if (stream instanceof Readable) {
        return new Promise((resolve, reject) = > {
            try {
                let res = ' ';
                stream
                    .on('data'.(chunk) = > res += chunk)
                    .on('end'.() = > resolve(res));
            } catch(error) { reject(error); }})}else {
        returnstream.toString(); }}exports.readBody = readBody;
Copy the code

parsing/@modulesImported files

  • Introducing intermediate plug-ins
// vite/src/server.js

const moduleResolvePlugin = require('./serverPluginModuleResolve');

const resolvedPlugins = [
    moduleRewritePlugin,
    moduleResolvePlugin,
    serveStaticPlugin
];
Copy the code
  • will/@modulesThe initial path is parsed into the corresponding real file and returned to the browser so that the requested path corresponds to the correct file
// vite/src/serverPluginModuleResolve.js

const fs = require('fs').promises;

const { resolveVue } = require('./utils');

function serverPluginModuleResolve({ app, root }) {
    const moduleRE = /^\/@modules\//;

    // The compiled module uses commonJS specification, other files use ES6 module
    const vueResolved = resolveVue(root);

    app.use(async (ctx, next) => {
        // Map the path starting with /@modules
        if(! moduleRE.test(ctx.path)) {return next();
        }
        // Remove the /@modules/ path
        const id = ctx.path.replace(moduleRE, ' ');
        ctx.type = 'js';
        const content = await fs.readFile(vueResolved[id], 'utf8');
        ctx.body = content;
    });
}

module.exports = serverPluginModuleResolve;
Copy the code

utils.js

// vite/src/utils.js

function resolveVue(root) {
    const compilerPkgPath = path.resolve(root, 'node_modules'.'@vue/compiler-sfc/package.json');
    const compilerPkg = require(compilerPkgPath);
    // Compile the module in node
    const compilerPath = path.join(path.dirname(compilerPkgPath), compilerPkg.main);
    const resolvePath = (name) = > path.resolve(root, 'node_modules'.`@vue/${name}/dist/${name}.esm-bundler.js`);
    / / the dom
    const runtimeDomPath = resolvePath('runtime-dom');
    // Core run
    const runtimeCorePath = resolvePath('runtime-core');
    // Responsive module
    const reactivityPath = resolvePath('reactivity');
    // Shared module
    const sharedPath = resolvePath('shared');
    return {
        vue: runtimeDomPath,
        '@vue/runtime-dom': runtimeDomPath,
        '@vue/runtime-core': runtimeCorePath,
        '@vue/reactivity': reactivityPath,
        '@vue/shared': sharedPath,
        compiler: compilerPath,
    }
}

exports.resolveVue = resolveVue;
Copy the code

Parse files that the browser knows about.vue

  • call@vue/compiler-sfcTo compile the
const path = require('path');
const fs = require('fs').promises;

const { resolveVue } = require('./utils');

const defaultExportRE = / ((? :^|\n|;) \s*)export default/;

function serverPluginVue({ app, root }) {
    app.use(async (ctx, next) => {
        if(! ctx.path.endsWith('.vue')) {
            return next();
        }
        //.vue file path processing
        const filePath = path.join(root, ctx.path);
        // Get the file contents
        const content = await fs.readFile(filePath, 'utf8');
        
        // Get the file contents (get @vue/compiler-sfc to compile.vue files)
        const { parse, compileTemplate } = require(resolveVue(root).compiler);
        // Use @vue/compiler-sfc to compile.vue files
        const { descriptor } = parse(content); // Parse the file contents
        
        if(! ctx.query.type) {let code = ` `;
            if (descriptor.script) {
                const content = descriptor.script.content;
                const replaced = content.replace(defaultExportRE, '$1const __script =');
                code += replaced;
            }
            if (descriptor.template) {
                const templateRequest = ctx.path + `? type=template`;
                code += `\nimport { render as __render } from The ${JSON.stringify(templateRequest)}`;
                code += `\n__script.render = __render`;
            }
            ctx.type = 'js';
            code += `\nexport default __script`;

            ctx.body = code;
        }
        if (ctx.query.type == 'template') {
            ctx.type = 'js';
            const content = descriptor.template.content;
            // Parse the imported template in the file again
            const { code } = compileTemplate({ source: content }); ctx.body = code; }})}module.exports = serverPluginVue;
Copy the code

portal