feat(plugin-replace-assets): improve replace assets (#628)

This commit is contained in:
pengzhanbo 2025-06-29 14:37:33 +08:00 committed by GitHub
parent cd2b7fd26d
commit 309be687b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 41 additions and 79 deletions

View File

@ -18,7 +18,12 @@ badge: 新
在这个过程中,通常需要先将资源上传到 CDN 服务,然后再获取 CDN 服务的资源链接,最后才在站点内容中使用。
这看起来并没有什么问题,然而在实际使用过程中,可能需要频繁的进行
__上传资源 -> 获取资源链接 -> 在内容中使用全量资源链接__ 的操作,内容创作被频繁的打断。
```txt
上传资源 -> 获取资源链接 -> 在内容中使用全量资源链接
```
在此过程中,内容创作被频繁的打断。
此功能旨在解决这个问题。在内容创作过程中,只需要直接使用本地资源地址,由主题内部在合适的阶段,完成资源地址的替换。
:::
@ -93,7 +98,7 @@ interface ReplacementRule {
interface ReplaceAssetsOptions {
/**
* 自定义替换规则
* 自定义资源替换规则
*/
rules?: ReplacementRule | ReplacementRule[]
/**

View File

@ -31,12 +31,12 @@ describe('plugin-replace-assets > normalizeRules', () => {
it('should work with single rule', () => {
const rules = normalizeRules({
find: '^/images/.*\.(jpe?g|png|gif|svg)$',
find: '^/images/.*\\.(jpe?g|png|gif|svg)$',
replacement: 'https://example.com/images/',
})
expect(rules).toEqual([{
find: '^/images/.*\.(jpe?g|png|gif|svg)$',
find: '^/images/.*\\.(jpe?g|png|gif|svg)$',
replacement: 'https://example.com/images/',
}])
})
@ -44,22 +44,22 @@ describe('plugin-replace-assets > normalizeRules', () => {
it('should work with multiple rules', () => {
const rules = normalizeRules([
{
find: '^/images/.*\.(jpe?g|png|gif|svg)$',
find: '^/images/.*\\.(jpe?g|png|gif|svg)$',
replacement: 'https://example.com/images/',
},
{
find: '^/medias/.*\.(mp4|ogg|ogv|webm)$',
find: '^/medias/.*\\.(mp4|ogg|ogv|webm)$',
replacement: 'https://example.com/medias/',
},
])
expect(rules).toEqual([
{
find: '^/images/.*\.(jpe?g|png|gif|svg)$',
find: '^/images/.*\\.(jpe?g|png|gif|svg)$',
replacement: 'https://example.com/images/',
},
{
find: '^/medias/.*\.(mp4|ogg|ogv|webm)$',
find: '^/medias/.*\\.(mp4|ogg|ogv|webm)$',
replacement: 'https://example.com/medias/',
},
])
@ -92,13 +92,13 @@ describe('plugin-replace-assets > normalizeRules', () => {
it('should work with custom single rule', () => {
const rules = normalizeRules({
rules: {
find: '^/images/.*\.(jpe?g|png|gif|svg)$',
find: '^/images/.*\\.(jpe?g|png|gif|svg)$',
replacement: 'https://example.com/images/',
},
})
expect(rules).toEqual([{
find: '^/images/.*\.(jpe?g|png|gif|svg)$',
find: '^/images/.*\\.(jpe?g|png|gif|svg)$',
replacement: 'https://example.com/images/',
}])
})
@ -107,11 +107,11 @@ describe('plugin-replace-assets > normalizeRules', () => {
const rules = normalizeRules({
rules: [
{
find: '^/images/.*\.(jpe?g|png|gif|svg)$',
find: '^/images/.*\\.(jpe?g|png|gif|svg)$',
replacement: 'https://example.com/images/',
},
{
find: '^/medias/.*\.(mp4|ogg|ogv|webm)$',
find: '^/medias/.*\\.(mp4|ogg|ogv|webm)$',
replacement: 'https://example.com/medias/',
},
],
@ -119,11 +119,11 @@ describe('plugin-replace-assets > normalizeRules', () => {
expect(rules).toEqual([
{
find: '^/images/.*\.(jpe?g|png|gif|svg)$',
find: '^/images/.*\\.(jpe?g|png|gif|svg)$',
replacement: 'https://example.com/images/',
},
{
find: '^/medias/.*\.(mp4|ogg|ogv|webm)$',
find: '^/medias/.*\\.(mp4|ogg|ogv|webm)$',
replacement: 'https://example.com/medias/',
},
])

View File

@ -7,7 +7,7 @@ describe('plugin-replace-assets > isMatchUrl', () => {
it.each([
{
name: 'string like regexp with ^ and $',
find: '^/images/.*\.(jpe?g|png|gif|svg)(\\?.*)?$',
find: '^/images/.*\\.(jpe?g|png|gif|svg)(\\?.*)?$',
expects: [
['/images/foo.jpg', true],
['/images/foo.png', true],
@ -31,7 +31,7 @@ describe('plugin-replace-assets > isMatchUrl', () => {
},
{
name: 'string like regexp end with $',
find: '\.(jpe?g|png|gif|svg)$',
find: '\\.(jpe?g|png|gif|svg)$',
expects: [
['/images/foo.jpg', true],
['/images/foo.png', true],

View File

@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { createAssetPattern, isHTMLRequest, isNonJsRequest, normalizeUrl } from '../src/unplugin/utils.js'
import { createAssetPattern, normalizeUrl } from '../src/unplugin/utils.js'
describe('plugin-replace-assets > utils', () => {
it('createAssetPattern', () => {
@ -15,24 +15,6 @@ describe('plugin-replace-assets > utils', () => {
expect(createAssetPattern('/[^/]').test(`"images/foo.jpg"`)).toBe(false)
})
it('isHTMLRequest', () => {
expect(isHTMLRequest('.html')).toBe(true)
expect(isHTMLRequest('.htm')).toBe(true)
expect(isHTMLRequest('.svg')).toBe(false)
expect(isHTMLRequest('.png')).toBe(false)
expect(isHTMLRequest('')).toBe(false)
})
it('isNonJsRequest', () => {
// everything request is js, but json is not
expect(isNonJsRequest('.json')).toBe(true)
expect(isNonJsRequest('.html')).toBe(false)
expect(isNonJsRequest('.htm')).toBe(false)
expect(isNonJsRequest('.svg')).toBe(false)
expect(isNonJsRequest('.png')).toBe(false)
expect(isNonJsRequest('')).toBe(false)
})
it('normalizeUrl', () => {
expect(normalizeUrl('')).toBe('')
expect(normalizeUrl('/images/foo.jpg')).toBe('/images/foo.jpg')

View File

@ -1,6 +1,6 @@
import type { Plugin } from 'vuepress/core'
import type { ReplaceAssetsPluginOptions } from './options.js'
import { addViteConfig, chainWebpack, getBundlerName } from '@vuepress/helper'
import { addViteConfig, configWebpack, getBundlerName } from '@vuepress/helper'
import { PLUGIN_NAME } from './constants.js'
import { normalizeRules } from './normalizeRules.js'
import { createVitePlugin, createWebpackPlugin } from './unplugin/index.js'
@ -22,18 +22,17 @@ export function replaceAssetsPlugin(
const bundle = getBundlerName(app)
if (bundle === 'vite') {
const viteReplaceAssets = createVitePlugin()
const replaceAssets = createVitePlugin()
addViteConfig(bundlerOptions, app, {
plugins: [viteReplaceAssets(rules)],
plugins: [replaceAssets(rules)],
})
}
if (bundle === 'webpack') {
chainWebpack(bundlerOptions, app, (config) => {
const webpackReplaceAssets = createWebpackPlugin()
config
.plugin(PLUGIN_NAME)
.use(webpackReplaceAssets, [rules])
const replaceAssets = createWebpackPlugin()
configWebpack(bundlerOptions, app, (config) => {
config.plugins ??= []
config.plugins.push(replaceAssets(rules))
})
}
},

View File

@ -1,31 +1,18 @@
import type { UnpluginFactory, UnpluginOptions } from 'unplugin'
import type { UnpluginFactory } from 'unplugin'
import type { ReplacementRule } from '../options.js'
import { transformAssets } from './transform.js'
import { createAssetPattern, isHTMLRequest, isNonJsRequest } from './utils.js'
import { createAssetPattern } from './utils.js'
export const unpluginFactory: UnpluginFactory<ReplacementRule[]> = (rules) => {
const plugins: UnpluginOptions[] = []
if (rules.length) {
plugins.push({
name: 'vuepress:replace-assets',
enforce: 'pre',
transformInclude(id: string) {
if (isHTMLRequest(id) || isNonJsRequest(id))
return false
return true
const pattern = createAssetPattern('/[^/]')
return {
name: 'vuepress:replace-assets',
enforce: 'pre',
transform: {
filter: { id: { exclude: [/\.json(?:$|\?)/, /\.html?$/] } },
handler(code) {
return transformAssets(code, pattern, rules)
},
transform(code) {
return {
code: transformAssets(
code,
createAssetPattern('/[^/]'),
rules,
),
}
},
})
},
}
return plugins
}

View File

@ -3,13 +3,12 @@ import type { ReplacementRule } from '../options.js'
import {
createVitePlugin as _createVitePlugin,
createWebpackPlugin as _createWebpackPlugin,
} from 'unplugin'
import { unpluginFactory } from './factory.js'
export const createVitePlugin: () => (
options: ReplacementRule[]
) => VitePlugin<any> | VitePlugin<any>[] = () => _createVitePlugin(unpluginFactory)
) => VitePlugin | VitePlugin[] = () => _createVitePlugin(unpluginFactory)
export const createWebpackPlugin: () => (
options: ReplacementRule[]

View File

@ -15,16 +15,6 @@ export function createAssetPattern(prefix: string): RegExp {
)
}
const htmlLangRE = /\.(?:html|htm)$/
export const isHTMLRequest = (request: string): boolean => htmlLangRE.test(request)
const nonJsRe = /\.json(?:$|\?)/
export function isNonJsRequest(request: string): boolean {
return nonJsRe.test(request)
}
export function normalizeUrl(url: string, base?: string): string {
if (!url)
return ''