Webpack与Babel无法解析TypeScript模块问题求助
模块导入问题:无后缀导入.ts文件失败,加后缀则成功
问题现象
从src目录下的services文件夹导入Config模块时,使用import Config from './services/Config';会触发模块找不到错误,但添加.ts后缀写成./services/Config.ts就能正常导入。
报错信息
Module build failed (from ./node_modules/babel-loader/lib/index.js): Error: Cannot find module './services/Config' from 'D:\github\repoName\src'
已确认Config文件存在于services文件夹中,且services文件夹与index.js文件在同一目录。
配置文件内容
tsconfig.json
{ "compilerOptions": { /* Basic Options */ "target": "ES6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "module": "NodeNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ "lib": [ "es2020", "DOM" ], /* Specify library files to be included in the compilation. */ "allowJs": true, /* Allow javascript files to be compiled. */ "resolveJsonModule": true, /* Basic Options */ "declaration": true, /* Generates corresponding '.d.ts' file. */ "emitDeclarationOnly": true, /* Generates '.d.ts' file only. */ "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ "outDir": "./dist", /* Redirect output structure to the directory. */ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ "strictNullChecks": true, /* Enable strict null checks. */ "strictFunctionTypes": true, /* Enable strict checking of function types. */ "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ "noUnusedLocals": true, /* Report errors on unused locals. */ "noUnusedParameters": true, /* Report errors on unused parameters. */ "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ "moduleResolution": "NodeNext", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ }, "include": [ "src/**/*", ], "exclude": [ "./node_modules" ], }
webpack.config.js
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); //const { PyodidePlugin } = require("@pyodide/webpack-plugin"); const webpack = require('webpack'); const fs = require('fs'); // to check if the file exists const dotenv = require('dotenv'); const TerserPlugin = require("terser-webpack-plugin"); const WebpackObfuscator = require('webpack-obfuscator'); module.exports = (env) => { const currentPath = path.join(__dirname); const basePath = currentPath + '/.env'; const envPath = basePath + '.' + env.ENVIRONMENT; const finalPath = fs.existsSync(envPath) ? envPath : basePath; const fileEnv = dotenv.config({ path: finalPath }).parsed; const envKeys = Object.keys(fileEnv).reduce((prev, next) => { prev[`process.env.${next}`] = JSON.stringify(fileEnv[next]); return prev; }, {}); return { entry: path.join(__dirname, 'src', 'index.js'), optimization: { minimize: true, minimizer: [ new TerserPlugin({ test: /\.js(\?.*)?$/i, }), ], }, output: { path: path.resolve(__dirname, "dist"), filename: "index.js", }, mode: "development", module: { rules: [ { test: /\.(js|jsx)$/, use: "babel-loader", exclude: /node_modules/, }, { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.(ts|tsx)$/, exclude: /node_modules/, use: { loader: "ts-loader", }, }, { test: /\.(woff(2)?|ttf|eot|png|svg|jpg|gif)(\?v=\d+\.\d+\.\d+)?$/, use: [ { loader: "file-loader", options: { name: "[name].[hash:8].[ext]", outputPath: "assets/", }, }, ], }, { test: /\.s[ac]ss$/i, use: [ // Creates `style` nodes from JS strings "style-loader", // Translates CSS into CommonJS "css-loader", // Compiles Sass to CSS "sass-loader", ], }, { test: /\.js$/, enforce: 'post', use: { loader: WebpackObfuscator.loader, options: { rotateStringArray: true } }, include: [ path.resolve(__dirname, 'src/stores/SimulationStore.js'), path.resolve(__dirname, 'src/stores/MapStore.js'), path.resolve(__dirname, 'src/stores/DevprojectsStore.js'), path.resolve(__dirname, 'src/stores/SimulationResultsStore/SimulationResultsStore.js'), path.resolve(__dirname, 'src/stores/AmenitiesStore.js'), path.resolve(__dirname, 'src/services/market_equilibrium_simulator.js'), ] } ] }, plugins: [ //new PyodidePlugin(), new HtmlWebpackPlugin({ template: path.join(__dirname, 'src', 'index.html') }), new webpack.DefinePlugin(envKeys), // new CopyWebpackPlugin({ // patterns: [{ from: 'public' }] // }), ], resolve: { alias: { "@": path.resolve(__dirname, "src/"), }, extensions: [".js", ".jsx", ".ts", ".tsx"], }, devServer: { historyApiFallback: true, port: 3001, open: true, // host: '192.168.0.142',//your ip address // port: 3001, // disableHostCheck: true allowedHosts: 'all' } } };
.babelrc
{ "presets": ["@babel/env", "@babel/react", "@babel/preset-typescript"], "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", { "loose": false }], // In contrast to MobX 4/5, "loose" must be false! ^ ["babel-plugin-inline-json", {}] ] }
.babel.config.js
module.exports = { presets: [['@babel/preset-env', { targets: { node: 'current' } }], "@babel/preset-typescript"], plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]] };
解决方案
原因分析
问题根源在于:
- Babel处理JS文件时,默认不会自动解析.ts后缀的模块,而入口文件是JS,由babel-loader优先编译,导致它找不到无后缀的.ts模块。
- tsconfig中
module和moduleResolution设为NodeNext,该模式严格遵循ES模块规范,不会自动补全文件后缀,与webpack、babel的解析逻辑未对齐。
具体修复步骤
- 调整webpack的resolve.extensions顺序
把.ts/.tsx放在.js/.jsx前面,确保webpack优先解析TypeScript文件:
resolve: { alias: { "@": path.resolve(__dirname, "src/"), }, extensions: [".ts", ".tsx", ".js", ".jsx"], // 调整顺序 },
- 给babel-loader添加后缀解析配置
让babel在处理JS文件时,能识别.ts后缀的模块:
{ test: /\.(js|jsx)$/, use: { loader: "babel-loader", options: { resolve: { extensions: [".js", ".jsx", ".ts", ".tsx"] } } }, exclude: /node_modules/, },
- 统一模块解析规范(可选)
如果不需要严格的NodeNext模式,可修改tsconfig让TypeScript适配打包工具的解析逻辑:
"module": "ESNext", "moduleResolution": "bundler", // 适配webpack等打包工具的解析规则
验证
修改配置后重启webpack dev server,使用import Config from './services/Config';即可正常导入模块。
内容的提问来源于stack exchange,提问作者Lautaro Aguilar
相关产品推荐
相关产品推荐

