- Basic Usage with Koa
- with express
- with Router
- with Client script
- with jQuery
- with jQuery from CDN
- use POST method
- use session
- with CSP (Content Security Policy)
- user Context
- async Component
- Contributing
- Credits
- Authors
- Show your support
- License
npm install @maskedeng-tom/ssrsx
npm install koa @types/koa
- change
jsx
andjsxImportSource
toreact-jsx
andjsx
respectively.
// tsconfig.json
{
"compilerOptions": {
"target": "es2016",
"jsx": "react-jsx", // !important
"jsxImportSource": "ssrsxjsx", // !important
"module": "CommonJS",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
// index.tsx
import Koa from 'koa';
import { ssrsxKoa } from '@maskedeng-tom/ssrsx';
const App = () => {
return <>
<div>
Hello Ssrsx world !
</div>
</>;
};
const app = new Koa();
app.use(ssrsxKoa({
development: true,
app: <App/>
}));
app.listen(3000);
npm install
npm run start
and access to http://localhost:3000/
npm install @maskedeng-tom/ssrsx
npm install express @types/express
// tsconfig.json
{
"compilerOptions": {
"target": "es2016",
"jsx": "react-jsx", // !important
"jsxImportSource": "jsx", // !important
"module": "CommonJS",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
// index.tsx
import express from 'express';
import { ssrsxExpress } from '@maskedeng-tom/ssrsx';
const App = () => {
return <>
<div>
Hello Ssrsx world !
</div>
</>;
};
const app = express();
app.use(ssrsxExpress({
development: true,
app: <App/>
}));
app.listen(3000);
// index.tsx
import Koa from 'koa';
import { ssrsxKoa, Router, Routes, Route, Link } from '@maskedeng-tom/ssrsx';
const Page1 = () => {
return <div>
<div>Page1</div>
<div><Link to="/page2">Link to Page2</Link></div>
<div><Link to="/">Top</Link></div>
</div>;
};
const Page2 = () => {
return <div>
<div>Page2</div>
<div><Link to="/page1">Link to Page1</Link></div>
<div><Link to="/">Top</Link></div>
</div>;
};
const App = () => {
return <html lang="en">
<head>
<meta charSet="UTF-8"/>
<title>Ssrsx</title>
</head>
<body>
<div>
<Router>
<Routes>
<Route path="/">
<div>
<div>Hello Ssrsx world !</div>
<div><Link to="/page1">Link to Page1</Link></div>
<div><Link to="/page2">Link to Page2</Link></div>
</div>
</Route>
<Route path="page1"><Page1/></Route>
<Route path="page2"><Page2/></Route>
</Routes>
</Router>
</div>
</body>
</html>;
};
const app = new Koa();
app.use(ssrsxKoa({
development: true,
app: <App/>
}));
app.listen(3000);
mkdir src
mkdir src/client
-
[client module name].js
is a client script file.
// src/client/test.js
const onClick = (e: Event) => {
alert('from js://onClick@test');
};
export { onClick }; // need export! important!
-
js://
is a protocol to call client script function from ssrsx.js://[exported function name]@[client module name]
// index.tsx
import Koa from 'koa';
import { ssrsxKoa, useGlobalStyle } from '@maskedeng-tom/ssrsx';
const App = () => {
useGlobalStyle({
'.clickable': {
cursor: 'pointer',
color: 'blue',
textDecoration: 'underline',
},
});
return <html lang="en">
<head>
<meta charSet="UTF-8"/>
<title>Ssrsx</title>
</head>
<body>
<div>
<div onClick="alert('inline js')" className="clickable">
Run inline js
</div>
<div onClick="js://onClick@test" className="clickable" >
Run outside client js (src/client/test.client.js -> onClick)
</div>
</div>
</body>
</html>;
};
const app = new Koa();
app.use(ssrsxKoa({
development: true,
clientRoot: 'src/client', // client script root
app: <App/>
}));
app.listen(3000);
This is a sample using jQuery. External libraries are loaded using requirejs. Specify the root folder of the client script in clientRoot and the path to place modules such as jQuery in requireJsRoot.
npm install @maskedeng-tom/ssrsx
npm install koa @types/koa
npm install jquery @types/jquery
mkdir src
mkdir src/client
cp node_modules/jquery/dist/jquery.min.js src/client
// src/client/test.client.js
import $ from 'jquery';
const onClick = (e: Event) => {
const input = $('#username');
alert(input.val());
};
export { onClick };
// index.tsx
import Koa from 'koa';
import { ssrsxKoa } from '@maskedeng-tom/ssrsx';
const App = () => {
return <html lang="en">
<head>
<meta charSet="UTF-8"/>
<title>Ssrsx</title>
</head>
<body>
<div>
<div>
<input type="text" id="username" name="username" value="foo"/>
</div>
<button type="text" onClick="js://test.onClick">
Show input tag value!
</button>
</div>
</body>
</html>;
};
const app = new Koa();
app.use(ssrsxKoa({
development: true,
clientRoot: 'src/client',
requireJsRoot: 'src/client', // for requirejs
requireJsPaths: { // for requirejs.config paths
'jquery': 'jquery.min', // define for jquery (cut '.js' extension)
},
app: <App/>
}));
app.listen(3000);
If you want to use a CDN, specify the same version of jQuery as the one you installed with npm install jquery
.
// index.tsx
...
app.use(ssrsxKoa({
development: true,
clientRoot: 'src/client',
requireJsRoot: 'src/client',
requireJsPaths: {
'jquery': 'https://code.jquery.com/jquery-3.7.1.min',
},
app: <App/>
}));
...
You can get the data sent by the POST method by using the useBody
function.
and You need to add a body parser
to get the data sent by the POST method.
// index.tsx
import Koa from 'koa';
import bodyParser from 'koa-bodyparser'; // add body parser
import { ssrsxKoa, Router, Routes, Route, Link, useBody } from '../';
////////////////////////////////////////////////////////////////////////////////
const LoginCheck = () => {
// post body data
const body = useBody<{username: string, password: string}>();
//
return <>
<h1>Login Post Result</h1>
<div>
<div>Username: {body.username}</div>
<div>Password: {body.password}</div>
</div>
<Link to="/">Top</Link>
</>;
};
const LoginForm = () => {
return <>
<h1>Login Form</h1>
<form method="post" action="/login">
<div>
<label>
username: <input type="text" name="username" />
</label>
</div>
<div>
<label>
password: <input type="password" name="password" />
</label>
</div>
<button type="submit">Login</button>
</form>
</>;
};
const App = () => {
return <html lang="en">
<head>
<meta charSet="utf-8"/>
<title>Ssrsx</title>
</head>
<body>
<Router>
<Routes>
<Route path="/"><LoginForm /></Route>
<Route path="/login"><LoginCheck /></Route>
</Routes>
</Router>
</body>
</html>;
};
const app = new Koa();
// body parser
app.use(bodyParser());
app.use(ssrsxKoa({
development: true,
clientRoot: 'test/client',
app: <App/>
}));
app.listen(3000);
// index.tsx
...
// body parser
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
...
// index.tsx
import Koa from 'koa';
import bodyParser from 'koa-bodyparser'; // add body parser
import session from 'koa-session'; // add session
//
import { ssrsxKoa, ssrsxExpress, useSearch } from '../';
import { Router, Routes, Route, Link, Navigate, useBody, useSession } from '../';
////////////////////////////////////////////////////////////////////////////////
interface SessionContext {
username?: string;
}
const Authorized = () => {
// session
const session = useSession<SessionContext>();
if(!session.username){
// not authorized
return <Navigate to="/"/>;
}
// authorized
return <>
<h1>Authorized</h1>
<div>
<div>Authorized Username: {session.username}</div>
</div>
<Link to="/logout">Logout</Link>
</>;
};
const Login = () => {
// session
const session = useSession<SessionContext>();
// post body data
const body = useBody<{username: string, password: string}>();
// check username and password
if(body.username === 'admin' && body.password === 'admin'){
session.username = body.username;
return <Navigate to="/authorized"/>;
}
// authorization failed
return <Navigate to="/?message=authorization_failed"/>;
};
const Logout = () => {
// set session
const session = useSession<SessionContext>();
// session clear
session.username = undefined;
// redirect to top
return <Navigate to="/"/>;
};
const LoginForm = () => {
// get search(query parameter) data (?message=...)
const search = useSearch<{message: string}>();
// set session
const session = useSession<SessionContext>();
if(session.username){
// already authorized
return <Navigate to="/authorized"/>;
}
// login form
return <>
<h1>Login Form</h1>
<form method="post" action="/login">
<div>
<label>
username: <input type="text" name="username" />
</label>
</div>
<div>
<label>
password: <input type="password" name="password" />
</label>
</div>
{
search.message && <div>{search.message}</div>
}
<button type="submit">Login</button>
</form>
</>;
};
const App = () => {
return <html lang="en">
<head>
<meta charSet="utf-8"/>
<title>Ssrsx</title>
</head>
<body>
<Router>
<Routes>
<Route path="/"><LoginForm /></Route>
<Route path="/login"><Login /></Route>
<Route path="/logout"><Logout /></Route>
<Route path="/authorized"><Authorized /></Route>
</Routes>
</Router>
</body>
</html>;
};
const app = new Koa();
app.keys = ['your custom secret']; // session key
app.use(session(app)); // add session
app.use(bodyParser());
app.use(ssrsxKoa({
development: true,
clientRoot: 'test/client',
app: <App/>
}));
app.listen(3000);
Setting CSP (Content Security Policy) requires allowing ws://
to communicate with WebSocket for ssrsx HotReload (development: true
).
Also, because inline scripts are used internally, you need to allow 'unsafe-inline'
or 'nonce-${nonce}'
.
// index.tsx
import helmet from 'koa-helmet';
import crypto from 'crypto';
...
if(process.env.NODE_ENV === 'production'){
app.use(helmet());
app.use((ctx, next) => {
// set nonce to state
ctx.state.nonce = crypto.randomBytes(16).toString('base64');
//
return helmet.contentSecurityPolicy({ directives: {
defaultSrc: ['\'self\'','ws'], // add 'ws'
connectSrc: ['\'self\'','ws://*:*'], // add 'ws://*:*'
scriptSrc: [
'\'self\'',
`'nonce-${ctx.state.nonce}'`, // add 'nonce-??' or 'unsafe-inline'
],
}})(ctx, next) as Koa.Middleware;
});
}
// index.tsx
import helmet from 'helmet';
import crypto from 'crypto';
...
if(process.env.NODE_ENV === 'production'){
app.use(helmet());
app.use((req, res, next) => {
// set nonce to locals
(res as express.Response).locals.nonce = crypto.randomBytes(16).toString('base64');
//
helmet.contentSecurityPolicy({ directives: {
defaultSrc: ['\'self\'','ws'], // add 'ws'
connectSrc: ['\'self\'','ws://*:*'], // add 'ws://*:*'
scriptSrc: [
'\'self\'',
// add 'nonce-??' or 'unsafe-inline'
(req, res) => `'nonce-${(res as express.Response).locals?.nonce}'`,
],
}})(req, res, next);
});
}
...
User context can be set with the context
property of the ssrsx(Koa or Express)
function.
The context
function is called every time it is rendered.
You can use the useContext
function to get the user context value.
interface UserContext {
lang: string;
}
const App = () => {
const user = useContext<{UserContext}>();
return <div>Lang: {user.lang}</div>;
};
...
app.use(ssrsxKoa({
...
context: (server): UserContext => {
return {
lang: 'en',
};
},
}));
interface UserContext {
lang: string;
}
const App = () => {
const user = useContext<{UserContext}>();
return <div>Lang: {user.lang}</div>;
};
...
app.use(ssrsxExpress({
...
context: (server): UserContext => {
return {
lang: 'en',
};
},
}));
In Ssrsx, you cannot perform asynchronous processing using useState
or useEffect
etc. , but you can create asynchronous components.
The ssrsx context
function is called every time it is rendered, so when specifying something like a database instance, create the instance externally and specify it in the context
function.
import { Redis } from 'ioredis';
interface UserContext {
redis: Redis;
}
const isAuthorized = async (username: string, password: string) => {
const context = useContext<UserContext>();
const value = await context.redis.get(username);
return value === password;
};
// async component
const LoginCheck = async () => {
// post body data
const body = useBody<{username: string, password: string}>();
// check login
const isLogin = await isAuthorized(body.username, body.password);
if(!isLogin){
return <Navigate to="/"/>;
}
return <>
<h1>Login OK !</h1>
<div>
<div>Username: {body.username}</div>
<div>Password: {body.password}</div>
</div>
<Link to="/">Top</Link>
</>;
};
...
// create redis instance
const redis = new Redis();
app.use(ssrsxKoa({
...
context: (ctx, next): UserContext => {
return {
redis,
};
},
}));
CONTRIBUTING.mdをお読みください。ここには行動規範やプルリクエストの提出手順が詳細に記載されています。
- フォークする
- フィーチャーブランチを作成する:
git checkout -b my-new-feature
- 変更を追加:
git add .
- 変更をコミット:
git commit -am 'Add some feature'
- ブランチをプッシュ:
git push origin my-new-feature
- プルリクエストを提出 😎
Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.
- Fork it!
- Create your feature branch:
git checkout -b my-new-feature
- Add your changes:
git add .
- Commit your changes:
git commit -am 'Add some feature'
- Push to the branch:
git push origin my-new-feature
- Submit a pull request 😎
昨今の複雑化していく開発現場にシンプルな力を! 💪
Simplify the complex development landscape of today! 💪
Maskedeng Tom - Initial work - Maskedeng Tom
😄 プロジェクト貢献者リスト 😄
See also the list of contributors who participated in this project.
お役に立った場合はぜひ ⭐ を!
Please ⭐ this repository if this project helped you!
MIT License © Maskedeng Tom