社区精选|Vite 入门,从手写一个乞丐版的Vite开始(上)
今天小编为大家带来的是社区作者 街角小林 的文章,让我们一起来学习 Vite。
前言
// app.js
const connect = require("connect");
const http = require("http");
const app = connect();
app.use(function (req, res) {
res.end("Hello from Connect!\n");
});
http.createServer(app).listen(3000);
// app.js
const path = require("path");
const fs = require("fs");
const basePath = path.join("../test/");
const typeAlias = {
js: "application/javascript",
css: "text/css",
html: "text/html",
json: "application/json",
};
app.use(function (req, res) {
// 提供html页面
if (req.url === "/index.html") {
let html = fs.readFileSync(path.join(basePath, "index.html"), "utf-8");
res.setHeader("Content-Type", typeAlias.html);
res.statusCode = 200;
res.end(html);
} else {
res.end('')
}
});
现在访问页面肯定还是一片空白,因为页面发起的 main.js 的请求我们还没有处理,main.js 的内容如下:
// app.js
const { init, parse: parseEsModule } = require("es-module-lexer");
app.use(async function (req, res) {
if (/\.js\??[^.]*$/.test(req.url)) {
// js请求
let js = fs.readFileSync(path.join(basePath, req.url), "utf-8");
await init;
let parseResult = parseEsModule(js);
// ...
}
});
解析结果为一个数组,第一项也是个数组代表导入的数据,第二项代表导出,main.js 没有,所以是空的。s、e 代表导入来源的起止位置,ss、se 代表整个导入语句的起止位置。
接下来我们检查当导入来源不是.或/开头的就转换为/@module/xxx 的形式:
// app.js
const MagicString = require("magic-string");
app.use(async function (req, res) {
if (/\.js\??[^.]*$/.test(req.url)) {
// js请求
let js = fs.readFileSync(path.join(basePath, req.url), "utf-8");
await init;
let parseResult = parseEsModule(js);
let s = new MagicString(js);
// 遍历导入语句
parseResult[0].forEach((item) => {
// 不是裸导入则替换
if (item.n[0] !== "." && item.n[0] !== "/") {
s.overwrite(item.s, item.e, `/@module/${item.n}`);
}
});
res.setHeader("Content-Type", typeAlias.js);
res.statusCode = 200;
res.end(s.toString());
}
});
// app.js
const { buildSync } = require("esbuild");
app.use(async function (req, res) {
if (/^\/@module\//.test(req.url)) {
// 拦截/@module请求
let pkg = req.url.slice(9);
// 获取该模块的package.json
let pkgJson = JSON.parse(
fs.readFileSync(
path.join(basePath, "node_modules", pkg, "package.json"),
"utf8"
)
);
// 找出该模块的入口文件
let entry = pkgJson.module || pkgJson.main;
// 使用esbuild编译
let outfile = path.join(`./esbuild/${pkg}.js`);
buildSync({
entryPoints: [path.join(basePath, "node_modules", pkg, entry)],
format: "esm",
bundle: true,
outfile,
});
let js = fs.readFileSync(outfile, "utf8");
res.setHeader("Content-Type", typeAlias.js);
res.statusCode = 200;
res.end(js);
}
})
// ...
// 遍历导入语句
parseResult[0].forEach((item) => {
// 不是裸导入则替换
if (item.n[0] !== "." && item.n[0] !== "/") {
s.overwrite(item.s, item.e, `/@module/${item.n}?import`);
} else {
s.overwrite(item.s, item.e, `${item.n}?import`);
}
});
//...
// ...
let pkg = removeQuery(req.url.slice(9));// 从/@module/vue?import中解析出vue
// ...
// 去除url的查询参数
const removeQuery = (url) => {
return url.split("?")[0];
};
// app.js
app.use(async function (req, res) {
if (/\.css\??[^.]*$/.test(req.url)) {
// 拦截css请求
let cssRes = fs.readFileSync(
path.join(basePath, req.url.split("?")[0]),
"utf-8"
);
if (checkQueryExist(req.url, "import")) {
// import请求,返回js文件
cssRes = `
const insertStyle = (css) => {
let el = document.createElement('style')
el.setAttribute('type', 'text/css')
el.innerHTML = css
document.head.appendChild(el)
}
insertStyle(\`${cssRes}\`)
export default insertStyle
`;
res.setHeader("Content-Type", typeAlias.js);
} else {
// link请求,返回css文件
res.setHeader("Content-Type", typeAlias.css);
}
res.statusCode = 200;
res.end(cssRes);
}
})
// 判断url的某个query名是否存在
const checkQueryExist = (url, key) => {
return new URL(path.resolve(basePath, url)).searchParams.has(key);
};
// app.js
const { parse: parseVue } = require("@vue/compiler-sfc");
app.use(async function (req, res) {
if (/\.vue\??[^.]*$/.test(req.url)) {
// Vue单文件
let vue = fs.readFileSync(
path.join(basePath, removeQuery(req.url)),
"utf-8"
);
let { descriptor } = parseVue(vue);
}
})
// ...
const { compileScript, rewriteDefault } = require("@vue/compiler-sfc");
let code = "";
// 处理js部分
let script = compileScript(descriptor);
if (script) {
code += rewriteDefault(script.content, "__script");
}
// 转换前
let js = `
export default {
data() {
return {}
}
}
`
// 转换后
let js = `
const __script = {
data() {
return {}
}
}
`
//然后可以给__script添加更多属性,最后再手动添加到导出即可
js += `\n__script.xxx = xxx`
js += `\nexport default __script`
// ...
// 处理模板
if (descriptor.template) {
let templateRequest = removeQuery(req.url) + `?type=template`;
code += `\nimport { render as __render } from ${JSON.stringify(
templateRequest
)}`;
code += `\n__script.render = __render`;
}
// ...
// 处理样式
if (descriptor.styles) {
descriptor.styles.forEach((s, i) => {
const styleRequest = removeQuery(req.url) + `?type=style&index=${i}`;
code += `\nimport ${JSON.stringify(styleRequest)}`
})
}
// ...
// 导出
code += `\nexport default __script`;
res.setHeader("Content-Type", typeAlias.js);
res.statusCode = 200;
res.end(code);
// app.js
const { compileTemplate } = require("@vue/compiler-sfc");
app.use(async function (req, res) {
if (/\.vue\??[^.]*$/.test(req.url)) {
// vue单文件
// 处理模板请求
if (getQuery(req.url, "type") === "template") {
// 编译模板为渲染函数
code = compileTemplate({
source: descriptor.template.content,
}).code;
res.setHeader("Content-Type", typeAlias.js);
res.statusCode = 200;
res.end(code);
return;
}
// ...
}
})
// 获取url的某个query值
const getQuery = (url, key) => {
return new URL(path.resolve(basePath, url)).searchParams.get(key);
};
// app.js
const { compileTemplate } = require("@vue/compiler-sfc");
app.use(async function (req, res) {
if (/\.vue\??[^.]*$/.test(req.url)) {
// vue单文件
}
// 处理样式请求
if (getQuery(req.url, "type") === "style") {
// 获取样式块索引
let index = getQuery(req.url, "index");
let styleContent = descriptor.styles[index].content;
code = `
const insertStyle = (css) => {
let el = document.createElement('style')
el.setAttribute('type', 'text/css')
el.innerHTML = css
document.head.appendChild(el)
}
insertStyle(\`${styleContent}\`)
export default insertStyle
`;
res.setHeader("Content-Type", typeAlias.js);
res.statusCode = 200;
res.end(code);
return;
}
})
// app.js
// css to js
const cssToJs = (css) => {
return `
const insertStyle = (css) => {
let el = document.createElement('style')
el.setAttribute('type', 'text/css')
el.innerHTML = css
document.head.appendChild(el)
}
insertStyle(\`${css}\`)
export default insertStyle
`;
};
// 处理裸导入
const parseBareImport = async (js) => {
await init;
let parseResult = parseEsModule(js);
let s = new MagicString(js);
// 遍历导入语句
parseResult[0].forEach((item) => {
// 不是裸导入则替换
if (item.n[0] !== "." && item.n[0] !== "/") {
s.overwrite(item.s, item.e, `/@module/${item.n}?import`);
} else {
s.overwrite(item.s, item.e, `${item.n}?import`);
}
});
return s.toString();
};
// 处理js部分
let script = compileScript(descriptor);
if (script) {
let scriptContent = await parseBareImport(script.content);// ++
code += rewriteDefault(scriptContent, "__script");
}
// 处理模板请求
if (
new URL(path.resolve(basePath, req.url)).searchParams.get("type") ===
"template"
) {
code = compileTemplate({
source: descriptor.template.content,
}).code;
code = await parseBareImport(code);// ++
res.setHeader("Content-Type", typeAlias.js);
res.statusCode = 200;
res.end(code);
return;
}
// vite/app.js
app.use(async function (req, res) {
if (isStaticAsset(req.url) && checkQueryExist(req.url, "import")) {
// import导入的静态文件
res.setHeader("Content-Type", typeAlias.js);
res.statusCode = 200;
res.end(`export default ${JSON.stringify(removeQuery(req.url))}`);
}
})
// 检查是否是静态文件
const imageRE = /\.(png|jpe?g|gif|svg|ico|webp)(\?.*)?$/;
const mediaRE = /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/;
const fontsRE = /\.(woff2?|eot|ttf|otf)(\?.*)?$/i;
const isStaticAsset = (file) => {
return imageRE.test(file) || mediaRE.test(file) || fontsRE.test(file);
};
// vite/app.js
const serveStatic = require("serve-static");
app.use(async function (req, res, next) {
if (xxx) {
// xxx
} else if (xxx) {
// xxx
// ...
} else {
next();// ++
}
})
// 静态文件服务
app.use(serveStatic(path.join(basePath, "public")));
app.use(serveStatic(path.join(basePath)));
关注公众号:拾黑(shiheibook)了解更多
赞助链接:
关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
关注网络尖刀微信公众号
随时掌握互联网精彩
随时掌握互联网精彩
赞助链接