| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- import path from 'path'
- import {
- ConstantTypes,
- createSimpleExpression,
- ExpressionNode,
- NodeTransform,
- NodeTypes,
- SourceLocation,
- TransformContext
- } from '@vue/compiler-core'
- import {
- isRelativeUrl,
- parseUrl,
- isExternalUrl,
- isDataUrl
- } from './templateUtils'
- import { isArray } from '@vue/shared'
- export interface AssetURLTagConfig {
- [name: string]: string[]
- }
- export interface AssetURLOptions {
- /**
- * If base is provided, instead of transforming relative asset urls into
- * imports, they will be directly rewritten to absolute urls.
- */
- base?: string | null
- /**
- * If true, also processes absolute urls.
- */
- includeAbsolute?: boolean
- tags?: AssetURLTagConfig
- }
- export const defaultAssetUrlOptions: Required<AssetURLOptions> = {
- base: null,
- includeAbsolute: false,
- tags: {
- video: ['src', 'poster'],
- source: ['src'],
- img: ['src'],
- image: ['xlink:href', 'href'],
- use: ['xlink:href', 'href']
- }
- }
- export const normalizeOptions = (
- options: AssetURLOptions | AssetURLTagConfig
- ): Required<AssetURLOptions> => {
- if (Object.keys(options).some(key => isArray((options as any)[key]))) {
- // legacy option format which directly passes in tags config
- return {
- ...defaultAssetUrlOptions,
- tags: options as any
- }
- }
- return {
- ...defaultAssetUrlOptions,
- ...options
- }
- }
- export const createAssetUrlTransformWithOptions = (
- options: Required<AssetURLOptions>
- ): NodeTransform => {
- return (node, context) =>
- (transformAssetUrl as Function)(node, context, options)
- }
- /**
- * A `@vue/compiler-core` plugin that transforms relative asset urls into
- * either imports or absolute urls.
- *
- * ``` js
- * // Before
- * createVNode('img', { src: './logo.png' })
- *
- * // After
- * import _imports_0 from './logo.png'
- * createVNode('img', { src: _imports_0 })
- * ```
- */
- export const transformAssetUrl: NodeTransform = (
- node,
- context,
- options: AssetURLOptions = defaultAssetUrlOptions
- ) => {
- if (node.type === NodeTypes.ELEMENT) {
- if (!node.props.length) {
- return
- }
- const tags = options.tags || defaultAssetUrlOptions.tags
- const attrs = tags[node.tag]
- const wildCardAttrs = tags['*']
- if (!attrs && !wildCardAttrs) {
- return
- }
- const assetAttrs = (attrs || []).concat(wildCardAttrs || [])
- node.props.forEach((attr, index) => {
- if (
- attr.type !== NodeTypes.ATTRIBUTE ||
- !assetAttrs.includes(attr.name) ||
- !attr.value ||
- isExternalUrl(attr.value.content) ||
- isDataUrl(attr.value.content) ||
- attr.value.content[0] === '#' ||
- (!options.includeAbsolute && !isRelativeUrl(attr.value.content))
- ) {
- return
- }
- const url = parseUrl(attr.value.content)
- if (options.base && attr.value.content[0] === '.') {
- // explicit base - directly rewrite relative urls into absolute url
- // to avoid generating extra imports
- // Allow for full hostnames provided in options.base
- const base = parseUrl(options.base)
- const protocol = base.protocol || ''
- const host = base.host ? protocol + '//' + base.host : ''
- const basePath = base.path || '/'
- // when packaged in the browser, path will be using the posix-
- // only version provided by rollup-plugin-node-builtins.
- attr.value.content =
- host +
- (path.posix || path).join(basePath, url.path + (url.hash || ''))
- return
- }
- // otherwise, transform the url into an import.
- // this assumes a bundler will resolve the import into the correct
- // absolute url (e.g. webpack file-loader)
- const exp = getImportsExpressionExp(url.path, url.hash, attr.loc, context)
- node.props[index] = {
- type: NodeTypes.DIRECTIVE,
- name: 'bind',
- arg: createSimpleExpression(attr.name, true, attr.loc),
- exp,
- modifiers: [],
- loc: attr.loc
- }
- })
- }
- }
- function getImportsExpressionExp(
- path: string | null,
- hash: string | null,
- loc: SourceLocation,
- context: TransformContext
- ): ExpressionNode {
- if (path) {
- const existing = context.imports.find(i => i.path === path)
- if (existing) {
- return existing.exp as ExpressionNode
- }
- const name = `_imports_${context.imports.length}`
- const exp = createSimpleExpression(
- name,
- false,
- loc,
- ConstantTypes.CAN_HOIST
- )
- context.imports.push({ exp, path })
- if (hash && path) {
- return context.hoist(
- createSimpleExpression(
- `${name} + '${hash}'`,
- false,
- loc,
- ConstantTypes.CAN_HOIST
- )
- )
- } else {
- return exp
- }
- } else {
- return createSimpleExpression(`''`, false, loc, ConstantTypes.CAN_HOIST)
- }
- }
|