在没有WebPack的情况下使用Deno和React构建同构应用程序

Currently setting up a Server Side Render (SSR) application is a pain in nodejs. There are many scaffolds available for nodejs. But it comes with its own tech-depth and learning curves. This also includes hidden configurations of Webpack.

当前,在Nodejs中设置服务器端渲染(SSR)应用程序很麻烦 。 有许多可用于nodejs的支架。 但是它具有自己的技术深度学习曲线 。 这还包括Webpack的隐藏配置。

All in all, when you give Webpack a chance, your encounter will rarely be a pleasant one.

总而言之,当您给Webpack一个机会时,遇到的机会很少会是一件愉快的事。

Read More: https://www.north-47.com/knowledge-base/webpack-the-good-the-bad-and-the-ugly/

了解更多https : //www.north-47.com/knowledge-base/webpack-the-good-the-bad-and-the-ugly/

1.概述 (1. Overview)

According to the wiki, An isomorphic JavaScript(also known as Universal JavaScript) is described as JavaScript applications that run both on the client and the server.

根据 Wiki的描述 ,同构JavaScript(也称为 Universal JavaScript )被描述为可在客户端和服务器上运行JavaScript应用程序。

If I say, you can build an entire SSR without setting up installing any external nodejs dependency. Would you believe it? I guess NO.

如果我说的话,您可以构建整个SSR,而无需设置安装任何外部nodejs依赖项。 你会相信吗? 我猜NO

However, In this tutorial, I will explain how to set up a simple SSR app without installing a single nodejs library or bundler. That also including a hydrate react app(isomorphic app).

但是,在本教程中,我将解释如何在不安装单个nodejs库bundler的情况下设置简单的SSR应用程序。 这还包括水合物React应用程序( 同构应用程序 )。

2.设置 (2. Set-up)

a. Start an app with npm inits: Don’t be afraid, To do things differently, we will not install any nodejs libraries. However, I still like npm as a task runner. So let’s use it. Create a folder SSR and init npm package.json

一个。 使用npm inits启动应用程序:不要害怕,要做不同的事情,我们不会安装任何nodejs库。 但是,我仍然喜欢npm作为任务运行程序 。 因此,让我们使用它。 创建一个文件夹SSR并初始化npm package.json

$ md -p examples/ssr$ cd examples/ssr## init npm package
$ npm init --y

3.后端 (3. Backend)

a. Add Basic Deno server: Create server.tsx a file and add below code

一个。 添加基本​​Deno服务器:创建server.tsx文件并添加以下代码

import { Application, Router } from "https://deno.land/x/oak@v6.0.1/mod.ts";const app = new Application();const router = new Router();
router.get("/", handlePage);app.use(router.routes());
app.use(router.allowedMethods());console.log("server is running on http://localhost:8000/");
await app.listen({ port: 8000 });function handlePage(ctx: any) {
try {
ctx.response.body = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body >
<div id="root"><h1>Hello SSR</h1></div>
</body>
</html>`;
} catch (error) {
console.error(error);
}
}

note: We will use oak module here to create Deno server. You can create your own server. For that read my article Creating Routing/Controller in Deno Server(From Scratch)

注意:我们将在此处使用Oak模块创建Deno服务器。 您可以创建自己的服务器。 为此,请阅读我在Deno Server中创建路由/控制器的文章(从头开始)

Add below command in package.json.

package.json添加以下命令。

"scripts": {
"start": "deno run --allow-net server.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},

Run: Now we can run the application and verify on http://localhost:8000/.

运行:现在我们可以运行应用程序并在http://localhost:8000/上进行验证

npm run start

b. Add React Server Render: Now we can run the application. Let us add our first rendering code. For that, we need to ReactJS. Since Deno uses ES Module import, We will use the CDN hosted version of react and react-dom. For that, there is a good CDN provider https://jspm.org/

b。 添加React Server Render:现在我们可以运行应用程序了。 让我们添加第一个渲染代码。 为此,我们需要ReactJS。 由于Deno使用ES模块导入 ,因此我们将使用CDN托管版本的reactreact-dom 。 为此,有一个很好的CDN提供程序https://jspm.org/

jspm provides a module CDN allowing any package from npm to be directly loaded in the the browser and other JS environments as a fully optimized native JavaScript module.

jspm 提供了一个模块CDN,该模块CDN允许将来自 npm的任何程序包 作为完全优化的本机JavaScript模块 直接加载到浏览器和其他JS环境中

Now since we are going to write some TSX syntax(typescript JSX). We have to change the file extension of server.ts to server.tsx. Let’s do that and update package.json.

现在,由于我们要编写一些TSX语法(打字稿JSX) 。 我们必须将server.ts的文件扩展名更改为server.tsx 。 让我们这样做并更新package.json

mv server.ts server.tsx

mv server.ts server.tsx

// package.json
"scripts": {
"start": "deno run --allow-net server.tsx",
"test": "echo \"Error: no test specified\" && exit 1"
},

c. Add below lines in server.tsx

C。 server.tsx 添加以下行

import { Application, Router } from "https://deno.land/x/oak@v6.0.1/mod.ts";import React from "https://dev.jspm.io/react@16.13.1";
import ReactDomServer from "https://dev.jspm.io/react-dom@16.13.1/server";const app = new Application();const router = new Router();
router.get("/", handlePage);app.use(router.routes());
app.use(router.allowedMethods());console.log("server is running on http://localhost:8000/");
await app.listen({ port: 8000 });function App() {
return <h1>Hello SSR</h1>;
}
function handlePage(ctx: any) {
try {
const body = ReactDomServer.renderToString(<App />);
ctx.response.body = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body >
<div id="root">${body}</div>
</body>
</html>`;
} catch (error) {
console.error(error);
}
}

Run the app again. You will see errors on the console.

再次运行该应用程序。 您将在控制台上看到错误

TS7026 [ERROR]: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
return <h1>Hello SSR</h1>

This error is due to missing typings to react. Since we do not include types to react. We have to let know the typescript compiler. How it should treat JSX(TSX) syntax.

此错误是由于缺少对react的 键入而导致的。 由于我们不包括要做出React的类型 。 我们必须让打字稿编译器知道。 它应该如何处理JSX(TSX)语法。

To suppress these errors, Add below lines.

消除这些错误,请在下面的行中添加。

declare global {
namespace JSX {
interface IntrinsicElements {
[key: string]: any;
}

}
}
function App() {
return <h1>Hello SSR</h1>;
}

Now run the server again. You can see your first React SSR running on the browser. Nice!

现在再次运行服务器。 您可以在浏览器中看到第一个React SSR 。 真好!

d. Adding Server Controller- Create Backend APIs

d。 添加服务器控制器-创建后端API

Let’s move further and start adding a few core features for Server. Let’s add some server-side data for our app. For that, we will include a few routes on Oak Server. Oak

让我们继续前进,开始为Server添加一些核心功能。 让我们为我们的应用程序添加一些服务器端数据。 为此,我们将在Oak Server上包括一些路由。 橡木

const router = new Router();
router.get("/", handlePage);let todos: Map<number, any> = new Map();function init() {
todos.set(todos.size + 1, { id: Date.now(), task: "build an ssr deno app" });
todos.set(todos.size + 1, {
id: Date.now(),
task: "write blogs on deno ssr",
});
}
init();
router
.get("/todos", (context) => {
context.response.body = Array.from(todos.values());
})
.get("/todos/:id", (context) => {
if (
context.params &&
context.params.id &&
todos.has(Number(context.params.id))
) {
context.response.body = todos.get(Number(context.params.id));
} else {
context.response.status = 404;
}
})
.post("/todos", async (context) => {
const body = context.request.body();
if (body.type === "json") {
const todo = await body.value;
todos.set(Date.now(), todo);
}
context.response.body = { status: "OK" };
});app.use(router.routes());
app.use(router.allowedMethods());

Here in the above code, We have created three routes.

在上面的代码中,我们创建了三个路由。

  1. GET /todos/ to get a list of the todos

    GET /todos/获取待办事项列表

  2. GET /todos/:id to todo by id

    GET /todos/:id按ID执行

  3. POST /todos/ create a new todo

    POST /todos/创建一个新的待办事项

function init() to create some initial dummy todos. You can use postman to try-out get and post data.

function init()创建一些初始虚拟待办事项。 您可以使用邮递员试用获取和发布数据。

4.客户端应用 (4. Client-Side App)

a. Add List Todos to React Client: Since now we have API to create todos and consume todos. Let’s list down all this on our react app. For that add the below-mentioned code.

一个。 将列表待办事项添加到React Client:从现在开始,我们有了API来创建待办事项和使用待办事项。 让我们在react应用程序中列出所有这些内容。 为此,添加以下代码。

// server.tsxfunction App() {
return (
<div>
<div className="jumbotron jumbotron-fluid">
<div className="container">
<h1 className="display-4">ToDo's App</h1>
<p className="lead">This is our simple todo app.</p>
<ListTodos items={Array.from(todos.values())} />
</div>
</div>
</div>
);
}function ListTodos({ items = [] }: any) {
return (
<>
<ul className="list-group">
{items.map((todo: any, index: number) => {
return (
<li key={index} className="list-group-item">
{todo.task}
<button
type="button"
className="ml-2 mb-1 close"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
</li>
);
})}
</ul>
</>
);
}
function handlePage(ctx: any) {
try {
const body = ReactDomServer.renderToString(<App />);
ctx.response.body = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<title>Document</title>
</head>
<body >
<div id="root">${body}</div>
</body>
</html>`;

Update all the changes and run the app. You will see a list of Todos containing two rows of initial data. You can use curl post data to route POST/todos/ to create new records. Once you add a post, refresh the page, You will see added new post data.

更新所有更改并运行应用程序 。 您将看到一个包含两行初始数据的待办事项列表 。 您可以使用curl发布数据来路由POST/todos/以创建新记录。 添加帖子后, 刷新页面,您将看到添加的新帖子数据。

curl --header "Content-Type: application/json" \
--request POST \
--data '{"task":"Create postman script"}' \
http://localhost:8000/todos/

If you noticed, I have added basic bootstrap to make UI nicer. You can use some other CSS library.

如果您注意到了,我添加了基本的 引导程序 以使UI更好。 您可以使用其他CSS库。

Image for post

Tada! Now you have running the SSR app. You can replace the in-memory todos store to any persistent database. The result will be the same.

多田 现在,您已经在运行SSR应用程序。 您可以将内存中的待办事项存储替换为任何持久数据库。 结果将是相同的。

Now time to add some interactive behavior in Our react app(client-side). But before doing that, let’s move our react code to some separate file app.tsx.

现在该在我们的react应用( client-side )中添加一些交互行为了。 但是在此之前,让我们将我们的react代码移动到一些单独的文件app.tsx

b. Create a file app.tsx:

b。 创建一个文件 app.tsx

import React from "https://dev.jspm.io/react@16.13.1";declare global {
namespace JSX {
interface IntrinsicElements {
[key: string]: any;
}
}
}function App({ todos = [] }: any) {
return (
<div>
<div className="jumbotron jumbotron-fluid">
<div className="container">
<h1 className="display-4">ToDo's App</h1>
<p className="lead">This is our simple todo app.</p>
<ListTodos items={todos} />
</div>
</div>
</div>
);
}function ListTodos({ items = [] }: any) {
return (
<>
<ul className="list-group">
{items.map((todo: any, index: number) => {
return (
<li key={index} className="list-group-item">
{todo.task}
<button
type="button"
className="ml-2 mb-1 close"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
</li>
);
})}
</ul>
</>
);
}
export default App;

Notice the change in the App component. Since we do not have direct access to todos now, We need to pass data as props while rendering it. Corresponding changes have been done for ListTodos.

请注意App组件中的更改。 由于我们没有待办事项直接访问现在,我们需要将数据传送道具 ,同时使其。 ListTodos已进行了相应的更改。

// server.tsximport React from "https://dev.jspm.io/react@16.13.1";
import ReactDomServer from "https://dev.jspm.io/react-dom@16.13.1/server";
import App from "./app.tsx";/// rest of the codefunction handlePage(ctx: any) {
try {
const body = ReactDomServer.renderToString(
<App todos={Array.from(todos.values())} /> // change here to pass todos as props
);// rest of the code
}

Run the app and see changes on the browser, If all good there will be no change in the final output.

运行该应用程序,然后在浏览器上查看更改,如果一切正常,则最终输出将保持不变。

c. Adding delete functionality on client-side

C。 在客户端添加删除功能

// app.tsxfunction ListTodos({ items = [] }: any) {  const [deletedIdxs, setDeletedIdxs] = (React as any).useState([]);  return (
<>
<ul className="list-group">
{items.map((todo: any, index: number) => { const deleted = deletedIdxs.indexOf(index) !== -1; return (
<li
key={index}
className="list-group-item"
style={{ color: deleted && "red" }}
>
{todo.task}
<button
type="button"
className="ml-2 mb-1 close"
aria-label="Close"
onClick={() => setDeletedIdxs([...deletedIdxs, index])}
>
<span aria-hidden="true">&times;</span>
</button>
</li>
);
})}
</ul>
</>
);
}

Once you do the above changes and try to delete by clicking on cross-button. You will see no change in UI. By code, it should turn the element color to red. So what could be the reason for that?

完成上述更改后,尝试通过单击交叉按钮进行删除。 您不会在UI中看到任何更改。 通过代码,它应该将元素颜色变为red 。 那么,这可能是什么原因呢?

Answer: Hydrate

答案:水合

Since we are using ReactDomServer.renderToString the library which converts React app to string. So we lose all JS capabilities. To re-enable react js on the client-side. For that React provides you Hydrate module(API). This hydrate API re-enable the react feature on the client-side again. This makes our app Isomorphic app. More: Hydrate

由于我们正在使用ReactDomServer.renderToString这个库,它将React应用程序转换为字符串。 因此,我们失去了所有JS功能。 要在客户端重新启用react js。 为此,React为您提供了水合模块(API)。 该水合物API再次在客户端重新启用了react功能。 这使我们的应用程序同构 。 更多: 水合

Adding hydrate is a tough task to do. But Awesome Deno shines well here too. Deno provides Bundle API to convert a script to js. We will use Deno.bundle to create hydrate js for the client-side.

添加水合物是一项艰巨的任务。 但是Awesome Deno在这里也很耀眼。 Deno提供Bundle API将脚本转换为js。 我们将使用Deno.bundle为客户端创建Deno.bundle js。

d. Create a new file client.tsx and add below codes:

d。 创建一个新文件 client.tsx 并添加以下代码:

// client.tsximport React from "https://dev.jspm.io/react@16.13.1";
import ReactDom from "https://dev.jspm.io/react-dom@16.13.1";import App from "./app.tsx";(ReactDom as any).hydrate(<App todos={[]} />, document.getElementById("root"));

Add below codes to compile and convert client.tsx to serve as a route in our server.

添加以下代码来编译和转换client.tsx以使其成为我们服务器中的路由。

// server.tsx// initial code
const [_, clientJS] = await Deno.bundle("./client.tsx");const serverrouter = new Router();
serverrouter.get("/static/client.js", (context) => {
context.response.headers.set("Content-Type", "text/html");
context.response.body = clientJS;
});
app.use(router.routes());
app.use(serverrouter.routes());
// rest of the code
function handlePage(ctx: any) {
try {
const body = ReactDomServer.renderToString(
<App todos={Array.from(todos.values())} /> // change here to pass todos as props
);
ctx.response.body = `<!DOCTYPE html>
<html lang="en">
<!--Rest of the code -->
<div id="root">${body}</div>
<script src="http://localhost:8000/static/client.js" defer></script>
</body>
</html>`;
} catch (error) {
console.error(error);
}

Since we are using unstable API deno.bundle, You have to update package.json and add more flags. Same time, We are using DOM with typescript. So we have to add custom tsconfig.json.

由于我们使用的是不稳定的API deno.bundle ,因此您必须更新package.json并添加更多标志。 同时,我们将DOM与Typescript一起使用。 因此,我们必须添加自定义tsconfig.json

// package.json{
"scripts": {
"start": "deno run --allow-net --allow-read --unstable server.tsx -c tsconfig.json",
"test": "echo \"Error: no test specified\" && exit 1"
}
}

e. Create a filetsconfig.json:

e。 创建一个文件 tsconfig.json

// tsconfig.json{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"lib": [
"DOM",
"ES2017",
"deno.ns"
]
,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}

Note: You can use bundler as CLI to convert client.tsx before even starting the server. However, I just wanna show a cool way of doing it. So I use Deno.bundle on runtime.

注意:您甚至可以在启动服务器之前,将bundler用作CLI来转换client.tsx 。 但是,我只是想展示一种很酷的方法。 所以我在运行时使用Deno.bundle

4.最后的接触 (4. Final Touch)

Initialize initial state: Once you do all the above-mentioned changes, Re-Run app. You will notice the list is the visible and hidden same time. This is because we react hydrate start working and it is trying to re-initialize the app. So all the data we render from the server is gone to persist data we need to pass data as application initial data. There are a lot of patterns to pass initial data. We will use the simple window global data.

初始化初始状态:完成所有上述更改后,重新运行应用程序。 您会注意到列表同时可见和隐藏。 这是因为我们React水合物开始工作,并且正在尝试重新初始化应用程序。 因此,我们从服务器渲染的所有数据都将保留为持久性数据,我们需要将数据作为应用程序初始数据传递。 有很多传递初始数据的模式。 我们将使用简单的窗口全局数据。

Let’s start data on the window after making below changes on the given files.

在对给定文件进行以下更改之后,让我们在窗口中开始数据

// server.tsxfunction handlePage(ctx: any) {
try {
const body = ReactDomServer.renderToString(
<App todos={[]} />
);
ctx.response.body = `<!DOCTYPE html>
<title>Document</title>
<script>
window.__INITIAL_STATE__ = {"todos": ${JSON.stringify(
Array.from(todos.values())
)}};

</script>
</head>

Let’s update client.tsx accordingly.

让我们相应地更新client.tsx

// client.tsx// initial codes
declare global {
var __INITIAL_STATE__: any;
}
import App from "./app.tsx";const { todos } = window.__INITIAL_STATE__ || { todos: [] };
(ReactDom as any).hydrate(
<App todos={todos} />,
document.getElementById("root")
);

Now you have a running, working SSR/Isomorphic App that is fully written in Deno. We didn’t use any nodejs/npm modules or WebPack.

现在您有了一个正在运行的SSR /同构应用 完全用Deno编写。 我们没有使用任何nodejs / npm模块或WebPack。

Thanks for reading this tutorial. Please follow me to support me. For more of my work, check-out my website https://decipher.dev/.

感谢您阅读本教程。 请跟我来支持我。 有关我的更多工作,请访问我的网站https://decipher.dev/

You can find all the code in examples/ssr folder on my Github repo.

您可以在我的Github存储库的 examples / ssr文件夹中找到所有代码。

Demo:

演示:

Image for post

Originally published at https://decipher.dev.

最初发布在 https://decipher.dev

翻译自: https://medium.com/swlh/build-an-isomorphic-application-using-deno-and-react-without-webpack-deno-by-example-6c748abb3243

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值