1. 从入口出发
1. package.json
// 根据package.json中bin字段找到bin下面的vite.js
"bin": {
"vite": "bin/vite.js"
},
// bin/vite.js 指定使用node执行
#!/usr/bin/env node
require('../dist/node/cli')
复制代码
2. cli
// 注册命令
cli.command('[root]').action(async(root, argv) => {
const options = await resolveOptions({ argv, defaultMode: 'development' })
return runServe(options)
})
cli..command('build [root]').action(async(root, argv) => {
const options = await resolveOptions({ argv, defaultMode: 'production' })
return runBuild(options)
})
复制代码
2.1 resolveOptions
function resolveOptions() {
// 读取用户的配置 可以通过 -c指定配置文件
// require('vite.config.js')
const userConfig = await resolveConfig()
}
复制代码
2.2 runServe
function runServer() {
// vite1启动一个koa服务器
const server = require('./server').createServer(options)
server.listen(port, () => {})
}
复制代码
2.3 runBuild
function runBuild() {
// 还是使用 rollup来打包
await require('./build')[options.ssr ? 'ssrBuild' : 'build'](options)
}
复制代码
2. build
// vite打包最终还是使用的rollup来处理的
function build() {
// 使用rollup打包
const rollup = require("rollup").rollup;
const bundle = await rollup();
await bundle.write();
}
复制代码
3. server
// 启动一个koa服务器
function createServer(config) {
const { root = process.cwd() } = config;
const app = new Koa();
const server = resolveServer(config, app.callback());
// HMRWatcher 监听文件的变化
const watcher = chokidar.watch(root, {});
// resolveAlias
// const resolver = createResolver();
// 构建上下文对象 给中间件的
const context = {
root,
app,
server,
watcher,
config,
port: config.port || 3000,
};
// 扩展ctx属性
app.use((ctx, next) => {
Object.assign(ctx, context);
return next();
});
// 一些列的中间件
const resolvedPlugins = [
// 这里是各种中间件
serveStaticPlugin, // 1. 让koa提供静态文件服务 koa-static
];
resolvedPlugins.forEach((m) => m && m(context));
const listen = server.listen.bind(server);
server.listen = async (port, ...args) => {
// 函数劫持 重写listen方法 runOptimize
// await require("../optimizer").optimizeDeps(config);
return listen(port, ...args);
};
return server;
}
复制代码
3.1 serveStaticPlugin
// 使用 koa-static 提供静态服务 这样我们就可以访问到 index.html 文件 会请求 /src/index.js 资源
// 下一步我们需要处理 index.js 这个文件
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
}
复制代码
3.2 moduleRewritePlugin
// index.js 浏览器不知道如何处理这些
// 改写路径 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;
// 处理 js 类型的文件 .vue文件单独处理
if (ctx.body && ctx.response.is("js") && !ctx.url.endsWith(".map")) {
// 读取文件内容 stream.toString() 会判断是否为stream流
const content = await readBody(ctx.body);
// 动态 import
// const importer = removeUnRelatedHmrQuery(
// resolver.normalizePublicPath(ctx.url)
// );
const importer = {};
// 重写 import
ctx.body = rewriteImports(root, content, importer, resolver);
}
});
// watcher.on("change", async (filePath) => {});
};
// 重写 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 操作字符串
const s = new MagicString(source);
for (let i = 0; i < imports.length; i++) {
// 不考虑 dynamic 的情况
const { s: start, e: end } = imports[i];
// if (isExternalUrl(id)) {
// continue
// }
// 加上 /@modules/ 还会处理相对路径 ./foo -> /some/path/foo 等
// const resolved = resolveImport(root, importer, id, resolver);
// s.overwrite(start, end);
let id = source.substring(start, end);
if (/^[^\/\.]/.test(id)) {
id = `/@modules/${id}`;
// 修改路径增加 /@modules 前缀
s.overwrite(start, end, id);
}
}
return s.toString();
} else {
return source;
}
}
复制代码
3.3 moduleResolvePlugin
// 请求 /@modules/vue 的时候找不到 解析 @modules 的模块
const moduleRE = /^\/@modules\//;
const moduleResolvePlugin = ({ root, app, resolver }) => {
// 处理 vue 找到 @vue/ 下内容 esm模块的
const vueResolved = resolveVue(root);
app.use(async (ctx, next) => {
// 不是 @modules 的模块 直接next
if (!moduleRE.test(ctx.path)) return next();
// 去掉 @modules 的前缀
const id = decodeURIComponent(ctx.path.replace(moduleRE, ""));
ctx.type = "js";
const serve = async (id, file) => {
// moduleIdToFileMap.set(id, file) // cache缓存
// ctx.read = cachedRead.bind(null, ctx) fs.readFile
await ctx.read(file);
return next();
};
if (id in vueResolved) {
return serve(id, vueResolved[id]);
}
// 第三方模块的处理?
// const referer = ctx.get("referer");
// let importer;
// // map文件
// 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");
});
};
// 处理vue的
const resolveVue = (root) => {
let vueVersion;
let vueBasePath;
let compilerPath;
// 找到package.json文件
const projectPkg = JSON.parse(lookupFile(root, ["package.json"]) || `{}`);
// 找到vue
let isLocal = !!(projectPkg.dependencies && projectPkg.dependencies.vue);
// 找到vue的 package resolve.sync()
const userVuePkg = resolveFrom(root, "vue/package.json");
vueBasePath = path.dirname(userVuePkg);
vueVersion = fs.readJSONSync(userVuePkg).version;
// 解析vue单文件的包
const compilerPkgPath = resolveFrom(root, "@vue/compiler-sfc/package.json");
const compilerPkg = require(compilerPkgPath);
compilerPath = path.join(path.dirname(compilerPkgPath), compilerPkg.main);
// vue的各个文件 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,
};
};
复制代码
3.4 vuePlugin
// 处理 App.vue vue的单文件
// 1. 改写app.vue的内容
// 2. 处理 template文件
// 3. 处理style的内容
const vuePlugin = () => {
// 只处理vue文件
if (!ctx.path.endsWith(".vue") && !ctx.vue) { return next(); }
// vue内容会改写 引入 template和style都是通过query来指定的
const query = ctx.query;
const publicPath = ctx.path;
const filePath = path.join(root, publicPath);
// 解析单文件组件
// {
// 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()})
}
复制代码
3.4.1 parseSFC
// 读取文件内容 通过 @vue/compiler-sfc 解析单文件解析
async function parseSFC(root, filePath, content) {
// 读取文件的内容
content = await cachedRead(null, filePath, true /* poll */);
if (typeof content !== "string") {
content = content.toString();
}
// @vue/compiler-sfc 解析单文件
const { parse } = resolveCompiler(root);
const { descriptor } = parse(content, {
filename: filePath,
sourceMap: true,
});
return descriptor;
}
复制代码
3.4.2 compileSFCMain
// 我们会讲 .vue单文件的内容做修改
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);
// 解析script内容
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");
// 样式的scoped和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 {
// 样式文件是通过 import的方式 然后会有 type属性
code += `\nimport ${JSON.stringify(styleRequest)}`;
}
if (hasScoped) {
// import "/src/App.vue?type=style&index=0"
code += `\n__script.__scopeId = "data-v-${id}"`;
}
});
}
// if(descriptor.customBlocks) {}
// template模版会变成render函数
if (descriptor.template) {
const templateRequest = publicPath + `?type=template`;
// import { render as __render } from "/src/App.vue?type=template"
code += `\nimport { render as __render } from ${JSON.stringify(
templateRequest
)}`;
// __script.render = __render
code += `\n__script.render = __render`;
}
code += `\n__script.__hmrId = ${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 = ${JSON.stringify(filepath)}`;
code += `\nexport default __script`;
return { code };
}
复制代码
3.4.3 compileSFCTemplate
// 处理模版还是 @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 };
}
复制代码
3.4.4 compileSFCStyle
// 处理样式
async function compileSFCStyle(root, style, index, filePath, publicPath) {
// const { generateCodeFrame } = resolveCompiler(root)
const resource = filePath + `?type=style&index=${index}`
// 编译css postcss ? 还是使用的 @vue/compiler-sfc 内部使用的postcss
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
}
复制代码
3.4.5 codegenCss
function codegenCss(id, css, modules) {
let code =
// clientPublicPath: /vite/client
`import { updateStyle } from "${clientPublicPath}"\n` +
`const css = ${JSON.stringify(css)}\n` +
`updateStyle(${JSON.stringify(id)}, css)\n`
if (modules) {
code += dataToEsm(modules, { namedExports: true })
} else {
code += `export default css`
}
return code
}
复制代码
3.5 clientPlugin
// 我们在处理样式的时候 codegenCss import { updateStyle } from ‘/vite/client’
const clientPlugin = ({app, config}) => {
// 就是读 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()
}
})
}
复制代码
3.6 htmlRewritePlugin
// 其实在 htmlRewritePlugin中间件中我们插入了 一段script脚本
// 修改了我们index.html的内容
const htmlRewritePlugin = ({ root, app, watcher, resolver, config}) => {
// 注入code <script type="module">import "/vite/client"</script>
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) => {})
}
复制代码
3.7 client/client.js
// 处理process环境变量的问题
const __MODE__ = ''
;(window).process = (window).process || {}
;(window).process.env = (window).process.env || {}
;(window).process.env.NODE_ENV = __MODE__
// 处理样式
function updateStyle(id, content) {
let style = document.createElement('style')
style.setAttribute('type', 'text/css')
style.innerHTML = content
document.head.appendChild(style)
}
// 还有很多其他的功能
复制代码
3.8 总结
1. 我们需要一个静态文件服务器 访问我们的页面
2. esModule无法识别 import {} from 'vue' 我们需要改写路径
3. 当引入 @module/xxx的模块时候 我们要对他们进行解析
4. 访问App.vue的时候我们需要对单文件处理
4.1 重写app.vue的内容
4.2 处理template render函数
4.3 处理style
5. 我们访问index.html的时候会注入script <script type="module">import "/vite/client"</script>
6. 在client.js中出处理 process的问题
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END