vue/dev-server

demo 介绍

demo 展示在页面中直接引入 esModule 文件,并在文件中引入 vue 组件的渲染流程

渲染流程

sequenceDiagram autonumber participant Client participant Server Client->>Server: /main.js Note left of Server: 返回资源的时候处理node_modules中依赖的路径 Server->>Client: main.js文件内容 Note left of Server: __module/vue 和 test.vue Client->>Server: 浏览器分析分析并请求import资源 Server->>Client: 返回依赖资源或是打包后的js文件
  • 浏览器向服务器请求入口文件 main.js, 请求路径为 import 路径 .main.js

  • 服务端接收到请求,交给 middleware 处理,首先检查缓存,如果缓存存在直接返回缓存结果,如果不存在,通过请求路径读取本地资源文件,处理后加入缓存并返回

  • 如果文件是 js 结尾, 这里表示的是入口文件

    • 将文件内容转为 ast 语法树, 分析依赖模块包括 vue 和 text.vue,将 node_module 中的依赖路径转换为自定义路径用于资源请求时区分资源并可以加入缓存优化,例如__module/vue,将 ast 生成的代码返回给浏览器
    • 浏览器会自动请求 esMoudle 中 import 的文件
      http://localhost:3000/\_\_modules/vue
      http://localhost:3000/test.vue
  • 如果文件路径中包含 \_\_moudles, 则尝试加载 node_modules 中的文件
    通过 require.resolve("vue") 可以获取某个包在 node_modules 中的绝对路径

  • 如果文件路径是以 .vue 结尾, 需要使用 vue 提供的编译模块将文件编译成单个 js 文件

    • 首先通过 vueComplier compileToDescriptor 将 vue 文件处理 template, styles,scripts 的描述对象
    • 在使用 vueCompiler.assemble 将描述文件中的各部分组装成代码,返回给浏览器

middleware.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
const vueCompiler = require("@vue/component-compiler");
const fs = require("fs");
const stat = require("util").promisify(fs.stat);
const root = process.cwd();
const path = require("path");
const parseUrl = require("parseurl");
const { transformModuleImports } = require("./transformModuleImports");
const readFile = require("util").promisify(fs.readFile);
const parseUrl = require("parseurl");
const defaultOptions = {
cache: true,
};

async function loadPkg(pkg) {
if (pkg === "vue") {
const dir = path.dirname(require.resolve("vue"));
const filepath = path.join(dir, "vue.esm.browser.js");
return readFile(filepath);
} else {
// TODO
// check if the package has a browser es module that can be used
// otherwise bundle it with rollup on the fly?
throw new Error("npm imports support are not ready yet.");
}
}
async function readSource(req) {
const { pathname } = parseUrl(req);
const filepath = path.resolve(root, pathname.replace(/^\//, ""));
return {
filepath,
source: await readFile(filepath, "utf-8"),
updateTime: (await stat(filepath)).mtime.getTime(),
};
}
function transformModuleImports(code) {
const ast = recast.parse(code);
recast.types.visit(ast, {
visitImportDeclaration(path) {
const source = path.node.source.value;
if (!/^\.\/?/.test(source) && isPkg(source)) {
path.node.source = recast.types.builders.literal(
`/__modules/${source}`
);
}
this.traverse(path);
},
});
return recast.print(ast).code;
}

const vueMiddleware = (options = defaultOptions) => {
let cache;
let time = {};
if (options.cache) {
const LRU = require("lru-cache");

cache = new LRU({
max: 500,
length: function (n, key) {
return n * 2 + key.length;
},
});
}

const compiler = vueCompiler.createDefaultCompiler();

function send(res, source, mime) {
res.setHeader("Content-Type", mime);
res.end(source);
}

function injectSourceMapToBlock(block, lang) {
const map = Base64.toBase64(JSON.stringify(block.map));
let mapInject;

switch (lang) {
case "js":
mapInject = `//# sourceMappingURL=data:application/json;base64,${map}\n`;
break;
case "css":
mapInject = `/*# sourceMappingURL=data:application/json;base64,${map}*/\n`;
break;
default:
break;
}

return {
...block,
code: mapInject + block.code,
};
}

function injectSourceMapToScript(script) {
return injectSourceMapToBlock(script, "js");
}

function injectSourceMapsToStyles(styles) {
return styles.map((style) => injectSourceMapToBlock(style, "css"));
}

async function tryCache(key, checkUpdateTime = true) {
const data = cache.get(key);

if (checkUpdateTime) {
const cacheUpdateTime = time[key];
const fileUpdateTime = (
await stat(path.resolve(root, key.replace(/^\//, "")))
).mtime.getTime();
if (cacheUpdateTime < fileUpdateTime) return null;
}

return data;
}

function cacheData(key, data, updateTime) {
const old = cache.peek(key);

if (old != data) {
cache.set(key, data);
if (updateTime) time[key] = updateTime;
return true;
} else return false;
}

async function bundleSFC(req) {
const { filepath, source, updateTime } = await readSource(req);
const descriptorResult = compiler.compileToDescriptor(filepath, source);
console.log(descriptorResult);
const assembledResult = vueCompiler.assemble(compiler, filepath, {
...descriptorResult,
script: injectSourceMapToScript(descriptorResult.script),
styles: injectSourceMapsToStyles(descriptorResult.styles),
});
return { ...assembledResult, updateTime };
}

return async (req, res, next) => {
if (req.path.endsWith(".vue")) {
const key = parseUrl(req).pathname;
let out = await tryCache(key);

if (!out) {
// Bundle Single-File Component
const result = await bundleSFC(req);
console.log(result);
out = result;
cacheData(key, out, result.updateTime);
}

send(res, out.code, "application/javascript");
} else if (req.path.endsWith(".js")) {
const key = parseUrl(req).pathname;
let out = await tryCache(key);

if (!out) {
// transform import statements
const result = await readSource(req);
out = transformModuleImports(result.source);
console.log(out);
cacheData(key, out, result.updateTime);
}

send(res, out, "application/javascript");
} else if (req.path.startsWith("/__modules/")) {
const key = parseUrl(req).pathname;
const pkg = req.path.replace(/^\/__modules\//, "");

let out = await tryCache(key, false); // Do not outdate modules
if (!out) {
out = (await loadPkg(pkg)).toString();
cacheData(key, out, false); // Do not outdate modules
}

send(res, out, "application/javascript");
} else {
next();
}
};
};

exports.vueMiddleware = vueMiddleware;
打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2025 SunZhiqi

此时无声胜有声!

支付宝
微信