일반 NodeJS 함수를 프론트 엔드 코드로 가져올 수 있다면 어떨까요?
어떻게 가능하게 했는지 보여 드리겠습니다.
https://zach.codes/es6-import-nodejs-on-the-frontend/
일반 NodeJS 함수를 프론트 엔드 코드로 가져올 수 있다면 어떨까요? 이 예에서 statuses는 Firestore의 결과를 반환합니다.
import { statuses } from './statuses.server.js'
export const Statuses = () => {
if(loading) return <div>Loading...</div>
return (
<div
onClick={() => {
let statuses = await statuses('timeline')
console.log(statuses)
}}
>
Load Statuses
</div>
)
}
네트워크 요청을 작성하는 대신 전체 스택 코드를 작성하여 프런트 엔드의 일반적인 부분처럼 가져 오는 것이 좋지 않습니까? 실제 응용 프로그램에서 어떻게 작동하는지 확인하려면 계속 읽으십시오.
현재 작업이 수행되는 방식
기존 앱에서 프론트 엔드에는 다음과 같은 Statuses.js라는 파일이 있을 수 있습니다.
import React from 'react'
export const Statuses = () => {
let statuses = ['first', 'second', 'third']
return statuses.map(status => (
<div key={status}>{status}</div>
))
}
다음으로 백엔드 라우트를 작성하기로 합니다. 다음과 같이 보일 수 있습니다 :
app.get('/statuses', (req, res) => {
// load some statuses from a database or other service
// hard code it for now
let statuses = ['first', 'second', 'third']
res.send(statuses)
}
그런 다음 프론트 엔드에서 다음과 같이 해야 합니다.
let loadStatuses = async () => {
let response = await fetch('https://myserver.com/statuses')
let json = await response.json()
return json
}
React에서 네트워크 요청을 사용하는 작업이 조금 더 있지만, 우리는 그것에 들어 가지 않을 것입니다.
요점은 이제 서버 코드가 변경되어 프론트 엔드를 깨뜨릴 수 있다는 것입니다. 함수를 직접 가져오고 일반과 같이 TypeScript를 사용하여 문제가 발생하지 않도록 하는 것이 좋지 않습니까?
GraphQL을 사용하든 다른 것을 사용하든 백엔드 프로젝트에 들어가야 합니다. 아마도 같은 저장소에 있을 것입니다 ....하지만 거의 그것을 사용하는 코드 옆에 앉아 있지는 않습니다.
서버 로직을 프론트 엔드와 함께 배치 할 수 있다면 좋지 않을까요?
우리는 이미 CSS, 네트워크 요청 및 기타 모든 것을 통합했습니다. 스택 전체에서 동일한 코드 위치에 전체 기능을 넣을 수 있다면 흥미로울 것입니다.
GRPC
grpc를 배우고 사용한 후에 아이디어가 있습니다. GRPC의 기본 부분은 단일 진실 소스를 갖는 것입니다. 이 경우 프로토 파일입니다. 이 파일은 GRPC 함수에 대한 유형 정의를 포함합니다. 필요한 매개 변수, 반환 되는 데이터 및 반환 방법
TypeScript와 비슷하지만 모든 언어에 적용됩니다. 이 하나의 유형 정의를 작성한 다음 원하는 언어로 코드를 자동 생성합니다. 하나의 GRPC 백엔드에 Rust를 사용하고 싶을 수도 있습니다. 그런 다음 웹 프론트 엔드에서 해당 함수를 호출하려고 합니다. 우리는 프로토 정의를 만들 것입니다. 우리는 프로토를 사용하여 서버 기능을 만들 것입니다. 그런 다음 웹에서 호출 할 수 있는 TypeScript 함수를 자동으로 생성합니다. 다음과 같이 보입니다.
import * as grpcWeb from 'grpc-web';
import {EchoServiceClient} from './echo_grpc_web_pb';
import {EchoRequest, EchoResponse} from './echo_pb';
const echoService = new EchoServiceClient('http://localhost:8080', null, null);
const request = new EchoRequest();
request.setMessage('Hello World!');
const call = echoService.echo(request, {'custom-header-1': 'value1'},
(err: grpcWeb.Error, response: EchoResponse) => {
console.log(response.getMessage());
});
call.on('status', (status: grpcWeb.Status) => {
// ...
});
당신이 나에게 묻는다면 .... 그것은 정말 추악한 구문입니다.
let response = await echoService.echo({message: 'Hello World'})
이것은 GRPC의 내 문제 중 하나입니다. 공식 웹 패키지는 JS를 자주 사용하지 않거나 자세한 구문을 좋아하는 사람들이 작성했습니다. 누군가가 프로토에서 자신의 웹 생성기를 만들고 구문을 개선 할 수 있기 때문에 불평해서는 안됩니다. 나는 그 시간에 대한 시간이나 의지가 없다.)
왜 GRPC를 키웠습니까? 글쎄요, 멋지고 양방향 스트리밍을 지원합니다. 이것이 제가 이 아이디어를 얻은 이유입니다! GRPC를 사용하면 함수가 마치 응용 프로그램에 로컬로 존재하는 것처럼 함수를 호출 할 수 있습니다. 그것이 그것의 기본 부분입니다. 모든 언어로 grpc 서버 또는 클라이언트를 작성하고 비동기 함수를 호출하여 일부 데이터를 가져 오십시오.
My Idea
백엔드 노드 서버와 관련된 것을 처리 할 필요가 없다고 상상해보십시오. 개발자 서버를 실행하면 웹 코드를 작성하고 .server.js 파일을 자동으로 꺼내 노드를 사용하여 실행합니다. 프로덕션 환경에서는 npm start를 시작하고 앱 서버는 이러한 파일을 선택하여 프런트 엔드가 필요할 때 파일 호출을 처리해야 합니다.
나는 이 구문이 가능하고 그것이 효과가 있다고 약속합니다. 정상적인 함수를 작성하고 끝에 .server.js를 넣으면 가져온 코드가 약속이 되어 서버에서 실행됩니다!
Webpack
webpack은 번들 애플리케이션 레벨 JS를 처리하는 강력한 도구입니다. 로더라는 간단한 개념이 있습니다. 로더는 앱에서 가져온 모든 파일의 모든 내용을 수신하고 이를 사용하여 무언가를 수행 할 수 있는 기능입니다. 가장 기본적인 것은 ts-loader babel-loader CSS-loader 등입니다.
이 미친 멋진 아이디어를 작동 시키려면 로더를 만들어야 합니다. 내가 시작한 방법은 다음과 같습니다.
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development",
entry: "./src/index.js",
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: path.resolve("./config/magic/serverLoader.js"),
},
],
},
],
},
plugins: [new HtmlWebpackPlugin({ template: "./src/index.html" })],
};
또한 여기에 babel-loader가있어 React 구성 요소를 렌더링하고 JSX를 구문 분석 할 수 있지만 이것이 핵심 webpack.config.js입니다.
내가 하고 있는 일은 webpack에게 개발 모드에서 실행하고 .js로 끝나는 모든 파일을 내 마술 로더에 전달하도록 지시하는 것입니다. 그런 다음 HtmlWebpackPlugin을 설정하여 webpack에서 JS를 포함하는 html 파일을 생성합니다.
Server Loader
이 전체 프로세스에서 획기적인 생각은 다음과 같습니다. 파일의 내용을 가져 오기 요청으로 바꾸고 .... 그런 다음 args가 전달 된 상태에서 네트워크를 통해 함수 이름을 보내면 어떻게 됩니까? 어떻게 쓰면 좋을까요 ....
module.exports = function (source) {
if (!this.resourcePath.match(".server")) return source;
let lines = source.split(/\n/);
}
내가 시작한 방법은 다음과 같습니다. 이름에 .server가 없는 모든 파일을 건너 뜁니다. 그런 다음 파일을 개별 줄로 나눕니다. 내 보낸 변수 이름을 모두 찾고 싶습니다 .... 한 가지 방법은 JS를 AST로 구문 분석하는 것이지만 파일을 그런 식으로 구문 분석하지 않고도 벗어날 수 있다고 생각했습니다.
//get the export variable names
let exports = lines
.filter((line) => line.match("export"))
.map((exportLine) => {
let [, , name] = exportLine.split(" ");
return name;
});
이것이 제가 다음에 쓴 것입니다. 텍스트 내보내기가 포함 된 줄에서 일치 시킵니다. 그런 다음 해당 줄을 나누고 내보내기 이름을 가져옵니다. export const Blah = ... 구문에는 항상 export 뒤에 var let const then name이 있으므로 이 간단한 구문 분석은 제대로 작동합니다.
실제로 남은 것은 내보내기를 일부 네트워크 코드로 바꾸는 것입니다.
let result = exports
.map((exportName) => (
`export const ${exportName} = serverRequest('${exportName}')`;
))
.join(/\n/);
프로덕션 라이브러리를 만드는 중이라면 아마도 'mylibrary'에서 import serverRequest를 던지고 실제로 라이브러리의 일부로 함수를 만듭니다. 이 개념 증명을 위해 로더에서 수행합니다. 최종 결과는 다음과 같습니다
serverLoader.js
module.exports = function (source) {
if (!this.resourcePath.match(".server")) return source;
let lines = source.split(/\n/);
//get the export variable names
let exports = lines
.filter((line) => line.match("export"))
.map((exportLine) => {
let [, , name] = exportLine.split(" ");
return name;
});
let result = exports
.map((exportName) => {
return `export const ${exportName} = serverRequest('${exportName}')`;
})
.join(/\n/);
let file = `
const serverRequest = (name) => async (...args) => {
let response = await fetch('http://localhost:8000', {
headers: {
'Content-type': 'application/json',
},
method: 'POST',
body: JSON.stringify({name, args})
})
let json = await response.json()
return json
}
${result}
`;
return file;
};
최종 사용자 관점에서 정상적인 기능을 가져오고 있습니다. 통화 서명은 정확히 동일합니다 .TypeScript는 서버 내보내기에 대한 모든 호출도 올바르게 검사합니다! 이 로더를 사용하면 모든 함수 호출이 약속되고 예상대로 작동합니다.
Gotchas
몇 가지 주의 할 사항이 있습니다. formdata 또는 무언가와 같이 json으로 구문 분석 할 수 없는 것을 서버 함수에 전달하면 작동하지 않습니다. 많은 노력 없이 추가 할 수 있지만 이 예제는 그렇게 하지 않습니다. 또한 실제 프레임 워크에서 수행하려는 오류도 처리하지 않습니다. 마지막으로 프로덕션 환경에서 변경할 수 있도록 환경 변수 또는 구성 변수에서 페치 URL을 설정하려고 합니다.
Node
글쎄, 우리는 프론트 엔드 부분이 작동하고 있습니다. 어떻게 서버 부분을 만들 수 있습니까? 자동으로 작동합니까?
var bodyParser = require("body-parser");
var express = require("express");
var cors = require("cors");
var bodyParser = require("body-parser");
const glob = require("glob");
var app = express();
app.use(cors());
app.use(bodyParser.json());
let functions = {};
glob(process.cwd() + "/**/*.server.js", {}, async (err, files) => {
files.forEach((file) => {
let exports = require(file);
functions = { ...functions, ...exports };
});
});
나는 이것을 매우 빨리 던졌다. esm을 사용하면 노드에서 es 모듈 가져 오기를 그대로 처리 할 수 있습니다. 따라서 이 스크립트를 다음과 같이 실행하면 됩니다 : node -r esm server.js. 우리는 express를 설정하고 cors를 추가하고 json body 데이터를 구문 분석합니다. 꽤 표준입니다.
다음으로 현재 디렉토리에 .server.js가있는 모든 파일을 재귀 적으로 검색합니다. 그런 다음 모든 파일을 가져오고 내 보낸 모든 메소드를 객체에 추가합니다. 두 개의 .server.js 파일이 있고 각각 test와 test2를 내 보내면 함수 객체는 다음과 같습니다.
{
test: () => {},
test2: () => {}
}
이러한 모든 내보내기를 단일 객체에 추가하면 서버 로더가 추가 한 JSON 본문에서 전달 된 이름을 가져 와서 이 서버를 완성 할 수 있습니다! 나머지 코드는 다음과 같습니다.
app.post("/", async (req, res) => {
try {
let data = await functions[req.body.name](...req.body.args);
res.send(data);
} catch (e) {
console.error(e);
res.send({ error: "Function not found" });
}
});
app.listen(process.env.PORT || 8000, () =>
console.log(
`ClearScript Server running at http://localhost:${process.env.PORT || 8000}`
)
);
Recap
요약 해보자.
이 아이디어를 한 단계 더 발전시키는 프로토 타입 코드가 이미 있습니다. 웹팩 개발 서버를 실행하면 노드 서버가 실행되고 코드를 변경할 때 핫 리로드가 수행됩니다. 따라서 개발자는 서버 코드를 작성할 때 아무것도 생각할 필요가 없습니다.
프로덕션으로 출시하려면 일반적인 프론트 엔드 빌드 프로세스가 필요하며 여기서 데모 한이 특수 노드 서버를 실행해야 합니다.
등록된 댓글이 없습니다.