자 바스 크 립 트 에서 매크로 를 어떻게 사용 하 는 지 상세 하 게 설명 합 니 다.

언어 에서 매크로 의 흔 한 용 도 는 DSL 을 실현 하 는 것 이다.매크로 를 통 해 개발 자 는 JSX 문법 을 실현 하 는 등 언어 형식 을 사용자 정의 할 수 있 습 니 다.WASM 이 이미 실현 한 오늘날,다른 언어 로 웹 페이지 를 쓰 는 것 은 사실 불가능 한 것 이 아니다.Rust 언어 와 같이 강력 한 매크로 기능 을 가지 기 때문에 Rust 를 바탕 으로 하 는 Yew 프레임 워 크 는 Babel 과 유사 한 것 을 실현 하지 않 고 언어 자체 로 JSX 와 유사 한 문법 을 실현 할 수 있다.JSX 와 같은 문법 을 지원 하 는 Yew 구성 요소 의 예

impl Component for MyComponent {
    // ...

    fn view(&self) -> Html {
        let onclick = self.link.callback(|_| Msg::Click);
        html! {
            <button onclick=onclick>{ self.props.button_text }</button>
        }
    }
}
JavaScript 매크로 의 한계 성
Rust 와 달리 자 바스 크 립 트 자체 가 매크로 를 지원 하지 않 기 때문에 전체 도구 체인 도 매크로 를 고려 하지 않 았 습 니 다.따라서 사용자 정의 문법 을 식별 하 는 매크로 를 쓸 수 있 지만 세트 로 된 도구 체인 이 지원 되 지 않 기 때문에 가장 흔히 볼 수 있 는 VSCode 와 Typescript 등 문법 적 오 류 를 얻 을 수 있 습 니 다.마찬가지 로 babel 자체 에 사용 되 는 parser 도 확장 문법 을 지원 하지 않 습 니 다.다른 Fork 에서 Babel 이 나 오지 않 는 한.따라서 babel-plugin-macros 는 사용자 정의 문법 을 지원 하지 않 습 니 다.그러나 템 플 릿 문자열 함 수 를 통 해 우 리 는 곡선 으로 나 라 를 구 할 수 있 고 적어도 일부 문법 트 리 를 사용자 정의 하 는 능력 을 얻 을 수 있 습 니 다.JavaScript 에서 GraphQL 을 직접 작성 할 수 있 는 GraphQL 의 예 입 니 다.

import { gql } from 'graphql.macro';

const query = gql`
  query User {
    user(id: 5) {
      lastName
      ...UserEntry1
    }
  }
`;

//           ↓ ↓ ↓ ↓ ↓ ↓

const query = {
  "kind": "Document",
  "definitions": [{
    ...
Babel 플러그 인 대신 매크로 를 사용 하 는 이 유 는 무엇 입 니까?
Babel 플러그 인의 능력 은 매크로 보다 훨씬 크 고 어떤 경우 에는 플러그 인 을 사용 해 야 합 니 다.매크로 가 Babel 플러그 인 보다 좋 은 점 은 매크로 의 이념 은 상 자 를 열 면 바로 사용 하 는 것 이다.React 를 사용 하 는 개발 자 는 모두 들 어 본 유명한 Create-React-app 을 믿 습 니 다.다양한 바 텀 디 테 일 을 밀봉 해 주 었 습 니 다.개발 자 는 코드 작성 에 전념 하면 됩 니 다.그러나 CRA 의 문 제 는 너무 엄격하게 봉 인 된 것 이다.바벨 플러그 인 을 사용자 정의 할 필요 가 있다 면 기본적으로 yarn react-script eject 를 실행 하여 모든 밑바닥 디 테 일 을 드 러 내야 한다.매크로 에 대해 서 는 프로젝트 의 Babel 설정 에 babel-plugin-macros 플러그 인 을 추가 하면 플러그 인 처럼 다양한 플러그 인 을 다운로드 해 야 하 는 것 이 아니 라 사용자 정의 Babel 매크로 를 완벽 하 게 지원 할 수 있 습 니 다.CRA 는 이미 babel-plugin-macros 가 내장 되 어 있 습 니 다.CRA 프로젝트 에서 임의의 Babel 매크로 를 사용 할 수 있 습 니 다.
어떻게 매크로 를 씁 니까?
소개 하 다.
하나의 매크로 는 Babel 플러그 인 과 매우 비슷 하기 때문에 Babel 플러그 인 을 어떻게 만 드 는 지 미리 알 아 보 는 것 이 도움 이 됩 니 다.Babel 플러그 인 을 어떻게 만 드 는 지 에 대해 Babel 공식 적 으로 한 권매 뉴 얼이 있 습 니 다.0 에서 Babel 플러그 인 을 만 드 는 방법 을 소개 합 니 다.Babel 플러그 인 을 어떻게 만 드 는 지 알 게 된 후에 우 리 는 먼저 매크로 를 사용 하 는 예 를 통 해 Babel 이 파일 의 매크로 를 어떻게 식별 하 는 지 소개 합 니 다.어떤 특수 한 문법 입 니까?아니면 썩 은$기 호 를 사용 합 니까?

import preval from 'preval.macro'

const one = preval`module.exports = 1 + 2 - 1 - 1`
이것 은 매우 흔히 볼 수 있 는 매크로 입 니 다.컴 파일 기간 에 문자열 의 자바 스 크 립 트 코드 를 실행 한 다음 에 실 행 된 결 과 를 해당 하 는 곳 으로 바 꾸 는 역할 을 합 니 다.예 를 들 어 위의 코드 는 컴 파일 기간 에 다음 과 같이 전 개 됩 니 다.

import preval from 'preval.macro'

const one = 1
사용 방식 으로 볼 때 매크로 와 의 관 계 를 식별 하 는 유일한 것 은*.macro 문자 입 니 다.이것 은 바로 Babel 이 매크로 를 어떻게 식별 하 는 방식 입 니 다.실제로*.macro 의 형식 뿐만 아니 라 Babel 은 라 이브 러 리 이름 이 정규/[./]macro(\.c?js)와 일치 하 다 고 생각 합 니까?$/표현 식 의 라 이브 러 리 는 Babel 매크로 입 니 다.표현 식 과 일치 하 는 예 입 니 다.

'my.macro'
'my.macro.js'
'my.macro.cjs'
'my/macro'
'my/macro.js'
'my/macro.cjs'
집필 하 다
다음은 importURL 매크로 를 간단하게 작성 할 것 입 니 다.url 을 통 해 라 이브 러 리 를 도입 하고 컴 파일 하 는 동안 이 라 이브 러 리 의 코드 를 미리 끌 어 내 서 처리 한 다음 파일 에 도입 하 는 역할 을 합 니 다.일부 웹 팩 플러그 인 은 url 에서 라 이브 러 리 를 도입 하 는 것 을 지원 한 다 는 것 을 알 고 있 습 니 다.그러나 이것 은 매크로 를 어떻게 만 드 는 지 배 우 는 좋 은 예 입 니 다.재 미 를 위해 서 입 니 다!그리고 NodeJS 에서 동기 화 요청 을 하 는 방법!:)
준비 하 다.
먼저 importURL 이라는 폴 더 를 만 들 고 npm init-y 를 실행 하여 항목 을 빠르게 만 듭 니 다.프로젝트 에 매크로 를 사용 하 는 사람 은 babel-plugin-macros 를 설치 해 야 합 니 다.마찬가지 로 매크로 를 작성 하 는 사람 도 이 플러그 인 을 설치 해 야 합 니 다.쓰기 전에 다른 라 이브 러 리 를 미리 설치 하여 매크로 를 작성 하 는 데 도움 을 주어 야 합 니 다.개발 하기 전에 미리:
  • package.json 에서 name 을 import-url.macro 로 바 꾸 고 Babel 인식 매크로 형식
  • 에 부합 합 니 다.
  • 우 리 는 Babel 이 제공 하 는 보조 적 인 방법 으로 매크로 를 만들어 야 한다.실행 원사 add babel-plugin-macros
  • yarn add fs-extra,Nodefs 모듈 대신 사용 하기 쉬 운 라 이브 러 리
  • yarn add find-root,매크로 를 작성 하 는 과정 에서 처리 한 파일 의 경로 에 따라 작업 디 렉 터 리 를 찾 아 캐 시 에 기록 해 야 합 니 다.이것 은 봉 인 된 라 이브 러 리
  • 입 니 다.
    예시
    우리 의 목 표 는 다음 과 같은 코드 를 변환 하 는 것 이다.
    
    import importURL from 'importurl.macros';
    
    const React = importURL('https://unpkg.com/[email protected]/umd/react.development.js');
    
    //    
    
    import importURL from 'importurl.macros';
    
    const React = require('../cache/pkg1.js');
    
    코드 importURL 함수 의 첫 번 째 인 자 를 원 격 라 이브 러 리 주소 로 분석 한 다음 컴 파일 기간 에 Get 요청 을 통 해 코드 내용 을 동기 화 합 니 다.그 다음 에 프로젝트 맨 위 폴 더 에.hache 를 기록 하고 해당 하 는 importURL 문 구 를 require(...)문 구 를 바 꿉 니 다.경 로 는 importURL 의 파일 이.cache 파일 의 상대 적 인 경 로 를 사용 하여 webpack 이 최종 포장 할 때 해당 하 는 코드 를 찾 을 수 있 도록 합 니 다.
    시작 하 다
    최종 코드 가 어떻게 생 겼 는 지 먼저 봅 시다.
    
    import { execSync } from 'child_process';
    import findRoot from 'find-root';
    import path from 'path';
    import fse from 'fs-extra';
    
    import { createMacro } from 'babel-plugin-macros';
    
    const syncGet = (url) => {
      const data = execSync(`curl -L ${url}`).toString();
      if (data === '') {
        throw new Error('empty data');
      }
      return data;
    }
    
    let count = 0;
    export const genUniqueName = () => `pkg${++count}.js`;
    
    module.exports = createMacro((ctx) => {
      const {
        references, //           
        babel: {
          types: t,
        }
      } = ctx;
      // babel                ctx.state.filename
      const workspacePath = findRoot(ctx.state.filename);
      //         
      const cacheDirPath = path.join(workspacePath, '.cache');
      //
      const calls = references.default.map(path => path.findParent(path => path.node.type === 'CallExpression' ));
      calls.forEach(nodePath => {
        //    astNode    
        if (nodePath.node.type === 'CallExpression') {
          //                
          if (nodePath.node.arguments[0]?.type === 'StringLiteral') {
            //       ,        
            const url = nodePath.node.arguments[0].value;
            //    url     
            const codes = syncGet(url);
            //         ,    
            const pkgName = genUniqueName();
            //             
            const cahceFilename = path.join(cacheDirPath, pkgName);
            //    fse  ,     , outputFileSync             
            fse.outputFileSync(cahceFilename, codes);
            //        
            const relativeFilename = path.relative(ctx.state.filename, cahceFilename);
            //        importURL   
            nodePath.replaceWith(t.stringLiteral(`require('${relativeFilename}')`))
          }
        }
      });
    });
    
    매크로 만 들 기
    우 리 는 createMacro 함 수 를 통 해 매크로 를 만 듭 니 다.createMacro 는 우리 가 작성 한 함 수 를 매개 변수 로 받 아들 여 매크로 를 만 듭 니 다.그러나 실제로 우 리 는 createMacro 의 반환 시간 이 무엇 인지 에 관심 이 없습니다.왜냐하면 우리 의 코드 는 최종 적 으로 자신 이 바 뀌 기 때문에 실행 기간 에 실행 되 지 않 습 니 다.우리 가 작성 한 함수 의 첫 번 째 매개 변 수 는 Babel 이 우리 에 게 전달 한 상태 입 니 다.우 리 는 그 유형 이 무엇 인지 대충 볼 수 있 습 니 다.
    
    function createMacro(handler: MacroHandler, options?: Options): any;
    interface MacroParams {
          references: { default: Babel.NodePath[] } & References;
          state: Babel.PluginPass;
          babel: typeof Babel;
          config?: { [key: string]: any };
      }
    export interface PluginPass {
        file: BabelFile;
        key: string;
        opts: PluginOptions;
        cwd: string;
        filename: string;
        [key: string]: unknown;
    }
    
    가시 화 AST
    우 리 는astexplorer을 통 해 코드 를 처리 할 문법 트 리 를 관찰 할 수 있 습 니 다.다음 코드 에 대해 서 는...
    
    import importURL from 'importurl.macros';
    
    const React = importURL('https://unpkg.com/[email protected]/umd/react.development.js');
    
    다음 문법 트 리 가 생 성 됩 니 다.

    빨간색 으로 표 시 된 문법 트 리 노드 는 바벨 이 ctx.references 를 통 해 우리 에 게 전달 되 기 때문에 우 리 는.findParent()방법 을 통 해 부모 노드 CallExpresstion 을 위로 찾 아야 arguments 속성 에서 의 인 자 를 얻 고 원 격 라 이브 러 리 의 URL 주 소 를 얻 을 수 있 습 니 다.
    동기 화 요청
    여기 서 어 려 운 점 은 바벨 이 비동기 변환 을 지원 하지 않 고 모든 변환 작업 이 동기 화 되 어 있 기 때문에 요청 을 할 때 도 동기 화 요청 이 어야 한 다 는 것 이다.나 는 이것 이 매우 간단 한 일이 라 고 생각 했 는데,Node 는 sync:true 와 유사 한 옵션 을 제공 할 것 이다.그러나 Node 는 다음 과 같은 이상 한 방식 을 선택 하지 않 는 한 동기 화 요청 을 지원 하지 않 습 니 다.
    
    const syncGet = (url) => {
      const data = execSync(`curl -L ${url}`).toString();
      if (data === '') {
        throw new Error('empty data');
      }
      return data;
    }
    
    마무리
    코드 를 받 은 후에 우 리 는 코드 를 계산 하기 시작 한 파일 경로 에 기록 합 니 다.여기 서 fs-extra 를 사용 하 는 목적 은 fs-extra 가 기록 할 때 폴 더 가 존재 하지 않 으 면 fs 처럼 오 류 를 직접 던 지지 않 고 해당 하 는 파일 을 자동 으로 만 드 는 것 입 니 다.쓰기 가 끝 난 후에 저 희 는 Babel 이 제공 하 는 보조 적 인 방법 인 string Literal 을 통 해 문자열 노드 를 만 든 다음 에 저희 importURL(...)을 교체 하면 저희 의 전체 전환 절 차 는 끝 납 니 다.
    마지막.
    이 매크로 는 약간의 결함 이 존재 하 므 로 관심 이 있 는 학생 들 은 계속 보완 할 수 있다.
    같은 URL 의 라 이브 러 리 를 인식 하지 못 하고 재 활용 을 했 지만 매크로 를 만 드 는 방법 에 만족 했다 고 생각 합 니 다.
    genUniqueName 은 크로스 파일 에서 중복 패키지 이름 을 계산 합 니 다.정확 한 알고리즘 은 url 에 따라 해시 값 을 계산 하여 유일한 패키지 이름 으로 해 야 합 니 다.
    자 바스 크 립 트 에서 매크로 를 어떻게 사용 하 는 지 에 관 한 이 글 은 여기까지 소개 되 었 습 니 다.더 많은 자 바스 크 립 트 는 매크로 내용 을 사용 합 니 다.예전 의 글 을 검색 하거나 아래 의 관련 글 을 계속 찾 아 보 세 요.앞으로 많은 응원 부 탁 드 리 겠 습 니 다!

    좋은 웹페이지 즐겨찾기