이 블로그는 제 블루버드 블로그의 2부입니다. 1부 보기
종속성이 없는 트위터 복제본의 경우 변수와 템플릿 이름을 반환하는 방식으로 경로 핸들러를 디자인하기로 결정했습니다. HTML 문서를 검사하는 대신 템플릿 이름과 변수에 대한 어설션만 테스트할 수 있으므로 테스트가 쉬워집니다.
// request { method: "GET", path: "/profile/1234", cookies: { "user-id": 54 }, } // response { status: 200, template: "public-profile-show", variables: { user: { id: 54, name: "John Doe", }, posts: [ { id: 55412, message: "Have you seen the new iThing?", createdAt: 1699788972 } ] } }
이번 블로그에서는 이 템플릿 언어를 구현해보겠습니다.
나에게 필요한 템플릿 언어는 일련의 변수만 입력으로 사용하여 HTML 문서를 출력해야 합니다. 템플릿을 JS 함수로 컴파일하고 싶습니다. 예를 들어 Hello <%= name %> 다음과 같이 컴파일하려면:
({ name }) => `Hello ${escapeHtml(name)}`;
저는 클래식 <%= %> 구문은 매우 일반적이고 잘 알려져 있기 때문입니다. 이 구문을 보는 대부분의 개발자는 거기에 일반 코드를 작성하면 코드의 출력이 출력에 추가된다는 것을 직관적으로 알 것입니다.
변수와 자동 이스케이프 HTML 엔터티를 지원해야 합니다. 루프, if/else 문 및 기타 템플릿 포함도 지원되어야 합니다. 임의의 함수를 호출하고 기본적인 계산을 수행할 수 있다면 좋을 것입니다.
그래서 기본적으로 임의의 자바스크립트를 실행할 수 있기를 바랍니다.
이제 막 코드 작성을 시작하고 결국 어디로 가는지 알 것 같습니다. 먼저 테스트입니다.
it("simple template", () => { const fn = Template.parse("Hello, <%= name %>"); assert.equal(fn({ name: "world" }), "Hello, world"); });
제가 생각할 수 있는 가장 간단한 구현은 정규식을 사용하는 것입니다. <%= %> 외부의 모든 콘텐츠 그냥 출력에 추가되고 그 사이의 내용은 JS로 실행됩니다.
사용된 정규 표현식은 /(.*?)<%(.*?)%>/sg입니다. 이 정규식은 (.*?)<%를 사용하여 찾은 첫 번째 <%까지 모든 텍스트를 캡처합니다. 그런 다음 %> (.*?)%>를 사용합니다. s 수정자는 . (점) 개행 문자와 일치합니다. g 수정자는 여러 일치를 허용합니다.
문자열에 대한 Javascript의 대체 기능을 사용하면 모든 일치 항목에 대해 코드를 실행하는 동시에 내 코드에서 대체 값 ""을 반환할 수도 있습니다. 모든 일치 항목은 빈 문자열로 대체되므로 마지막 %> 내가 tail이라고 부르는 교체 함수에 의해 반환됩니다.
JSON.stringify를 사용하여 문자열 리터럴을 만듭니다.
const Template = { parse(template) { let body = [ "eval(`var { ${Object.keys(vars).join(', ')} } = vars;`);", `out = [];` ]; const tail = template.replace(/(.*?)<%(.*?)%>/sg, (_, content, code) => { body.push(`out.push(${JSON.stringify(content)});`); if (code.startsWith("=")) body.push(`out.push(${code.substr(1)});`); return ""; }); body.push(`out.push(${JSON.stringify(tail)});`); body.push(`return out.join("");`); return new Function('vars', body.join("\n")) } };
테스트의 템플릿에 대해 이 함수는 다음과 같은 함수를 반환합니다.
function(vars) { eval(`var { ${Object.keys(vars).join(', ')} } = vars;`); out = []; out.push("Hello, "); out.push(name); out.push(""); return out.join(""); }
이 코드에서 또 다른 주목할만한 부분은 eval 문입니다. 템플릿이 vars의 모든 변수(이 예에서는 이름)를 참조할 수 있도록 하려면 vars의 속성을 함수의 로컬 변수로 사용할 수 있도록 해야 합니다.
// request { method: "GET", path: "/profile/1234", cookies: { "user-id": 54 }, } // response { status: 200, template: "public-profile-show", variables: { user: { id: 54, name: "John Doe", }, posts: [ { id: 55412, message: "Have you seen the new iThing?", createdAt: 1699788972 } ] } }
({ name }) => `Hello ${escapeHtml(name)}`;
포함을 포함시키기 위해 모든 템플릿 파일을 디렉토리에 구문 분석하는 함수를 추가하겠습니다. 이 함수는 템플릿 이름을 키로 키우고 구문 분석 된 템플릿 기능을 값으로 유지합니다.
src/template.mjs
테스트/템플릿/**. EJS
it("simple template", () => { const fn = Template.parse("Hello, <%= name %>"); assert.equal(fn({ name: "world" }), "Hello, world"); });test/template.test.mjs
const Template = { parse(template) { let body = [ "eval(`var { ${Object.keys(vars).join(', ')} } = vars;`);", `out = [];` ]; const tail = template.replace(/(.*?)<%(.*?)%>/sg, (_, content, code) => { body.push(`out.push(${JSON.stringify(content)});`); if (code.startsWith("=")) body.push(`out.push(${code.substr(1)});`); return ""; }); body.push(`out.push(${JSON.stringify(tail)});`); body.push(`return out.join("");`); return new Function('vars', body.join("\n")) } };
src/main.mjs
function(vars) { eval(`var { ${Object.keys(vars).join(', ')} } = vars;`); out = []; out.push("Hello, "); out.push(name); out.push(""); return out.join(""); }
위 내용은 의존성이없는 템플릿 언어의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!