From 309be687b4eb6f96c4771437ae1ce744f23d68ae Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Sun, 29 Jun 2025 14:37:33 +0800 Subject: [PATCH] feat(plugin-replace-assets): improve replace assets (#628) --- .../theme/guide/features/replace-assets.md | 9 +++-- .../__test__/normalizeRules.spec.ts | 24 ++++++------- .../__test__/transform.spec.ts | 4 +-- .../__test__/unplugin-utils.spec.ts | 20 +---------- plugins/plugin-replace-assets/src/plugin.ts | 15 ++++---- .../src/unplugin/factory.ts | 35 ++++++------------- .../src/unplugin/index.ts | 3 +- .../src/unplugin/utils.ts | 10 ------ 8 files changed, 41 insertions(+), 79 deletions(-) diff --git a/docs/notes/theme/guide/features/replace-assets.md b/docs/notes/theme/guide/features/replace-assets.md index b4cf6442..9f26e109 100644 --- a/docs/notes/theme/guide/features/replace-assets.md +++ b/docs/notes/theme/guide/features/replace-assets.md @@ -18,7 +18,12 @@ badge: 新 在这个过程中,通常需要先将资源上传到 CDN 服务,然后再获取 CDN 服务的资源链接,最后才在站点内容中使用。 这看起来并没有什么问题,然而在实际使用过程中,可能需要频繁的进行 -__上传资源 -> 获取资源链接 -> 在内容中使用全量资源链接__ 的操作,内容创作被频繁的打断。 + +```txt +上传资源 -> 获取资源链接 -> 在内容中使用全量资源链接 +``` + +在此过程中,内容创作被频繁的打断。 此功能旨在解决这个问题。在内容创作过程中,只需要直接使用本地资源地址,由主题内部在合适的阶段,完成资源地址的替换。 ::: @@ -93,7 +98,7 @@ interface ReplacementRule { interface ReplaceAssetsOptions { /** - * 自定义替换规则 + * 自定义资源替换规则 */ rules?: ReplacementRule | ReplacementRule[] /** diff --git a/plugins/plugin-replace-assets/__test__/normalizeRules.spec.ts b/plugins/plugin-replace-assets/__test__/normalizeRules.spec.ts index e69ccf0d..de4f7891 100644 --- a/plugins/plugin-replace-assets/__test__/normalizeRules.spec.ts +++ b/plugins/plugin-replace-assets/__test__/normalizeRules.spec.ts @@ -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/', }, ]) diff --git a/plugins/plugin-replace-assets/__test__/transform.spec.ts b/plugins/plugin-replace-assets/__test__/transform.spec.ts index 8274566a..cb9edc59 100644 --- a/plugins/plugin-replace-assets/__test__/transform.spec.ts +++ b/plugins/plugin-replace-assets/__test__/transform.spec.ts @@ -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], diff --git a/plugins/plugin-replace-assets/__test__/unplugin-utils.spec.ts b/plugins/plugin-replace-assets/__test__/unplugin-utils.spec.ts index 1878e958..e6a23307 100644 --- a/plugins/plugin-replace-assets/__test__/unplugin-utils.spec.ts +++ b/plugins/plugin-replace-assets/__test__/unplugin-utils.spec.ts @@ -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') diff --git a/plugins/plugin-replace-assets/src/plugin.ts b/plugins/plugin-replace-assets/src/plugin.ts index b1927042..e4b9c011 100644 --- a/plugins/plugin-replace-assets/src/plugin.ts +++ b/plugins/plugin-replace-assets/src/plugin.ts @@ -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)) }) } }, diff --git a/plugins/plugin-replace-assets/src/unplugin/factory.ts b/plugins/plugin-replace-assets/src/unplugin/factory.ts index 80f31418..181a075d 100644 --- a/plugins/plugin-replace-assets/src/unplugin/factory.ts +++ b/plugins/plugin-replace-assets/src/unplugin/factory.ts @@ -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 = (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 } diff --git a/plugins/plugin-replace-assets/src/unplugin/index.ts b/plugins/plugin-replace-assets/src/unplugin/index.ts index 46974136..cbfc7d1b 100644 --- a/plugins/plugin-replace-assets/src/unplugin/index.ts +++ b/plugins/plugin-replace-assets/src/unplugin/index.ts @@ -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 | VitePlugin[] = () => _createVitePlugin(unpluginFactory) +) => VitePlugin | VitePlugin[] = () => _createVitePlugin(unpluginFactory) export const createWebpackPlugin: () => ( options: ReplacementRule[] diff --git a/plugins/plugin-replace-assets/src/unplugin/utils.ts b/plugins/plugin-replace-assets/src/unplugin/utils.ts index 75c89198..daca09c6 100644 --- a/plugins/plugin-replace-assets/src/unplugin/utils.ts +++ b/plugins/plugin-replace-assets/src/unplugin/utils.ts @@ -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 ''