Skip to content
This repository has been archived by the owner on Feb 17, 2023. It is now read-only.

Commit

Permalink
fix: fix assets alias handling
Browse files Browse the repository at this point in the history
close #26, close #21
  • Loading branch information
underfin committed Jan 31, 2021
1 parent 3be490d commit fed3acf
Show file tree
Hide file tree
Showing 10 changed files with 562 additions and 178 deletions.
4 changes: 1 addition & 3 deletions package.json
Expand Up @@ -9,8 +9,7 @@
"build": "rm -rf dist && tsc -p . --skipLibCheck",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
"prepublishOnly": "yarn build && yarn changelog",
"postpublish": "git add CHANGELOG.md && git commit -m 'chore: changelog [ci skip]'",
"postinstall": "patch-package"
"postpublish": "git add CHANGELOG.md && git commit -m 'chore: changelog [ci skip]'"
},
"files": [
"dist"
Expand Down Expand Up @@ -52,7 +51,6 @@
"debug": "^4.3.1",
"fs-extra": "^9.0.1",
"hash-sum": "^2.0.0",
"patch-package": "^6.2.2",
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.0.5",
"querystring": "^0.2.0",
Expand Down
4 changes: 4 additions & 0 deletions playground/test-assets/TestAssets.vue
Expand Up @@ -8,6 +8,10 @@
Relative asset reference in template:
<img src="./nested/testAssets.png" style="width: 30px;" />
</p>
<p>
Alias asset reference in template:
<img src="/@/test-assets/nested/testAssets.png" style="width: 30px;" />
</p>
<p>
Absolute asset reference in template:
<img src="/favicon.ico" style="width: 30px;" />
Expand Down
3 changes: 3 additions & 0 deletions playground/vite.config.ts
Expand Up @@ -2,6 +2,9 @@ import { defineConfig } from 'vite'
const { createVuePlugin } = require('../dist')

const config = defineConfig({
alias: {
'/@': __dirname,
},
build: {
minify: false,
},
Expand Down
7 changes: 5 additions & 2 deletions src/template.ts
@@ -1,8 +1,9 @@
import { SFCBlock, compileTemplate } from '@vue/component-compiler-utils'
import { SFCBlock } from '@vue/component-compiler-utils'
import * as vueTemplateCompiler from 'vue-template-compiler'
import { TransformPluginContext } from 'rollup'
import { ResolvedOptions } from './index'
import { createRollupError } from './utils/error'
import { compileTemplate } from './template/compileTemplate'

export function compileSFCTemplate(
source: string,
Expand All @@ -16,7 +17,9 @@ export function compileSFCTemplate(
filename,
compiler: vueTemplateCompiler as any,
transformAssetUrls: true,
transformAssetUrlsOptions: {},
transformAssetUrlsOptions: {
forceRequire: true,
},
isProduction,
isFunctional: !!block.attrs.functional,
optimizeSSR: false,
Expand Down
80 changes: 80 additions & 0 deletions src/template/assetUrl.ts
@@ -0,0 +1,80 @@
// vue compiler module for transforming `<tag>:<attribute>` to `require`

import { urlToRequire, ASTNode, Attr } from './utils'

export interface AssetURLOptions {
[name: string]: string | string[]
}

export interface TransformAssetUrlsOptions {
/**
* @deprecated
* If base is provided, instead of transforming relative asset urls into
* imports, they will be directly rewritten to absolute urls.
*/
base?: string
forceRequire?: boolean
}

const defaultOptions: AssetURLOptions = {
audio: 'src',
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: ['xlink:href', 'href'],
use: ['xlink:href', 'href'],
}

export default (
userOptions?: AssetURLOptions,
transformAssetUrlsOption?: TransformAssetUrlsOptions
) => {
const options = userOptions
? Object.assign({}, defaultOptions, userOptions)
: defaultOptions

return {
postTransformNode: (node: ASTNode) => {
transform(node, options, transformAssetUrlsOption)
},
}
}

function transform(
node: ASTNode,
options: AssetURLOptions,
transformAssetUrlsOption?: TransformAssetUrlsOptions
) {
for (const tag in options) {
if ((tag === '*' || node.tag === tag) && node.attrs) {
const attributes = options[tag]
if (typeof attributes === 'string') {
node.attrs.some((attr) =>
rewrite(attr, attributes, transformAssetUrlsOption)
)
} else if (Array.isArray(attributes)) {
attributes.forEach((item) =>
node.attrs.some((attr) =>
rewrite(attr, item, transformAssetUrlsOption)
)
)
}
}
}
}

function rewrite(
attr: Attr,
name: string,
transformAssetUrlsOption?: TransformAssetUrlsOptions
) {
if (attr.name === name) {
const value = attr.value
// only transform static URLs
if (value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') {
attr.value = urlToRequire(value.slice(1, -1), transformAssetUrlsOption)
return true
}
}
return false
}
203 changes: 203 additions & 0 deletions src/template/compileTemplate.ts
@@ -0,0 +1,203 @@
import {
VueTemplateCompiler,
VueTemplateCompilerOptions,
ErrorWithRange,
} from './types'

import assetUrlsModule, {
AssetURLOptions,
TransformAssetUrlsOptions,
} from './assetUrl'
import srcsetModule from './srcset'

const consolidate = require('consolidate')
const transpile = require('vue-template-es2015-compiler')

export interface TemplateCompileOptions {
source: string
filename: string
compiler: VueTemplateCompiler
compilerOptions?: VueTemplateCompilerOptions
transformAssetUrls?: AssetURLOptions | boolean
transformAssetUrlsOptions?: TransformAssetUrlsOptions
preprocessLang?: string
preprocessOptions?: any
transpileOptions?: any
isProduction?: boolean
isFunctional?: boolean
optimizeSSR?: boolean
prettify?: boolean
}

export interface TemplateCompileResult {
ast: Object | undefined
code: string
source: string
tips: (string | ErrorWithRange)[]
errors: (string | ErrorWithRange)[]
}

export function compileTemplate(
options: TemplateCompileOptions
): TemplateCompileResult {
const { preprocessLang } = options
const preprocessor = preprocessLang && consolidate[preprocessLang]
if (preprocessor) {
return actuallyCompile(
Object.assign({}, options, {
source: preprocess(options, preprocessor),
})
)
} else if (preprocessLang) {
return {
ast: {},
code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
source: options.source,
tips: [
`Component ${options.filename} uses lang ${preprocessLang} for template. Please install the language preprocessor.`,
],
errors: [
`Component ${options.filename} uses lang ${preprocessLang} for template, however it is not installed.`,
],
}
} else {
return actuallyCompile(options)
}
}

function preprocess(
options: TemplateCompileOptions,
preprocessor: any
): string {
const { source, filename, preprocessOptions } = options

const finalPreprocessOptions = Object.assign(
{
filename,
},
preprocessOptions
)

// Consolidate exposes a callback based API, but the callback is in fact
// called synchronously for most templating engines. In our case, we have to
// expose a synchronous API so that it is usable in Jest transforms (which
// have to be sync because they are applied via Node.js require hooks)
let res: any, err
preprocessor.render(
source,
finalPreprocessOptions,
(_err: Error | null, _res: string) => {
if (_err) err = _err
res = _res
}
)

if (err) throw err
return res
}

function actuallyCompile(
options: TemplateCompileOptions
): TemplateCompileResult {
const {
source,
compiler,
compilerOptions = {},
transpileOptions = {},
transformAssetUrls,
transformAssetUrlsOptions,
isProduction = process.env.NODE_ENV === 'production',
isFunctional = false,
optimizeSSR = false,
prettify = true,
} = options

const compile =
optimizeSSR && compiler.ssrCompile ? compiler.ssrCompile : compiler.compile

let finalCompilerOptions = compilerOptions
if (transformAssetUrls) {
const builtInModules = [
transformAssetUrls === true
? assetUrlsModule(undefined, transformAssetUrlsOptions)
: assetUrlsModule(transformAssetUrls, transformAssetUrlsOptions),
srcsetModule(transformAssetUrlsOptions),
]
finalCompilerOptions = Object.assign({}, compilerOptions, {
modules: [...builtInModules, ...(compilerOptions.modules || [])],
filename: options.filename,
})
}

const { ast, render, staticRenderFns, tips, errors } = compile(
source,
finalCompilerOptions
)

if (errors && errors.length) {
return {
ast,
code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
source,
tips,
errors,
}
} else {
const finalTranspileOptions = Object.assign({}, transpileOptions, {
transforms: Object.assign({}, transpileOptions.transforms, {
stripWithFunctional: isFunctional,
}),
})

const toFunction = (code: string): string => {
return `function (${isFunctional ? `_h,_vm` : ``}) {${code}}`
}

// transpile code with vue-template-es2015-compiler, which is a forked
// version of Buble that applies ES2015 transforms + stripping `with` usage
let code =
transpile(
`var __render__ = ${toFunction(render)}\n` +
`var __staticRenderFns__ = [${staticRenderFns.map(toFunction)}]`,
finalTranspileOptions
) + `\n`

// #23 we use __render__ to avoid `render` not being prefixed by the
// transpiler when stripping with, but revert it back to `render` to
// maintain backwards compat
code = code.replace(/\s__(render|staticRenderFns)__\s/g, ' $1 ')

if (!isProduction) {
// mark with stripped (this enables Vue to use correct runtime proxy
// detection)
code += `render._withStripped = true`

if (prettify) {
try {
code = require('prettier').format(code, {
semi: false,
parser: 'babel',
})
} catch (e) {
if (e.code === 'MODULE_NOT_FOUND') {
tips.push(
'The `prettify` option is on, but the dependency `prettier` is not found.\n' +
'Please either turn off `prettify` or manually install `prettier`.'
)
}
tips.push(
`Failed to prettify component ${options.filename} template source after compilation.`
)
}
}
}

return {
ast,
code,
source,
tips,
errors,
}
}
}

0 comments on commit fed3acf

Please sign in to comment.