最近在作跨平臺框架的試點,選擇了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
},
}
複製代碼
嗯,不是辦法的辦法。
你若是有更好的解決方法歡迎評論區留言,討論。