Skip to content

Commit bc8313d

Browse files
committed
Client: Support ES5
1 parent 4780124 commit bc8313d

File tree

6 files changed

+79
-74
lines changed

6 files changed

+79
-74
lines changed

README.md

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,9 @@ To turn off code splitting using multiple SPAs simply leave one SPA in the SPA C
203203

204204
> Tip: Let's assume over the time the application has grown and acquired extensive reporting capabilities, perhaps with a reporting dashboard that imports many components. In this case the third SPA and its entry point `reporting.tsx` can be added to the SPA Configuration block. The entry point would import the dashboard and use it for rendering. Such an addition would take little time but bring performance and development/testing benefits. For example, some tests can focus on a React application which has the reporting SPA as the only entry in the SPA Configuration block thus taking the rest of the application out of the testing scope.
205205
### Integration with UI and CSS Libraries
206-
Both libraries ([Semantic UI](https://react.semantic-ui.com) and [Typestyle](https://typestyle.github.io) respectively) provide React with the type safety afforded by Typescript.
206+
Both libraries ([Semantic UI](https://react.semantic-ui.com) and [Typestyle](https://typestyle.github.io) respectively) provide React with the type safety afforded by TypeScript.
207207
### Testing
208-
Debuggable test cases written in Typescript. Integration with [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) on the client and [Supertest](https://github.com/visionmedia/supertest) on the backend. Both using [Jest](https://jestjs.io/) as an engine.<br/>
208+
Debuggable test cases written in TypeScript. Integration with [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) on the client and [Supertest](https://github.com/visionmedia/supertest) on the backend. Both using [Jest](https://jestjs.io/) as an engine.<br/>
209209
The client and backend can be tested independently by executing the `yarn test` command. Alternatively the same command can be executed at the workspace level.
210210

211211
The repository is integrated with Travis CI and the test outcome is reflected by the test badge.
@@ -383,11 +383,24 @@ In case you have a utility class used infrequently, it can also be imported dyna
383383
Q: Do dynamic imports negate the need to have multiple SPAs.<br/>
384384
A: It depends. These two are complimentary techniques. Obviously once a bundle grows larger, it starts affecting performance as its loading time increases. But the reverse is also true, having too many small bundles could result in more network round-trips and the bundle compression will become less efficient. It can also complicate attempts to scrutinise network traffic including requests for bundles.
385385

386-
Q: The client project does not have .html file(s). How can I add my own HTML?<br/>
387-
A: You can add .html snippet file to the project and change the `HtmlWebpackPlugin` configuration in `webpack.config.js` to include the content of your snippet into the generated .html files. That's how you would include polyfills etc. Look for the [headHtmlSnippet](https://github.com/jaketrent/html-webpack-template) configuration setting (and the bodyHtmlSnippet setting), it accepts a name of .html file.
386+
Q: How can I add my own HTML including polyfills etc. to the generated .html files?<br/>
387+
A: Use react-helmet to add additional HTML tags to the `<head>` element and modify the existing ones. Alternatively use the `client\src\entrypoints\head-snippet.html` file. Its content is inserted into the `<head>` element. You can add a [bodyHtmlSnippet](https://github.com/jaketrent/html-webpack-template) by changing the `HtmlWebpackPlugin` configuration in `webpack.config.js` (search for `headHtmlSnippet` and add similar code).
388388

389-
Q: How can I fix Typescript compilation errors?<br/>
390-
A: Note the Typescript version in `package.json`. Ensure the Typescript version shown at the VS Code status bar when .ts or .tsx file is opened is not lower.
389+
Q: I need to support Microsoft Edge and IE11.<br/>
390+
A: Edge is supported provided it has been updated using Windows updates. To support IE11 add the following 5 lines to the `client\src\entrypoints\head-snippet.html` file:
391+
```
392+
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script>
393+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/fetch.umd.min.js"></script>
394+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd-polyfill.min.js"></script>
395+
<script src="https://cdn.jsdelivr.net/npm/mdn-polyfills/String.prototype.includes.js"></script>
396+
<script src="https://cdn.jsdelivr.net/npm/mdn-polyfills/Object.assign.js"></script>
397+
```
398+
399+
Q: In addition to Edge and IE11, I also need to support earlier versions of Internet Explorer.<br/>
400+
A: IE10 is officially [unsupported](https://support.microsoft.com/en-au/help/4488955/support-ending-for-internet-explorer-10) and therefore insecure. This project aims to support browsers that can be made secure.
401+
402+
Q: How can I fix TypeScript compilation errors?<br/>
403+
A: Note the TypeScript version in `package.json`. Ensure the TypeScript version shown at the VS Code status bar when .ts or .tsx file is opened is not lower.
391404

392405
Q: Breakpoints in Chrome DevTools are not hit. How can I fix it?<br/>
393406
A: Open the Settings page of the Chrome DevTools and ensure 'Enable JavaScript source maps' and 'Disable cache (while DevTools is open)' boxes are ticked. Close the Settings page and on the Network tab tick the 'Disable cache' box. If debugging a production build, change the `sourceMap` setting of the TerserPlugin config to `true` in `webpack.config.js`, then restart debugging.

client/config/spa.config.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,31 +72,37 @@ var ConfiguredSPAs = function() {
7272

7373
SPAs.getEntrypoints = function() {
7474
var entryPoints = new Object();
75-
SPAs.forEach(spa => (entryPoints[spa.params.name] = spa.params.entryPoint));
75+
SPAs.forEach(function(spa) {
76+
entryPoints[spa.params.name] = spa.params.entryPoint;
77+
});
7678
return entryPoints;
7779
};
7880

7981
SPAs.getRedirectName = function() {
80-
return SPAs.find(spa => spa.params.redirect).params.name;
82+
return SPAs.find(function(spa) {
83+
return spa.params.redirect;
84+
}).params.name;
8185
};
8286

8387
SPAs.getNames = function() {
8488
var spaNames = new Array();
85-
SPAs.forEach(spa => spaNames.push(spa.params.name));
89+
SPAs.forEach(function(spa) {
90+
spaNames.push(spa.params.name);
91+
});
8692
return spaNames;
8793
};
8894

8995
SPAs.getRewriteRules = function() {
9096
var ret = new Array();
91-
SPAs.forEach(spa => {
97+
SPAs.forEach(function(spa) {
9298
var rule = new Object();
93-
rule.from = new RegExp(`^/${spa.params.name}` + "(\\.html)?$");
94-
rule.to = `${spa.params.name}.html`;
99+
rule.from = new RegExp("^/" + spa.params.name + "(\\.html)?$");
100+
rule.to = spa.params.name + ".html";
95101
ret.push(rule);
96102
});
97103
ret.push({
98104
from: new RegExp("^.*$"),
99-
to: `/${SPAs.getRedirectName()}.html`
105+
to: "/" + SPAs.getRedirectName() + ".html"
100106
});
101107
return ret;
102108
};

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"copy": "copyfiles -f dist/* ../server/build/client/"
3838
},
3939
"dependencies": {
40-
"debug": "^4.1.1",
40+
"loglevel": "^1.6.6",
4141
"react": "16.12.0",
4242
"react-dom": "16.12.0",
4343
"react-helmet": "5.2.1",

client/src/entrypoints/head-snippet.html

Whitespace-only changes.

client/src/utils/logger.ts

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,24 @@
1-
import debug from "debug";
1+
import * as log from "loglevel";
22
import * as SPAs from "../../config/spa.config";
33

4-
const enum LOG_LEVEL {
5-
LOG_TRACE,
6-
LOG_INFO,
7-
LOG_WARN,
8-
LOG_ERROR
9-
}
10-
114
export class Log {
12-
private readonly BASE = SPAs.appTitle;
13-
14-
private generateMessage(level: LOG_LEVEL, message: string, source?: string) {
15-
const namespace = `${this.BASE}:${level}`;
16-
const logger: debug.Debugger = debug(namespace);
17-
18-
if (source) {
19-
logger(source, message);
20-
} else {
21-
logger(message);
22-
}
23-
}
5+
private readonly m_title = SPAs.appTitle;
6+
private readonly m_logger = log.getLogger(this.m_title);
247

25-
public trace(message: string, source?: string) {
26-
return this.generateMessage(LOG_LEVEL.LOG_TRACE, message, source);
8+
public trace(message: string) {
9+
return this.m_logger.trace(message);
2710
}
2811

29-
public info(message: string, source?: string) {
30-
return this.generateMessage(LOG_LEVEL.LOG_INFO, message, source);
12+
public info(message: string) {
13+
return this.m_logger.info(message);
3114
}
3215

33-
public warn(message: string, source?: string) {
34-
return this.generateMessage(LOG_LEVEL.LOG_WARN, message, source);
16+
public warn(message: string) {
17+
return this.m_logger.warn(message);
3518
}
3619

37-
public error(message: string, source?: string) {
38-
return this.generateMessage(LOG_LEVEL.LOG_ERROR, message, source);
20+
public error(message: string) {
21+
return this.m_logger.error(message);
3922
}
4023
}
4124

client/webpack.config.js

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
1-
const path = require('path');
2-
const webpack = require('webpack')
3-
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
4-
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
5-
const HtmlWebpackPlugin = require('html-webpack-plugin');
6-
const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin');
7-
const TerserPlugin = require('terser-webpack-plugin');
8-
const configuredSPAs = require('./config/spa.config');
1+
const path = require("path");
2+
const fs = require("fs");
3+
const webpack = require("webpack")
4+
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
5+
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
6+
const HtmlWebpackPlugin = require("html-webpack-plugin");
7+
const HtmlWebpackHarddiskPlugin = require("html-webpack-harddisk-plugin");
8+
const TerserPlugin = require("terser-webpack-plugin");
9+
const configuredSPAs = require("./config/spa.config");
910
const verifier = require("./config/verifySpaParameters");
10-
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
11+
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
12+
const headHtmlSnippet = fs.readFileSync(path.join("src", "entrypoints", "head-snippet.html"), "utf8");
1113

1214
configuredSPAs.verifyParameters(verifier);
1315

1416
const getWebpackConfig = (env, argv) => {
1517
const isProduction = (env && env.prod) ? true : false;
1618

1719
const config = {
18-
mode: isProduction ? 'production' : 'development',
19-
devtool: 'source-map',
20+
mode: isProduction ? "production" : "development",
21+
devtool: "source-map",
2022
entry: configuredSPAs.getEntrypoints(),
2123
module: {
2224
rules: [
@@ -25,45 +27,45 @@ const getWebpackConfig = (env, argv) => {
2527
exclude: /node_modules/,
2628
use: [
2729
{
28-
loader: 'ts-loader',
30+
loader: "ts-loader",
2931
options: {
3032
transpileOnly: true,
3133
happyPackMode: true,
32-
configFile: path.resolve(__dirname, 'tsconfig.json'),
34+
configFile: path.resolve(__dirname, "tsconfig.json"),
3335
},
3436
}
3537
],
3638
},
3739
{
3840
test: /\.css$/,
39-
use: ['style-loader', 'css-loader'],
41+
use: ["style-loader", "css-loader"],
4042
},
4143
]
4244
},
4345
resolve: {
44-
extensions: ['.tsx', '.ts', '.js'],
46+
extensions: [".tsx", ".ts", ".js"],
4547
plugins: [
4648
new TsconfigPathsPlugin()
4749
]
4850
},
4951
output: {
50-
filename: '[name].[hash].bundle.js',
51-
chunkFilename: '[name].[hash].bundle.js',
52-
path: path.resolve(__dirname, 'dist'),
53-
publicPath: '/static/',
52+
filename: "[name].[hash].bundle.js",
53+
chunkFilename: "[name].[hash].bundle.js",
54+
path: path.resolve(__dirname, "dist"),
55+
publicPath: "/static/",
5456
},
5557
optimization: {
5658
splitChunks: {
5759
cacheGroups: {
5860
vendor: {
5961
test: /node_modules/,
60-
chunks: 'initial',
61-
name: 'vendor',
62+
chunks: "initial",
63+
name: "vendor",
6264
enforce: true
6365
},
6466
},
6567
},
66-
runtimeChunk: 'single',
68+
runtimeChunk: "single",
6769
...(isProduction && {
6870
minimizer: [
6971
new TerserPlugin({
@@ -86,7 +88,7 @@ const getWebpackConfig = (env, argv) => {
8688
plugins: [
8789
new CleanWebpackPlugin(),
8890
new webpack.DefinePlugin({
89-
'process.env.DEVELOPMENT': JSON.stringify(isProduction === false)
91+
"process.env.DEVELOPMENT": JSON.stringify(isProduction === false)
9092
}),
9193
new ForkTsCheckerWebpackPlugin({
9294
tslint: false,
@@ -96,8 +98,8 @@ const getWebpackConfig = (env, argv) => {
9698
],
9799
devServer: {
98100
index: `/${configuredSPAs.getRedirectName()}.html`,
99-
publicPath: '/static/',
100-
contentBase: path.join(__dirname, 'dist'),
101+
publicPath: "/static/",
102+
contentBase: path.join(__dirname, "dist"),
101103
compress: false,
102104
hot: true,
103105
inline: true,
@@ -114,14 +116,15 @@ const getWebpackConfig = (env, argv) => {
114116
configuredSPAs.getNames().forEach((entryPoint) => {
115117
config.plugins.push(
116118
new HtmlWebpackPlugin({
117-
template: require('html-webpack-template'),
119+
template: require("html-webpack-template"),
118120
inject: false,
119121
title: configuredSPAs.appTitle,
120122
appMountId: "react-root",
121123
alwaysWriteToDisk: true,
122124
filename: `${entryPoint}.html`,
123-
chunks: [`${entryPoint}`, 'vendor', 'runtime'],
125+
chunks: [`${entryPoint}`, "vendor", "runtime"],
124126
addBrotliExtension: isProduction,
127+
headHtmlSnippet,
125128
links: [
126129
"//cdn.jsdelivr.net/npm/[email protected]/dist/semantic.min.css"
127130
],
@@ -135,17 +138,17 @@ const getWebpackConfig = (env, argv) => {
135138
config.plugins.push(new HtmlWebpackHarddiskPlugin());
136139

137140
if (isProduction) {
138-
const BrotliPlugin = require('brotli-webpack-plugin');
141+
const BrotliPlugin = require("brotli-webpack-plugin");
139142
const CompressionPlugin = require("compression-webpack-plugin")
140143
const HtmlWebpackBrotliPlugin = require("html-webpack-brotli-plugin")
141144

142145
config.plugins.push(
143146
new webpack.DefinePlugin({
144-
'process.env.NODE_ENV': JSON.stringify('production')
147+
"process.env.NODE_ENV": JSON.stringify("production")
145148
}));
146149
config.plugins.push(
147150
new BrotliPlugin({
148-
asset: '[path].br[query]',
151+
asset: "[path].br[query]",
149152
test: /\.(js|css|html|svg)$/,
150153
threshold: 10240,
151154
minRatio: 0.8
@@ -155,8 +158,8 @@ const getWebpackConfig = (env, argv) => {
155158
);
156159
config.plugins.push(
157160
new CompressionPlugin({
158-
filename: '[path].gz[query]',
159-
algorithm: 'gzip',
161+
filename: "[path].gz[query]",
162+
algorithm: "gzip",
160163
test: /\.js$|\.css$|\.html$/,
161164
threshold: 10240,
162165
minRatio: 0.8

0 commit comments

Comments
 (0)