重新build,确实生效了,看一下生成的代码大概是这样的:
return createElement("v-uni-image", { staticClass: "logo", attrs: { src: require(// ... ) } })
关于transformAssetUrls
趁热打铁,又看一下vue-loader的源码,看看到底是如何处理transformAssetUrls
这个属性的,一些关键代码:
// 详见 https://github.com/vuejs/vue-loader/blob/master/lib/loaders/templateLoader.js#L32 const { compileTemplate } = require('@vue/component-compiler-utils') // ... // for vue-component-compiler // 最终将传递给模板编译器的所有选项 const finalOptions = { source, filename: this.resourcePath, compiler, compilerOptions, // allow customizing behavior of vue-template-es2015-compiler transpileOptions: options.transpileOptions, transformAssetUrls: options.transformAssetUrls || true, // 注意这里!!! isProduction, isFunctional, optimizeSSR: isServer && options.optimizeSSR !== false, prettify: options.prettify } const compiled = compileTemplate(finalOptions) // 将所有的选项传递给compileTemplate模板编译器 // ...
追下去:
// 详见 https://github.com/vuejs/component-compiler-utils/blob/master/lib/compileTemplate.ts#L113 import assetUrlsModule from './templateCompilerModules/assetUrl' // ... let finalCompilerOptions = finalOptions if (transformAssetUrls) { // 如果传入了自定义的transformAssetUrls,将其与默认的合并 const builtInModules = [ transformAssetUrls === true ? assetUrlsModule() : assetUrlsModule(transformAssetUrls), srcsetModule() ] finalCompilerOptions = Object.assign({}, compilerOptions, { modules: [...builtInModules, ...(compilerOptions.modules || [])] // 是不是很眼熟 }) } const { render, staticRenderFns, tips, errors } = compile( source, finalCompilerOptions )
继续追:
// 详见 https://github.com/vuejs/component-compiler-utils/blob/master/lib/templateCompilerModules/assetUrl.ts // 熟悉的面孔 const defaultOptions: AssetURLOptions = { video: ['src', 'poster'], source: 'src', img: 'src', image: ['xlink:href', 'href'], use: ['xlink:href', 'href'] } // 原来是通过返回一个postTransformNode来处理上面这些标签的 export default (userOptions?: AssetURLOptions) => { const options = userOptions ? Object.assign({}, defaultOptions, userOptions) : defaultOptions return { postTransformNode: (node: ASTNode) => { transform(node, options) } } } function transform(node: ASTNode, options: AssetURLOptions) { for (const tag in options) { // ... attributes.forEach(item => node.attrs.some(attr => rewrite(attr, item))) } } function rewrite(attr: Attr, name: string) { if (attr.name === name) { // ... 大概是这个意思 attr.value = `require(${attr.value})` } return false }
可以看到,原来transformAssetUrls
里面的选项会直接生成一个叫的postTransformNode
的钩子,他的作用就是用来处理模板template
里面的每一个元素element
,生成单独的语法树节点ASTNode
,并在ASTNode
被进一步处理之后要执行的。与postTransformNode
对应的还有preTransformNode
钩子,顾名思义,就是在生成的ASTNode
即将被进一步处理之前要执行的钩子。这两类钩子可以放到一个 { modules: [ 钩子 ] } 的对象中,一并传入给最终的模板编译器。
而在uni-app
的自定义编译器的编译器选项@dcloudio/vue-cli-plugin-uni/lib/h5/compiler-options.js`中,也可以看到类似的代码:
// @dcloudio/vue-cli-plugin-uni/lib/h5/compiler-options.js#L113 module.exports = { isUnaryTag, preserveWhitespace: false, modules: [require('../format-text'), { preTransformNode (el, { warn }) { // ... }, postTransformNode (el, { warn, filterModules }) { // ... }
uni-app
自有组件的v-uni-
前缀就是通过这种方式添加的。所以,上面的遇到问题也可以直接在这里比较暴力的方式处理:
// @dcloudio/vue-cli-plugin-uni/lib/h5/compiler-options.js#L113 // 将vue自带的处理方法引进来 const assetUrlsModule = require('@vue/component-compiler-utils/dist/templateCompilerModules/assetUrl').default // 生成标签处理的钩子 const builtInModules = assetUrlsModule({ 'v-uni-image': 'src' }) module.exports = { isUnaryTag, preserveWhitespace: false, modules: [require('../format-text'), { ...builtInModules, }, { preTransformNode (el, { warn }) { // ... }, postTransformNode (el, { warn, filterModules }) { // ... }
更多关于modules
的信息可参考: 编译器模块的数组
直接在项目中的解决办法
如果想直接使用官方的脚手架来解决这个问题,就可以在vue.config.js中加入如下代码来解决:
module.exports = { chainWebpack(webpackConfig) { webpackConfig.module .rule('vue') .test([/\.vue$/, /\.nvue$/]) .use('vue-loader') .tap(options => Object.assign(options, { transformAssetUrls: { 'v-uni-image': 'src' } })) .end() }, configureWebpack (config) { // ...blablabla }, }
嗯,不是办法的办法。
你如果有更好的解决方法欢迎评论区留言,讨论。