>웹 프론트엔드 >JS 튜토리얼 >의존성이없는 템플릿 언어

의존성이없는 템플릿 언어

Patricia Arquette
Patricia Arquette원래의
2025-01-27 20:32:13485검색

Template language without dependencies

템플릿 언어

이 블로그는 제 블루버드 블로그의 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의 속성을 함수의 로컬 변수로 사용할 수 있도록 해야 합니다.

컴파일하는 동안 가능한 변수를 결정하는 쉬운 방법은 없으므로 런타임에 생성됩니다. 런타임에 임의의 로컬 변수를 할당하는 유일한 방법은 Eval을 사용하는 것입니다.


또 다른 방법은 낙담 한 상태로 사용하는 것입니다. 어쨌든 시도해 봅시다.

// 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 }
    ]
  }
}

생성 된 기능은 완벽하게 작동합니다. 너무 나쁘게 기능이 누가 물어 보는 사람에 따라 낙담하거나 유산 또는 감가 상각됩니다. 지금까지 내 옵션은 악의적 인 평가를 받거나 감가 상징적입니다. 이상적으로는 컴파일 타임에 사용 된 변수를 결정하고 싶지만 사용 된 변수를 결정하기 위해 JavaScript 코드를 컴파일해야합니다. 일반 nodejs를 사용하여 일부 JavaScript 조각의 초록 구문 트리를 얻는 쉬운 방법은 없습니다. 이제 HTML 엔티티를 탈출하려면 IF/Else 문을 지원하고 약간의 수정 사항을 추가하십시오.

나는 또한 더 많은 테스트를 추가했습니다.
({ 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


이제이 템플릿 엔진을 main.mjs 파일에 통합하려면 .ejs 템플릿을 사용하여 템플릿을 렌더링합니다.

템플릿/홈 .ejs
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.