本文簡單紀錄一下如何在一個 TypeScript 專案中,以 Webpack 將其打包為前後端通用模塊 。過程中會經過 TSLint 檢查語法,模塊會以 UMD 的方式導出,並且包含 Source Map。

安裝開發依賴

1
2
3
4
npm i -D \
typescript ts-loader \
tslint tslint-loader tslint-config-standard \
webpack webpack-cli

簡單斷行整理一下安裝命令,可以看出依賴之間的關聯性。

ts-loader 依賴 typescript 為原始碼做編譯。

tslint-loader 依賴 tslint 進行語法檢查,並且採用 tslint-config-standard 配置標準風格(StandardJS)[1]的語法。

webpackwebpack-cli 為基本使用 Webpack 的標準操作。

檔案系統

1
2
3
4
5
6
7
8
9
10
11
├─ dist/
│ ├─ lib.min.js
│ └─ lib.min.js.map
├─ src/
│ └─ index.ts
├─ types/
│ └─ index.d.ts
├─ package.json
├─ tsconfig.json
├─ tslint.json
└─ webpack.config.js

這邊假定了原始碼只有 src/index.ts 一個檔案,經過編譯後的檔案系統長這個樣子。

配置文件

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const path = require('path')

const library = 'lib' // 模塊導出名稱,若為瀏覽器環境使用 <script> 載入,則此名稱將作為變數名稱。

module.exports = {
mode: 'production',
devtool: 'source-map',
entry: './src/index.ts',
module: {
rules: [
{
test: /\.ts$/,
enforce: 'pre',
use: [
{
loader: 'tslint-loader',
options: {
configFile: './tslint.json',
typeCheck: true,
failOnHint: true
}
}
]
},
{
test: /\.tsx?$/,
loader: 'ts-loader'
}
]
},
resolve: {
extensions: [ '.ts', '.tsx', '.js' ]
},
output: {
library,
libraryTarget: 'umd',
path: path.join(__dirname, 'dist'),
filename: library + '.min.js',
globalObject: 'this'
}
}

module 項下面配置了打包規則,首先會經過 tslint-loader 然後進入 ts-loader

我給 tslint-loader 配置了 failOnHint 項,是為了使整個流程因為語法錯誤而噴出例外,預設配置只是警告而已,但我會認為噴例外中斷程序的處理方式更為嚴謹。不開 typeCheck 項的話會收到看起來像是以下的警告訊息,TSLint 的某些規則需要型別才可以斷言,因此需要開啟 typeCheck

1
2
3
4
5
6
Warning: The 'await-promise' rule requires type information.
Warning: The 'deprecation' rule requires type information.
Warning: The 'no-floating-promises' rule requires type information.
Warning: The 'no-unnecessary-qualifier' rule requires type information.
Warning: The 'no-unnecessary-type-assertion' rule requires type information.
Warning: The 'strict-type-predicates' rule requires type information.

雖然 tslint-loader 中已經檢查過型別了,仍沒有給 ts-loader 配置 transpileOnly 的目的是為了生成聲明檔(.d.ts)。

output 中,libraryTarget 項設為 umd。需要特別注意的是 globalObject 給了 this,若沒配置這個選項,預設 Webpack 會嘗試使用 window 物件(因為 Webpack 預設的 target 是 web),在 NodeJS 環境中引入則會噴出這樣的錯誤。

1
ReferenceError: window is not defined

tsconfig.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"compilerOptions": {
"strict": true,
"baseUrl": ".",
"declaration": true,
"target": "esnext",
"module": "commonjs",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"typeRoots": [
"node_modules/@types",
"types"
],
"outDir": "types"
},
"include": [
"src"
]
}

TS 專案的核心配置文件,ts-loadertslint-loader 均會遵循這個文件。

由於 TS 轉換為 JS 之後的代碼是由 Webpack 輸出的,在這邊配置的 outDir 下僅輸出聲明檔。

你可以在 package.json 中加入這一句來指定聲明檔來源。

1
2
3
{
"types": "types/index.d.ts"
}

tslint.json

1
2
3
4
5
6
7
8
9
{
"defaultSeverity": "error",
"extends": [
"tslint-config-standard"
],
"rules": {
"no-empty": false
}
}

TSLint 配置文件,繼承了 tslint-config-standard 的規則,也就是使用了 StandardJS 中定義的語法規則。

然而我做了一點修改,其中 no-empty 規則被我禁用了,原先若是啟用的話,就不能寫出 Noop function [2] 了。

package.json

1
2
3
4
5
{
"main": "dist/lib.min.js",
"module": "src/index.ts",
"types": "types/index.d.ts"
}

這邊只做重點提示幾個配置項,main 使得 NodeJS 環境下可以順利 require,module 貌似是給 ES6 模塊的,還不確定 TS 模塊能不能順利運作,types (也可以使用 typings)指明聲明檔來源。


小結

本文把重心放在 Webpack + TypeScript + TSLint 的組合,還有其他 Webpack 插件或 loader 可以自行加入。

謝謝閱讀,並歡迎指教。



  1. https://standardjs.com/ ↩︎

  2. 就是如 function () { } 這樣、什麼事也沒幹的方法。 ↩︎