环境配置
默认的vite+vue+js
断点调试
由于第一次编译,不存在.vite/deps的缓存文件
需要重新收集依赖
depsOptimizer.scanProcessing = new Promise((resolve) => {
// Ensure server listen is called before the scanner
setTimeout(async () => {
try {
debuggerViteDeps(picocolorsExports.green(`scanning for dependencies...`));
const deps = await discoverProjectDependencies(config);
debuggerViteDeps(picocolorsExports.green(Object.keys(deps).length > 0
? `dependencies found by scanner: ${depsLogString(Object.keys(deps))}`
: `no dependencies found by scanner`));
// Add these dependencies to the discovered list, as these are currently
// used by the preAliasPlugin to support aliased and optimized deps.
// This is also used by the CJS externalization heuristics in legacy mode
for (const id of Object.keys(deps)) {
if (!metadata.discovered[id]) {
addMissingDep(id, deps[id]);
}
}
const knownDeps = prepareKnownDeps();
// For dev, we run the scanner and the first optimization
// run on the background, but we wait until crawling has ended
// to decide if we send this result to the browser or we need to
// do another optimize step
postScanOptimizationResult = runOptimizeDeps(config, knownDeps);
}
catch (e) {
logger.error(e.message);
}
finally {
resolve();
depsOptimizer.scanProcessing = undefined;
}
}, 0);
});
async function discoverProjectDependencies(config) {
const { deps, missing } = await scanImports(config);
const missingIds = Object.keys(missing);
if (missingIds.length) {
throw new Error(`The following dependencies are imported but could not be resolved:\n\n ${missingIds
.map((id) => `${picocolorsExports.cyan(id)} ${picocolorsExports.white(picocolorsExports.dim(`(imported by ${missing[id]})`))}`)
.join(`\n `)}\n\nAre they installed?`);
}
return deps;
}
默认入口是index.html
这是esbuild收集依赖的插件vite:dep-scan
function esbuildScanPlugin(config, container, depImports, missing, entries) {
const seen = new Map();
const resolve = async (id, importer, options) => {
const key = id + (importer && path$o.dirname(importer));
if (seen.has(key)) {
return seen.get(key);
}
const resolved = await container.resolveId(id, importer && normalizePath$3(importer), {
...options,
scan: true,
});
const res = resolved?.id;
seen.set(key, res);
return res;
};
const include = config.optimizeDeps?.include;
const exclude = [
...(config.optimizeDeps?.exclude || []),
'@vite/client',
'@vite/env',
];
const externalUnlessEntry = ({ path }) => ({
path,
external: !entries.includes(path),
});
const doTransformGlobImport = async (contents, id, loader) => {
let transpiledContents;
// transpile because `transformGlobImport` only expects js
if (loader !== 'js') {
transpiledContents = (await transform$2(contents, { loader })).code;
}
else {
transpiledContents = contents;
}
const result = await transformGlobImport(transpiledContents, id, config.root, resolve, config.isProduction);
return result?.s.toString() || transpiledContents;
};
return {
name: 'vite:dep-scan',
setup(build) {
const scripts = {};
// external urls
build.onResolve({ filter: externalRE }, ({ path }) => ({
path,
external: true,
}));
// data urls
build.onResolve({ filter: dataUrlRE }, ({ path }) => ({
path,
external: true,
}));
// local scripts (`<script>` in Svelte and `<script setup>` in Vue)
build.onResolve({ filter: virtualModuleRE }, ({ path }) => {
return {
// strip prefix to get valid filesystem path so esbuild can resolve imports in the file
path: path.replace(virtualModulePrefix, ''),
namespace: 'script',
};
});
build.onLoad({ filter: /.*/, namespace: 'script' }, ({ path }) => {
return scripts[path];
});
// html types: extract script contents -----------------------------------
build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => {
const resolved = await resolve(path, importer);
if (!resolved)
return;
// It is possible for the scanner to scan html types in node_modules.
// If we can optimize this html type, skip it so it's handled by the
// bare import resolve, and recorded as optimization dep.
if (resolved.includes('node_modules') &&
isOptimizable(resolved, config.optimizeDeps))
return;
return {
path: resolved,
namespace: 'html',
};
});
// extract scripts inside HTML-like files and treat it as a js module
build.onLoad({ filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => {
let raw = fs$l.readFileSync(path, 'utf-8');
// Avoid matching the content of the comment
raw = raw.replace(commentRE, '<!---->');
const isHtml = path.endsWith('.html');
const regex = isHtml ? scriptModuleRE : scriptRE;
regex.lastIndex = 0;
let js = '';
let scriptId = 0;
let match;
while ((match = regex.exec(raw))) {
const [, openTag, content] = match;
const typeMatch = openTag.match(typeRE);
const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]);
const langMatch = openTag.match(langRE);
const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]);
// skip type="application/ld+json" and other non-JS types
if (type &&
!(type.includes('javascript') ||
type.includes('ecmascript') ||
type === 'module')) {
continue;
}
let loader = 'js';
if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') {
loader = lang;
}
else if (path.endsWith('.astro')) {
loader = 'ts';
}
const srcMatch = openTag.match(srcRE);
if (srcMatch) {
const src = srcMatch[1] || srcMatch[2] || srcMatch[3];
js += `import ${JSON.stringify(src)}\n`;
}
else if (content.trim()) {
// The reason why virtual modules are needed:
// 1. There can be module scripts (`<script context="module">` in Svelte and `<script>` in Vue)
// or local scripts (`<script>` in Svelte and `<script setup>` in Vue)
// 2. There can be multiple module scripts in html
// We need to handle these separately in case variable names are reused between them
// append imports in TS to prevent esbuild from removing them
// since they may be used in the template
const contents = content +
(loader.startsWith('ts') ? extractImportPaths(content) : '');
const key = `${path}?id=${scriptId++}`;
if (contents.includes('import.meta.glob')) {
scripts[key] = {
loader: 'js',
contents: await doTransformGlobImport(contents, path, loader),
pluginData: {
htmlType: { loader },
},
};
}
else {
scripts[key] = {
loader,
contents,
pluginData: {
htmlType: { loader },
},
};
}
const virtualModulePath = JSON.stringify(virtualModulePrefix + key);
const contextMatch = openTag.match(contextRE);
const context = contextMatch &&
(contextMatch[1] || contextMatch[2] || contextMatch[3]);
// Especially for Svelte files, exports in <script context="module"> means module exports,
// exports in <script> means component props. To avoid having two same export name from the
// star exports, we need to ignore exports in <script>
if (path.endsWith('.svelte') && context !== 'module') {
js += `import ${virtualModulePath}\n`;
}
else {
js += `export * from ${virtualModulePath}\n`;
}
}
}
// This will trigger incorrectly if `export default` is contained
// anywhere in a string. Svelte and Astro files can't have
// `export default` as code so we know if it's encountered it's a
// false positive (e.g. contained in a string)
if (!path.endsWith('.vue') || !js.includes('export default')) {
js += '\nexport default {}';
}
return {
loader: 'js',
contents: js,
};
});
// bare imports: record and externalize ----------------------------------
build.onResolve({
// avoid matching windows volume
filter: /^[\w@][^:]/,
}, async ({ path: id, importer, pluginData }) => {
if (moduleListContains(exclude, id)) {
return externalUnlessEntry({ path: id });
}
if (depImports[id]) {
return externalUnlessEntry({ path: id });
}
const resolved = await resolve(id, importer, {
custom: {
depScan: { loader: pluginData?.htmlType?.loader },
},
});
if (resolved) {
if (shouldExternalizeDep(resolved, id)) {
return externalUnlessEntry({ path: id });
}
if (resolved.includes('node_modules') || include?.includes(id)) {
// dependency or forced included, externalize and stop crawling
if (isOptimizable(resolved, config.optimizeDeps)) {
depImports[id] = resolved;
}
return externalUnlessEntry({ path: id });
}
else if (isScannable(resolved)) {
const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined;
// linked package, keep crawling
return {
path: path$o.resolve(resolved),
namespace,
};
}
else {
return externalUnlessEntry({ path: id });
}
}
else {
missing[id] = normalizePath$3(importer);
}
});
// Externalized file types -----------------------------------------------
// these are done on raw ids using esbuild's native regex filter so it
// should be faster than doing it in the catch-all via js
// they are done after the bare import resolve because a package name
// may end with these extensions
// css
build.onResolve({ filter: CSS_LANGS_RE }, externalUnlessEntry);
// json & wasm
build.onResolve({ filter: /\.(json|json5|wasm)$/ }, externalUnlessEntry);
// known asset types
build.onResolve({
filter: new RegExp(`\\.(${KNOWN_ASSET_TYPES.join('|')})$`),
}, externalUnlessEntry);
// known vite query types: ?worker, ?raw
build.onResolve({ filter: SPECIAL_QUERY_RE }, ({ path }) => ({
path,
external: true,
}));
// catch all -------------------------------------------------------------
build.onResolve({
filter: /.*/,
}, async ({ path: id, importer, pluginData }) => {
// use vite resolver to support urls and omitted extensions
const resolved = await resolve(id, importer, {
custom: {
depScan: { loader: pluginData?.htmlType?.loader },
},
});
if (resolved) {
if (shouldExternalizeDep(resolved, id) || !isScannable(resolved)) {
return externalUnlessEntry({ path: id });
}
const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined;
return {
path: path$o.resolve(cleanUrl(resolved)),
namespace,
};
}
else {
// resolve failed... probably unsupported type
return externalUnlessEntry({ path: id });
}
});
// for jsx/tsx, we need to access the content and check for
// presence of import.meta.glob, since it results in import relationships
// but isn't crawled by esbuild.
build.onLoad({ filter: JS_TYPES_RE }, async ({ path: id }) => {
let ext = path$o.extname(id).slice(1);
if (ext === 'mjs')
ext = 'js';
let contents = fs$l.readFileSync(id, 'utf-8');
if (ext.endsWith('x') && config.esbuild && config.esbuild.jsxInject) {
contents = config.esbuild.jsxInject + `\n` + contents;
}
const loader = config.optimizeDeps?.esbuildOptions?.loader?.[`.${ext}`] ||
ext;
if (contents.includes('import.meta.glob')) {
return {
loader: 'js',
contents: await doTransformGlobImport(contents, id, loader),
};
}
return {
loader,
contents,
};
});
},
};
}
由于入口文件是index.html,所以主要看html的解析
从html里面找到script的部分
完整的js
之后处理main.js的时候,将vue收集进依赖
这里如果是虚拟模块,虚拟模块为\0开头的,就不会进入依赖收集
main.js有import App from './App.vue'
虚拟模块不会进行依赖收集
helloWorld.vue有import { ref } from 'vue'
但是vue已经收集过依赖了,不会重新收集
收集完依赖后,就对依赖进行打包
5个vue的文件打包成了一个,还有一份sourcemap
将预处理结果存到.vite/deps