Initial commit.
This commit is contained in:
commit
bf8a79e467
13 changed files with 5884 additions and 0 deletions
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
dist
|
||||
.fusebox
|
||||
_site
|
||||
.alm
|
||||
.history
|
||||
.git
|
||||
build
|
||||
.build
|
||||
.idea
|
||||
.jshintrc
|
||||
.nyc_output
|
||||
.sass-cache
|
||||
.vscode
|
||||
coverage
|
||||
jsconfig.json
|
||||
Gemfile.lock
|
||||
node_modules
|
||||
.DS_Store
|
||||
*.map
|
||||
*.log
|
||||
*.swp
|
||||
*~
|
||||
test/data/result.json
|
||||
|
||||
package-lock.json
|
||||
*.orig
|
||||
|
4
README.md
Normal file
4
README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
# lemmy-isomorphic-ui
|
||||
|
||||
A trial run at making an isomorphic UI for lemmy, based off of MrFoxPro's [inferno-isomorphic-template](https://github.com/MrFoxPro/inferno-isomorphic-template).
|
||||
|
76
fuse.ts
Normal file
76
fuse.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { CSSPlugin, FuseBox, FuseBoxOptions, Sparky } from "fuse-box";
|
||||
import path = require("path");
|
||||
import TsTransformClasscat from "ts-transform-classcat";
|
||||
import TsTransformInferno from "ts-transform-inferno";
|
||||
/**
|
||||
* Some of FuseBoxOptions overrides by ts config (module, target, etc)
|
||||
* https://fuse-box.org/page/working-with-targets
|
||||
*/
|
||||
let fuse: FuseBox;
|
||||
const fuseOptions: FuseBoxOptions = {
|
||||
homeDir: "./src",
|
||||
output: "dist/$name.js",
|
||||
sourceMaps: { inline: false, vendor: false },
|
||||
/**
|
||||
* Custom TypeScript Transformers (compile Inferno tsx to ts)
|
||||
*/
|
||||
transformers: {
|
||||
before: [TsTransformClasscat(), TsTransformInferno()]
|
||||
}
|
||||
};
|
||||
const fuseClientOptions: FuseBoxOptions = {
|
||||
...fuseOptions,
|
||||
plugins: [
|
||||
/**
|
||||
* https://fuse-box.org/page/css-resource-plugin
|
||||
* Compile Sass {SassPlugin()}
|
||||
* Make .css files modules-like (allow import them like modules) {CSSModules}
|
||||
* Make .css files modules like and allow import it from node_modules too {CSSResourcePlugin}
|
||||
* Use them all and bundle with {CSSPlugin}
|
||||
* */
|
||||
CSSPlugin()
|
||||
]
|
||||
};
|
||||
const fuseServerOptions: FuseBoxOptions = {
|
||||
...fuseOptions
|
||||
};
|
||||
Sparky.task("clean", () => {
|
||||
/**Clean distribute (dist) folder */
|
||||
Sparky.src("dist")
|
||||
.clean("dist")
|
||||
.exec();
|
||||
});
|
||||
Sparky.task("config", () => {
|
||||
fuse = FuseBox.init(fuseOptions);
|
||||
fuse.dev();
|
||||
});
|
||||
Sparky.task("test", ["&clean", "&config"], () => {
|
||||
fuse.bundle("client/bundle").test("[**/**.test.tsx]", null);
|
||||
});
|
||||
Sparky.task("client", () => {
|
||||
fuse.opts = fuseClientOptions;
|
||||
fuse
|
||||
.bundle("client/bundle")
|
||||
.target("browser@esnext")
|
||||
.watch("client/**")
|
||||
.hmr()
|
||||
.instructions("> client/index.tsx");
|
||||
});
|
||||
Sparky.task("server", () => {
|
||||
/**Workaround. Should be fixed */
|
||||
fuse.opts = fuseServerOptions;
|
||||
fuse
|
||||
.bundle("server/bundle")
|
||||
.watch("**")
|
||||
.target("server@esnext")
|
||||
.instructions("> [server/index.tsx]")
|
||||
.completed(proc => {
|
||||
proc.require({
|
||||
// tslint:disable-next-line:no-shadowed-variable
|
||||
close: ({ FuseBox }) => FuseBox.import(FuseBox.mainFile).shutdown()
|
||||
});
|
||||
});
|
||||
});
|
||||
Sparky.task("dev", ["&clean", "&config", "&client", "&server"], () => {
|
||||
fuse.run();
|
||||
});
|
39
package.json
Normal file
39
package.json
Normal file
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "lemmy-isomorphic-ui",
|
||||
"scripts": {
|
||||
"dev": "set NODE_ENV=development && node -r ts-node/register --inspect fuse.ts dev",
|
||||
"test": "node -r ts-node/register --inspect fuse.ts test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/serialize-javascript": "^4.0.0",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"express": "~4.17.1",
|
||||
"inferno": "^7.4.3",
|
||||
"inferno-create-element": "^7.4.3",
|
||||
"inferno-hydrate": "^7.4.3",
|
||||
"inferno-router": "^7.4.3",
|
||||
"inferno-server": "^7.4.3",
|
||||
"serialize-javascript": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cookie-parser": "^1.4.1",
|
||||
"@types/enzyme": "^3.1.10",
|
||||
"@types/express": "^4.11.1",
|
||||
"@types/jest": "^26.0.10",
|
||||
"@types/node": "^14.6.0",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-inferno": "^1.3.0",
|
||||
"fuse-box": "3.7.1",
|
||||
"fuse-test-runner": "^1.0.16",
|
||||
"inferno-devtools": "^7.4.3",
|
||||
"inferno-test-utils": "^7.4.3",
|
||||
"jest": "^26.4.2",
|
||||
"jsdom": "16.4.0",
|
||||
"jsdom-global": "3.0.2",
|
||||
"ts-node": "^9.0.0",
|
||||
"ts-transform-classcat": "^1.0.0",
|
||||
"ts-transform-inferno": "^4.0.3",
|
||||
"tslint-react-recommended": "^1.0.15",
|
||||
"typescript": "^4.0.2"
|
||||
}
|
||||
}
|
10
src/client/components/About/About.css
Normal file
10
src/client/components/About/About.css
Normal file
|
@ -0,0 +1,10 @@
|
|||
.text {
|
||||
color: brown;
|
||||
font-size: 25pt;
|
||||
}
|
||||
.count {
|
||||
color: blue;
|
||||
}
|
||||
.button {
|
||||
color: red;
|
||||
}
|
19
src/client/components/About/About.test.tsx
Normal file
19
src/client/components/About/About.test.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import "jsdom-global/register";
|
||||
import { configure, mount, render, shallow } from "enzyme";
|
||||
import InfernoEnzymeAdapter = require("enzyme-adapter-inferno");
|
||||
import { should } from "fuse-test-runner";
|
||||
import { Component } from "inferno";
|
||||
import { renderToSnapshot } from "inferno-test-utils";
|
||||
import About from "./About";
|
||||
configure({ adapter: new InfernoEnzymeAdapter() });
|
||||
|
||||
export class AboutTest {
|
||||
public "Should be okay"() {
|
||||
const wrapper = mount(<About />);
|
||||
wrapper.find(".button").simulate("click");
|
||||
const countText = wrapper.find(".count").text();
|
||||
should(countText)
|
||||
.beString()
|
||||
.equal("1");
|
||||
}
|
||||
}
|
32
src/client/components/About/About.tsx
Normal file
32
src/client/components/About/About.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { Component } from "inferno";
|
||||
import "./About.css";
|
||||
interface IState {
|
||||
clickCount: number;
|
||||
}
|
||||
interface IProps {}
|
||||
export default class About extends Component<IProps, IState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
clickCount: 0
|
||||
};
|
||||
this.increment = this.increment.bind(this);
|
||||
}
|
||||
protected increment() {
|
||||
this.setState({
|
||||
clickCount: this.state.clickCount + 1
|
||||
});
|
||||
}
|
||||
public render() {
|
||||
return (
|
||||
<div>
|
||||
Simple Inferno SSR template
|
||||
<p className="text">Hello, world!</p>
|
||||
<button onClick={this.increment} className="button">
|
||||
Increment
|
||||
</button>
|
||||
<p className="count">{this.state.clickCount}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
36
src/client/components/App/App.tsx
Normal file
36
src/client/components/App/App.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { Component, render } from "inferno";
|
||||
import { Link, Route, StaticRouter, Switch } from "inferno-router";
|
||||
import About from "../About/About";
|
||||
import Home from "../Home/Home";
|
||||
interface IState {}
|
||||
interface IProps {name: string}
|
||||
export default class App extends Component<IProps, IState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
public render() {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<h3>{this.props.name}</h3>
|
||||
<div>
|
||||
<Link to="/Home">
|
||||
<p>Home</p>
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Link to="/About" className="link">
|
||||
<p>About</p>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Switch>
|
||||
<Route exact path="/Home" component={Home} />
|
||||
<Route exact path="/About" component={About} />
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
22
src/client/components/Home/Home.tsx
Normal file
22
src/client/components/Home/Home.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { Component } from "inferno";
|
||||
interface IState {}
|
||||
interface IProps {}
|
||||
export default class Home extends Component<IProps, IState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
protected click() {
|
||||
/**
|
||||
* Try to debug next line
|
||||
*/
|
||||
console.log("hi");
|
||||
}
|
||||
public render() {
|
||||
return (
|
||||
<div>
|
||||
Home page
|
||||
<button onClick={this.click}>Click me</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
21
src/client/index.tsx
Normal file
21
src/client/index.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { Component } from "inferno";
|
||||
import { hydrate } from 'inferno-hydrate';
|
||||
import { BrowserRouter } from "inferno-router";
|
||||
import App from "./components/App/App";
|
||||
import { initDevTools } from "inferno-devtools";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
isoData: {
|
||||
name: string,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const wrapper = (
|
||||
<BrowserRouter>
|
||||
<App name={window.isoData.name}/>
|
||||
</BrowserRouter>
|
||||
);
|
||||
initDevTools();
|
||||
hydrate(wrapper, document.getElementById("root"));
|
56
src/server/index.tsx
Normal file
56
src/server/index.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import cookieParser = require("cookie-parser");
|
||||
import * as serialize from "serialize-javascript";
|
||||
import * as express from "express";
|
||||
import { StaticRouter } from "inferno-router";
|
||||
import { renderToString } from "inferno-server";
|
||||
import path = require("path");
|
||||
import App from "../client/components/App/App";
|
||||
const server = express();
|
||||
const port = 1234;
|
||||
|
||||
server.use(express.json());
|
||||
server.use(express.urlencoded({ extended: false }));
|
||||
server.use("/static", express.static(path.resolve("./dist/client")));
|
||||
|
||||
server.use(cookieParser());
|
||||
|
||||
server.get("/*", (req, res) => {
|
||||
const context = {} as any;
|
||||
const isoData = {
|
||||
name: "fishing sux",
|
||||
}
|
||||
|
||||
const wrapper = (
|
||||
<StaticRouter location={req.url} context={context}>
|
||||
<App name={isoData.name} />
|
||||
</StaticRouter>
|
||||
);
|
||||
if (context.url) {
|
||||
return res.redirect(context.url);
|
||||
}
|
||||
|
||||
res.send(`
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My Universal App</title>
|
||||
<script>window.isoData = ${serialize(isoData)}</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id='root'>${renderToString(wrapper)}</div>
|
||||
<script src='./static/bundle.js'></script>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
});
|
||||
let Server = server.listen(port, () => {
|
||||
console.log(`http://localhost:${port}`);
|
||||
});
|
||||
|
||||
/**
|
||||
* Used to restart server by fuseBox
|
||||
*/
|
||||
export async function shutdown() {
|
||||
Server.close();
|
||||
Server = undefined;
|
||||
}
|
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "esnext",
|
||||
"sourceMap": true,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"exclude": ["node_modules", "fuse.ts"]
|
||||
}
|
Loading…
Reference in a new issue