From 1e3bfeb987e7527bcf29758675b00e379d7f3122 Mon Sep 17 00:00:00 2001 From: King Wang Date: Wed, 9 Jun 2021 22:38:55 +0800 Subject: [PATCH] first-api & file-upload --- README.md | 4 +- examples/file-upload/backend/.gitignore | 4 ++ .../file-upload}/backend/.vscode/launch.json | 0 .../backend/.vscode/settings.json | 0 .../file-upload}/backend/README.md | 0 .../file-upload}/backend/package.json | 0 .../file-upload/backend/src/api/ApiUpload.ts | 12 ++++ examples/file-upload/backend/src/index.ts | 47 +++++++++++++++ .../backend/src/shared/protocols/PtlUpload.ts | 8 +++ .../src/shared/protocols/serviceProto.ts | 59 +++++++++++++++++++ .../file-upload}/backend/tsconfig.json | 0 examples/file-upload/frontend/.gitignore | 3 + .../file-upload}/frontend/package.json | 0 .../file-upload/frontend/public/index.html | 24 ++++++++ examples/file-upload/frontend/src/index.ts | 46 +++++++++++++++ .../src/shared/protocols/PtlUpload.ts | 8 +++ .../src/shared/protocols/serviceProto.ts | 59 +++++++++++++++++++ .../file-upload}/frontend/tsconfig.json | 0 .../file-upload}/frontend/webpack.config.js | 0 examples/first-api/backend/.gitignore | 3 + .../first-api/backend/.vscode/launch.json | 30 ++++++++++ .../first-api/backend/.vscode/settings.json | 3 + examples/first-api/backend/README.md | 35 +++++++++++ examples/first-api/backend/package.json | 23 ++++++++ .../first-api}/backend/src/api/ApiHello.ts | 0 .../first-api}/backend/src/index.ts | 0 .../backend/src/shared/protocols/PtlHello.ts | 0 .../src/shared/protocols/serviceProto.ts | 0 examples/first-api/backend/tsconfig.json | 18 ++++++ examples/first-api/frontend/.gitignore | 3 + examples/first-api/frontend/package.json | 21 +++++++ .../first-api}/frontend/public/index.html | 0 .../first-api}/frontend/src/index.ts | 3 +- .../frontend/src/shared/protocols/PtlHello.ts | 0 .../src/shared/protocols/serviceProto.ts | 0 examples/first-api/frontend/tsconfig.json | 23 ++++++++ examples/first-api/frontend/webpack.config.js | 58 ++++++++++++++++++ 37 files changed, 491 insertions(+), 3 deletions(-) create mode 100644 examples/file-upload/backend/.gitignore rename {first-api => examples/file-upload}/backend/.vscode/launch.json (100%) rename {first-api => examples/file-upload}/backend/.vscode/settings.json (100%) rename {first-api => examples/file-upload}/backend/README.md (100%) rename {first-api => examples/file-upload}/backend/package.json (100%) create mode 100644 examples/file-upload/backend/src/api/ApiUpload.ts create mode 100644 examples/file-upload/backend/src/index.ts create mode 100644 examples/file-upload/backend/src/shared/protocols/PtlUpload.ts create mode 100644 examples/file-upload/backend/src/shared/protocols/serviceProto.ts rename {first-api => examples/file-upload}/backend/tsconfig.json (100%) create mode 100644 examples/file-upload/frontend/.gitignore rename {first-api => examples/file-upload}/frontend/package.json (100%) create mode 100644 examples/file-upload/frontend/public/index.html create mode 100644 examples/file-upload/frontend/src/index.ts create mode 100644 examples/file-upload/frontend/src/shared/protocols/PtlUpload.ts create mode 100644 examples/file-upload/frontend/src/shared/protocols/serviceProto.ts rename {first-api => examples/file-upload}/frontend/tsconfig.json (100%) rename {first-api => examples/file-upload}/frontend/webpack.config.js (100%) create mode 100644 examples/first-api/backend/.gitignore create mode 100644 examples/first-api/backend/.vscode/launch.json create mode 100644 examples/first-api/backend/.vscode/settings.json create mode 100644 examples/first-api/backend/README.md create mode 100644 examples/first-api/backend/package.json rename {first-api => examples/first-api}/backend/src/api/ApiHello.ts (100%) rename {first-api => examples/first-api}/backend/src/index.ts (100%) rename {first-api => examples/first-api}/backend/src/shared/protocols/PtlHello.ts (100%) rename {first-api => examples/first-api}/backend/src/shared/protocols/serviceProto.ts (100%) create mode 100644 examples/first-api/backend/tsconfig.json create mode 100644 examples/first-api/frontend/.gitignore create mode 100644 examples/first-api/frontend/package.json rename {first-api => examples/first-api}/frontend/public/index.html (100%) rename {first-api => examples/first-api}/frontend/src/index.ts (93%) rename {first-api => examples/first-api}/frontend/src/shared/protocols/PtlHello.ts (100%) rename {first-api => examples/first-api}/frontend/src/shared/protocols/serviceProto.ts (100%) create mode 100644 examples/first-api/frontend/tsconfig.json create mode 100644 examples/first-api/frontend/webpack.config.js diff --git a/README.md b/README.md index 9e1ae27..2de9070 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ Examples for [TSRPC](https://github.com/k8w/tsrpc). Start local backend server: ``` -cd /backend +cd /backend npm install npm run dev ``` Start local frontend server: ``` -cd /frontend +cd /frontend npm install npm run dev ``` \ No newline at end of file diff --git a/examples/file-upload/backend/.gitignore b/examples/file-upload/backend/.gitignore new file mode 100644 index 0000000..f05826c --- /dev/null +++ b/examples/file-upload/backend/.gitignore @@ -0,0 +1,4 @@ +node_modules +dist +.DS_STORE +uploads \ No newline at end of file diff --git a/first-api/backend/.vscode/launch.json b/examples/file-upload/backend/.vscode/launch.json similarity index 100% rename from first-api/backend/.vscode/launch.json rename to examples/file-upload/backend/.vscode/launch.json diff --git a/first-api/backend/.vscode/settings.json b/examples/file-upload/backend/.vscode/settings.json similarity index 100% rename from first-api/backend/.vscode/settings.json rename to examples/file-upload/backend/.vscode/settings.json diff --git a/first-api/backend/README.md b/examples/file-upload/backend/README.md similarity index 100% rename from first-api/backend/README.md rename to examples/file-upload/backend/README.md diff --git a/first-api/backend/package.json b/examples/file-upload/backend/package.json similarity index 100% rename from first-api/backend/package.json rename to examples/file-upload/backend/package.json diff --git a/examples/file-upload/backend/src/api/ApiUpload.ts b/examples/file-upload/backend/src/api/ApiUpload.ts new file mode 100644 index 0000000..6308ced --- /dev/null +++ b/examples/file-upload/backend/src/api/ApiUpload.ts @@ -0,0 +1,12 @@ +import fs from "fs/promises"; +import { ApiCall } from "tsrpc"; +import { ReqUpload, ResUpload } from "../shared/protocols/PtlUpload"; + +export async function ApiUpload(call: ApiCall) { + // Write to file, or push to remote OSS... + await fs.writeFile('uploads/' + call.req.fileName, call.req.fileData); + + call.succ({ + url: 'http://127.0.0.1:3000/uploads/' + call.req.fileName + }); +} \ No newline at end of file diff --git a/examples/file-upload/backend/src/index.ts b/examples/file-upload/backend/src/index.ts new file mode 100644 index 0000000..90af195 --- /dev/null +++ b/examples/file-upload/backend/src/index.ts @@ -0,0 +1,47 @@ +import fs from "fs/promises"; +import * as path from "path"; +import { HttpConnection, HttpServer } from "tsrpc"; +import { serviceProto } from "./shared/protocols/serviceProto"; + +// Create the Server +const server = new HttpServer(serviceProto, { + port: 3000, + cors: '*' +}); + +// Flow: Serve static files +server.flows.preRecvBufferFlow.push(async v => { + let conn = v.conn as HttpConnection; + if (conn.httpReq.method === 'GET') { + if (conn.httpReq.url?.startsWith('/uploads/')) { + let file = await fs.readFile(decodeURIComponent(conn.httpReq.url.replace(/^\//, ''))).catch(e => { }); + if (file) { + server.logger.log('GET', conn.httpReq.url); + conn.httpRes.end(file); + return undefined; + } + } + + conn.httpRes.end('404 Not Found') + return undefined; + } + + return v; +}) + +// Entry function +async function main() { + // Auto implement APIs + await server.autoImplementApi(path.resolve(__dirname, 'api')); + + // Ensure "uploads" dir is exists + await fs.mkdir('uploads').catch(e => { }); + + await server.start(); +}; + +main().catch(e => { + // Exit if any error during the startup + server.logger.error(e); + process.exit(-1); +}); \ No newline at end of file diff --git a/examples/file-upload/backend/src/shared/protocols/PtlUpload.ts b/examples/file-upload/backend/src/shared/protocols/PtlUpload.ts new file mode 100644 index 0000000..b5dbb54 --- /dev/null +++ b/examples/file-upload/backend/src/shared/protocols/PtlUpload.ts @@ -0,0 +1,8 @@ +export interface ReqUpload { + fileName: string, + fileData: Uint8Array +} + +export interface ResUpload { + url: string; +} \ No newline at end of file diff --git a/examples/file-upload/backend/src/shared/protocols/serviceProto.ts b/examples/file-upload/backend/src/shared/protocols/serviceProto.ts new file mode 100644 index 0000000..ed596b5 --- /dev/null +++ b/examples/file-upload/backend/src/shared/protocols/serviceProto.ts @@ -0,0 +1,59 @@ +import { ServiceProto } from 'tsrpc-proto'; +import { ReqUpload, ResUpload } from './PtlUpload'; + +export interface ServiceType { + api: { + "Upload": { + req: ReqUpload, + res: ResUpload + } + }, + msg: { + + } +} + +export const serviceProto: ServiceProto = { + "version": 2, + "services": [ + { + "id": 0, + "name": "Upload", + "type": "api" + } + ], + "types": { + "PtlUpload/ReqUpload": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "fileName", + "type": { + "type": "String" + } + }, + { + "id": 1, + "name": "fileData", + "type": { + "type": "Buffer", + "arrayType": "Uint8Array" + } + } + ] + }, + "PtlUpload/ResUpload": { + "type": "Interface", + "properties": [ + { + "id": 2, + "name": "url", + "type": { + "type": "String" + } + } + ] + } + } +}; \ No newline at end of file diff --git a/first-api/backend/tsconfig.json b/examples/file-upload/backend/tsconfig.json similarity index 100% rename from first-api/backend/tsconfig.json rename to examples/file-upload/backend/tsconfig.json diff --git a/examples/file-upload/frontend/.gitignore b/examples/file-upload/frontend/.gitignore new file mode 100644 index 0000000..d84f0da --- /dev/null +++ b/examples/file-upload/frontend/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +.DS_STORE \ No newline at end of file diff --git a/first-api/frontend/package.json b/examples/file-upload/frontend/package.json similarity index 100% rename from first-api/frontend/package.json rename to examples/file-upload/frontend/package.json diff --git a/examples/file-upload/frontend/public/index.html b/examples/file-upload/frontend/public/index.html new file mode 100644 index 0000000..8257156 --- /dev/null +++ b/examples/file-upload/frontend/public/index.html @@ -0,0 +1,24 @@ + + + + + + + + TSRPC Example + + + +

File Upload

+ +
+

+

+
+ +
    + +
+ + + \ No newline at end of file diff --git a/examples/file-upload/frontend/src/index.ts b/examples/file-upload/frontend/src/index.ts new file mode 100644 index 0000000..7ed60de --- /dev/null +++ b/examples/file-upload/frontend/src/index.ts @@ -0,0 +1,46 @@ +import { HttpClient } from 'tsrpc-browser'; +import { serviceProto } from './shared/protocols/serviceProto'; + +let client = new HttpClient(serviceProto, { + server: 'http://127.0.0.1:3000', + logger: console +}); + +async function upload() { + // Load File + let file = document.querySelector('input[type=file]') as HTMLInputElement; + if (!file.files || !file.files[0]) { + alert('Please select a file') + return; + } + let fileData = await loadFile(file.files[0]); + + // Upload + let ret = await client.callApi('Upload', { + fileData: fileData, + fileName: file.files[0].name + }); + + // Error + if (!ret.isSucc) { + alert(ret.err.message); + return; + } + + // Succ + document.querySelector('ol')!.innerHTML += `
  • ${ret.res.url}
  • \n`; + file.value = ''; + alert('Upload successfully!') +} + +function loadFile(file: File): Promise { + return new Promise(rs => { + let reader = new FileReader(); + reader.onload = e => { + rs(new Uint8Array(e.target!.result as ArrayBuffer)) + } + reader.readAsArrayBuffer(file); + }) +} + +document.querySelector('button')!.onclick = upload; \ No newline at end of file diff --git a/examples/file-upload/frontend/src/shared/protocols/PtlUpload.ts b/examples/file-upload/frontend/src/shared/protocols/PtlUpload.ts new file mode 100644 index 0000000..b5dbb54 --- /dev/null +++ b/examples/file-upload/frontend/src/shared/protocols/PtlUpload.ts @@ -0,0 +1,8 @@ +export interface ReqUpload { + fileName: string, + fileData: Uint8Array +} + +export interface ResUpload { + url: string; +} \ No newline at end of file diff --git a/examples/file-upload/frontend/src/shared/protocols/serviceProto.ts b/examples/file-upload/frontend/src/shared/protocols/serviceProto.ts new file mode 100644 index 0000000..ed596b5 --- /dev/null +++ b/examples/file-upload/frontend/src/shared/protocols/serviceProto.ts @@ -0,0 +1,59 @@ +import { ServiceProto } from 'tsrpc-proto'; +import { ReqUpload, ResUpload } from './PtlUpload'; + +export interface ServiceType { + api: { + "Upload": { + req: ReqUpload, + res: ResUpload + } + }, + msg: { + + } +} + +export const serviceProto: ServiceProto = { + "version": 2, + "services": [ + { + "id": 0, + "name": "Upload", + "type": "api" + } + ], + "types": { + "PtlUpload/ReqUpload": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "fileName", + "type": { + "type": "String" + } + }, + { + "id": 1, + "name": "fileData", + "type": { + "type": "Buffer", + "arrayType": "Uint8Array" + } + } + ] + }, + "PtlUpload/ResUpload": { + "type": "Interface", + "properties": [ + { + "id": 2, + "name": "url", + "type": { + "type": "String" + } + } + ] + } + } +}; \ No newline at end of file diff --git a/first-api/frontend/tsconfig.json b/examples/file-upload/frontend/tsconfig.json similarity index 100% rename from first-api/frontend/tsconfig.json rename to examples/file-upload/frontend/tsconfig.json diff --git a/first-api/frontend/webpack.config.js b/examples/file-upload/frontend/webpack.config.js similarity index 100% rename from first-api/frontend/webpack.config.js rename to examples/file-upload/frontend/webpack.config.js diff --git a/examples/first-api/backend/.gitignore b/examples/first-api/backend/.gitignore new file mode 100644 index 0000000..d84f0da --- /dev/null +++ b/examples/first-api/backend/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +.DS_STORE \ No newline at end of file diff --git a/examples/first-api/backend/.vscode/launch.json b/examples/first-api/backend/.vscode/launch.json new file mode 100644 index 0000000..9ba4218 --- /dev/null +++ b/examples/first-api/backend/.vscode/launch.json @@ -0,0 +1,30 @@ +{ + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "mocha current file", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "${file}" + ], + "internalConsoleOptions": "openOnSessionStart", + "cwd": "${workspaceFolder}" + }, + { + "type": "node", + "request": "launch", + "name": "ts-node current file", + "protocol": "inspector", + "args": [ + "${relativeFile}" + ], + "cwd": "${workspaceRoot}", + "runtimeArgs": [ + "-r", + "ts-node/register" + ], + "internalConsoleOptions": "openOnSessionStart" + } + ] +} \ No newline at end of file diff --git a/examples/first-api/backend/.vscode/settings.json b/examples/first-api/backend/.vscode/settings.json new file mode 100644 index 0000000..00ad71f --- /dev/null +++ b/examples/first-api/backend/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules\\typescript\\lib" +} \ No newline at end of file diff --git a/examples/first-api/backend/README.md b/examples/first-api/backend/README.md new file mode 100644 index 0000000..7492c22 --- /dev/null +++ b/examples/first-api/backend/README.md @@ -0,0 +1,35 @@ +# TSRPC Server + +## Run +### Local Dev Server +``` +npm run dev +``` + + + +### Build +``` +npm run build +``` + +--- + +## Files +### Generate ServiceProto +``` +npm run proto +``` + +### Generate API templates +``` +npm run api +``` + +### Sync shared code to client + +``` +npm run sync +``` + +> If you chose symlink when using `create-tsrpc-app`, it would re-create the symlink instead of copy files. diff --git a/examples/first-api/backend/package.json b/examples/first-api/backend/package.json new file mode 100644 index 0000000..7119eae --- /dev/null +++ b/examples/first-api/backend/package.json @@ -0,0 +1,23 @@ +{ + "name": "first-api-backend", + "version": "0.1.0", + "main": "index.js", + "private": true, + "scripts": { + "proto": "tsrpc proto -i src/shared/protocols -o src/shared/protocols/serviceProto.ts", + "sync": "tsrpc sync --from src/shared --to ../frontend/src/shared", + "api": "tsrpc api -i src/shared/protocols/serviceProto.ts -o src/api", + "dev": "onchange \"src/**/*.ts\" -i -k -- ts-node \"src/index.ts\"", + "build": "tsrpc build" + }, + "devDependencies": { + "@types/node": "^15.12.2", + "onchange": "^7.1.0", + "ts-node": "^9.1.1", + "tsrpc-cli": "^2.0.1-dev.11", + "typescript": "^4.3.2" + }, + "dependencies": { + "tsrpc": "^3.0.0-dev.17" + } +} diff --git a/first-api/backend/src/api/ApiHello.ts b/examples/first-api/backend/src/api/ApiHello.ts similarity index 100% rename from first-api/backend/src/api/ApiHello.ts rename to examples/first-api/backend/src/api/ApiHello.ts diff --git a/first-api/backend/src/index.ts b/examples/first-api/backend/src/index.ts similarity index 100% rename from first-api/backend/src/index.ts rename to examples/first-api/backend/src/index.ts diff --git a/first-api/backend/src/shared/protocols/PtlHello.ts b/examples/first-api/backend/src/shared/protocols/PtlHello.ts similarity index 100% rename from first-api/backend/src/shared/protocols/PtlHello.ts rename to examples/first-api/backend/src/shared/protocols/PtlHello.ts diff --git a/first-api/backend/src/shared/protocols/serviceProto.ts b/examples/first-api/backend/src/shared/protocols/serviceProto.ts similarity index 100% rename from first-api/backend/src/shared/protocols/serviceProto.ts rename to examples/first-api/backend/src/shared/protocols/serviceProto.ts diff --git a/examples/first-api/backend/tsconfig.json b/examples/first-api/backend/tsconfig.json new file mode 100644 index 0000000..d18498f --- /dev/null +++ b/examples/first-api/backend/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "lib": [ + "es2018" + ], + "module": "commonjs", + "target": "es2018", + "outDir": "dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node" + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/examples/first-api/frontend/.gitignore b/examples/first-api/frontend/.gitignore new file mode 100644 index 0000000..d84f0da --- /dev/null +++ b/examples/first-api/frontend/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +.DS_STORE \ No newline at end of file diff --git a/examples/first-api/frontend/package.json b/examples/first-api/frontend/package.json new file mode 100644 index 0000000..2319a82 --- /dev/null +++ b/examples/first-api/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "first-api-frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "webpack serve --mode=development --open", + "build": "webpack --mode=production" + }, + "devDependencies": { + "copy-webpack-plugin": "^9.0.0", + "html-webpack-plugin": "^5.3.1", + "ts-loader": "^9.2.3", + "typescript": "^4.3.2", + "webpack": "^5.38.1", + "webpack-cli": "^4.7.2", + "webpack-dev-server": "^3.11.2" + }, + "dependencies": { + "tsrpc-browser": "^3.0.0-dev.15" + } +} diff --git a/first-api/frontend/public/index.html b/examples/first-api/frontend/public/index.html similarity index 100% rename from first-api/frontend/public/index.html rename to examples/first-api/frontend/public/index.html diff --git a/first-api/frontend/src/index.ts b/examples/first-api/frontend/src/index.ts similarity index 93% rename from first-api/frontend/src/index.ts rename to examples/first-api/frontend/src/index.ts index b047c69..106f145 100644 --- a/first-api/frontend/src/index.ts +++ b/examples/first-api/frontend/src/index.ts @@ -1,13 +1,14 @@ import { HttpClient } from 'tsrpc-browser'; import { serviceProto } from './shared/protocols/serviceProto'; -// 创建一个 TSRPC Client +// Create the Client let client = new HttpClient(serviceProto, { server: 'http://127.0.0.1:3000', logger: console }); async function test() { + // callApi let ret = await client.callApi('Hello', { name: 'World' }); diff --git a/first-api/frontend/src/shared/protocols/PtlHello.ts b/examples/first-api/frontend/src/shared/protocols/PtlHello.ts similarity index 100% rename from first-api/frontend/src/shared/protocols/PtlHello.ts rename to examples/first-api/frontend/src/shared/protocols/PtlHello.ts diff --git a/first-api/frontend/src/shared/protocols/serviceProto.ts b/examples/first-api/frontend/src/shared/protocols/serviceProto.ts similarity index 100% rename from first-api/frontend/src/shared/protocols/serviceProto.ts rename to examples/first-api/frontend/src/shared/protocols/serviceProto.ts diff --git a/examples/first-api/frontend/tsconfig.json b/examples/first-api/frontend/tsconfig.json new file mode 100644 index 0000000..7517bf5 --- /dev/null +++ b/examples/first-api/frontend/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "es2018" + ], + "module": "esnext", + "target": "es2018", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "outDir": "dist", + "skipLibCheck": true, + "strict": true, + "jsx": "react-jsx", + "sourceMap": true, + "isolatedModules": true + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/examples/first-api/frontend/webpack.config.js b/examples/first-api/frontend/webpack.config.js new file mode 100644 index 0000000..062d1a4 --- /dev/null +++ b/examples/first-api/frontend/webpack.config.js @@ -0,0 +1,58 @@ +const webpack = require('webpack'); +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); + +const isProduction = process.argv.indexOf('--mode=production') > -1; + +module.exports = { + entry: './src/index.ts', + output: { + filename: 'bundle.[contenthash].js', + path: path.resolve(__dirname, 'dist'), + clean: true + }, + resolve: { + extensions: ['.ts', '.tsx', '.js'] + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: [{ + loader: 'ts-loader', + options: { + compilerOptions: isProduction ? { + "lib": [ + "dom", + "es2015.promise" + ], + "target": "es5", + } : undefined + } + }], + exclude: /node_modules/ + }, + ] + }, + plugins: [ + // Copy "public" to "dist" + new CopyWebpackPlugin({ + patterns: [{ + from: 'public', + to: '.', + toType: 'dir', + globOptions: { + gitignore: true, + ignore: [path.resolve(__dirname, 'public/index.html').replace(/\\/g, '/')] + }, + noErrorOnMissing: true + }] + }), + // Auto add