Skip to content

Commit

Permalink
Add support for replacement in template literals.
Browse files Browse the repository at this point in the history
This addresses issue chenxch#8.

Signed-off-by: Jonas Klein <giga@jonasklein.dev>
  • Loading branch information
joarfish committed Jan 26, 2024
1 parent 42723d9 commit 570626b
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 16 deletions.
25 changes: 18 additions & 7 deletions src/core/ast.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import {ModuleItem, parse, StringLiteral} from "@swc/core";
import {Expression, ModuleItem, parse, StringLiteral, TemplateElement, TemplateLiteral} from "@swc/core";
import Visitor from "@swc/core/Visitor";

/**
* Traverses an AST (or parts of it) to collect all StringLiterals that contain
* Traverses an AST (or parts of it) to collect all StringLiterals and TemplateElements that contain
* needle in their value.
*/
export class StringLiteralCollector extends Visitor {
export class StringCollector extends Visitor {

public baseStringLiterals: StringLiteral[] = [];
public baseTemplateElements: TemplateElement[] = [];
private readonly needle: string;

constructor(needle: string) {
Expand All @@ -23,6 +24,16 @@ export class StringLiteralCollector extends Visitor {

return super.visitStringLiteral(n);
}

visitTemplateLiteral(n: TemplateLiteral): Expression {
for(const q of n.quasis) {
if (q.raw.indexOf(this.needle) !== 1) {
this.baseTemplateElements.push(q);
}
}

return super.visitTemplateLiteral(n);
}
}

/**
Expand Down Expand Up @@ -60,13 +71,13 @@ export async function parseCode(code: string): Promise<[number, ModuleItem[]]> {
}

/**
* Returns an array of StringLiterals from an AST that contain needle in their value.
* Returns an array of StringLiterals and TemplateElements from an AST that contain needle in their value.
* @param needle
* @param ast
*/
export function collectMatchingStringLiterals(needle: string, ast: ModuleItem[]): StringLiteral[] {
const visitor = new StringLiteralCollector(needle);
export function collectMatchingStrings(needle: string, ast: ModuleItem[]): (StringLiteral|TemplateElement)[] {
const visitor = new StringCollector(needle);
visitor.visitModuleItems(ast);

return visitor.baseStringLiterals;
return [ ...visitor.baseStringLiterals, ...visitor.baseTemplateElements ];
}
22 changes: 14 additions & 8 deletions src/core/transform.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import type {TransformOptions} from '../../index'
import {parse} from 'node-html-parser'
import {replace, replaceImport, replaceInStringLiteral, replaceSrc} from './utils'
import {StringAsBytes, collectMatchingStringLiterals, parseCode} from "./ast";
import {replace, replaceImport, replaceInStringLiteral, replaceInTemplateElement, replaceSrc} from './utils'
import {StringAsBytes, collectMatchingStrings, parseCode} from "./ast";

export async function transformChunk(codeStr: string, options: TransformOptions): Promise<string> {
const { base, publicPath } = options
const [spanOffset, ast] = await parseCode(codeStr);

const stringLiterals = collectMatchingStringLiterals(base, ast);
const strings = collectMatchingStrings(base, ast);

if (stringLiterals.length === 0) {
if (strings.length === 0) {
return codeStr;
}

Expand All @@ -18,11 +18,17 @@ export async function transformChunk(codeStr: string, options: TransformOptions)
let lastIdx = 0;
let transformedCode = "";

for (const literal of stringLiterals) {
const prev = code.slice(lastIdx, literal.span.start - spanOffset);
const transformed = replaceInStringLiteral(literal, base, publicPath);
for (const str of strings) {
const prev = code.slice(lastIdx, str.span.start - spanOffset);

lastIdx = literal.span.end - spanOffset;
let transformed: string;
if (str.type === 'TemplateElement') {
transformed = replaceInTemplateElement(str, base, publicPath);
} else if (str.type === 'StringLiteral') {
transformed = replaceInStringLiteral(str, base, publicPath);
}

lastIdx = str.span.end - spanOffset;
transformedCode += prev + transformed;
}
transformedCode += code.slice(lastIdx);
Expand Down
7 changes: 6 additions & 1 deletion src/core/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// replace
import {StringLiteral} from "@swc/core";
import {StringLiteral, TemplateElement} from "@swc/core";

export function replace(mark: string, placeholder: string, code: string) {
const re = new RegExp(mark, 'g')
Expand Down Expand Up @@ -37,3 +37,8 @@ export function replaceInStringLiteral(literal: StringLiteral, base: string, pla

return `${prefix}${transformedStr}${quoteMark}`;
}

export function replaceInTemplateElement(element: TemplateElement, base: string, placeholder: string): string {
const regex = new RegExp(base, 'g');
return element.raw.replace(regex, () => '/${' + placeholder + '}/');
}
12 changes: 12 additions & 0 deletions tests/transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,16 @@ describe('transform', () => {
const result = await transformChunk(code, options);
expect(result).toEqual(`var reportError=function(e){return "Couldn't find "+window.__dynamic_base__+"/assets/some.file or "+window.__dynamic_base__+"/assets/some_other.file";}`);
})

test('transformChunk-template-literal', async () => {
const code = 'var foo=function(part1,part2){return \`${part1}/__dynamic_base__/test/${part2}\`;}';
const result = await transformChunk(code, options);
expect(result).toEqual('var foo=function(part1,part2){return \`$\{part1}/${window.__dynamic_base__}/test/${part2}\`;}');
})

test('transformChunk-template-literal-with-multiple-elements', async () => {
const code = "var reportError=function(e){return \`Couldn't find /__dynamic_base__/assets/${filename1} or /__dynamic_base__/assets/${filename2}\`;}";
const result = await transformChunk(code, options);
expect(result).toEqual("var reportError=function(e){return `Couldn't find /${window.__dynamic_base__}/assets/${filename1} or /${window.__dynamic_base__}/assets/${filename2}`;}");
})
})

0 comments on commit 570626b

Please sign in to comment.