preact: convert to typescript

This commit is contained in:
Shuanglei Tao
2019-05-26 10:31:55 +08:00
parent 2b3075698d
commit 3f52934418
21 changed files with 4517 additions and 3263 deletions

14
preact/gulpfile.js Normal file
View File

@@ -0,0 +1,14 @@
const { src, dest, task } = require("gulp");
const clean = require('gulp-clean');
const inlinesource = require('gulp-inline-source');
task('clean', () => {
return src('build', {read: false, allowEmpty: true})
.pipe(clean());
});
task('default', () => {
return src('build/index.html')
.pipe(inlinesource())
.pipe(dest('../src/'));
});

View File

@@ -10,16 +10,39 @@
"author": "Shuanglei Tao <tsl0922@gmail.com>",
"license": "MIT",
"scripts": {
"start": "preact watch",
"build": "preact build --no-service-worker --no-prerender"
"prestart": "gulp clean",
"start": "webpack-dev-server",
"build": "NODE_ENV=production webpack && gulp",
"lint": "tslint -c tslint.json 'src/**/*.ts'"
},
"devDependencies": {
"preact-cli": "^2.2.1"
"copy-webpack-plugin": "^5.0.3",
"css-loader": "^1.0.1",
"gulp": "^4.0.2",
"gulp-clean": "^0.4.0",
"gulp-inline-source": "^4.0.0",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.6.0",
"node-sass": "^4.12.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"terser-webpack-plugin": "^1.3.0",
"ts-loader": "^6.0.1",
"tslint": "^5.16.0",
"tslint-consistent-codestyle": "^1.15.1",
"tslint-loader": "^3.5.4",
"typescript": "^3.4.5",
"webpack": "^4.32.2",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.4.1",
"webpack-merge": "^4.2.1"
},
"dependencies": {
"decko": "^1.2.0",
"fast-text-encoding": "^1.0.0",
"preact": "^8.4.2",
"xterm": "^3.12.2"
"preact-compat": "^3.18.5",
"preact-router": "^2.6.1",
"xterm": "^3.13.2"
}
}

View File

@@ -1,19 +0,0 @@
/**
* Function that mutates original webpack config.
* Supports asynchronous changes when promise is returned.
*
* @param {object} config - original webpack config.
* @param {object} env - options passed to CLI.
* @param {WebpackConfigHelpers} helpers - object with useful helpers when working with config.
**/
export default function (config, env, helpers) {
const { devServer, plugins } = config;
if (devServer) {
devServer.proxy = {
'/ws': {
target: 'ws://127.0.0.1:7681',
ws: true
},
};
}
}

View File

@@ -1,5 +0,0 @@
{
"presets": [
["preact-cli/babel", { "modules": "commonjs" }]
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -1,15 +0,0 @@
import { h, Component } from 'preact';
import Terminal from './terminal';
if (module.hot) {
require('preact/debug');
}
export default class App extends Component {
render() {
return (
<Terminal />
);
}
}

View File

@@ -0,0 +1,45 @@
import { h, Component } from 'preact';
import { ITerminalOptions, ITheme } from 'xterm';
import Terminal from './terminal';
if ((module as any).hot) {
require('preact/debug');
}
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
const wsPath = window.location.pathname.endsWith('/') ? 'ws' : '/ws';
const url = [protocol, window.location.host, window.location.pathname, wsPath, window.location.search].join('');
const termOptions = {
fontSize: 13,
fontFamily: '"Menlo for Powerline", Menlo, Consolas, "Liberation Mono", Courier, monospace',
theme: {
foreground: '#d2d2d2',
background: '#2b2b2b',
cursor: '#adadad',
black: '#000000',
red: '#d81e00',
green: '#5ea702',
yellow: '#cfae00',
blue: '#427ab3',
magenta: '#89658e',
cyan: '#00a7aa',
white: '#dbded8',
brightBlack: '#686a66',
brightRed: '#f54235',
brightGreen: '#99e343',
brightYellow: '#fdeb61',
brightBlue: '#84b0d8',
brightMagenta: '#bc94b7',
brightCyan: '#37e6e8',
brightWhite: '#f1f1f0'
} as ITheme
} as ITerminalOptions;
export default class App extends Component {
public render() {
return (
<Terminal id='terminal-container' url={url} options={termOptions} />
);
}
}

View File

@@ -1,177 +0,0 @@
import { h, Component } from 'preact';
import { bind } from 'decko';
import { Terminal as TERMINAL } from 'xterm';
import style from 'xterm/dist/xterm.css';
require('fast-text-encoding');
export default class Terminal extends Component {
componentDidMount() {
TERMINAL.applyAddon(require('xterm/lib/addons/fit/fit'));
TERMINAL.applyAddon(require('./overlay'));
this.url = (window.location.protocol === 'https:' ? 'wss://' : 'ws://')
+ window.location.host + window.location.pathname + 'ws';
this.autoReconnect = 0;
this.textDecoder = new TextDecoder();
this.textEncoder = new TextEncoder();
this.connectWebsocket();
window.addEventListener('resize', this.onWindowSize);
}
componentWillUnmount() {
window.removeEventListener('resize', this.onWindowSize);
}
@bind
onWindowSize() {
const { terminal } = this;
clearTimeout(this.resizedFinished);
this.resizedFinished = setTimeout(function () {
if (terminal) {
terminal.fit();
}
}, 250);
}
@bind
connectWebsocket() {
this.socket = new WebSocket(this.url, ['tty']);
const { socket } = this;
socket.binaryType = 'arraybuffer';
socket.onopen = this.onSocketOpen;
socket.onmessage = this.onSocketData;
socket.onclose = this.onSocketClose;
}
@bind
onSocketOpen(event) {
console.log('Websocket connection opened');
const { socket, textEncoder } = this;
socket.send(textEncoder.encode(JSON.stringify({AuthToken: ''})));
if (this.terminal) {
this.terminal.destroy();
}
this.terminal = new TERMINAL({
fontSize: 13,
fontFamily: '"Menlo for Powerline", Menlo, Consolas, "Liberation Mono", Courier, monospace',
theme: {
foreground: '#d2d2d2',
background: '#2b2b2b',
cursor: '#adadad',
black: '#000000',
red: '#d81e00',
green: '#5ea702',
yellow: '#cfae00',
blue: '#427ab3',
magenta: '#89658e',
cyan: '#00a7aa',
white: '#dbded8',
brightBlack: '#686a66',
brightRed: '#f54235',
brightGreen: '#99e343',
brightYellow: '#fdeb61',
brightBlue: '#84b0d8',
brightMagenta: '#bc94b7',
brightCyan: '#37e6e8',
brightWhite: '#f1f1f0'
},
});
const { terminal } = this;
terminal.on('title', this.onTerminalTitle);
terminal.on('data', this.onTerminalData);
terminal.on('resize', this.onTerminalResize);
terminal.open(this.container, true);
terminal.fit();
terminal.focus();
}
@bind
onSocketClose(event) {
console.log('Websocket connection closed with code: ' + event.code);
const { terminal, autoReconnect, connectWebsocket } = this;
terminal.showOverlay('Connection Closed', null);
// 1000: CLOSE_NORMAL
if (event.code !== 1000 && autoReconnect > 0) {
setTimeout(connectWebsocket, autoReconnect * 1000);
}
}
@bind
onSocketData(event) {
const { terminal, textDecoder } = this;
let rawData = new Uint8Array(event.data),
cmd = String.fromCharCode(rawData[0]),
data = rawData.slice(1).buffer;
switch(cmd) {
case '0':
terminal.write(textDecoder.decode(data));
break;
case '1':
let title = textDecoder.decode(data);
document.title = title;
break;
case '2':
let preferences = JSON.parse(textDecoder.decode(data));
Object.keys(preferences).forEach(function(key) {
console.log('Setting ' + key + ': ' + preferences[key]);
terminal.setOption(key, preferences[key]);
});
break;
case '3':
this.autoReconnect = JSON.parse(textDecoder.decode(data));
console.log('Enabling reconnect: ' + this.autoReconnect + ' seconds');
break;
default:
console.warn('Unknown command: ' + cmd);
break;
}
}
@bind
onTerminalTitle(title) {
if (title && title !== '') {
document.title = title;
}
}
@bind
onTerminalResize({ cols, rows }) {
const { terminal, socket, textEncoder } = this;
if (socket.readyState === WebSocket.OPEN) {
let msg = '1' + JSON.stringify({columns: cols, rows: rows});
socket.send(textEncoder.encode(msg));
}
setTimeout(function() {
terminal.showOverlay(cols + 'x' + rows);
}, 500);
}
@bind
onTerminalData(data) {
const { terminal, socket, textEncoder } = this;
if (socket.readyState === WebSocket.OPEN) {
socket.send(textEncoder.encode('0' + data));
}
}
render() {
return (
<div id="terminal-container" ref={(div) => { this.container = div; }}></div>
);
}
}

View File

@@ -0,0 +1,188 @@
import { h, Component } from 'preact';
import { bind } from 'decko';
import { Terminal as Xterm, ITerminalOptions } from 'xterm';
import * as fit from 'xterm/lib/addons/fit/fit';
import * as overlay from './overlay';
import 'xterm/dist/xterm.css';
enum Command {
// server side
OUTPUT = '0',
SET_WINDOW_TITLE = '1',
SET_PREFERENCES = '2',
SET_RECONNECT = '3',
// client side
INPUT = '0',
RESIZE_TERMINAL = '1'
}
interface ITerminal extends Xterm {
fit(): void;
showOverlay(msg: string, timeout?: number): void;
}
interface Props {
id: string;
url: string;
options: ITerminalOptions;
}
export default class Terminal extends Component<Props> {
private textEncoder: TextEncoder;
private textDecoder: TextDecoder;
private container: HTMLElement;
private terminal: ITerminal;
private socket: WebSocket;
private title: string;
private autoReconnect: number;
private resizeTimeout: number;
constructor(props) {
super(props);
Xterm.applyAddon(fit);
Xterm.applyAddon(overlay);
this.textEncoder = new TextEncoder();
this.textDecoder = new TextDecoder();
}
componentDidMount() {
this.openTerminal();
}
componentWillUnmount() {
this.socket.close();
this.terminal.dispose();
window.removeEventListener('resize', this.onWindowResize);
window.removeEventListener('beforeunload', this.onWindowUnload);
}
@bind
onWindowResize() {
const { terminal } = this;
clearTimeout(this.resizeTimeout);
this.resizeTimeout = setTimeout(() => terminal.fit(), 250) as any;
}
onWindowUnload(event: BeforeUnloadEvent): string {
const message = 'Close terminal? this will also terminate the command.';
event.returnValue = message;
return message;
}
@bind
openTerminal() {
if (this.terminal) {
this.terminal.dispose();
}
this.socket = new WebSocket(this.props.url, ['tty']);
this.terminal = new Xterm(this.props.options) as ITerminal;
const { socket, terminal, container } = this;
socket.binaryType = 'arraybuffer';
socket.onopen = this.onSocketOpen;
socket.onmessage = this.onSocketData;
socket.onclose = this.onSocketClose;
terminal.onTitleChange((data) => {
if (data && data !== '') {
document.title = (data + ' | ' + this.title);
}
});
terminal.onData(this.onTerminalData);
terminal.onResize(this.onTerminalResize);
terminal.open(container);
terminal.focus();
window.addEventListener('resize', this.onWindowResize);
window.addEventListener('beforeunload', this.onWindowUnload);
}
@bind
onSocketOpen() {
console.log('Websocket connection opened');
const { socket, textEncoder, terminal } = this;
socket.send(textEncoder.encode(JSON.stringify({AuthToken: ''})));
terminal.fit();
}
@bind
onSocketClose(event: CloseEvent) {
console.log('Websocket connection closed with code: ' + event.code);
const { terminal, openTerminal, autoReconnect } = this;
terminal.showOverlay('Connection Closed', null);
window.removeEventListener('beforeunload', this.onWindowUnload);
// 1008: POLICY_VIOLATION - Auth failure
if (event.code === 1008) {
window.location.reload();
}
// 1000: CLOSE_NORMAL
if (event.code !== 1000 && autoReconnect > 0) {
setTimeout(openTerminal, autoReconnect * 1000);
}
}
@bind
onSocketData(event: MessageEvent) {
const { terminal, textDecoder } = this;
let rawData = new Uint8Array(event.data),
cmd = String.fromCharCode(rawData[0]),
data = rawData.slice(1).buffer;
switch(cmd) {
case Command.OUTPUT:
terminal.write(textDecoder.decode(data));
break;
case Command.SET_WINDOW_TITLE:
this.title = textDecoder.decode(data);
document.title = this.title;
break;
case Command.SET_PREFERENCES:
let preferences = JSON.parse(textDecoder.decode(data));
Object.keys(preferences).forEach(function(key) {
console.log('Setting ' + key + ': ' + preferences[key]);
terminal.setOption(key, preferences[key]);
});
break;
case Command.SET_RECONNECT:
this.autoReconnect = JSON.parse(textDecoder.decode(data));
console.log('Enabling reconnect: ' + this.autoReconnect + ' seconds');
break;
default:
console.warn('Unknown command: ' + cmd);
break;
}
}
@bind
onTerminalResize(size: {cols: number, rows: number}) {
const { terminal, socket, textEncoder } = this;
if (socket.readyState === WebSocket.OPEN) {
let msg = JSON.stringify({columns: size.cols, rows: size.rows});
socket.send(textEncoder.encode(Command.RESIZE_TERMINAL + msg));
}
setTimeout(() => {terminal.showOverlay(size.cols + 'x' + size.rows)}, 500);
}
@bind
onTerminalData(data: string) {
const { socket, textEncoder } = this;
if (socket.readyState === WebSocket.OPEN) {
socket.send(textEncoder.encode(Command.INPUT + data));
}
}
public render({ id }: Props) {
return (
<div id={id} ref={(c) => this.container = c}></div>
);
}
}

View File

@@ -1,66 +0,0 @@
// ported from hterm.Terminal.prototype.showOverlay
// https://chromium.googlesource.com/apps/libapps/+/master/hterm/js/hterm_terminal.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function showOverlay(term, msg, timeout) {
if (!term.overlayNode_) {
if (!term.element)
return;
term.overlayNode_ = document.createElement('div');
term.overlayNode_.style.cssText = (
'border-radius: 15px;' +
'font-size: xx-large;' +
'opacity: 0.75;' +
'padding: 0.2em 0.5em 0.2em 0.5em;' +
'position: absolute;' +
'-webkit-user-select: none;' +
'-webkit-transition: opacity 180ms ease-in;' +
'-moz-user-select: none;' +
'-moz-transition: opacity 180ms ease-in;');
term.overlayNode_.addEventListener('mousedown', function(e) {
e.preventDefault();
e.stopPropagation();
}, true);
}
term.overlayNode_.style.color = "#101010";
term.overlayNode_.style.backgroundColor = "#f0f0f0";
term.overlayNode_.textContent = msg;
term.overlayNode_.style.opacity = '0.75';
if (!term.overlayNode_.parentNode)
term.element.appendChild(term.overlayNode_);
var divSize = term.element.getBoundingClientRect();
var overlaySize = term.overlayNode_.getBoundingClientRect();
term.overlayNode_.style.top =
(divSize.height - overlaySize.height) / 2 + 'px';
term.overlayNode_.style.left = (divSize.width - overlaySize.width) / 2 + 'px';
if (term.overlayTimeout_)
clearTimeout(term.overlayTimeout_);
if (timeout === null)
return;
term.overlayTimeout_ = setTimeout(function() {
term.overlayNode_.style.opacity = '0';
term.overlayTimeout_ = setTimeout(function() {
if (term.overlayNode_.parentNode)
term.overlayNode_.parentNode.removeChild(term.overlayNode_);
term.overlayTimeout_ = null;
term.overlayNode_.style.opacity = '0.75';
}, 200);
}, timeout || 1500);
}
exports.showOverlay = showOverlay;
function apply(terminalConstructor) {
terminalConstructor.prototype.showOverlay = function (msg, timeout) {
return showOverlay(this, msg, timeout);
};
}
exports.apply = apply;

View File

@@ -0,0 +1,74 @@
// ported from hterm.Terminal.prototype.showOverlay
// https://chromium.googlesource.com/apps/libapps/+/master/hterm/js/hterm_terminal.js
import { Terminal } from 'xterm';
interface IOverlayAddonTerminal extends Terminal {
__overlayNode: HTMLElement | null;
__overlayTimeout: number | null;
}
export function showOverlay(term: Terminal, msg: string, timeout?: number): void {
const addonTerminal = <IOverlayAddonTerminal> term;
if (!addonTerminal.__overlayNode) {
if (!term.element) {
return;
}
addonTerminal.__overlayNode = document.createElement('div');
addonTerminal.__overlayNode.style.cssText = (
'border-radius: 15px;' +
'font-size: xx-large;' +
'opacity: 0.75;' +
'padding: 0.2em 0.5em 0.2em 0.5em;' +
'position: absolute;' +
'-webkit-user-select: none;' +
'-webkit-transition: opacity 180ms ease-in;' +
'-moz-user-select: none;' +
'-moz-transition: opacity 180ms ease-in;');
addonTerminal.__overlayNode.addEventListener('mousedown', (e) => {
e.preventDefault();
e.stopPropagation();
}, true);
}
addonTerminal.__overlayNode.style.color = '#101010';
addonTerminal.__overlayNode.style.backgroundColor = '#f0f0f0';
addonTerminal.__overlayNode.textContent = msg;
addonTerminal.__overlayNode.style.opacity = '0.75';
if (!addonTerminal.__overlayNode.parentNode) {
term.element.appendChild(addonTerminal.__overlayNode);
}
const divSize = term.element.getBoundingClientRect();
const overlaySize = addonTerminal.__overlayNode.getBoundingClientRect();
addonTerminal.__overlayNode.style.top = (divSize.height - overlaySize.height) / 2 + 'px';
addonTerminal.__overlayNode.style.left = (divSize.width - overlaySize.width) / 2 + 'px';
if (addonTerminal.__overlayTimeout) {
clearTimeout(addonTerminal.__overlayTimeout);
}
if (timeout === null) {
return;
}
addonTerminal.__overlayTimeout = <number><any>setTimeout(() => {
addonTerminal.__overlayNode.style.opacity = '0';
addonTerminal.__overlayTimeout = <number><any>setTimeout(() => {
if (addonTerminal.__overlayNode.parentNode) {
addonTerminal.__overlayNode.parentNode.removeChild(addonTerminal.__overlayNode);
}
addonTerminal.__overlayTimeout = null;
addonTerminal.__overlayNode.style.opacity = '0.75';
}, 200);
}, timeout || 1500);
}
export function apply(terminalConstructor: typeof Terminal): void {
(<any>terminalConstructor.prototype).showOverlay = function (msg: string, timeout?: number): void {
return showOverlay(this, msg, timeout);
};
}

BIN
preact/src/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,4 +0,0 @@
import './style';
import App from './components/app';
export default App;

5
preact/src/index.tsx Normal file
View File

@@ -0,0 +1,5 @@
import './style/index.scss';
import { h, render } from 'preact';
import App from './components/app';
render(<App />, document.body);

View File

@@ -11,8 +11,7 @@ html, body {
margin: 0 auto;
padding: 0;
background-color: #2b2b2b;
.terminal {
padding: 5px;
}
}
#terminal-container .terminal {
padding: 5px;
}

18
preact/src/template.html Normal file
View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title><%= htmlWebpackPlugin.options.title %></title>
<link inline rel="icon" type="image/png" href="favicon.png">
<% for (const css in htmlWebpackPlugin.files.css) { %>
<link inline rel="stylesheet" src="<%= htmlWebpackPlugin.files.css[css] %>">
<% } %>
</head>
<body>
<script src="auth_token.js"></script>
<% for (const js in htmlWebpackPlugin.files.js) { %>
<script inline type="text/javascript" src="<%= htmlWebpackPlugin.files.js[js] %>"></script>
<% } %>
</body>
</html>

22
preact/tsconfig.json Normal file
View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ES5",
"module": "ESNext",
"lib": [
"dom",
"es5",
"es2015.promise"
],
"allowJs": true,
"jsx": "react",
"jsxFactory": "h",
"sourceMap": true,
"moduleResolution": "node",
"esModuleInterop": true,
"experimentalDecorators": true
},
"include": [
"src/**/*.tsx",
"src/**/*.ts"
]
}

111
preact/tslint.json Normal file
View File

@@ -0,0 +1,111 @@
{
"rulesDirectory": [
"tslint-consistent-codestyle"
],
"rules": {
"array-type": [
true,
"array"
],
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": [
true,
"ignore-same-line"
],
"indent": [
true,
"spaces"
],
"interface-name": [
true,
"always-prefix"
],
"interface-over-type-literal": true,
"typedef": [
true,
"call-signature",
"parameter"
],
"eofline": true,
"new-parens": true,
"no-duplicate-imports": true,
"no-eval": true,
"no-internal-module": true,
"no-trailing-whitespace": true,
"one-variable-per-declaration": true,
"no-unsafe-finally": true,
"no-var-keyword": true,
"prefer-const": true,
"quotemark": [
true,
"single"
],
"semicolon": [
true,
"always"
],
"trailing-comma": [
true,
{
"multiline": {
"objects": "never",
"arrays": "never",
"functions": "never",
"typeLiterals": "ignore"
},
"esSpecCompliant": true
}
],
"triple-equals": true,
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-module",
"check-operator",
"check-rest-spread",
"check-separator",
"check-type",
"check-type-operator",
"check-preblock"
],
"naming-convention": [
true,
{"type": "default", "format": "camelCase", "leadingUnderscore": "forbid"},
{"type": "type", "format": "PascalCase"},
{"type": "class", "format": "PascalCase"},
{"type": "property", "modifiers": ["const"], "format": "UPPER_CASE"},
{"type": "member", "modifiers": ["protected"], "format": "camelCase", "leadingUnderscore": "allow"},
{"type": "member", "modifiers": ["protected"], "format": "camelCase", "leadingUnderscore": "require"},
{"type": "member", "modifiers": ["private"], "format": "camelCase", "leadingUnderscore": "require"},
{"type": "variable", "modifiers": ["const"], "format": ["camelCase", "UPPER_CASE"]},
{"type": "interface", "prefix": "I"}
],
"no-else-after-return": {
"options": "allow-else-if"
},
"prefer-const-enum": [
true
]
}
}

103
preact/webpack.config.js Normal file
View File

@@ -0,0 +1,103 @@
const path = require('path');
const merge = require('webpack-merge');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const TerserPlugin = require('terser-webpack-plugin');
const devMode = process.env.NODE_ENV !== 'production';
const baseConfig = {
context: path.resolve(__dirname, 'src'),
entry: {
app: './index.tsx'
},
output: {
path: path.resolve(__dirname, 'build'),
filename: devMode ? '[name].js' : '[name].[hash].js',
},
module: {
rules: [
{
test: /\.ts$/,
enforce: 'pre',
use: 'tslint-loader',
},
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.s?[ac]ss$/,
use: [
devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader',
],
},
]
},
resolve: {
extensions: [ '.tsx', '.ts', '.js' ]
},
plugins: [
new CopyWebpackPlugin([
{ from: './favicon.png', to: '.' }
], {}),
new MiniCssExtractPlugin({
filename: devMode ? '[name].css' : '[name].[hash].css',
chunkFilename: devMode ? '[id].css' : '[id].[hash].css',
}),
new HtmlWebpackPlugin({
inject: false,
minify: {
removeComments: true,
collapseWhitespace: true,
},
title: 'ttyd - Terminal',
template: './template.html'
})
],
performance : {
hints : false
},
devtool: 'source-map',
};
const devConfig = {
mode: 'development',
devServer: {
contentBase: path.join(__dirname, 'build'),
compress: true,
port: 9000,
proxy: [{
context: ['/auth_token.js', '/ws'],
target: 'http://localhost:7681',
ws: true
}]
}
};
const prodConfig = {
mode: 'production',
optimization: {
minimizer: [
new TerserPlugin({
sourceMap: true
}),
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
map: {
inline: false,
annotation: true
}
}
}),
]
}
};
module.exports = merge(baseConfig, devMode ? devConfig : prodConfig);

File diff suppressed because it is too large Load Diff

4
src/index.html vendored

File diff suppressed because one or more lines are too long