I am currently in the process of transitioning a project from JavaScript to TypeScript. The original JavaScript code is legacy and was not structured for exporting/importing, but rather concatenated together. I am facing challenges when trying to import these legacy JavaScript classes into my TypeScript code and encounter errors when attempting to instantiate an imported JavaScript class.
My build setup involves using Webpack to compile the final app.js file with Babel being run within Webpack.
Directory structure:
webpack.config.js
tsconfig.json
.babelrc
legacy-js/
--LegacyJSClass.js
new-ts/
--NewTSClass.ts
ts-build/
--legacy-js/
----LegacyJSClass.js
----LegacyJSClass.js.map
--new-ts/
----NewTSClass.js
----NewTSClass.js.map
dist/
--webpack-output-file.js
./legacy-js/LegacyJSClass.js:
class LegacyJSClass {
constructor(data) {
data = data || {};
this.ExampleJsProp = data.ExampleProp;
}
}
export { LegacyJSClass };
./new-ts/NewTSClass.ts
import { LegacyJSClass } from "../legacy-js/LegacyJSClass";
export class NewTSClass {
ExampleTsProp: any = new LegacyJSClass();
constructor() {
console.log(this.ExampleTsProp);
}
}
let tsClass = new NewTSClass();
./tsconfig.json
{
"compilerOptions": {
"outDir": "./ts-build",
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "es5",
"allowSyntheticDefaultImports": true,
"allowJs": true,
"traceResolution": true
},
"compileOnSave": true,
"include": [
"./new-ts/**/*"
],
"exclude": [
"node_modules"
]
}
./webpack.config.js
var webpack = require('webpack');
var ManifestPlugin = require('webpack-manifest-plugin');
module.exports = {
name: 'app',
devtool: 'inline-source-map',
plugins: [
new ManifestPlugin()
],
module: {
preLoaders: [
{
test: /\.js%/,
loader: 'eslint',
exclude: /node_modules/
},
{
test: /\.js$/,
loader: 'source-map-loader',
exclude: /node_modules/
}
],
loaders: [
{
test: /\.ts?$/,
loader: 'awesome-typescript-loader',
exclude: /node_modules/
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: ['env']
}
}
]
},
resolve: {
extensions: [".ts", ".js"]
},
entry: __dirname + '/new-ts/NewTSClass',
output: {
filename: 'webpack-output-file.js',
path: __dirname + '/dist'
},
eslint: {
failOnWarning: false,
failOnError: true
},
externals: {
"jquery": "jquery"
}
};
./.babelrc
{
"presets": [ "env" ],
"plugins": [
"transform-remove-export" // This removes the export statements from the LegacyJS since it is still contatenated together and output as one big file and doesn't use a module loader.
]
}
Although everything compiles without errors, attempting to load webpack-output-file.js
in the browser results in the following error:
NewTSClass.ts:20 Uncaught TypeError: LegacyJSClass_1.LegacyJSClass is not a constructor
at new NewTSClass (NewTSClass.ts:20)
at Object.<anonymous> (NewTSClass.ts:26)
at __webpack_require__ (bootstrap 4a128ff…:19)
at Object.defineProperty.value (bootstrap 4a128ff…:39)
at bootstrap 4a128ff…:39
Further investigation reveals that Typescript is transpiling correctly:
./ts-build/legacy-js/LegacyJSClass.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var LegacyJSClass = (function () {
function LegacyJSClass(data) {
data = data || {};
this.ExampleJsProp = data.ExampleProp;
}
return LegacyJSClass;
}());
exports.LegacyJSClass = LegacyJSClass;
//# sourceMappingURL=LegacyJSClass.js.map
./ts-build/new-ts/NewTSClass.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var LegacyJSClass_1 = require("../legacy-js/LegacyJSClass");
var NewTSClass = (function () {
function NewTSClass() {
this.ExampleTsProp = new LegacyJSClass_1.LegacyJSClass();
console.log(this.ExampleTsProp);
}
return NewTSClass;
}());
exports.NewTSClass = NewTSClass;
var tsClass = new NewTSClass();
//# sourceMappingURL=NewTSClass.js.map
However, after going through Webpack/Babel, the transformation looks different:
./dist/webpack-output-file.js
/******/
// Webpack bootstrap stuff...
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var LegacyJSClass_1 = __webpack_require__(1);
var NewTSClass = (function () {
function NewTSClass() {
this.ExampleTsProp = new LegacyJSClass_1.LegacyJSClass();
console.log(this.ExampleTsProp);
}
return NewTSClass;
}());
exports.NewTSClass = NewTSClass;
var tsClass = new NewTSClass();
/***/ }),
/* 1 */
/***/ (function(module, exports) {
"use strict";
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var LegacyJSClass = function LegacyJSClass(data) {
_classCallCheck(this, LegacyJSClass);
data = data || {};
this.ExampleJsProp = data.ExampleProp;
};
/***/ })
/******/ ]);
// Sourcemap info...
I have tried various solutions without success. Changing the JS module export to export default LegacyJSClass
throws the error
LegacyJSClass_1.default is not a constructor
.
The reason behind wanting this to work is to avoid creating separate type files for each legacy class used, given the extensive nature of the legacy JS code. As other parts of the application rely on legacy JS, it's crucial for both TypeScript and JavaScript components to seamlessly coexist during the transition process.