| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116 |
- import { console } from 'node:inspector'
- /**
- * @typedef {object} AssetPathResolverOptions
- * @property {string} [alias='~@assets'] - 要匹配替换的路径前缀,例如 "~@assets"
- * @property {string} [local='/src/assets'] - 本地资源根路径,例如 "/src/assets" 或 "/public/assets"
- * @property {string} [cdn] - CDN 路径前缀,例如 "https://cdn.assets.com"
- * @property {boolean} [remote=false] - 是否启用远程资源路径,默认 false(即使用本地路径)
- * @property {RegExp} [fileRegex=/\.(js|ts|jsx|tsx|vue|html|svelte|css)$/] 需要处理的文件类型的正则表达式。
- */
- /**
- * 创建资源路径替换插件
- * @param {AssetPathResolverOptions} options - 插件配置选项
- * @returns {import('vite').Plugin} Vite插件
- */
- export default function vitePluginAssetPathResolver(options) {
- // 设置默认选项
- options = {
- alias: options.alias || '~@assets',
- local: options.local || '/src/assets',
- cdn: options.cdn,
- remote: options.remote || false,
- fileRegex: options.fileRegex || /\.(js|ts|jsx|tsx|vue|nvue|html|svelte|css)$/,
- }
- // 验证必要的配置项
- if (!options.alias)
- throw new Error('Missing required option: alias')
- if (!options.local)
- throw new Error('Missing required option: local')
- if (!options.cdn)
- throw new Error('Missing required option: cdn')
- // 规范化路径前缀
- const aliasPattern = new RegExp(`${escapeRegExp(options.alias)}\\/(.*?)(['"])`, 'g')
- const isRemote = options.remote === true
- const localPath = options.local.replace(/\\/g, '/').startsWith('/') ? options.local.replace(/\\/g, '/') : `/${options.local.replace(/\\/g, '/')}`
- // 确保CDN路径结尾有斜杠
- const cdnPath = options.cdn.endsWith('/') ? options.cdn : `${options.cdn}/`
- return {
- name: 'vite-plugin-asset-path-resolver',
- enforce: 'pre',
- /**
- * 转换代码中的资源路径
- * @param {string} code - 源代码
- * @param {string} id - 文件ID
- * @returns {string|undefined} 转换后的代码或undefined(如果未修改)
- */
- transform(code, id) {
- // 跳过不匹配的文件
- if (!options?.fileRegex?.test?.(id)) {
- return undefined
- }
- // 跳过不包含目标前缀的文件
- if (options.alias && !code.includes(options.alias)) {
- return undefined
- }
- // --- FIX START ---
- // The replacement now correctly uses forward slashes for the path.
- const replacement = isRemote
- ? (_, assetPath, quote) => `${cdnPath}${assetPath.replace(/\\/g, '/')}${quote}`
- : (_, assetPath, quote) => `${localPath}/${assetPath.replace(/\\/g, '/')}${quote}`
- // --- FIX END ---
- // 执行替换
- const result = code.replace(aliasPattern, replacement)
- // 如果代码没变,返回undefined
- return result !== code ? result : undefined
- },
- /**
- * 转换HTML代码中的资源路径
- * @param {string} html - HTML代码
- * @returns {string} 转换后的HTML代码
- */
- transformIndexHtml(html) {
- // --- FIX START ---
- // The replacement now correctly uses forward slashes for the path.
- const replacement = isRemote
- ? (_, assetPath, quote) => `${cdnPath}${assetPath.replace(/\\/g, '/')}${quote}`
- : (_, assetPath, quote) => `${localPath}/${assetPath.replace(/\\/g, '/')}${quote}`
- // --- FIX END ---
- // 处理HTML中的资源路径
- return html.replace(aliasPattern, replacement)
- },
- /**
- * 配置插件
- * @param {import('vite').ResolvedConfig} config - Vite配置
- */
- configResolved(config) {
- console.log(`[asset-path-resolver] Mode: ${isRemote ? 'remote (CDN)' : 'local'}`)
- console.log(`[asset-path-resolver] Alias: ${options.alias}`)
- console.log(`[asset-path-resolver] Path: ${isRemote ? cdnPath : localPath}`)
- },
- }
- }
- /**
- * 转义正则表达式中的特殊字符
- * @param {string} string - 需要转义的字符串
- * @returns {string} 转义后的字符串
- */
- function escapeRegExp(string) {
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
- }
|