案例1(Vue3 + webpack)编译<img :src="'../assets' + '/11.png'" />
的结果:
js
<img :src="'../assets/' + imageName" />
编译后的render函数:
js
const _hoisted_1 = ["src"];
function render(_ctx, _cache, $props, $setup, $data, $options) {
return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("img", {
src: '../assets/' + $setup.imageName
}, null, 8 /* PROPS */, _hoisted_1));
}
案例2(vue3 + webpack)编译<img src="../assets/11.jpg" />
的结果:
js
<img src="../assets/11.jpg" />
编译后的render函数:
js
__webpack_require__.r(__webpack_exports__);
/* harmony export */
__webpack_require__.d(__webpack_exports__, {
/* harmony export */
render: ()=>(/* binding */
render)/* harmony export */
});
/* harmony import */
var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */
"./node_modules/.pnpm/vue@3.4.25_typescript@5.4.5/node_modules/vue/dist/vue.runtime.esm-bundler.js");
/* harmony import */
var _assets_11_jpg__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../assets/11.jpg */
"./src/assets/11.jpg");
const _withScopeId = n=>((0,
vue__WEBPACK_IMPORTED_MODULE_0__.pushScopeId)("data-v-103335b0"),
n = n(),
(0,
vue__WEBPACK_IMPORTED_MODULE_0__.popScopeId)(),
n);
const _hoisted_1 = {
src: _assets_11_jpg__WEBPACK_IMPORTED_MODULE_1__
};
function render(_ctx, _cache, $props, $setup, $data, $options) {
return ((0,
vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(),
(0,
vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("img", _hoisted_1));
}
案例3 (Vue3 + Vite)编译<img src="../assets/11.png" />
的结果:
Vue的编译过程
- webpack/lib/NormalModule.js
_doBuild------->processResult----->runLoaders
- loader-runner/lib/LoaderRunner.js
- vue-loader/dist/index.js
- @vue/compiler-sfc/dist/compiler-sfc.cjs.js
compileTemplate------>doCompileTemplate
- @vue/compiler-dom/dist/compiler-dom.cjs.js
compile
- @vue/compiler-core/dist/compiler-core.cjs.js
baseCompile(parse--->transform---->generate) generate(genNode)
vue处理<img src="../assets/11.jpg" />
的逻辑源码如下
@vue/compiler-sfc/dist/compiler-sfc.cjs.js
js
const defaultAssetUrlOptions = {
base: null,
includeAbsolute: false,
tags: {
video: ["src", "poster"],
source: ["src"],
img: ["src"],
image: ["xlink:href", "href"],
use: ["xlink:href", "href"]
}
};
export enum NodeTypes {
ROOT,
ELEMENT,
TEXT,
COMMENT,
SIMPLE_EXPRESSION,
INTERPOLATION,
ATTRIBUTE, // 6 属性
DIRECTIVE, // 7 指令
// containers
COMPOUND_EXPRESSION,
IF,
IF_BRANCH,
FOR,
TEXT_CALL,
// codegen
VNODE_CALL,
JS_CALL_EXPRESSION,
JS_OBJECT_EXPRESSION,
JS_PROPERTY,
JS_ARRAY_EXPRESSION,
JS_FUNCTION_EXPRESSION,
JS_CONDITIONAL_EXPRESSION,
JS_CACHE_EXPRESSION,
// ssr codegen
JS_BLOCK_STATEMENT,
JS_TEMPLATE_LITERAL,
JS_IF_STATEMENT,
JS_ASSIGNMENT_EXPRESSION,
JS_SEQUENCE_EXPRESSION,
JS_RETURN_STATEMENT,
}
/**
* A `@vue/compiler-core` plugin that transforms relative asset urls into
* either imports or absolute urls.
*
* ``` js
* // Before
* createVNode('img', { src: './logo.png' })
*
* // After
* import _imports_0 from './logo.png'
* createVNode('img', { src: _imports_0 })
* ```
*/
export const transformAssetUrl: NodeTransform = (
node,
context,
options: AssetURLOptions = defaultAssetUrlOptions,
) => {
if (node.type === NodeTypes.ELEMENT) {
if (!node.props.length) {
return
}
const tags = options.tags || defaultAssetUrlOptions.tags
const attrs = tags[node.tag]
const wildCardAttrs = tags['*']
if (!attrs && !wildCardAttrs) {
return
}
const assetAttrs = (attrs || []).concat(wildCardAttrs || [])
node.props.forEach((attr, index) => {
if (
attr.type !== NodeTypes.ATTRIBUTE || // 当前节点的属性类型不是属性
!assetAttrs.includes(attr.name) ||
!attr.value ||
isExternalUrl(attr.value.content) ||
isDataUrl(attr.value.content) ||
attr.value.content[0] === '#' ||
(!options.includeAbsolute && !isRelativeUrl(attr.value.content))
) {
return
}
const url = parseUrl(attr.value.content)
if (options.base && attr.value.content[0] === '.') {
// explicit base - directly rewrite relative urls into absolute url
// to avoid generating extra imports
// Allow for full hostnames provided in options.base
const base = parseUrl(options.base)
const protocol = base.protocol || ''
const host = base.host ? protocol + '//' + base.host : ''
const basePath = base.path || '/'
// when packaged in the browser, path will be using the posix-
// only version provided by rollup-plugin-node-builtins.
attr.value.content =
host +
(path.posix || path).join(basePath, url.path + (url.hash || ''))
return
}
// otherwise, transform the url into an import.
// this assumes a bundler will resolve the import into the correct
// absolute url (e.g. webpack file-loader)
const exp = getImportsExpressionExp(url.path, url.hash, attr.loc, context)
node.props[index] = {
type: NodeTypes.DIRECTIVE,
name: 'bind',
arg: createSimpleExpression(attr.name, true, attr.loc),
exp,
modifiers: [],
loc: attr.loc,
}
})
}
}
调用栈如下:
Vue处理<img src="../assets/11.png" />
的官方文档:
vue-loader.vuejs.org/zh/guide/as...
从源码发现,节点属性类型不为NodeTypes.ELEMENT(6),则不会按上述逻辑处理。<img :src="" />
:src是指令(7),而不是属性(6),所以被return了
webpack5处理图像
import MyImage from './my-image.png'
将会处理图像,将其添加到 output
目录,并且 MyImage
变量将包含该图像在处理后的最终的 url。
js
<img :src="'../assets/' + imageName" />
<img :src="require('../assets/' + imageName)" />
<img src="../assets/11.jpg" />
最后的html如下: