1. Start from the entrance
1. package.json
// Use the bin field in package.json to find vite
"bin": {
"vite": "bin/vite.js"
},
// bin/ viet. js specifies node for execution
#!/usr/bin/env node
require('.. /dist/node/cli')
Copy the code
2. cli
// Register the command
cli.command('[root]').action(async(root, argv) => {
const options = await resolveOptions({ argv, defaultMode: 'development' })
returnrunServe(options) }) cli.. command('build [root]').action(async(root, argv) => {
const options = await resolveOptions({ argv, defaultMode: 'production' })
return runBuild(options)
})
Copy the code
2.1 resolveOptions
function resolveOptions() {
// To read the user's configuration, you can specify the configuration file through -c
// require('vite.config.js')
const userConfig = await resolveConfig()
}
Copy the code
2.2 runServe
function runServer() {
// Vite1 starts a KOA server
const server = require('./server').createServer(options)
server.listen(port, () = >{})}Copy the code
2.3 runBuild
function runBuild() {
// Use rollup again
await require('./build')[options.ssr ? 'ssrBuild' : 'build'](options)
}
Copy the code
2. build
// Vite packaging is ultimately handled using rollup
function build() {
// Package with rollup
const rollup = require("rollup").rollup;
const bundle = await rollup();
await bundle.write();
}
Copy the code
3. server
// Start a KOA server
function createServer(config) {
const { root = process.cwd() } = config;
const app = new Koa();
const server = resolveServer(config, app.callback());
// HMRWatcher listens for file changes
const watcher = chokidar.watch(root, {});
// resolveAlias
// const resolver = createResolver();
// Build the context object for the middleware
const context = {
root,
app,
server,
watcher,
config,
port: config.port || 3000};// Extend the CTX property
app.use((ctx, next) = > {
Object.assign(ctx, context);
return next();
});
// A list of middleware
const resolvedPlugins = [
// Here are the various middleware
serveStaticPlugin, // 1. Let koA provide static file service koa-static
];
resolvedPlugins.forEach((m) = > m && m(context));
const listen = server.listen.bind(server);
server.listen = async(port, ... args) => {// this function overwrites the listen method runOptimize
// await require(".. /optimizer").optimizeDeps(config);
returnlisten(port, ... args); };return server;
}
Copy the code
3.1 serveStaticPlugin
// Use koa-static to provide static services so that we can access the index.html file and request/SRC /index.js resources
// Next we need to process the index.js file
function serveStaticPlugin({ root, app, resolver, config }) {
app.use(async (ctx, next) => {
await next();
});
app.use(require("koa-etag") ()); app.use(require("koa-static")(root));
app.use(require("koa-static")(path.join(root, "public")));
// history api fallback
}
Copy the code
3.2 moduleRewritePlugin
// index.js browser doesn't know how to handle this
Import {createApp} from "/@modules/vue"
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");
const moduleRewritePlugin = ({ root, app, watcher, resolver }) = > {
app.use(async (ctx, next) => {
await next();
const publicPath = ctx.path;
// Handle js type files. Vue files separately
if (ctx.body && ctx.response.is("js") && !ctx.url.endsWith(".map")) {
// Reading stream.tostring () will determine if it is a stream
const content = await readBody(ctx.body);
/ / dynamic import
// const importer = removeUnRelatedHmrQuery(
// resolver.normalizePublicPath(ctx.url)
// );
const importer = {};
/ / rewrite the importctx.body = rewriteImports(root, content, importer, resolver); }});// watcher.on("change", async (filePath) => {});
};
/ / rewrite the import
function rewriteImports(root, source, importer, resolver) {
let imports = [];
// es-module-lexer const [imports, exports] = parse(source, {})
imports = parse(source)[0];
if (imports.length) {
// magic-string Specifies the operation string
const s = new MagicString(source);
for (let i = 0; i < imports.length; i++) {
// Do not consider dynamic
const { s: start, e: end } = imports[i];
// if (isExternalUrl(id)) {
// continue
// }
// plus /@modules/ also handles relative paths./foo -> /some/path/foo, etc
// const resolved = resolveImport(root, importer, id, resolver);
// s.overwrite(start, end);
let id = source.substring(start, end);
if (/ / ^ ^ \ / \].test(id)) {
id = `/@modules/${id}`;
// Modify the path to add the /@modules prefixs.overwrite(start, end, id); }}return s.toString();
} else {
returnsource; }}Copy the code
3.3 moduleResolvePlugin
// @modules/vue could not be found parsing @modules
const moduleRE = /^\/@modules\//;
const moduleResolvePlugin = ({ root, app, resolver }) = > {
// Handle vue find the esM module under @vue/
const vueResolved = resolveVue(root);
app.use(async (ctx, next) => {
// Modules that are not @modules are directly next
if(! moduleRE.test(ctx.path))return next();
// Remove the @modules prefix
const id = decodeURIComponent(ctx.path.replace(moduleRE, ""));
ctx.type = "js";
const serve = async (id, file) => {
Moduleidtofilemap. set(id, file) // Cache cache
// ctx.read = cachedRead.bind(null, ctx) fs.readFile
await ctx.read(file);
return next();
};
if (id in vueResolved) {
return serve(id, vueResolved[id]);
}
// Third party module processing?
// const referer = ctx.get("referer");
// let importer;
// // map file
// const isMapFile = ctx.path.endsWith(".map");
// const importerFilePath = importer ? resolver.requestToFile(importer) : root;
// const nodeModuleInfo = resolveNodeModule(root, id, resolver);
// return serve(id, nodeModuleFilePath, "node_modules");
});
};
// Handle vUE
const resolveVue = (root) = > {
let vueVersion;
let vueBasePath;
let compilerPath;
// Find package.json file
const projectPkg = JSON.parse(lookupFile(root, ["package.json") | |` ` {});
/ / find the vue
letisLocal = !! (projectPkg.dependencies && projectPkg.dependencies.vue);// Find vue's package resolve.sync()
const userVuePkg = resolveFrom(root, "vue/package.json");
vueBasePath = path.dirname(userVuePkg);
vueVersion = fs.readJSONSync(userVuePkg).version;
// Parse vue single file package
const compilerPkgPath = resolveFrom(root, "@vue/compiler-sfc/package.json");
const compilerPkg = require(compilerPkgPath);
compilerPath = path.join(path.dirname(compilerPkgPath), compilerPkg.main);
// Various files of vue and modules of vue3
const resolvePath = (name, from) = >
resolveFrom(from.`@vue/${name}/dist/${name}.esm-bundler.js`);
const runtimeDomPath = resolvePath("runtime-dom", vueBasePath);
const runtimeCorePath = resolvePath("runtime-core", runtimeDomPath);
const reactivityPath = resolvePath("reactivity", runtimeCorePath);
const sharedPath = resolvePath("shared", runtimeCorePath);
return {
version: vueVersion,
vue: runtimeDomPath,
"@vue/runtime-dom": runtimeDomPath,
"@vue/runtime-core": runtimeCorePath,
"@vue/reactivity": reactivityPath,
"@vue/shared": sharedPath,
compiler: compilerPath,
isLocal,
};
};
Copy the code
3.4 vuePlugin
// Handle app.vue vue single file
// 1. Rewrite the content of app.vue
// 2. Process the template file
// 3. Handle style content
const vuePlugin = () = > {
// Only vue files are processed
if(! ctx.path.endsWith(".vue") && !ctx.vue) { return next(); }
// The vue content will be overwritten to introduce template and style to be specified via query
const query = ctx.query;
const publicPath = ctx.path;
const filePath = path.join(root, publicPath);
// Parse single-file components
/ / {
// filename: '',
// source: '',
// template: {
// type: 'template',
// content: '',
// ast: ''
/ /},
// script: {
// type: 'script',
/ /},
// styles: [
// { type: 'style', content: ''}
/ /]
// }
const descriptor = await parseSFC(root, filePath, ctx.body);
if(query.type) {
ctx.type = "js";
const {code} = await compileSFCMain()
ctx.body = code
}
if (query.type === "template") {
const templateBlock = descriptor.template;
ctx.type = "js";
const { code } = compileSFCTemplate()
ctx.body = code;
}
if(query.type === "style") {
const styleBlock = descriptor.styles[index];
const result = await compileSFCStyle()
ctx.type = "js";
ctx.body = codegenCss()
}
if (query.type === "custom") {}// watcher.on('change', file => {handleVueReload()})
}
Copy the code
3.4.1 track parseSFC
// Read the contents of the file through @vue/ compiler-sFC parsing single file parsing
async function parseSFC(root, filePath, content) {
// Read the contents of the file
content = await cachedRead(null, filePath, true /* poll */);
if (typeofcontent ! = ="string") {
content = content.toString();
}
// @vue/compiler- SFC parses single files
const { parse } = resolveCompiler(root);
const { descriptor } = parse(content, {
filename: filePath,
sourceMap: true});return descriptor;
}
Copy the code
3.4.2 compileSFCMain
// We will change the contents of the.vue single file
async function compileSFCMain(descriptor, filepath, publicPath, root) {
// creates unique hashes
const id = hash_sum(publicPath)
let code = ` `;
let content = ` `;
let script = descriptor.script;
const compiler = resolveCompiler(root);
// Parse the script contents
if ((descriptor.script || descriptor.scriptSetup) && compiler.compileScript) {
script = compiler.compileScript(descriptor, { id });
}
if (script) {
content = script.content;
// ts
// if (script.lang === "ts") {
// // esbuild的transform
// const res = await transform(content, publicPath, {
// loader: "ts",
/ /});
// content = res.code;
// }
}
// @vue/compiler-sfc
code += rewriteDefault(content, "__script");
// Style scoped and CSS-module
let hasScoped = false;
// let hasCSSModules = false;
if (descriptor.styles) {
descriptor.styles.forEach((s, i) = > {
const styleRequest = publicPath + `? type=style&index=${i}`;
if (s.scoped) hasScoped = true;
if (s.module) {
} else {
// The style file is imported and has the type attribute
code += `\nimport The ${JSON.stringify(styleRequest)}`;
}
if (hasScoped) {
// import "/src/App.vue? type=style&index=0"
code += `\n__script.__scopeId = "data-v-${id}"`; }}); }// if(descriptor.customBlocks) {}
// The template becomes the render function
if (descriptor.template) {
const templateRequest = publicPath + `? type=template`;
// import { render as __render } from "/src/App.vue? type=template"
code += `\nimport { render as __render } from The ${JSON.stringify(
templateRequest
)}`;
// __script.render = __render
code += `\n__script.render = __render`;
}
code += `\n__script.__hmrId = The ${JSON.stringify(publicPath)}`;
code += `\ntypeof __VUE_HMR_RUNTIME__ ! == 'undefined' && __VUE_HMR_RUNTIME__.createRecord(__script.__hmrId, __script)`;
// __script.__file = "/Users/liu/vitejs/hello-vite/src/App.vue"
code += `\n__script.__file = The ${JSON.stringify(filepath)}`;
code += `\nexport default __script`;
return { code };
}
Copy the code
Rule 3.4.3 compileSFCTemplate
// The processing template is @vue/ compiler-sFC
function compileSFCTemplate(root, template, filePath, publicPath, scoped) {
const { compileTemplate } = resolveCompiler(root);
const id = hash_sum(publicPath);
const { code } = compileTemplate({
source: template.content,
id,
});
return { code };
}
Copy the code
3.4.4 compileSFCStyle
// Handle styles
async function compileSFCStyle(root, style, index, filePath, publicPath) {
// const { generateCodeFrame } = resolveCompiler(root)
const resource = filePath + `? type=style&index=${index}`
// Compile CSS postcss? Again using @vue/ Compiler-postCSS used internally in SFC
const result = await compileCss(root, publicPath, {
source: style.content,
filename: resource,
id: ` `.// will be computed in compileCss
scoped: style.scoped ! =null.modules: style.module ! =null.preprocessLang: style.lang // css less scss stylus
})
// recordCssImportChain(result.dependencies, resource)
// result.code = await rewriteCssUrls(result.code, publicPath)
return result
}
Copy the code
3.4.5 codegenCss
function codegenCss(id, css, modules) {
let code =
// clientPublicPath: /vite/client
`import { updateStyle } from "${clientPublicPath}"\n` +
`const css = The ${JSON.stringify(css)}\n` +
`updateStyle(The ${JSON.stringify(id)}, css)\n`
if (modules) {
code += dataToEsm(modules, { namedExports: true})}else {
code += `export default css`
}
return code
}
Copy the code
3.5 clientPlugin
CodegenCss import {updateStyle} from '/vite/client'
const clientPlugin = ({app, config}) = > {
// Read the contents of client/client.js
const clientCode = fs.readFileSync(clientFilePath, 'utf-8')
app.use(async(ctx, next) => {
if (ctx.path === clientPublicPath) { // /vite/client
// if (config.hmr){} // socket
ctx.type = 'js'
ctx.status = 200
ctx.body = clientCode
} else {
return next()
}
})
}
Copy the code
3.6 htmlRewritePlugin
// We inserted a script script in the htmlRewritePlugin middleware
// Changed the contents of our index. HTML
const htmlRewritePlugin = ({ root, app, watcher, resolver, config}) = > {
const devInjectionCode = `\n<script type="module">import "${clientPublicPath}"</script>\n`
return injectScriptToHtml(html, devInjectionCode)
app.use(async(ctx, next) => {
await next()
if (ctx.response.is('html') && ctx.body) {
const importer = ctx.path
const html = await readBody(ctx.body)
if(! html)return
ctx.body = await rewriteHtml(importer, html)
return
}
})
watcher.on('change'.(file) = >{})}Copy the code
3.7 the client/client. Js
// Handle the process environment variable
const __MODE__ = ' '; (window).process = (window).process || {} ; (window).process.env = (window).process.env || {} ; (window).process.env.NODE_ENV = __MODE__
// Handle styles
function updateStyle(id, content) {
let style = document.createElement('style')
style.setAttribute('type'.'text/css')
style.innerHTML = content
document.head.appendChild(style)
}
// There are many other functions
Copy the code
3.8 summarize
1. We need a static file server to access our page 2. EsModule cannot recognize import {} from 'vue' we need to rewrite the path 3. When introducing @module/ XXX modules we need to parse them. When accessing app.vue, we need to handle the single file. 4.1 Rewrite the contents of app.vue. <script type="module">import "/vite/client"</script> There is a problem with processing in client.jsCopy the code