background
Recently, I am making a trial of cross-platform framework, choosing UNI-app, and I plan to start the trial on H5 first.
Since the vuE-CLI-based scaffolding provided by Uni-App is slightly different from our internal scaffolding and there is a little bit of learning cost to use directly, we forked a little bit and made an internal version of the scaffolding (basically taking publicPath out of manifest.json, Dynamically configured), with the goal of making it work much like our own scaffolding.
The problem
After modifying the scaffolding, we found a problem when testing it locally, using this code:
<template>
<view class="content">
<image class="logo" src=".. /.. /static/logo.png"></image>.</view>
</template>
Copy the code
After publicPath was configured, we uploaded the dist/build/ H5 results to our static server and found that the images could not be displayed. Check the code on the console and the IMG tag looks like this:
That is, the configured publicPath does not take effect.
Look for a solution
I began to wonder if I had missed some configuration. I looked for the usage method of image component in UNI-app, and there seemed to be no special instructions in it. Well, Google around and find some solutions online that require the following code:
<template>
<view class="content">
<image class="logo" :src="require('.. /.. /static/logo.png')"></image>.</view>
</template>
Copy the code
Try it and, yes, the picture comes back:
But this solution is still slightly rough, change scaffolding can not stop here. So the search for a solution continued
Train of thought
As a quick thought, the problem is that the.vue file does not automatically require the SRC attribute value of the image component at compile time and therefore cannot be handled properly by file-loader or url-loader. It seems that the key to the problem is to compile the.vue file here.
So go to see the official documentation of VUe-Loader, vue-Loader documentation is obviously a special section to introduce this function:
There is a property transformAssetUrls that can be used to handle this problem. The default value for this property is:
{
video: ['src'.'poster'].source: 'src'.img: 'src'.image: ['xlink:href'.'href'].use: ['xlink:href'.'href']}Copy the code
That is, vue-loader by default processes SRC attributes such as img tags and replaces them with require(…) if SRC is a relative path. The call. See how uni-App scaffolding works with vue-Loader. Node_modules = @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()
// ...
Copy the code
As found in.tap, uni-app scaffolding does not have the property transformAssetUrls configured, probably just the default configuration. Node_modules /@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.// Add here
transformAssetUrls: {
'image': ['xlink:href'.'href'.'src']
}
}))
.end()
// ...
Copy the code
A closer look at the build source shows that the image component will actually be processed as a component created in the form of a rendering function:
// ...
return createElement("v-uni-image", {
staticClass: "logo".attrs: {
src: '.. /.. /static/logo.png'}})// ...
Copy the code
As you can see, the component name is changed to V-uni-image, which is why the above configuration does not take effect.
To solve
Continue with this:
// @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.// Add here
transformAssetUrls: {
'v-uni-image': 'src'
}
}))
.end()
// ...
Copy the code
The rebuild does work, and the generated code looks something like this:
return createElement("v-uni-image", {
staticClass: "logo".attrs: {
src: require(/ /...).}})Copy the code
abouttransformAssetUrls
How to handle the property 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
// All options that will eventually be passed to the template compiler
const finalOptions = {
source,
filename: this.resourcePath,
compiler,
compilerOptions,
// allow customizing behavior of vue-template-es2015-compiler
transpileOptions: options.transpileOptions,
transformAssetUrls: options.transformAssetUrls || true.// Watch this!!
isProduction,
isFunctional,
optimizeSSR: isServer && options.optimizeSSR ! = =false.prettify: options.prettify
}
const compiled = compileTemplate(finalOptions) // Pass all options to the compileTemplate template compiler
// ...
Copy the code
To continue:
/ / https://github.com/vuejs/component-compiler-utils/blob/master/lib/compileTemplate.ts#L113
import assetUrlsModule from './templateCompilerModules/assetUrl'
// ...
let finalCompilerOptions = finalOptions
if (transformAssetUrls) { // If a custom transformAssetUrls is passed in, merge it with the default
const builtInModules = [
transformAssetUrls === true
? assetUrlsModule()
: assetUrlsModule(transformAssetUrls),
srcsetModule()
]
finalCompilerOptions = Object.assign({}, compilerOptions, {
modules: [...builtInModules, ...(compilerOptions.modules || [])] // Does it look familiar})}const { render, staticRenderFns, tips, errors } = compile(
source,
finalCompilerOptions
)
Copy the code
Continue to chase:
/ / https://github.com/vuejs/component-compiler-utils/blob/master/lib/templateCompilerModules/assetUrl.ts
// Familiar faces
const defaultOptions: AssetURLOptions = {
video: ['src'.'poster'].source: 'src'.img: 'src'.image: ['xlink:href'.'href'].use: ['xlink:href'.'href']}// These tags are processed by returning a 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) {
/ /... Something like that
attr.value = `require(${attr.value}) `
}
return false
}
Copy the code
As you can see, the options in transformAssetUrls directly generate a hook called postTransformNode, which processes each element of the Template template to create a separate syntax tree node ASTNode. And to be executed after ASTNode has been further processed. The postTransformNode counterpart is the preTransformNode hook, which, as the name suggests, executes before the generated ASTNode is ready to be processed further. These two types of hooks can be put into a {modules: [hook]} object and passed into the final template compiler.
Similar code can be seen in the compiler option @dcloudio/vue-cli-plugin-uni/lib/h5/compiler-options.js’ of uni-app’s custom compiler:
// @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
}) {
// ...
}
Copy the code
The V-UNI – prefix for uni-App’s own components is added in this way. Therefore, the above problems can also be directly dealt with in a more violent way here:
// @dcloudio/vue-cli-plugin-uni/lib/h5/compiler-options.js#L113
// Import vue's own processing methods
const assetUrlsModule = require('@vue/component-compiler-utils/dist/templateCompilerModules/assetUrl').default
// Generate hooks for label handling
const builtInModules = assetUrlsModule({ 'v-uni-image': 'src' })
module.exports = {
isUnaryTag,
preserveWhitespace: false.modules: [require('.. /format-text'), {
...builtInModules,
}, {
preTransformNode (el, {
warn
}) {
// ...
},
postTransformNode (el, {
warn,
filterModules
}) {
// ...
}
Copy the code
For more information on modules, see: Arrays of compiler modules
Solutions directly in the project
If you want to use the official scaffolding directly to solve this problem, you can add the following code to 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}},Copy the code
Well, not the way to do it.
If you have a better solution, please leave a comment in the comments section.