如何配置React項目直接使用TypeScript包(babel版)

上期我們說到了TypeScript裝飾器(decorators)和JavaScript裝飾器編譯出的代碼不同,我們的組件庫已經(jīng)改成了TypeScript,但很多項目還在使用JavaScript,所以這里來說說怎么在我們JavaScript版的React項目中直接使用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
    }
}

這里我們?yōu)門ypeScript設(shè)置了一些東西:

讀取所有可識別的src目錄下的文件(通過include)。
接受JavaScript做為輸入(通過allowJs)。
生成的所有文件放在dist目錄下(通過outDir)。
...
你可以在這里了解更多關(guān)于tsconfig.json文件的說明。

修改webpack配置文件

修改工程根目錄下的webpack.config.js文件。

module.exports = {
    // ...
    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',
                options: {
                    presets: ['@babel/preset-typescript'],
                    plugins: [
                        ['@babel/plugin-transform-typescript', { allowNamespaces: true }],
                    ]
                },
            },
            {
                test: /\.jsx?$/,
                loader: 'babel-loader',
            },
        ]
    }
};

這里我們使用@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設(shè)為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導(dǎo)致的。
怎么辦?在線等,挺急的... ??
我們發(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)有些包的導(dǎo)入有問題
比如,將i18next作為外部資源引用時(webpackexternals可以幫助我們實現(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 模塊不同,這會導(dǎo)致一些問題:

  • 當導(dǎo)入一個 CommonJs/AMD/UMD 模塊時,TypeScript 視 import * as koa from 'koa'const koa = require('koa') 等價,但使用 import * as 創(chuàng)建的模塊對象實際上不可被調(diào)用以及被實例化。
  • 類似的,當導(dǎo)入一個 CommonJs/AMD/UMD 模塊時,TypeScript 視 import koa from 'koa'const koa = require('koa').default 等價,但在大部分 CommonJs/AMD/UMD 模塊里,它們并沒有默認導(dǎo)出。

在 2.7 后的版本里,TypeScript提供了一個新的 esModuleInterop標記,旨在解決上述問題。
當使用這個新的esModuleInterop標記時,可調(diào)用的CommonJS模塊必須被做為默認導(dǎo)入:

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模塊必須被做為默認導(dǎo)入,在已有的“老式”模塊模式之間保證最佳的互通性
        "moduleResolution": "node",
        "allowJs": true
    },
    "files": [
        "./node_modules/tsx-control-statements/index.d.tsx"
    ]
}

到了這里,我們的程序終于能完美的運行起來了。
我們不想再區(qū)分哪些需要使用import * as,哪些使用import,因此我們將格式統(tǒng)一為

import XX from 'XX'
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容