diff --git a/src/core/transform.ts b/src/core/transform.ts index fd3f064..0e1aa73 100644 --- a/src/core/transform.ts +++ b/src/core/transform.ts @@ -4,7 +4,7 @@ import {replace, replaceImport, replaceInStringLiteral, replaceInTemplateElement import {StringAsBytes, collectMatchingStrings, parseCode} from "./ast"; export async function transformChunk(codeStr: string, options: TransformOptions): Promise { - const { base, publicPath } = options + const { base, publicPath, removeStartingSlash } = options const [spanOffset, ast] = await parseCode(codeStr); const strings = collectMatchingStrings(base, ast); @@ -23,9 +23,9 @@ export async function transformChunk(codeStr: string, options: TransformOptions) let transformed: string; if (str.type === 'TemplateElement') { - transformed = replaceInTemplateElement(str, base, publicPath); + transformed = replaceInTemplateElement(str, base, publicPath, removeStartingSlash); } else if (str.type === 'StringLiteral') { - transformed = replaceInStringLiteral(str, base, publicPath); + transformed = replaceInStringLiteral(str, base, publicPath, removeStartingSlash); } lastIdx = str.span.end - spanOffset; @@ -44,25 +44,33 @@ export function transformAsset(code: string, options: TransformOptions) { } export function transformLegacyHtml(code: string, options: TransformOptions) { - const { base, publicPath } = options + const { base, publicPath, removeStartingSlash } = options let content = replaceSrc(publicPath, code) content = replace(base, '/', content) content = replaceImport(publicPath, content) const document = parse(content, { comment: true }) const legacyPolyfill = document.getElementById('vite-legacy-polyfill') + let legacyPolyfillSrc = legacyPolyfill?.getAttribute('src') + + const legacyEntry = document.getElementById('vite-legacy-entry') + let legacyEntrySrc = legacyEntry?.getAttribute('data-src') + if (removeStartingSlash) { + legacyPolyfillSrc = legacyPolyfillSrc?.replace(/^\//, '') + legacyEntrySrc = legacyEntrySrc?.replace(/^\//, '') + } + if (legacyPolyfill) { - legacyPolyfill.setAttribute('data-src', legacyPolyfill.getAttribute('src')) + legacyPolyfill.setAttribute('data-src', legacyPolyfillSrc) legacyPolyfill.removeAttribute('src') legacyPolyfill.innerHTML = `!(function() { var e = document.createElement('script') - e.src = ${publicPath} + document.getElementById('vite-legacy-polyfill').getAttribute('data-src'); + e.src = ${publicPath} + "${legacyPolyfillSrc}"; e.onload = function() { - System.import(${publicPath}+document.getElementById('vite-legacy-entry').getAttribute('data-src')) + System.import(${publicPath} + "${legacyEntrySrc}") }; document.body.appendChild(e) })();` } - const legacyEntry = document.getElementById('vite-legacy-entry') if (legacyEntry) { legacyEntry.innerHTML = '' } @@ -74,6 +82,7 @@ export function transformHtml(html: string, options: TransformOptions,transformI const { base, publicPath } = options const document = parse(html, { comment: true }) const baseMarker = `${base}` + const replaceMarker = options.removeStartingSlash ? '' : '/' const assetsTags = document.querySelectorAll(`head>link[href^="${baseMarker}"],head>script[src^="${baseMarker}"]`) const preloads = assetsTags.map(o => { const result = { @@ -82,7 +91,7 @@ export function transformHtml(html: string, options: TransformOptions,transformI attrs: Object.assign( {}, o.attrs, - o.attrs.src ? { src: o.attrs.src.replace(baseMarker, '/') } : { href: o.attrs.href.replace(baseMarker, '/') } + o.attrs.src ? { src: o.attrs.src.replace(baseMarker, replaceMarker) } : { href: o.attrs.href.replace(baseMarker, replaceMarker) } ) } o.parentNode.removeChild(o) diff --git a/src/core/utils.ts b/src/core/utils.ts index dd4411c..60d27e8 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -15,30 +15,39 @@ export function replaceImport(placeholder: string, code: string) { return code.replace(/(System.import\()/g, `$1${placeholder}+`) } -export function replaceInStringLiteral(literal: StringLiteral, base: string, placeholder: string): string { +export function replaceInStringLiteral(literal: StringLiteral, base: string, placeholder: string, removeStartingSlash: boolean): string { const quoteMark = literal.raw.charAt(0); const regex = new RegExp(base, 'g'); // Keep track of whether we need to add quotation marks at the beginning of the // final output - let withStartQuote = true; - const transformedStr = literal.value.replace(regex, (match, index) => { + const resourceStartingSlash = removeStartingSlash ? '' : '/'; + + /** + * original StringLiteral may have escaped quote marks, so we need to use raw instead of value + * otherwise, will fail on style strings such as + * "@charset \"UTF-8\";cursor: url(/__vite_dynamic_public_path__/assets/cursor.svg);" + * + * since the value is + * @charset "UTF-8";cursor: url(/__vite_dynamic_public_path__/assets/cursor.svg); + * + * and does not reflect the escaped quote marks + */ + const transformedStr = literal.raw.replace(regex, (match, index) => { let prefix = `${quoteMark}+`; if (index === 0) { prefix = ''; - withStartQuote = false; } - return `${prefix}${placeholder}+${quoteMark}/`; + return `${prefix}${placeholder}+${quoteMark}${resourceStartingSlash}`; }); - const prefix = withStartQuote ? quoteMark : ''; - - return `${prefix}${transformedStr}${quoteMark}`; + return `${transformedStr}`; } -export function replaceInTemplateElement(element: TemplateElement, base: string, placeholder: string): string { +export function replaceInTemplateElement(element: TemplateElement, base: string, placeholder: string, removeStartingSlash: boolean): string { + const resourceStartingSlash = removeStartingSlash ? '' : '/'; const regex = new RegExp(base, 'g'); - return element.raw.replace(regex, () => '/${' + placeholder + '}/'); + return element.raw.replace(regex, () => '/${' + placeholder + '}' + resourceStartingSlash); } diff --git a/src/index.ts b/src/index.ts index 7ed46f3..f79601c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,17 +6,18 @@ export function dynamicBase(options?: Options): Plugin { const defaultOptions: Options = { publicPath: 'window.__dynamic_base__', transformIndexHtml: false, // maybe default true - transformIndexHtmlConfig: {} + transformIndexHtmlConfig: {}, + removeStartingSlash: false } - const { publicPath, transformIndexHtml, transformIndexHtmlConfig } = { ...defaultOptions, ...(options || {}) } + const { publicPath, transformIndexHtml, transformIndexHtmlConfig, removeStartingSlash } = { ...defaultOptions, ...(options || {}) } // const preloadHelperId = 'vite/preload-helper' let assetsDir = 'assets' let base = '/' let legacy = false - let baseOptions: TransformOptions = { assetsDir, base, legacy, publicPath: ` ${publicPath}`, transformIndexHtml } - + let baseOptions: TransformOptions = { assetsDir, base, legacy, publicPath: ` ${publicPath}`, transformIndexHtml, removeStartingSlash } + return { name: 'vite-plugin-dynamic-base', enforce: 'post', diff --git a/src/types.ts b/src/types.ts index 5297b8e..226467c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,6 @@ export interface Options { publicPath?: string, + removeStartingSlash?: boolean, transformIndexHtml?: boolean transformIndexHtmlConfig?: TransformIndexHtmlConfig }