npm - RxJS 与 Typescript 类型不兼容
我正在使用两个仓库,称它们为web-common和A-frontend。我通常会在A-frontend中执行npm link web-common。两个仓库都有一些相同的依赖项,如React、Typescript、Google Maps、MobX等。我以前从未遇到过这个工作流程的问题。当我添加RxJS时,我开始收到类似这样的错误:
TS2345: Argument of type 'import("/Users/fharvey/code/monolith/A-frontend/node_modules/rxjs/internal/types").OperatorFunction<import("/Users/fharvey/code/monolith/web-common/services/scaffold/replay_pb").ReplayAllMessagesResult, import("/Users/fharvey/code/monolith/A-frontend/src/app/components/pages/RobotReporting/index").TypeSiloCollec...' is not assignable to parameter of type 'import("/Users/fharvey/code/monolith/web-common/node_modules/rxjs/internal/types").OperatorFunction<import("/Users/fharvey/code/monolith/web-common/services/scaffold/replay_pb").ReplayAllMessagesResult, import("/Users/fharvey/code/monolith/A-frontend/src/app/components/pages/RobotReporting/index").TypeSiloCollecti...'.
Types of parameters 'source' and 'source' are incompatible.
简而言之,这就是说 ~"A-frontend/node_modules/rxjs/internal/types".OperatorFunction 与 ~"web-common/node_modules/rxjs/internal/types".OperatorFunction 不一样。我已经看到很多关于这个的内容:
- https://github.com/ReactiveX/rxjs/issues/1858
- https://github.com/Microsoft/typescript/issues/6496
这里有一个棘手的部分,那就是这在我的开发版本中可行,但在生产版本中不可行。
常用的 Webpack 配置
const path = require('path')
const webpack = require('webpack')
const CircularDependencyPlugin = require('circular-dependency-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const GitRevisionPlugin = require('git-revision-webpack-plugin')
const gitRevisionPlugin = new GitRevisionPlugin({
/*
* The gist of this command is
* 1) Make -rc.X tags sort after main tags (so v3.0.2 is a higher version than v3.0.2-rc.3)
* 2) Get all tags that point to the current commit, sort by version
* 3) Return the first (i.e. most recent) tag name
*/
versionCommand: 'config versionsort.suffix --add "-rc"; git tag --points-at HEAD --sort=-version:refname | head -n 1'
})
// Paths are relative to the client folder, not to this config file, as this is where node is run from
const commonConfig = {
context: path.resolve('./src'),
entry: {
// 'sign-in': 'sign-in',
// 'app': 'app',
// home: 'home', // Signed-out bundle
'index': 'global' // Signed-in bundle
},
module: {
rules: [
{
test: /\.md$/, use: [
{ loader: 'html-loader' },
{ loader: 'markdown-loader' }
]
},
{
test: /\.tsx?$/,
loader: 'ts-loader'
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: { importLoaders: 1 }
},
{ loader: 'postcss-loader' },
{
loader: 'less-loader',
options: {
javascriptEnabled: true
}
}
],
// fallback: 'style-loader'
},
{
test: /\.ttf$/,
use: 'file-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(svg|png)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192
}
}
]
}
]
},
output: {
publicPath: '/static/',
},
plugins: [
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), // don't bundle unnecessary moment.js bloat
new webpack.IgnorePlugin(/\.test\.tsx?/),
new CircularDependencyPlugin({
exclude: /a\.js|node_modules/,
failOnError: true
}),
gitRevisionPlugin,
new webpack.EnvironmentPlugin({
VERSION: gitRevisionPlugin.version()
})
],
resolve: {
extensions: ['.js', '.ts', '.tsx'],
modules: [
path.resolve('./src'),
'node_modules'
],
symlinks: false, // linked dependency peer dependencies resolve correctly
alias: {
rxjs: path.resolve('./node_modules/rxjs'),
},
},
optimization: {
splitChunks: {
// include all types of chunks
chunks: 'all',
cacheGroups: {
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'vendor-react',
chunks: 'all',
},
antd: {
test: /[\\/]node_modules[\\/](antd)[\\/]/,
name: 'vendor-antd',
chunks: 'all',
}
}
}
},
target: 'web'
}
module.exports = commonConfig
开发 Webpack 配置
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const commonConfig = require('./common.config')
const devConfig = Object.assign(commonConfig, {
mode: 'development',
devtool: 'eval-cheap-source-map',
output: Object.assign(commonConfig.output, {
filename: '[name].js', // [name] resolves to name of bundle (e.g., home, customer)
chunkFilename: '[name].js',
path: path.resolve('./build/static')
}),
stats: {
warnings: false
},
module: {
rules: [{
test: /\.md$/,
use: [{
loader: 'html-loader'
},
{
loader: 'markdown-loader'
}
]
},
{
test: /\.tsx?$/,
use: [{
loader: 'cache-loader'
}, // caches typescript compilation,
{
loader: 'ts-loader',
options: {
// disables type-checking so it can be handled by fork-ts-checker-webpack-plugin
transpileOnly: true
}
}
]
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
{
loader: 'postcss-loader'
},
{
loader: 'less-loader',
options: {
javascriptEnabled: true
}
}
]
},
{
test: /\.ttf$/,
use: 'file-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(svg|png)$/,
use: [{
loader: 'url-loader',
options: {
limit: 8192
}
}]
}
]
},
plugins: commonConfig.plugins.concat(
new MiniCssExtractPlugin({
filename: '[name].css'
}),
// Move typescript type checking to a separate process to speed up compile time
new ForkTsCheckerWebpackPlugin({
tsconfig: '../tsconfig.json',
useTypescriptIncrementalApi: true, // uses incremental compilation api from typescript (2.7+)
async: true // Used for docker
}),
new HtmlWebpackPlugin({
filename: '../index.html',
template: 'signed-in-template.html',
favicon: 'assets/images/favicon.ico'
}),
new webpack.EnvironmentPlugin({
// some stuff
})
)
})
module.exports = devConfig
生产环境 Webpack 配置
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const TerserJSPlugin = require("terser-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const commonConfig = require('./common.config')
const prodConfig = Object.assign(commonConfig, {
mode: 'production',
devtool: false,
output: Object.assign(commonConfig.output, {
filename: '[name].[contenthash].js', // [name] resolves to name of bundle (e.g., home, customer)
chunkFilename: '[name].[contenthash].js',
path: path.resolve('./prod/static')
}),
plugins: commonConfig.plugins.concat(
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
}),
new ForkTsCheckerWebpackPlugin({
tsconfig: '../tsconfig.json',
useTypescriptIncrementalApi: true, // uses incremental compilation api from typescript (2.7+)
async: true // Makes docker work
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
}),
new HtmlWebpackPlugin({
filename: '../index.html',
template: 'signed-in-template.html',
favicon: 'assets/images/favicon.ico'
}),
new webpack.EnvironmentPlugin({
// some stuff
})
),
optimization: Object.assign(commonConfig.optimization, {
minimizer: [
new TerserJSPlugin({
parallel: true
}),
new OptimizeCSSAssetsPlugin({})
]
})
})
module.exports = prodConfig
tsconfig
{
"compilerOptions": {
"skipLibCheck": true,
"alwaysStrict": true,
"baseUrl": "src",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react",
"module": "esnext",
"moduleResolution": "node",
"lib": [
"dom",
"es2015" // Promise, classes, etc. tsc injects shims for ES5 browsers
],
"noImplicitAny": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strictNullChecks": true,
"target": "es5",
"types": [
"reflect-metadata",
"googlemaps",
"google.analytics",
"jest",
"jest-enzyme"
],
"paths": {
"*": [
"node_modules/@types/*",
"*"
],
"rxjs": [
"node_modules/rxjs"
],
"rxjs/*": [
"node_modules/rxjs/*"
]
},
},
"exclude": [
"node_modules",
"src/**/*.test.ts",
"src/**/*.test.tsx",
"node_modules/web-common/node_modules/*"
],
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"typings/*.d.ts"
]
}
我为没有减少我的粘贴行为道歉,此刻我真的不知道为什么会发生这种情况,所以我想干脆把所有内容都包括进来。我没有使用任何import ... from "rxjs/internal",但这在幕后发生了。这种情况没有发生过,也从未发生过在任何其他依赖项上。我尝试过安装web-common而不是链接,但无济于事。
答案#1
我认为这对你的开发版本也是一个错误,但它只是不在 TypeScript 错误上失败,因为你在开发环境的 ts-loader 配置中设置了 transpileOnly: true。
这些类型总是不兼容的,因为它们来自两个不同的 RxJS 安装——作为两个不同的模块。我能想到的唯一直接解决方法是将 web-common 实体安装到你的项目中,至少在进行生产构建之前。
这只是一个 Typescript 错误,只要两个 RxJS 安装版本兼容,实际上构建出来的 JS 代码很可能可以正常运行。
然而,webpack 可能也会以同样的方式考虑这个问题,并且 很可能 会向打包中添加两个版本的 RxJS——一个由 web-common 使用,另一个由 A-frontend 使用。