(RSC from scratch 읽다가) node와 babel의 역할

Sooo·2023년 9월 4일
0

궁금한 점

두 프로젝트 비교해보니까 jsx버전에서 node-jsx-loader.js라는 파일이 추가되었는데, 이게 뭐 하는 파일인지 궁금했다.
어차피 jsx를 html로 변환하는 과정은 server.js파일에서 끝내고 있는데, 왜 추가 코드가 필요하지?

import babel from "@babel/core";

const babelOptions = {
  babelrc: false,
  ignore: [/\/(build|node_modules)\//],
  plugins: [["@babel/plugin-transform-react-jsx", { runtime: "automatic" }]],
};

export async function load(url, context, defaultLoad) {
  const result = await defaultLoad(url, context, defaultLoad);
  console.log('result');
  console.log(result);
  if (result.format === "module") {
    
    const opt = Object.assign({ filename: url }, babelOptions);
    const newResult = await babel.transformAsync(result.source, opt);
    if (!newResult) {
      if (typeof result.source === "string") {
        return result;
      }
      return {
        source: Buffer.from(result.source).toString("utf8"),
        format: "module",
      };
    }
    return { source: newResult.code, format: "module" };
  }
  return defaultLoad(url, context, defaultLoad);
}
 "scripts": {
    "start": "nodemon -- --experimental-loader ./node-jsx-loader.js ./server.js"
  },

result의 format이 module이면 loader에 뭔가 처리를 해서 바꿔주고, 아니면 defaultLoader를 사용하도록 하고 있다.
result는 바벨이 가져오는 각각의 소스 파일이다.
server.js파일의 format은 import/export로 가져오므로 항상 module이다. (콘솔을 찍어봐도 그렇다)

[Object: null prototype] {
  format: 'module',
  responseURL: 'file:///workspace/server.js',
  source: <Buffer 69 6d 70 6f 72 74 20 7b 20 63 72 65 61 74 65 53 65 72 76 65 72 20 7d 20 66 72 6f 6d 20 22 68 74 74 70 22 3b 0a 69 6d 70 6f 72 74 20 7b 20 72 65 61 64 ... 1708 more bytes>
}
[Object: null prototype] {
  format: 'builtin',
  responseURL: 'node:http',
  source: null
}
[Object: null prototype] {
  format: 'builtin',
  responseURL: 'node:fs/promises',
  source: null
}
[Object: null prototype] {
  format: 'commonjs',
  responseURL: 'file:///workspace/node_modules/escape-html/index.js',
  source: null
}
[Object: null prototype] {
  format: 'commonjs',
  responseURL: 'file:///workspace/node_modules/react/jsx-runtime.js',
  source: null
}

loader를 거치면, defaultLoader와 달리 소스가 Buffer형식이 아닌 string이 유지된 값이다.

{
  source: 'import { createServer } from "http";\n' +
    'import { readFile } from "fs/promises";\n' +
    'import escapeHtml from "escape-html";\n' +
    'import { jsx as _jsx } from "react/jsx-runtime";\n' +
    'import { jsxs as _jsxs } from "react/jsx-runtime";\n' +
    'createServer(async (req, res) => {\n' +
    '  const author = "Jae Doe";\n' +
    '  const postContent = await readFile("./posts/hello-world.txt", "utf8");\n' +
    '  sendHTML(res, /*#__PURE__*/_jsxs("html", {\n' +
    '    children: [/*#__PURE__*/_jsx("head", {\n' +
    '      children: /*#__PURE__*/_jsx("title", {\n' +
    '        children: "My blog"\n' +
    '      })\n' +
    '    }), /*#__PURE__*/_jsxs("body", {\n' +
    '      children: [/*#__PURE__*/_jsxs("nav", {\n' +
    '        children: [/*#__PURE__*/_jsx("a", {\n' +
    '          href: "/",\n' +
    '          children: "Home"\n' +
    '        }), /*#__PURE__*/_jsx("hr", {})]\n' +
    '      }), /*#__PURE__*/_jsx("article", {\n' +
    '        children: postContent\n' +
    '      }), /*#__PURE__*/_jsxs("footer", {\n' +
    '        children: [/*#__PURE__*/_jsx("hr", {}), /*#__PURE__*/_jsx("p", {\n' +
    '          children: /*#__PURE__*/_jsxs("i", {\n' +
    '            children: ["(c) ", author, " ", new Date().getFullYear()]\n' +
    '          })\n' +
    '        })]\n' +
    '      })]\n' +
    '    })]\n' +
    '  }));\n' +
    '}).listen(8080);\n' +
    'function sendHTML(res, jsx) {\n' +
    '  const html = renderJSXToHTML(jsx);\n' +
    '  res.setHeader("Content-Type", "text/html");\n' +
    '  res.end(html);\n' +
    '}\n' +
    'function renderJSXToHTML(jsx) {\n' +
    '  if (typeof jsx === "string" || typeof jsx === "number") {\n' +
    '    return escapeHtml(jsx);\n' +
    '  } else if (jsx == null || typeof jsx === "boolean") {\n' +
    '    return "";\n' +
    '  } else if (Array.isArray(jsx)) {\n' +
    '    return jsx.map(child => renderJSXToHTML(child)).join("");\n' +
    '  } else if (typeof jsx === "object") {\n' +
    '    if (jsx.$$typeof === Symbol.for("react.element")) {\n' +
    '      let html = "<" + jsx.type;\n' +
    '      for (const propName in jsx.props) {\n' +
    '        if (jsx.props.hasOwnProperty(propName) && propName !== "children") {\n' +
    '          html += " ";\n' +
    '          html += propName;\n' +
    '          html += "=";\n' +
    '          html += escapeHtml(jsx.props[propName]);\n' +
    '        }\n' +
    '      }\n' +
    '      html += ">";\n' +
    '      html += renderJSXToHTML(jsx.props.children);\n' +
    '      html += "</" + jsx.type + ">";\n' +
    '      return html;\n' +
    '    } else throw new Error("Cannot render an object.");\n' +
    '  } else throw new Error("Not implemented.");\n' +
    '}',
  format: 'module'
}

결론

server.js 파일 내부에서 jsx형식은 string으로 동적으로 파싱해서 브라우저로 전달하므로 이 jsx형식을 브라우저가 이해하기에는 문제가 없지만, node가 server.js파일을 읽어들일 때는 jsx형식을 이해하지 못하기 때문에 에러를 뱉는다. 그래서 babel의 "@babel/plugin-transform-react-jsx" 플러그인을 이용해 node가 jsx을 처리할 수 있게 한 번 컴파일해서 전달한다.

file:///workspace/server.js:10
    <html>
    ^

SyntaxError: Unexpected token '<'
    at CustomizedModuleLoader.moduleStrategy (node:internal/modules/esm/translators:116:18)
    at CustomizedModuleLoader.moduleProvider (node:internal/modules/esm/loader:206:14)
    at async link (node:internal/modules/esm/module_job:67:21)

Node.js v20.2.0

+ 또 궁금한 점

일반적인 react플젝에서 Node의 역할은 이렇게 있는데(gpt참조)

서버 환경 제공: Node.js는 JavaScript 런타임 환경을 제공하여 서버 측 애플리케이션을 개발할 수 있게 합니다. React 애플리케이션은 브라우저에서 실행되지만, Node.js를 사용하여 서버를 구축하고 서버 측 렌더링 (Server-Side Rendering, SSR)을 수행할 수 있습니다.
패키지 관리: Node.js는 npm (Node Package Manager)를 통해 프로젝트의 의존성 관리를 제공합니다. React 프로젝트에서는 필요한 라이브러리와 모듈을 npm을 통해 설치하고 관리할 수 있습니다.
빌드 도구 실행: React 애플리케이션을 개발하고 빌드하기 위해 Node.js는 다양한 빌드 도구와 플랫폼을 지원합니다. 예를 들어, Webpack, Babel, ESLint 등의 도구를 사용하여 개발, 번들링, 테스트, 코드 분석 등을 수행할 수 있습니다.

위 에러 메시지를 보면 모듈을 로딩하는 과정에서 언어를 이해하지 못한 상황이다.
그러면 세 가지 중 어떤 파트에서 node가 에러를 낸 걸까?
패키지 관리? 번들링?

일반적인 react 프로젝트에서 소스의 format에 따라 loader를 결정하는 로직은?
https://learn.microsoft.com/ko-kr/visualstudio/javascript/tutorial-nodejs-with-react-and-jsx?view=vs-2022#configure-webpack-and-typescript-compiler-options

0개의 댓글