記錄使用typescript配合webpack打包一個javascript library的配置過程. 目標是構建一個可以同時支持`CommonJs`, `esm`, `amd`這個幾個js模塊系統的javascript庫, 然後還有一個單獨打包出一個css的樣式文件的需求. ...
記錄使用typescript配合webpack打包一個javascript library的配置過程.
目標是構建一個可以同時支持CommonJs
, esm
, amd
這個幾個js模塊系統的javascript庫, 然後還有一個單獨打包出一個css的樣式文件的需求.
為此以構建一個名為loaf
的javascript庫為例; 首先新建項目文件目錄loaf
, 併進入此文件目錄執行npm init
命令, 然後按照控制台的提示輸入對應的信息, 完成後就會在loaf目錄下得到一個package.json
文件
然後使用npm i
命令安裝所需的依賴
npm i -D webpack webpack-cli typescript babel-loader @babel/core @babel/preset-env @babel/preset-typescript ts-node @types/node @types/webpack mini-css-extract-plugin css-minimizer-webpack-plugin less less-loader terser-webpack-plugin
依賴說明
webpack webpack-cli
: webpack打包工具和webpack命令行介面typescript
: 用於支持typescript語言babel-loader @babel/core @babel/preset-env @babel/preset-typescript
: babel相關的東西, 主要是需要babel-loader
將編寫的typescript代碼轉譯成es5或es6已獲得更好的瀏覽器相容性ts-node @types/node @types/webpack
: 安裝這幾個包是為了能用typescript編寫webpack配置文件(webpack.config.ts
)mini-css-extract-plugin less less-loader
: 編譯提取less文件到單獨的css文件的相關依賴css-minimizer-webpack-plugin terser-webpack-plugin
: 用於最小化js和css文件尺寸的webpack插件
入口文件
通常使用index.ts
作為入口, 並將其放到src
目錄下, 由於有輸出樣式文件的需求, 所以還要新建styles/index.less
mkdir src && touch src/index.ts
mkdir src/styles && touch src/styles/index.less
tsconfig配置
新建tsconfig.json
文件
touch tsconfig.json
填入以下配置(部分選項配有註釋):
{
"compilerOptions": {
"outDir": "dist/lib",
"sourceMap": false,
"noImplicitAny": true,
"module": "commonjs",
// 開啟這個選項, 可以讓你直接通過`import`的方式來引用commonjs模塊
// 這樣你的代碼庫中就可以統一的使用import導入依賴了, 而不需要另外再使用require導入commonjs模塊
"esModuleInterop": true,
// 是否允許合成預設導入
// 開啟後, 依賴的模塊如果沒有導出預設的模塊
// 那麼typescript會幫你給該模塊自動合成一個預設導出讓你可以通過預設導入的方式引用該模塊
"allowSyntheticDefaultImports": true,
// 是否生成`.d.ts`的類型聲明文件
"declaration": true,
// 輸出的目標js版本, 這裡用es6, 然後配和babel進行轉譯才以獲得良好的瀏覽器相容
"target": "es6",
"allowJs": true,
"moduleResolution": "node",
"lib": ["es2015", "dom"],
"declarationMap": true,
// 啟用嚴格的null檢查
"strictNullChecks": true,
// 啟用嚴格的屬性初始化檢查
// 啟用後類屬性必須顯示標註為可空或賦一個非空的初始值
"strictPropertyInitialization": true
},
"exclude": ["node_modules"],
"include": ["src/**/*"]
}
webpack配置文件
創建webpack.config.ts
touch webpack.config.ts
webpack.config.ts
import path from "path";
import { Configuration, Entry } from "webpack";
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import CssMinimizer from 'css-minimizer-webpack-plugin';
import TerserPlugin from 'terser-webpack-plugin'
const isProd = process.env.NODE_ENV === 'production';
/**
* 這裡用到了webpack的[Multiple file types per entry](https://webpack.js.org/guides/entry-advanced/)特性
* 註意`.less`入口文件必須放在`.ts`文件前 */
const entryFiles: string[] = ['./src/styles/index.less', './src/index.ts'];
const entry: Entry = {
index: entryFiles,
'index.min': entryFiles,
};
const config: Configuration = {
entry,
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({ test: /.min.js$/ }),
new CssMinimizer({
test: /.min.css$/,
}),
],
},
module: {
rules: [
{
test: /.ts$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/env', '@babel/typescript'],
},
},
{
test: /.less$/,
use: [
isProd ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: 'css-loader',
},
'postcss-loader',
'less-loader',
],
},
],
},
output: {
path: path.resolve(__dirname, 'dist/umd'),
library: {
type: 'umd',
name: {
amd: 'loaf',
commonjs: 'loaf',
root: 'loaf',
},
},
},
resolve: {
extensions: ['.ts', '.less'],
},
devtool: 'source-map',
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
}),
],
};
export default config;
webpack入口文件配置
...
const isProd = process.env.NODE_ENV === 'production';
/**
* 這裡用到了webpack的[Multiple file types per entry](https://webpack.js.org/guides/entry-advanced/)特性
* 註意`.less`入口文件必須放在`.ts`文件前 */
const entryFiles: string[] = ['./src/styles/index.less', './src/index.ts'];
const entry: Entry = {
index: entryFiles,
'index.min': entryFiles,
};
const config: Configuration = {
entry,
...
}
...
在上面的webpack.config.json
中,我們配置了兩個入口分別是index
和index.min
,不難看出,多出的一個index.min
入口是為了經過壓縮後js和css文件,在生產環境使用一般都會使用.min.js
結尾的文件以減少網路傳輸時的尺寸; 實現這個還需要結合optimization
相關配置, 如下:
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({ test: /.min.js$/ }),
new CssMinimizer({
test: /.min.css$/,
}),
],
},
另外,index
和index.min
的值都是相同的entryFiles
對象,這個對象是一個字元串數組,裡面放的就是我們的入口文件相對路徑,這裡一定要註意把./src/styles/index.less
置於./src/index.ts
之前。
webpack為typescript和less文件配置各自的loader
配置完入口後, 就需要為typescript和less代碼配置各自的loader
module: {
rules: [
{
test: /.ts$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/env', '@babel/typescript'],
},
},
{
test: /.less$/,
use: [
isProd ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: 'css-loader',
},
'postcss-loader',
'less-loader',
],
},
],
},
mini-css-extract-plugin less less-loader
: 編譯提取less文件到單獨的css文件的相關依賴
上面的配置為.ts結尾的文件配置了babel-loader
; 為.less
結尾的文件配置一串loader, 使用了use
, use中的loader的執行順序是從後往前的, 上面less的配置就是告訴webpack遇到less文件時, 一次用less-loader
->postcss-loader
->css-loader
->生產環境用 MiniCssExtractPlugin.loader() 否則用 style-loader
;
MiniCssExtractPlugin.loader
使用前要先在plugins
進行初始化
...
const config = {
...
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
}),
],
...
}
...
webpack的output配置
...
const config = {
...
output: {
path: path.resolve(__dirname, 'dist/umd'),
library: {
type: 'umd',
name: {
amd: 'loaf',
commonjs: 'loaf',
root: 'loaf',
},
},
},
...
}
...
這裡配置webpack以umd的方式輸出到相對目錄dist/umd
目錄中, umd
是Universal Module Definition
(通用模塊定義)的縮寫, umd格式輸出library允許用戶通過commonjs
, AMD
, <script src="...">
的方式對library進行引用config.library.name
可以為不同的模塊系統配置不同的導出模塊名供客戶端來進行引用; 由於這裡的導出模塊名都是loaf
, 所以也可以直接config.library.name
設置成loaf
.
運行webpack進行打包
現在回到最開始通過npm init
生成的package.json
文件, 在修改其內容如下
{
"name": "loaf",
"version": "1.0.0",
"description": "A demo shows how to create & build a javascript library with webpack & typescript",
"main": "index.js",
"scripts": {
"build:umd": "webpack -c webpack.config.ts --node-env production --env NODE_ENV=production",
"test": "npm run test"
},
"keywords": [
"demo"
],
"author": "laggage",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.18.6",
"@babel/preset-env": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@types/node": "^18.0.0",
"@types/webpack": "^5.28.0",
"babel-loader": "^8.2.5",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.0.0",
"less": "^4.1.3",
"less-loader": "^11.0.0",
"mini-css-extract-plugin": "^2.6.1",
"postcss-loader": "^7.0.0",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.3",
"ts-node": "^10.8.2",
"typescript": "^4.7.4",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0"
}
}
新增了一個腳本命令 "build:umd": "webpack -c webpack.config.ts --node-env production --env NODE_ENV=production"
, 然後命令行到項目目錄下執行npm run build:umd
, 不出意外應該就構建成功了, 此時生成的dist目錄結構如下
dist
└── umd
├── index.css
├── index.js
├── index.js.map
├── index.min.css
├── index.min.js
└── index.min.js.map
1 directory, 6 files
測試驗證
新建demo.html
進行測試
mkdir demo && touch demo/demo.html
demo/demo.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="../dist/umd/index.js"></script>
<script type="text/javascript">
console.log(loaf, '\n', loaf.Foo)
</script>
</body>
</html>
用瀏覽器打開demo.html
, 然後F12打開控制台, 可以看到如下輸出則說明初步達成了目標:
Module {__esModule: true, Symbol(Symbol.toStringTag): 'Module'}
demo.html:13 ƒ Foo() {
var _bar = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Bar();
src_classCallCheck(this, Foo);
this._bar = _bar;
}
輸出esm模塊
完成上面的步驟後, 我們已經到了一個umd模塊的輸出, 相關文件都在dist/umd
目錄下; 其中包含可供CommonJs
ESM
AMD
模塊系統和script標簽
使用的umd格式的javascript文件和一個單獨的css樣式文件.
已經輸出了umd格式的js了, 為什麼還要輸出esm模塊? ----TreeShaking
Tree shaking is a term commonly used in the JavaScript context for dead-code elimination. It relies on the static structure of ES2015 module syntax, i.e. import and export. The name and concept have been popularized by the ES2015 module bundler rollup.
此庫的使用者也使用了類似webpack之類的支持Tree Shaking
的模塊打包工具,需要讓使用者的打包工具能對這個js庫loaf
進行死代碼優化Tree Shaking
從webpack文檔中看出, tree-shaking依賴於ES2015(ES2015 module syntax
, ES2015=ES6)的模塊系統, tree-shaking可以對打包體積有不錯優化, 所以為了支持使用者進行tree-shaking
, 輸出esm模塊(esm模塊就是指 ES2015 module syntax)是很有必要的.
用tsc輸出esm和類型聲明文件
tsc -p tsconfig.json --declarationDir ./dist/typings -m es6 --outDir dist/lib-esm
上面的命令使用typescript編譯器命令行介面tsc
輸出了ES6模塊格式的javascript文件到dist/lib-esm
目錄下
將這個目錄加入到package.json
的scripts
配置中:
package.json
{
"name": "loaf",
"version": "1.0.0",
"description": "A demo shows how to create & build a javascript library with webpack & typescript",
"main": "index.js",
"scripts": {
"build:umd": "webpack -c webpack.config.ts --node-env production --env NODE_ENV=production",
"build:lib-esm": "tsc -p tsconfig.json --declarationDir ./dist/typings -m es6 --outDir dist/lib-esm",
"test": "npm run test"
},
"keywords": [
"demo"
],
"author": "laggage",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.18.6",
"@babel/preset-env": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@types/node": "^18.0.0",
"@types/webpack": "^5.28.0",
"babel-loader": "^8.2.5",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.0.0",
"less": "^4.1.3",
"less-loader": "^11.0.0",
"mini-css-extract-plugin": "^2.6.1",
"postcss-loader": "^7.0.0",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.3",
"ts-node": "^10.8.2",
"typescript": "^4.7.4",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0"
}
}
然後運行: npm run build:lib-esm
, 此時dist目錄結構如下:
dist
├── lib-esm
│ ├── bar.js
│ └── index.js
├── typings
│ ├── bar.d.ts
│ ├── bar.d.ts.map
│ ├── index.d.ts
│ └── index.d.ts.map
└── umd
├── index.css
├── index.js
├── index.js.map
├── index.min.css
├── index.min.js
└── index.min.js.map
3 directories, 12 files
多出了兩個子目錄分別為lib-esm
和typings
, 分別放著es6模塊格式的javascript輸出文件和typescript類型聲明文件.
完善package.json文件
到目前為止, package.json
的scripts配置中, 已經有了build:umd
和build:lib-esm
用於構建umd格式的輸出和esm格式的輸出, 現在我們再向添加一個build
用來組合build:umd
和build:lib-esm
併進行最終的構建, 再次之前先安裝一個依賴shx
, 用於跨平臺執行一些shell腳本: npm i -D shx
;
更新package.json
文件:
package.json
{
"name": "loaf",
"version": "1.0.0",
"description": "A demo shows how to create & build a javascript library with webpack & typescript",
"main": "index.js",
"scripts": {
"build": "shx rm -rf dist/** && npm run build:umd && npm run build:lib-esm",
"build:umd": "webpack -c webpack.config.ts --node-env production --env NODE_ENV=production",
"build:lib-esm": "tsc -p tsconfig.json --declarationDir ./dist/typings -m es6 --outDir dist/lib-esm",
"test": "npm run test"
},
"keywords": [
"demo"
],
"author": "laggage",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.18.6",
"@babel/preset-env": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@types/node": "^18.0.0",
"@types/webpack": "^5.28.0",
"babel-loader": "^8.2.5",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.0.0",
"less": "^4.1.3",
"less-loader": "^11.0.0",
"mini-css-extract-plugin": "^2.6.1",
"postcss-loader": "^7.0.0",
"shx": "^0.3.4",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.3",
"ts-node": "^10.8.2",
"typescript": "^4.7.4",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0"
}
}
package.json
文件生成typescript聲明文件所在的路徑(可以參考typescript官網:Including declarations in your npm package):
package.json
{
"name": "loaf",
"version": "1.0.0",
"description": "A demo shows how to create & build a javascript library with webpack & typescript",
"main": "index.js",
"typings": "./typings",
"scripts": {
"build": "shx rm -rf dist/** && npm run build:umd && npm run build:lib-esm",
"build:umd": "webpack -c webpack.config.ts --node-env production --env NODE_ENV=production",
"build:lib-esm": "tsc -p tsconfig.json --declarationDir ./dist/typings -m es6 --outDir dist/lib-esm",
"test": "npm run test"
},
"keywords": [
"demo"
],
"author": "laggage",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.18.6",
"@babel/preset-env": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@types/node": "^18.0.0",
"@types/webpack": "^5.28.0",
"babel-loader": "^8.2.5",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.0.0",
"less": "^4.1.3",
"less-loader": "^11.0.0",
"mini-css-extract-plugin": "^2.6.1",
"postcss-loader": "^7.0.0",
"shx": "^0.3.4",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.3",
"ts-node": "^10.8.2",
"typescript": "^4.7.4",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0"
}
}
package.json中添加exports
配置聲明模塊導出路徑
package.json
中的exports
欄位用於告訴使用者引用此庫時從哪裡尋找對應的模塊文件. 比如使用者可能通過esm模塊引用此庫:
import { Foo } from 'loaf';
const foo = new Foo();
此時如果我們的package.json中沒有指定exports
欄位, 那麼模塊系統會去尋找node_modules/index.js
, 結果肯定是找不到的, 因為我們真正的esm格式的輸出文件應該是在node_modules/loaf/lib-esm
中的
於是我們可以這樣來配置exports
:
package.json
{
"name": "loaf",
"version": "1.0.0",
"description": "A demo shows how to create & build a javascript library with webpack & typescript",
"main": "index.js",
"typings": "./typings",
"exports": {
"./*": "./lib-esm/*",
"./umd/*": "./umd"
},
"scripts": {
"build": "shx rm -rf dist/** && npm run build:umd && npm run build:lib-esm",
"build:umd": "webpack -c webpack.config.ts --node-env production --env NODE_ENV=production",
"build:lib-esm": "tsc -p tsconfig.json --declarationDir ./dist/typings -m es6 --outDir dist/lib-esm",
"test": "npm run test"
},
"keywords": [
"demo"
],
"author": "laggage",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.18.6",
"@babel/preset-env": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@types/node": "^18.0.0",
"@types/webpack": "^5.28.0",
"babel-loader": "^8.2.5",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.0.0",
"less": "^4.1.3",
"less-loader": "^11.0.0",
"mini-css-extract-plugin": "^2.6.1",
"postcss-loader": "^7.0.0",
"shx": "^0.3.4",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.3",
"ts-node": "^10.8.2",
"typescript": "^4.7.4",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0"
}
}
用api-extractor提取出乾凈的.d.ts
在上面的用tsc輸出esm和類型聲明文件這一段中, 我們通過tsc命令輸出了typescript了類型聲明文件到dist/types
目錄下, 這個目錄下有兩個.d.ts
文件, 分別是bar.d.ts
和foo.d.ts
, 通常是希望這些聲明文件都在一個文件index.d.ts
中的, 如果他們分散開了, 以本庫為例, 如果我要使用本庫中的Bar
類, 那麼我可能需要這樣來導入:
import { Bar } from 'loaf/typings/bar';
我不覺得的這種導入方式是好的做法, 理想的導入方式應該像下麵這樣:
import { Bar } from 'loaf';
所以接下來, 還要引入微軟提供的api-extractor
配置使用API extractor
安裝依賴:
npm install -D @microsoft/api-extractor
再全局安裝下:
npm install -g @microsoft/api-extractor
生成api-extractor.json
api-extractor init
稍微修改下api-extractor.json
<
api-extractor.json
/**
* Config file for API Extractor. For more info, please visit: https://api-extractor.com
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
/**
* Optionally specifies another JSON config file that this file extends from. This provides a way for
* standard settings to be shared across multiple projects.
*
* If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains
* the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be
* resolved using NodeJS require().
*
* SUPPORTED TOKENS: none
* DEFAULT VALUE: ""
*/
// "extends": "./shared/api-extractor-base.json"
// "extends": "my-package/include/api-extractor-base.json"
/**
* Determines the "<projectFolder>" token that can be used with other config file settings. The project folder
* typically contains the tsconfig.json and package.json config files, but the path is user-defined.
*
* The path is resolved relative to the folder of the config file that contains the setting.
*
* The default value for "projectFolder" is the token "<lookup>", which means the folder is determined by traversing
* parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder
* that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error
* will be reported.
*
* SUPPORTED TOKENS: <lookup>
* DEFAULT VALUE: "<lookup>"
*/
// "projectFolder": "..",
/**
* (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor
* analyzes the symbols exported by this module.
*
* The file extension must be ".d.ts" and not ".ts".
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
*/
"mainEntryPointFilePath": "<projectFolder>/dist/typings-temp/index.d.ts",
/**
* A list of NPM package names whose exports should be treated as part of this package.
*
* For example, suppose that Webpack is used to generate a distributed bundle for the project "library1",
* and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part
* of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly
* imports library2. To avoid this, we can specify:
*
* "bundledPackages": [ "library2" ],
*
* This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been
* local files for library1.
*/
"bundledPackages": [],
/**
* Determines how the TypeScript compiler engine will be invoked by API Extractor.
*/
"compiler": {
/**
* Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* Note: This setting will be ignored if "overrideTsconfig" is used.
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/tsconfig.json"
*/
// "tsconfigFilePath": "<projectFolder>/tsconfig.json",
/**
* Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk.
* The object must conform to the TypeScript tsconfig schema:
*
* http://json.schemastore.org/tsconfig
*
* If omitted, then the tsconfig.json file will be read from the "projectFolder".
*
* DEFAULT VALUE: no overrideTsconfig section
*/
// "overrideTsconfig": {
// . . .
// }
/**
* This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended
* and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when
* dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses
* for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck.
*
* DEFAULT VALUE: false
*/
// "skipLibCheck": true,
},
/**
* Configures how the API report file (*.api.md) will be generated.
*/
"apiReport": {
/**
* (REQUIRED) Whether to generate an API report.
*/
"enabled": true
/**
* The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce
* a full file path.
*
* The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/".
*
* SUPPORTED TOKENS: <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<unscopedPackageName>.api.md"
*/
// "reportFileName": "<unscopedPackageName>.api.md",
/**
* Specifies the folder where the API report file is written. The file name portion is determined by
* the "reportFileName" setting.
*
* The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy,
* e.g. for an API review.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/etc/"
*/
// "reportFolder": "<projectFolder>/etc/",
/**
* Specifies the folder where the temporary report file is written. The file name portion is determined by
* the "reportFileName" setting.
*
* After the temporary file is written to disk, it is compared with the file in the "reportFolder".
* If they are different, a production build will fail.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/temp/"
*/
// "reportTempFolder": "<projectFolder>/temp/"
},
/**
* Configures how the doc model file (*.api.json) will be generated.
*/
"docModel": {
/**
* (REQUIRED) Whether to generate a doc model file.
*/
"enabled": true
/**
* The output path for the doc model file. The file extension should be ".api.json".
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/temp/<unscopedPackageName>.api.json"
*/
// "apiJsonFilePath": "<projectFolder>/temp/<unscopedPackageName>.api.json"
},
/**
* Configures how the .d.ts rollup file will be generated.
*/
"dtsRollup": {
/**
* (REQUIRED) Whether to generate the .d.ts rollup file.
*/
"enabled": true,
/**
* Specifies the output path for a .d.ts rollup file to be generated without any trimming.
* This file will include all declarations that are exported by the main entry point.
*
* If the path is an empty string, then this file will not be written.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/dist/<unscopedPackageName>.d.ts"
*/
"untrimmedFilePath": "<projectFolder>/dist/typing/index.d.ts"
/**
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release.
* This file will include only declarations that are marked as "@public" or "@beta".
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: ""
*/
// "betaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-beta.d.ts",
/**
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release.
* This file will include only declarations that are marked as "@public".
*
* If the path is an empty string, then this file will not be written.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: ""
*/
// "publicTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-public.d.ts",
/**
* When a declaration is trimmed, by default it will be replaced by a code comment such as
* "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the
* declaration completely.
*
* DEFAULT VALUE: false
*/
// "omitTrimmingComments": true
},
/**
* Configures how the tsdoc-metadata.json file will be generated.
*/
"tsdocMetadata": {
/**
* Whether to generate the tsdoc-metadata.json file.
*
* DEFAULT VALUE: true
*/
// "enabled": true,
/**
* Specifies where the TSDoc metadata file should be written.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* The default value is "<lookup>", which causes the path to be automatically inferred from the "tsdocMetadata",
* "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup
* falls back to "tsdoc-metadata.json" in the package folder.
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<lookup>"
*/
// "tsdocMetadataFilePath": "<projectFolder>/dist/tsdoc-metadata.json"
},
/**
* Specifies what type of newlines API Extractor should use when writing output files. By default, the output files
* will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead.
* To use the OS's default newline kind, specify "os".
*
* DEFAULT VALUE: "crlf"
*/
// "newlineKind": "crlf",
/**
* Configures how API Extractor reports error and warning messages produced during analysis.
*
* There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages.
*/
"messages": {
/**
* Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing
* the input .d.ts files.
*
* TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551"
*
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
*/
"compilerMessageReporting": {
/**
* Configures the default routing for messages that don't match an explicit rule in this table.
*/
"default": {
/**
* Specifies whether the message should be written to the the tool's output log. Note that
* the "addToApiReportFile" property may supersede this option.
*
* Possible values: "error", "warning", "none"
*
* Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail
* and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes
* the "--local" option), the warning is displayed but the build will not fail.
*
* DEFAULT VALUE: "warning"
*/
"logLevel": "warning"
/**
* When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md),
* then the message will be written inside that file; otherwise, the message is instead logged according to
* the "logLevel" option.
*
* DEFAULT VALUE: false
*/
// "addToApiReportFile": false
}
// "TS2551": {
// "logLevel": "warning",
// "addToApiReportFile": true
// },
//
// . . .
},
/**
* Configures handling of messages reported by API Extractor during its analysis.
*
* API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag"
*
* DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings
*/
"extractorMessageReporting": {
"default": {
"logLevel": "warning"
// "addToApiReportFile": false
}
// "ae-extra-release-tag": {
// "logLevel": "warning",
// "addToApiReportFile": true
// },
//
// . . .
},
/**
* Configures handling of messages reported by the TSDoc parser when analyzing code comments.
*
* TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text"
*
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
*/
"tsdocMessageReporting": {
"default": {
"logLevel": "warning"
// "addToApiReportFile": false
}
// "tsdoc-link-tag-unescaped-text": {
// "logLevel": "warning",
// "addToApiReportFile": true
// },
//
// . . .
}
}
}
更新package.json
{
...
"scripts": {
"build": "shx rm -rf dist/** && npm run build:umd && npm run build:lib-esm && npm run build:extract-api",
"build:umd": "webpack -c webpack.config.ts --node-env production --env NODE_ENV=production",
"build:lib-esm": "tsc -p tsconfig.json --declarationDir ./dist/typings-temp -m es6 --outDir dist/lib-esm",
"build:extract-api": "api-extractor run && shx rm -rf dist/typings-temp",
"build:extract-api-local": "shx mkdir -p ./etc && npm run build:lib-esm && api-extractor run -l",
"test": "npm run test"
},
...
}
註意, 這裡處理新增了一個build:extract-api
到scripts配置中, 還修改了build:lib-esm
的配置, 將其輸出的typescript類型聲明文件放到了, typings-temp目錄中, 最後這個目錄是要刪除; 還要註意, 每次提交代碼到版本管理工具前, 要先運行npm run build:extract-api-local
, 這個命令會生成./etc/<libraryName>.api.md
文件, 這個文件是api-extractor生成的api文檔, 應該要放到版本管理工具中去的, 以便可以看到每次提交代碼時API的變化.
用@internal標註只希望在內部使用的class
比如, 我希望Bar
類不能被此庫的使用者使用, 我可以加上下麵這段註釋
/**
*
* @internal
*/
export class Bar {
bar() {}
}
然後來看看生成的index.d.ts
文件:
/**
*
* @internal
*/
declare class Bar {
bar(): void;
}
export declare class Foo {
private _bar;
constructor(_bar?: Bar);
foo(): void;
loaf(): void;
}
export { }
可以看出index.d.ts
文件中雖然declare了Bar
, 但是並未導出Bar
這個特性是由api-extractor提供的, 更多api-extractor的內容移步官方文檔
小結
至此, 我們就可以構建一個可以通過諸如AMD
CommonJs
esm
等js模塊系統或是使用script標簽
的方式引用的js庫了, 主要用到了webpack
typescript
api-extractor
這些工具. 完整的示例代碼可以訪問github-laggage/loaf查看.
作者:Laggage
說明:轉載請註明來源