最近在作跨平台框架的试点,选择了uni-app
,并打算先在h5上开始试点。html
因为uni-app
提供的基于vue-cli
的脚手架与咱们内部的脚手架稍有些不一样,直接使用稍微有点学习成本,因此fork了一下,稍做修改,作了一个内部版本的脚手架(主要就是将publicPath从manifest.json中拿出来,实现动态配置),目的就是让其用起来和咱们本身的脚手架差很少。vue
修改完脚手架,在本地测试时,发现了一个问题,当时使用这样一段代码:node
<template>
<view class="content">
<image class="logo" src="../../static/logo.png"></image>
...
</view>
</template>
复制代码
在配置好publicPath
后,将dist/build/h5
的结果上传到咱们的静态服务器上后,发现图片显示不出来。在控制台检查一下代码,发现此处的img
标签是这样的:webpack
也就是说配置的publicPath
并未生效。git
开始怀疑是否是本身漏掉了什么配置,在uni-app
找了关于image
组件的使用方法,里面看起来并无什么特殊的说明。好吧,Google一下,发现网上有一些解决方案,须要把代码改为下面这样:github
<template>
<view class="content">
<image class="logo" :src="require('../../static/logo.png')"></image>
...
</view>
</template>
复制代码
试了一下,确实,图片回来了:web
可是这样解决仍是略有些简陋,改脚手架不能止步于此。因而继续寻找解决办法vue-cli
迅速的捋一下思路,出现这个问题的缘由是,.vue
文件在编译阶段并无为这个image组件的src属性的值自动加上require
,从而没法被file-loader或url-loader来正确的处理。看来问题的关键就出在编译.vue
文件这里。json
因此去看了一下vue-loader的官方文档,vue-loader的文档很明显的有专门的一节来介绍这个功能:数组
大概意思就是说有一个transformAssetUrls
的属性能够用来处理这种问题。这个属性的默认值是:
{
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: ['xlink:href', 'href'],
use: ['xlink:href', 'href']
}
复制代码
也就是说,vue-loader默认会处理好比img
标签的src
属性,若是src
的值为相对路径,就会将其替换为require(...)
调用。去看一下uni-app
的脚手架是怎么配vue-loader便知。直接去node_modules下看源码,找到配置vue-loader的地方在@dcloudio/vue-cli-plugin-uni/lib/h5/index
里:
// @dcloudio/vue-cli-plugin-uni/lib/h5/index.js#L113
webpackConfig.module
.rule('vue')
.test([/\.vue$/, /\.nvue$/])
.use('vue-loader')
.tap(options => Object.assign(options, {
compiler: getPlatformCompiler(),
compilerOptions: require('./compiler-options'),
cacheDirectory: false,
cacheIdentifier: false
}))
.end()
// ...
复制代码
在.tap
中发现,uni-app
的脚手架并无配置transformAssetUrls
这个属性,可能只是走了默认的配置。直接在本地修改一下试试吧,直接修改node_modules/@dcloudio/vue-cli-plugin-uni/lib/h5/index
,试着为image增长一个src
属性:
// @dcloudio/vue-cli-plugin-uni/lib/h5/index.js#L113
webpackConfig.module
.rule('vue')
.test([/\.vue$/, /\.nvue$/])
.use('vue-loader')
.tap(options => Object.assign(options, {
compiler: getPlatformCompiler(),
compilerOptions: require('./compiler-options'),
cacheDirectory: false,
cacheIdentifier: false,
// 新增的这里
transformAssetUrls: {
'image': ['xlink:href', 'href', 'src']
}
}))
.end()
// ...
复制代码
发现并无生效,仔细察看build后的源码,发现实际上image
组件最终会被处理成以渲染函数的形式来建立的组件:
// ...
return createElement("v-uni-image", {
staticClass: "logo",
attrs: {
src: '../../static/logo.png'
}
})
// ...
复制代码
能够看到,组件名会被修改成v-uni-image,因此上面的配置才没有生效。
继续改为这样:
// @dcloudio/vue-cli-plugin-uni/lib/h5/index.js#L113
webpackConfig.module
.rule('vue')
.test([/\.vue$/, /\.nvue$/])
.use('vue-loader')
.tap(options => Object.assign(options, {
compiler: getPlatformCompiler(),
compilerOptions: require('./compiler-options'),
cacheDirectory: false,
cacheIdentifier: false,
// 新增的这里
transformAssetUrls: {
'v-uni-image': 'src'
}
}))
.end()
// ...
复制代码
从新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
},
}
复制代码
嗯,不是办法的办法。
你若是有更好的解决方法欢迎评论区留言,讨论。