This commit is contained in:
Eugene Pankov 2021-07-25 14:00:22 +02:00
parent d9d019e15f
commit e11193b807
No known key found for this signature in database
GPG Key ID: 5896FCBBDD1CF4F4
24 changed files with 961 additions and 193 deletions

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

123
.eslintrc.yml Normal file
View File

@ -0,0 +1,123 @@
parser: '@typescript-eslint/parser'
parserOptions:
project:
- tsconfig.json
- '*/tsconfig.typings.json'
extends:
- 'plugin:@typescript-eslint/all'
plugins:
- '@typescript-eslint'
env:
browser: true
es6: true
node: true
commonjs: true
rules:
'@typescript-eslint/semi':
- error
- never
'@typescript-eslint/indent':
- error
- 4
'@typescript-eslint/explicit-member-accessibility':
- error
- accessibility: no-public
overrides:
parameterProperties: explicit
'@typescript-eslint/no-require-imports': off
'@typescript-eslint/no-parameter-properties': off
'@typescript-eslint/explicit-function-return-type': off
'@typescript-eslint/no-explicit-any': off
'@typescript-eslint/no-magic-numbers': off
'@typescript-eslint/member-delimiter-style': off
'@typescript-eslint/promise-function-async': off
'@typescript-eslint/require-array-sort-compare': off
'@typescript-eslint/no-floating-promises': off
'@typescript-eslint/prefer-readonly': off
'@typescript-eslint/require-await': off
'@typescript-eslint/strict-boolean-expressions': off
'@typescript-eslint/no-misused-promises':
- error
- checksVoidReturn: false
'@typescript-eslint/typedef': off
'@typescript-eslint/consistent-type-imports': off
'@typescript-eslint/sort-type-union-intersection-members': off
'@typescript-eslint/no-use-before-define':
- error
- classes: false
no-duplicate-imports: error
array-bracket-spacing:
- error
- never
block-scoped-var: error
brace-style: off
'@typescript-eslint/brace-style':
- error
- 1tbs
- allowSingleLine: true
computed-property-spacing:
- error
- never
comma-dangle: off
'@typescript-eslint/comma-dangle':
- error
- always-multiline
curly: error
eol-last: error
eqeqeq:
- error
- smart
max-depth:
- 1
- 5
max-statements:
- 1
- 80
no-multiple-empty-lines: error
no-mixed-spaces-and-tabs: error
no-trailing-spaces: error
'@typescript-eslint/no-unused-vars':
- error
- vars: all
args: after-used
argsIgnorePattern: ^_
no-undef: error
no-var: error
object-curly-spacing: off
'@typescript-eslint/object-curly-spacing':
- error
- always
quote-props:
- warn
- as-needed
- keywords: true
numbers: true
quotes: off
'@typescript-eslint/quotes':
- error
- single
- allowTemplateLiterals: true
'@typescript-eslint/no-confusing-void-expression':
- error
- ignoreArrowShorthand: true
'@typescript-eslint/no-non-null-assertion': off
'@typescript-eslint/no-unnecessary-condition':
- error
- allowConstantLoopConditions: true
'@typescript-eslint/restrict-template-expressions': off
'@typescript-eslint/prefer-readonly-parameter-types': off
'@typescript-eslint/no-unsafe-member-access': off
'@typescript-eslint/no-unsafe-call': off
'@typescript-eslint/no-unsafe-return': off
'@typescript-eslint/no-unsafe-assignment': off
'@typescript-eslint/naming-convention': off
'@typescript-eslint/lines-between-class-members':
- error
- exceptAfterSingleLine: true
'@typescript-eslint/dot-notation': off
'@typescript-eslint/no-implicit-any-catch': off
'@typescript-eslint/member-ordering': off
'@typescript-eslint/no-var-requires': off
'@typescript-eslint/no-unsafe-argument': off
'@typescript-eslint/restrict-plus-operands': off
'@typescript-eslint/space-infix-ops': off

View File

@ -4,10 +4,11 @@
"main": "index.js",
"scripts": {
"build": "webpack --progress",
"watch": "DEV=1 webpack --progress --watch",
"build:server": "webpack --progress -c webpack.server.config.js",
"watch:server": "DEV=1 webpack --progress --watch -c webpack.server.config.js",
"start": "./manage.py runserver"
"watch": "BACKEND_URL=http://localhost:8001 DEV=1 webpack --progress --watch",
"build:server": "webpack --progress -c webpack.config.server.js",
"watch:server": "DEV=1 webpack --progress --watch -c webpack.config.server.js",
"start": "./manage.py runserver 8001",
"start:server": "PORT=8000 node build-server/server.js"
},
"private": true,
"devDependencies": {
@ -32,6 +33,8 @@
"@ngtools/webpack": "^12.0.4",
"@tabby-gang/to-string-loader": "^1.1.7-beta.1",
"@types/node": "^11.9.5",
"@typescript-eslint/eslint-plugin": "^4.28.4",
"@typescript-eslint/parser": "^4.28.4",
"apply-loader": "^2.0.0",
"awesome-typescript-loader": "^5.2.1",
"bootstrap": "^5.0.1",
@ -39,6 +42,7 @@
"core-js": "^3.14.0",
"css-loader": "^2.1.0",
"deepmerge": "^4.2.2",
"eslint": "^7.31.0",
"file-loader": "^1.1.11",
"html-loader": "^2.1.2",
"html-webpack-plugin": "^5.3.2",
@ -63,6 +67,7 @@
"webpack": "^5.38.1",
"webpack-bundle-analyzer": "^4.4.2",
"webpack-cli": "^4.7.2",
"webpack-node-externals": "^3.0.0",
"zone.js": "^0.11.4"
},
"dependencies": {

17
poetry.lock generated
View File

@ -237,6 +237,17 @@ sqlparse = ">=0.2.2"
argon2 = ["argon2-cffi (>=19.1.0)"]
bcrypt = ["bcrypt"]
[[package]]
name = "django-cors-headers"
version = "3.7.0"
description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
Django = ">=2.2"
[[package]]
name = "django-rest-framework"
version = "0.1.0"
@ -852,7 +863,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "198155cefe870371fb615a4787e9117dd1f1b9ea25116df55741b1dcd4eb2c12"
content-hash = "cc070c0414bb554395cbc518a5857a6f239ad17b1573c27012441cef86320b7a"
[metadata.files]
appdirs = [
@ -984,6 +995,10 @@ django = [
{file = "Django-3.2.3-py3-none-any.whl", hash = "sha256:7e0a1393d18c16b503663752a8b6790880c5084412618990ce8a81cc908b4962"},
{file = "Django-3.2.3.tar.gz", hash = "sha256:13ac78dbfd189532cad8f383a27e58e18b3d33f80009ceb476d7fcbfc5dcebd8"},
]
django-cors-headers = [
{file = "django-cors-headers-3.7.0.tar.gz", hash = "sha256:96069c4aaacace786a34ee7894ff680780ec2644e4268b31181044410fecd12e"},
{file = "django_cors_headers-3.7.0-py3-none-any.whl", hash = "sha256:1ac2b1213de75a251e2ba04448da15f99bcfcbe164288ae6b5ff929dc49b372f"},
]
django-rest-framework = [
{file = "django-rest-framework-0.1.0.tar.gz", hash = "sha256:47a8f496fa69e3b6bd79f68dd7a1527d907d6b77f009e9db7cf9bb21cc565e4a"},
]

View File

@ -23,6 +23,7 @@ Twisted = "20.3.0"
semver = "^2.13.0"
requests = "^2.25.1"
pyga = "^2.6.2"
django-cors-headers = "^3.7.0"
[tool.poetry.dev-dependencies]
flake8 = "^3.9.2"

View File

@ -5,10 +5,11 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { CommonModule } from '@angular/common'
import { FormsModule } from '@angular/forms'
import { RouterModule } from '@angular/router'
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'
import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http'
import { ClipboardModule } from '@angular/cdk/clipboard'
import { TransferHttpCacheModule } from '@nguniversal/common'
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'
import { UniversalInterceptor } from './interceptor'
import { AppComponent } from './components/app.component'
import { MainComponent } from './components/main.component'
import { ConfigModalComponent } from './components/configModal.component'
@ -17,7 +18,7 @@ import { HomeComponent } from './components/home.component'
import { LoginComponent } from './components/login.component'
import { InstanceInfoResolver } from './api'
import '@fortawesome/fontawesome-svg-core/styles.css'
// import '@fortawesome/fontawesome-svg-core/styles.css'
const ROUTES = [
{
@ -25,28 +26,28 @@ const ROUTES = [
component: HomeComponent,
resolve: {
instanceInfo: InstanceInfoResolver,
}
},
},
{
path: 'app',
component: MainComponent,
resolve: {
instanceInfo: InstanceInfoResolver,
}
},
},
{
path: 'login',
component: LoginComponent,
resolve: {
instanceInfo: InstanceInfoResolver,
}
},
},
]
@NgModule({
imports: [
BrowserModule.withServerTransition({
appId: 'tabby'
appId: 'tabby',
}),
TransferHttpCacheModule,
BrowserAnimationsModule,
@ -60,6 +61,13 @@ const ROUTES = [
ClipboardModule,
RouterModule.forRoot(ROUTES),
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: UniversalInterceptor,
multi: true,
},
],
declarations: [
AppComponent,
MainComponent,
@ -68,6 +76,6 @@ const ROUTES = [
ConfigModalComponent,
SettingsModalComponent,
],
bootstrap: [AppComponent]
bootstrap: [AppComponent],
})
export class AppModule { }

View File

@ -1,9 +1,7 @@
import { HTTP_INTERCEPTORS } from '@angular/common/http'
import { NgModule } from '@angular/core'
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server'
import { AppModule } from './app.module'
import { AppComponent } from './components/app.component'
import { UniversalInterceptor } from './ssr-interceptor'
@NgModule({
imports: [
@ -13,13 +11,6 @@ import { UniversalInterceptor } from './ssr-interceptor'
ServerModule,
ServerTransferStateModule,
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: UniversalInterceptor,
multi: true
}
],
// Since the bootstrapped component is not inherited from your
// imported AppModule, it needs to be repeated here.
bootstrap: [AppComponent],

View File

@ -6,11 +6,18 @@ import { InstanceInfo, Version } from '../api'
import { faCoffee, faDownload, faSignInAlt } from '@fortawesome/free-solid-svg-icons'
import { faGithub } from '@fortawesome/free-brands-svg-icons'
import { ActivatedRoute } from '@angular/router'
import { CommonService } from '../services/common.service'
class DemoConnector {
constructor (targetWindow: Window, private version: Version) {
targetWindow['tabbyWebDemoDataPath'] = `${this.getDistURL()}/${version.version}/tabby-web-demo/data`
constructor (
targetWindow: Window,
private commonService: CommonService,
private version: Version,
) {
this.getDistURL().then(distURL => {
targetWindow['tabbyWebDemoDataPath'] = `${distURL}/${version.version}/tabby-web-demo/data`
})
}
async loadConfig (): Promise<string> {
@ -22,15 +29,15 @@ class DemoConnector {
}`
}
async saveConfig (content: string): Promise<void> {
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
async saveConfig (_content: string): Promise<void> { }
getAppVersion (): string {
return this.version.version
}
getDistURL (): string {
return '/app-dist'
async getDistURL (): Promise<string> {
return await this.commonService.getBackendURL() + '/app-dist'
}
getPluginsToLoad (): string[] {
@ -94,12 +101,14 @@ export class HomeComponent {
constructor (
private http: HttpClient,
private commonService: CommonService,
route: ActivatedRoute,
) {
window.addEventListener('message', this.connectorRequestHandler)
this.instanceInfo = route.snapshot.data.instanceInfo
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
connectorRequestHandler = event => {
if (event.data === 'request-connector') {
this.iframe.nativeElement.contentWindow['__connector__'] = this.connector
@ -107,14 +116,14 @@ export class HomeComponent {
}
}
async ngAfterViewInit () {
async ngAfterViewInit (): Promise<void> {
const versions = await this.http.get('/api/1/versions').toPromise()
versions.sort((a, b) => -semverCompare(a.version, b.version))
this.connector = new DemoConnector(this.iframe.nativeElement.contentWindow, versions[0])
this.iframe.nativeElement.src = '/terminal'
this.connector = new DemoConnector(this.iframe.nativeElement.contentWindow, this.commonService, versions[0])
this.iframe.nativeElement.src = '/terminal.html'
}
ngOnDestroy () {
ngOnDestroy (): void {
window.removeEventListener('message', this.connectorRequestHandler)
}
}

View File

@ -80,7 +80,7 @@ export class MainComponent {
async loadApp (config, version) {
this.showApp = true
this.iframe.nativeElement.src = '/terminal'
this.iframe.nativeElement.src = '/terminal.html'
await this.http.patch(`/api/1/configs/${config.id}`, {
last_used_with_version: version.version,
}).toPromise()

24
src/interceptor.ts Normal file
View File

@ -0,0 +1,24 @@
import { Injectable } from '@angular/core'
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'
import { Observable, from } from 'rxjs'
import { switchMap } from 'rxjs/operators'
import { CommonService } from './services/common.service'
@Injectable()
export class UniversalInterceptor implements HttpInterceptor {
constructor (private commonService: CommonService) { }
intercept (request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!request.url.startsWith('//') && request.url.startsWith('/')) {
return from(this.commonService.getBackendURL()).pipe(switchMap((baseUrl: string) => {
const endpoint = request.url
request = request.clone({
url: `${baseUrl}${endpoint}`,
})
return next.handle(request)
}))
}
return next.handle(request)
}
}

View File

@ -1,12 +1,13 @@
import 'zone.js/dist/zone-node';
import { enableProdMode } from '@angular/core';
require('source-map-support').install()
import 'zone.js/dist/zone-node';
import './ssr-polyfills'
import { enableProdMode } from '@angular/core';
// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';
import './ssr-polyfills'
import * as express from 'express'
import { join } from 'path'
@ -37,15 +38,15 @@ app.use('/static', express.static(DIST_FOLDER, {
maxAge: '1y'
}));
var proxy = require('express-http-proxy');
// var proxy = require('express-http-proxy');
app.get(['/', '/login'], (req, res) => {
app.get('*', (req, res) => {
res.render('index', { req });
});
app.use('/', proxy('http://tabby.local:8000/api/', {
}))
// app.use('/', proxy('http://tabby.local:8000/api/', {
// }))
// Start up the Node server
app.listen(PORT, () => {

View File

@ -2,9 +2,10 @@ import { Buffer } from 'buffer'
import { Subject } from 'rxjs'
import { debounceTime } from 'rxjs/operators'
import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { LoginService } from '../services/login.service'
import { Injectable, Injector } from '@angular/core'
import { Config, Gateway, Version } from '../api'
import { LoginService } from './login.service'
import { CommonService } from './common.service'
export class SocketProxy {
connect$ = new Subject<void>()
@ -21,12 +22,21 @@ export class SocketProxy {
port: number
}
constructor (private appConnector: AppConnectorService) { }
private appConnector: AppConnectorService
private loginService: LoginService
async connect (options) {
constructor (
injector: Injector,
) {
this.appConnector = injector.get(AppConnectorService)
this.loginService = injector.get(LoginService)
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async connect (options: any): Promise<void> {
this.options = options
this.url = this.appConnector.loginService.user.custom_connection_gateway
this.authToken = this.appConnector.loginService.user.custom_connection_gateway_token
this.url = this.loginService.user.custom_connection_gateway
this.authToken = this.loginService.user.custom_connection_gateway_token
if (!this.url) {
try {
const gateway = await this.appConnector.chooseConnectionGateway()
@ -120,9 +130,11 @@ export class AppConnectorService {
sockets: SocketProxy[] = []
constructor (
private injector: Injector,
private http: HttpClient,
public loginService: LoginService,
private commonService: CommonService,
) {
this.configUpdate.pipe(debounceTime(1000)).subscribe(async content => {
const result = await this.http.patch(`/api/1/configs/${this.config.id}`, { content }).toPromise()
Object.assign(this.config, result)
@ -147,8 +159,8 @@ export class AppConnectorService {
return this.version.version
}
getDistURL (): string {
return '../app-dist'
async getDistURL (): Promise<string> {
return await this.commonService.getBackendURL() + '/app-dist'
}
getPluginsToLoad (): string[] {
@ -168,7 +180,7 @@ export class AppConnectorService {
}
createSocket () {
const socket = new SocketProxy(this)
const socket = new SocketProxy(this.injector)
this.sockets.push(socket)
socket.close$.subscribe(() => {
this.sockets = this.sockets.filter(x => x !== socket)

View File

@ -0,0 +1,18 @@
import { Injectable } from '@angular/core'
@Injectable({ providedIn: 'root' })
export class CommonService {
private backendURL?: string
async getBackendURL (): Promise<string> {
if (!this.backendURL) {
const config = await (await fetch('/config.json')).json()
this.backendURL = config.backendURL
if (this.backendURL.endsWith('/')) {
this.backendURL = this.backendURL.slice(0, -1)
}
}
return this.backendURL
}
}

View File

@ -1,45 +0,0 @@
/**
* This interceptor ensures that the app makes requests
* with relative paths correctly server-side.
* Requests which start with a dot (ex. ./assets/...)
* or relative ones ( ex. /assets/...) will be converted
* to absolute paths
*/
import { Inject, Injectable, Injector, PLATFORM_ID } from '@angular/core';
import { isPlatformServer } from '@angular/common';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { Request } from 'express';
import { Observable } from 'rxjs';
@Injectable()
export class UniversalInterceptor implements HttpInterceptor {
constructor(
private readonly injector: Injector,
@Inject(PLATFORM_ID) private readonly platformId: any) {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const isServer = isPlatformServer(this.platformId);
if (isServer && !request.url.startsWith('//') && (request.url.startsWith('./') || request.url.startsWith('/'))) {
const serverRequest = this.injector.get(REQUEST) as Request;
console.log(serverRequest)
const baseUrl = `${serverRequest.protocol}://${serverRequest.get('Host')}`;
let endpoint = request.url;
/**
* ISSUE https://github.com/angular/angular/issues/19224
* HttpClient doesn't support relative requests server-side
*/
if (endpoint.startsWith('.')) {
endpoint = endpoint.substring(1);
}
// Now the endpoint starts with '/'
request = request.clone({
url: `${baseUrl}${endpoint}`
});
}
return next.handle(request);
}
}

View File

@ -2,7 +2,7 @@ import * as domino from 'domino';
import * as fs from 'fs';
import * as path from 'path';
const template = fs.readFileSync(path.join(process.cwd(), 'build-server', 'index.html')).toString();
const template = fs.readFileSync(path.join(process.cwd(), 'build', 'index.html')).toString();
const win = domino.createWindow(template);
global['window'] = win;

View File

@ -31,7 +31,7 @@ async function start () {
await (await fetch(url)).text()
}
const baseUrl = `${connector.getDistURL()}/${appVersion}`
const baseUrl = `${await connector.getDistURL()}/${appVersion}`
const coreURLs = [
`${baseUrl}/tabby-web-container/dist/preload.js`,
`${baseUrl}/tabby-web-container/dist/bundle.js`,

View File

@ -16,9 +16,9 @@ urlpatterns = [
path('api/1/instance-info', api.InstanceInfoViewSet.as_view({'get': 'retrieve'})),
path('api/1/gateways/choose', api.ChooseGatewayViewSet.as_view({'post': 'retrieve'})),
re_path('^(|login|app)$', views.IndexView.as_view()),
# re_path('^(|login|app)$', views.IndexView.as_view()),
path('terminal', views.TerminalView.as_view()),
# path('terminal', views.TerminalView.as_view()),
path('app-dist/<version>/<path:path>', views.AppDistView.as_view()),
path('', include(router.urls)),
]

View File

@ -35,6 +35,7 @@ INSTALLED_APPS = [
'channels',
'rest_framework',
'social_django',
'corsheaders',
'tabby.app',
]
@ -46,6 +47,7 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
'tabby.middleware.TokenMiddleware',
'tabby.middleware.GAMiddleware',
]
@ -135,15 +137,7 @@ LOGGING = {
},
}
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'static'
STATICFILES_DIRS = [BASE_DIR / 'build']
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
@ -180,9 +174,11 @@ LOGIN_REDIRECT_URL = '/app'
APP_DIST_PATH = Path(os.getenv('APP_DIST_PATH', BASE_DIR / 'app-dist'))
NPM_REGISTRY = os.getenv('NPM_REGISTRY', 'https://registry.npmjs.org').rstrip('/')
FRONTEND_URL = None
GITHUB_ELIGIBLE_SPONSORSHIPS = None
for key in [
'FRONTEND_URL',
'SOCIAL_AUTH_GITHUB_KEY',
'SOCIAL_AUTH_GITHUB_SECRET',
'SOCIAL_AUTH_GITLAB_KEY',
@ -229,3 +225,7 @@ if GITHUB_ELIGIBLE_SPONSORSHIPS:
GITHUB_ELIGIBLE_SPONSORSHIPS = GITHUB_ELIGIBLE_SPONSORSHIPS.split(',')
else:
GITHUB_ELIGIBLE_SPONSORSHIPS = []
if FRONTEND_URL:
CORS_ALLOWED_ORIGINS = [FRONTEND_URL]

View File

@ -1,11 +1,14 @@
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
from .app.urls import urlpatterns
from django.views.static import serve
from .app.urls import urlpatterns as app_urlpatterns
urlpatterns = [
path('', include(urlpatterns)),
path('', include(app_urlpatterns)),
path('api/1/auth/social/', include('social_django.urls', namespace='social')),
path('admin/', admin.site.urls),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
path(f'^{settings.STATIC_URL}<path:path>', serve, kwargs={
'document_root': settings.STATIC_ROOT,
}),
]

View File

@ -1,12 +1,6 @@
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const htmlPluginOptions = {
hash: true,
minify: false
}
module.exports = {
mode: process.env.DEV ? 'development' : 'production',
context: __dirname,
@ -61,17 +55,5 @@ module.exports = {
},
plugins: [
new MiniCssExtractPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
chunks: ['index'],
...htmlPluginOptions,
}),
new HtmlWebpackPlugin({
template: './src/terminal.html',
filename: 'terminal.html',
chunks: ['terminal'],
...htmlPluginOptions,
}),
],
}

View File

@ -1,7 +1,20 @@
const baseConfig = require('./webpack.base.config.js')
const baseConfig = require('./webpack.config.base.js')
const fs = require('fs')
const path = require('path')
const { AngularWebpackPlugin } = require('@ngtools/webpack')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const HtmlWebpackPlugin = require('html-webpack-plugin')
const htmlPluginOptions = {
hash: true,
minify: false
}
const outputPath = path.join(__dirname, 'build')
const backendURL = process.env.BACKEND_URL
if (!backendURL) {
throw new Error('BACKEND_URL env var is required')
}
module.exports = {
name: 'browser',
@ -18,11 +31,32 @@ module.exports = {
directTemplateLoading: false,
skipCodeGeneration: false,
}),
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
chunks: ['index'],
...htmlPluginOptions,
}),
new HtmlWebpackPlugin({
template: './src/terminal.html',
filename: 'terminal.html',
chunks: ['terminal'],
...htmlPluginOptions,
}),
{
apply: (compiler) => {
compiler.hooks.afterEmit.tap('AfterEmitPlugin', _ => {
fs.writeFileSync(path.join(outputPath, 'config.json'), JSON.stringify({
backendURL,
}))
})
},
},
],
output: {
path: path.join(__dirname, 'build'),
path: outputPath,
pathinfo: true,
publicPath: '/static/',
publicPath: '/',
filename: '[name].js',
chunkFilename: '[name].bundle.js',
},

39
webpack.config.server.js Normal file
View File

@ -0,0 +1,39 @@
const baseConfig = require('./webpack.config.base.js')
const path = require('path')
const { AngularWebpackPlugin } = require('@ngtools/webpack')
const nodeExternals = require('webpack-node-externals')
const outputPath = path.join(__dirname, 'build-server')
module.exports = {
name: 'server',
target: 'node',
...baseConfig,
entry: {
// 'index.server': path.resolve(__dirname, 'src/index.server.ts'),
'server': path.resolve(__dirname, 'src/server.ts'),
},
resolve: {
...baseConfig.resolve,
mainFields: ['esm2015', 'module', 'main'],
},
plugins: [
...baseConfig.plugins,
new AngularWebpackPlugin({
entryModule: path.resolve(__dirname, 'src/app.server.module#AppServerModule'),
mainPath: path.resolve(__dirname, 'src/server.ts'),
tsconfig: 'tsconfig.json',
directTemplateLoading: false,
platform: 1,
skipCodeGeneration: false,
}),
],
output: {
// libraryTarget: 'commonjs',
path: outputPath,
pathinfo: true,
publicPath: '/',
filename: '[name].js',
chunkFilename: '[name].bundle.js',
},
}

View File

@ -1,32 +0,0 @@
const baseConfig = require('./webpack.base.config.js')
const path = require('path')
const { AngularWebpackPlugin } = require('@ngtools/webpack')
module.exports = {
name: 'server',
target: 'node',
...baseConfig,
entry: {
'index.server': path.resolve(__dirname, 'src/index.server.ts'),
'server': path.resolve(__dirname, 'src/server.ts'),
},
plugins: [
...baseConfig.plugins,
new AngularWebpackPlugin({
entryModule: path.resolve(__dirname, 'src/app/app.server.module`#AppServerModule'),
mainPath: path.resolve(__dirname, 'src/index.server.ts'),
tsconfig: 'tsconfig.json',
directTemplateLoading: false,
platform: 1,
skipCodeGeneration: false,
}),
],
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, 'build-server'),
pathinfo: true,
publicPath: '/static/',
filename: '[name].js',
chunkFilename: '[name].bundle.js',
},
}

605
yarn.lock

File diff suppressed because it is too large Load Diff