Typescript DOM Webpack
Setup
tsc --init
npm init -y
npm install lite-server
./tsconfig.json
{
"include": "./src",
"exclude": "./node_modules",
"compilerOptions": {
"target": "ES2015",
"module": "ES2015", /* Specify what module code is generated. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
"outDir": "./public", /* Specify an output folder for all emitted files. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
./package.json
{
"name": "tsc-webpack",
"version": "1.0.0",
"description": "",
"main": "src/index.ts",
"type": "module",
"scripts": {
"tsc": "tsc --watch",
"start": "lite-server --baseDir='public'",
"dev": "node public/index.js",
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"lite-server": "^2.6.1"
}
}
tree -L 2
.
├── public
│ ├── index.js
│ └── modules
├── node_modules
├── package.json
├── package-lock.json
├── src
│ ├── index.html
│ ├── index.tss
│ └── modules
│ ├── actions.ts
│ ├── events.ts
│ └── utils.ts
└── tsconfig.json
Provide a couple of dummy functions and classes in ./src/modules
and import them into:
./src/index.ts
import Event from './modules/events.js'
import Action from './modules/actions.js'
import { add, multiply, subtract } from './modules/utils.js'
console.log('INFO :: Hello World!')
const processEvent = new Event(88, 'motion detected', 'pir sensor')
processEvent.notify()
const processAction = new Action(89, 'motion detected', 'alarm input', 'recording triggered')
processAction.triggerAction()
console.log(add(33, 77))
console.log(multiply(33, 77))
console.log(subtract(33, 77))
Run the tsc
compiler and import the generated JS file into:
./public/index.html
<script type="module" src="index.js"></script>
Running the dev
script as defined in package.json
now executes all functions in the generated public/index.js
:
npm run dev
> tsc-webpack@1.0.0 dev
> node public/index.js
INFO :: Hello World!
[ 88, 'motion detected', 'pir sensor' ]
INFO :: action alarm recording was triggered!
110
2541
-44
The same works inside a web browser by running the start
script:
npm run start
> tsc-webpack@1.0.0 start
> lite-server --baseDir='public'
[Browsersync] Access URLs:
--------------------------------------
Local: http://localhost:3000
External: http://192.168.2.112:3000
--------------------------------------
UI: http://localhost:3001
UI External: http://localhost:3001
--------------------------------------
[Browsersync] Serving files from: public
[Browsersync] Watching files...
24.01.17 23:00:25 200 GET /index.html
24.01.17 23:00:25 200 GET /index.js
24.01.17 23:00:25 200 GET /modules/events.js
24.01.17 23:00:25 200 GET /modules/actions.js
24.01.17 23:00:25 200 GET /modules/utils.js
24.01.17 23:00:37 200 GET /
24.01.17 23:00:37 200 GET /index.js
All files are loaded successfully and the browser console output is identical to the terminal output from the previous step:
INFO :: Hello World! index.js:4:9
Array(3) [ 88, "motion detected", "pir sensor" ] events.js:10:17
INFO :: action alarm recording was triggered! actions.js:11:17
110 index.js:9:9
2541 index.js:10:9
-44 index.js:11:9
Adding NPM Modules
As a simple example install lodash to 'empower' your src/modules/utils.ts
file:
npm install lodash
npm install --save-dev @types/lodash
import _ from 'lodash'
export function add(x: number, y:number) {
return _.add(x, y)
}
export function multiply(x: number, y: number) {
return _.multiply(x, y)
}
export function subtract(x: number, y: number) {
return _.subtract(x, y)
}
Rerun the dev
script to execute the script in Node.js and you should see that it executes just fine. Running the start
script, however, leads to the following error message inside your browser:
Uncaught TypeError: The specifier “lodash” was a bare specifier, but was not remapped to anything. Relative module specifiers must start with “./”, “../” or “/”.
This can be resolved by using Webpack to bundle all dependencies, including imported npm modules, into a single JS file.
Webpack
Setup
npm install --save-dev webpack webpack-cli typescript ts-loader
./webpack.config.cjs
const path = require('path');
module.exports = {
mode: "development", // production
entry: './src/index.ts',
devtool: 'inline-source-map', // remove in 'production'
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'public'),
publicPath: "/public"s
},
};
In the previous test run all imports had to have the .js
file extension. For Webpack the need to be removed:
Before:
import Event from './modules/events.js'
import Action from './modules/actions.js'
import { add, multiply, subtract } from './modules/utils.js'
After:
import Event from './modules/events'
import Action from './modules/actions'
import { add, multiply, subtract } from './modules/utils'
Now run the build
script to run the bundler:
npm run build
> tsc-webpack@1.0.0 build
> webpack
asset bundle.js 1.39 MiB [emitted] (name: main)
runtime modules 1.25 KiB 6 modules
cacheable modules 533 KiB
modules by path ./src/modules/*.ts 960 bytes
./src/modules/events.ts 342 bytes [built] [code generated]
./src/modules/actions.ts 412 bytes [built] [code generated]
./src/modules/utils.ts 206 bytes [built] [code generated]
./src/index.ts 476 bytes [built] [code generated]
./node_modules/lodash/lodash.js 531 KiB [built] [code generated]
webpack 5.89.0 compiled successfully in 1829 ms
Change to the HTML import to the following to use the newly generated bundled JS file and run the start
script to verify that your browser is now able to find the imported lodash dependency:
./public/index.html
<script src="bundle.js"></script>
Webpack Dev-Server
Replacing the lite-server
with the official webpack-dev-server:
npm install --save-dev webpack-dev-server
And replace the npm scripts accordingly:
./package.json
"scripts": {
"tsc": "tsc --watch",
"start": "webpack serve",
"dev": "node public/bundle.js",
"build": "webpack"
}
And add the following configuration to:
./webpack.config.cjs
devServer: {
static: {
directory: path.join(__dirname, 'public'),
watch: true,
},
client: {
overlay: {
errors: true,
warnings: false,
runtimeErrors: true
},
logging: 'info', // 'log' | 'info' | 'warn' | 'error' | 'none' | 'verbose'
},
compress: true,
port: 3000,
}
The start
script will now run the dev server on port 3000:
npm run start
> tsc-webpack@1.0.0 start
> webpack serve
<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:3000/
asset bundle.js 1.99 MiB [emitted] (name: main)
runtime modules 27.5 KiB 13 modules
modules by path ./node_modules/ 709 KiB
modules by path ./node_modules/webpack-dev-server/client/ 71.8 KiB 16 modules
modules by path ./node_modules/webpack/hot/*.js 5.3 KiB 4 modules
modules by path ./node_modules/html-entities/lib/*.js 81.8 KiB
./node_modules/html-entities/lib/index.js 7.91 KiB [built] [code generated]
+ 3 modules
./node_modules/ansi-html-community/index.js 4.16 KiB [built] [code generated]
./node_modules/events/events.js 14.5 KiB [built] [code generated]
./node_modules/lodash/lodash.js 531 KiB [built] [code generated]
modules by path ./src/ 1.4 KiB
./src/index.ts 476 bytes [built] [code generated]
./src/modules/events.ts 342 bytes [built] [code generated]
./src/modules/actions.ts 412 bytes [built] [code generated]
./src/modules/utils.ts 206 bytes [built] [code generated]
webpack 5.89.0 compiled successfully in 2123 ms
And the browser console should show the results as before:
[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled. index.js:485
[HMR] Waiting for update signal from WDS... log.js:39
INFO :: Hello World! index.ts:5:8
Array(3) [ 88, "motion detected", "pir sensor" ] events.ts:15:16
INFO :: action alarm recording was triggered! actions.ts:12:16
110 index.ts:13:8
2541 index.ts:14:8
-44 index.ts:15:8
Production
npm install --save-dev clean-webpack-plugin html-webpack-plugin
Duplicate the Webpack configuration file and name those files:
webpack.dev.config.cjs
webpack.prod.config.cjs
Keep the development/not-production file as is and change the latter to:
./webpack.prod.config.cjs
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: "production",
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin()
],
output: {
filename: '[contenthash].bundle.js',
path: path.resolve(__dirname, 'public')
}
};
The clean
webpack plugin will now delete all files inside the public
directory. While the html
plugin generates an HTML file in public
that embeds bundle.js
file that now contains hash in it's name to prevent cashing.
This configuration file can be referenced inside the build script:
./package.json
"scripts": {
"tsc": "tsc --watch",
"start": "webpack serve --config webpack.dev.config.cjs",
"dev": "node public/bundle.js",
"build": "webpack --config webpack.prod.config.cjs"
}