上期我們說到了TypeScript裝飾器(decorators)和JavaScript裝飾器編譯出的代碼不同窒典,雖然我們的庫是用TypeScript寫的肾筐,但很多時候需要提供給JavaScript使用吻商,所以這里來說說怎么將項目遷移至TypeScript
甲馋,并用babel
編譯既忆。
安裝TypeScript
npm install typescript
書寫配置文件
TypeScript使用tsconfig.json
文件管理工程配置铅祸,例如你想包含哪些文件和進行哪些檢查。 讓我們先創(chuàng)建一個簡單的工程配置文件:
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"strictNullChecks": false,
"module": "commonjs",
"target": "ESNext",
"jsx": "react",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": "node",
"allowJs": true
},
"include": [
"./src/**/*"
]
}
這里我們?yōu)門ypeScript設置了一些東西:
讀取所有可識別的src
目錄下的文件(通過include
)硬梁。
接受JavaScript做為輸入(通過allowJs
)前硫。
生成的所有文件放在dist
目錄下(通過outDir
)。
...
你可以在這里了解更多關于tsconfig.json
文件的說明荧止。
創(chuàng)建一個webpack配置文件
在工程根目錄下創(chuàng)建一個webpack.config.js
文件屹电。
module.exports = {
context: __dirname,
entry: './src/index.tsx',
output: {
filename: 'bundle.js',
path: `${__dirname}/dist`
},
// Enable sourcemaps for debugging webpack's output.
devtool: "#source-map",
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: ['.js', '.ts', '.tsx']
},
module: {
rules: [
// All files with a '.ts' or '.tsx' extension will be handled by 'babel-loader'.
{
test: /\.tsx?$/,
loader: 'babel-loader'
},
]
}
};
修改babel配置文件
安裝需要的包
npm install @babel/preset-typescript @babel/plugin-transform-typescript
將上面安裝的包加入工程目錄下的babel.config.js
文件。
module.exports = {
presets: ["@babel/preset-typescript", '@babel/preset-react', '@babel/preset-env', 'mobx'],
plugins: [
['@babel/plugin-transform-typescript', { allowNamespaces: true }],
// ... other
]
}
這里我們使用@babel/plugin-transform-typescript插件來處理TypeScript
跃巡。
那么危号,TypeScript
的類型檢測怎么辦呢?不是相當于廢了嗎素邪?這里我們使用 fork-ts-checker-webpack-plugin來啟用TypeScript
類型檢測外莲。
配置TypeScript類型檢查器
Install
npm install fork-ts-checker-webpack-plugin fork-ts-checker-notifier-webpack-plugin
webpack.config.js
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const ForkTsCheckerNotifierWebpackPlugin = require('fork-ts-checker-notifier-webpack-plugin');
module.exports = {
// ...
plugins: [
new ForkTsCheckerWebpackPlugin({
// 將async設為false,可以阻止Webpack的emit以等待類型檢查器/linter娘香,并向Webpack的編譯添加錯誤苍狰。
async: false
}),
// 將TypeScript類型檢查錯誤以彈框提示
// 如果fork-ts-checker-webpack-plugin的async為false時可以不用
// 否則建議使用,以方便發(fā)現(xiàn)錯誤
new ForkTsCheckerNotifierWebpackPlugin({
title: 'TypeScript',
excludeWarnings: true,
skipSuccessful: true,
}),
]
};
準備工作完成烘绽。
終于能試試期待已久的TypeScript
了淋昭,心情好happy ??
但是,等等安接,What翔忽?為什么報錯了?
TS2304: Cannot find name 'If'.
TS2304: Cannot find name 'Choose'.
TS2304: Cannot find name 'When'.
原來是我們在React
項目中使用了jsx-control-statements導致的盏檐。
怎么辦歇式?在線等,挺急的... ??
我們發(fā)現(xiàn)胡野,這里我們可以用tsx-control-statements來代替材失。
配置 tsx-control-statements
安裝
npm install tsx-control-statements
在tsconfig.json
文件的files
選項中添加
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"strictNullChecks": false,
"module": "commonjs",
"target": "ESNext",
"jsx": "react",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": "node",
"allowJs": true
},
"files": [
"./node_modules/tsx-control-statements/index.d.tsx"
]
}
接下來我們按照TypeScript官網(wǎng)指南來把我們的代碼改成TypeScript
就可以了,這里就不作詳細介紹了硫豆。
更便利的與ECMAScript模塊的互通性
但是這就結(jié)束了么龙巨,no no no...
在編譯過程中,我們發(fā)現(xiàn)有些包的導入有問題
比如熊响,將i18next
作為外部資源引用時(webpack
的externals
可以幫助我們實現(xiàn)該方式)旨别,我們發(fā)現(xiàn)代碼被編譯成
i18next_1['default'].t
但是i18next_1['default']
的值是undefined
,執(zhí)行出錯
為什么汗茄?哪里又雙叒叕...有問題了秸弛???
ECMAScript模塊在ES2015里才被標準化,在這之前,JavaScript生態(tài)系統(tǒng)里存在幾種不同的模塊格式递览,它們工作方式各有不同叼屠。 當新的標準通過后,社區(qū)遇到了一個難題绞铃,就是如何在已有的“老式”模塊模式之間保證最佳的互通性环鲤。
TypeScript與Babel采取了不同的方案,并且直到現(xiàn)在憎兽,還沒出現(xiàn)真正地固定標準冷离。
在之前的版本,TypeScript 對 CommonJs/AMD/UMD 模塊的處理方式與 ES6 模塊不同纯命,這會導致一些問題:
- 當導入一個 CommonJs/AMD/UMD 模塊時西剥,TypeScript 視
import * as koa from 'koa'
與const koa = require('koa')
等價,但使用import * as
創(chuàng)建的模塊對象實際上不可被調(diào)用以及被實例化亿汞。 - 類似的瞭空,當導入一個 CommonJs/AMD/UMD 模塊時,TypeScript 視
import koa from 'koa'
與const koa = require('koa').default
等價疗我,但在大部分 CommonJs/AMD/UMD 模塊里咆畏,它們并沒有默認導出。
在 2.7 后的版本里吴裤,TypeScript提供了一個新的 esModuleInterop
標記旧找,旨在解決上述問題。
當使用這個新的esModuleInterop
標記時麦牺,可調(diào)用的CommonJS模塊必須被做為默認導入:
import express from "express";
let app = express();
我們將其加入tsconfig.json
文件中
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"strictNullChecks": false,
"module": "commonjs",
"target": "ESNext",
"jsx": "react",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"allowSyntheticDefaultImports": true, // 允許使用 ES2015 默認的 import 風格
"esModuleInterop": true, // 可調(diào)用的CommonJS模塊必須被做為默認導入钮蛛,在已有的“老式”模塊模式之間保證最佳的互通性
"moduleResolution": "node",
"allowJs": true
},
"files": [
"./node_modules/tsx-control-statements/index.d.tsx"
]
}
到了這里,我們的程序終于能完美的運行起來了剖膳。
我們不想再區(qū)分哪些需要使用import * as
魏颓,哪些使用import
,因此我們將格式統(tǒng)一為
import XX from 'XX'