Reputation: 1079
I've just got into React environment integrated with Typescript. So I don't fully understand how it actually works internally, and I hope to see answers not omitting in between solutions.
I found a solution that I didn't want from https://stackoverflow.com/a/62084097/10694438, it's downgrading typescript version, and it seems to work with the version < 4. But I don't want to downgrade it.
The error occurs when I run script npm run build
when index.tsx contains a custom element whose class file contains the corresponding namespace, like
class Foo ... {
...
}
namespace Foo {
*Some inner classes of Foo are here*
}
export default Foo;
The message is Namespace not marked type-only declare. Non-declarative namespaces are only supported experimentally in Babel
.
I've tried to figure it out on https://babeljs.io/docs/en/babel-plugin-transform-typescript#impartial-namespace-support. So I tried making .babelrc
file and put "plugins": [["@babel/plugin-transform-typescript", {"allowNamespaces": true}]]
in the file, but it didn't work.
My package.json is like this currently (some unrelevant stuff is omitted):
"dependencies": {
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.3"
},
"devDependencies": {
"@types/reach__router": "^1.3.6",
"@types/react": "^16.9.49",
"@types/react-dom": "^16.9.8",
"typescript": "^4.0.3"
},
"scripts": {
"build": "react-scripts build",
},
Upvotes: 3
Views: 1787
Reputation: 1079
I tried to find solutions more, but it seems there is no clear solution when you are on boilerplate from create-react-app, as another issue occurs if I solve it in some ways. so I'm leaving this solution which is setting up the project on your own without using create-react-app, which I wanted but couldn't try because of my lack of knowledge about React and Webpack things. Really appreciate to https://youtu.be/nCoQg5qbLcY.
package.json
"scripts": {
"build": "webpack --mode production --progress",
"dev": "webpack-dev-server --mode development"
},
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.11.5",
"@babel/preset-react": "^7.10.4",
"@types/mini-css-extract-plugin": "^0.9.1",
"@types/node": "^14.11.2",
"@types/optimize-css-assets-webpack-plugin": "^5.0.1",
"@types/react": "^16.9.49",
"@types/react-dom": "^16.9.8",
"@types/webpack": "^4.41.22",
"@types/webpack-dev-server": "^3.11.0",
"babel-loader": "^8.1.0",
"core-js": "^3.6.5",
"css-loader": "^4.3.0",
"html-webpack-plugin": "^4.5.0",
"mini-css-extract-plugin": "^0.11.2",
"optimize-css-assets-webpack-plugin": "^5.0.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"resolve-url-loader": "^3.1.1",
"sass": "^1.26.11",
"sass-loader": "^10.0.2",
"ts-loader": "^8.0.4",
"ts-node": "^9.0.0",
"tsconfig-paths-webpack-plugin": "^3.3.0",
"typescript": "^4.0.3",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
}
Let me describe about the dependencies outside of the code for those who want to copy and paste it.
*-loader
s are modules resolving imports in your source files. Because webpack understands only javascript files, you need to give it a loader that understands the imported files of specific extensions.
html-webpack-plugin
takes js/css outputs from loaders and inserts them into the html template you specify and outputs the html file in output.path
of webpack.config so that the styles and the bundled javascript are active on the page. This is a replacement of style-loader
, as style-loader
doesn't work with css minimizers like mini-css-extract-plugin
.
For the reason why you use resolve-url-loader
, check here.
optimize-css-assets-webpack-plugin
only works with a css minimizer as it works by hooking a minimizier.
Some loaders need a transpiler corresponding to each extension, sass
and typescript
are those instances.
tsconfig-paths-webpack-plugin
lets webpack use the paths
option in your tsconfig.
Although it will work without core-js
as it's included in maybe @babel/preset-env
(I don't remember it clearly but it's like using the version of embedded core-js
is a deprecated way), it is because if you set useBuiltIns
option as either usage
or entry
in @babel/preset-env
in babel.config.json
, @babel/preset-env
will grumble that you'd better set the version of core-js
.
ts-node
will be needed for webpack.config.ts.
@babel/plugin-syntax-dynamic-import
can be omitted if you don't use dynamic imports.
webpack.config.ts
import Webpack from "webpack";
import Path from "path";
import TsconfigPathsWebpackPlugin from "tsconfig-paths-webpack-plugin"; // makes webpack use the paths option in tsconfig
import HtmlWebpackPlugin from "html-webpack-plugin"; // bundles assets into one html file
import MiniCssExtractPlugin from "mini-css-extract-plugin"; // minimizes css files
import OptimizeCssAssetsPlugin from "optimize-css-assets-webpack-plugin"; // removes duplicate selectors in css files by hooking mini-css-extract-plugin loader
const factory: Webpack.ConfigurationFactory = (env, args): Webpack.Configuration => {
const outputPath = Path.resolve(__dirname, "build");
const config: Webpack.Configuration = {
context: Path.resolve(__dirname),
entry: "./src/index.tsx",
output: {
filename: "index.js",
path: outputPath // must be an absolute path
},
resolve: {
// locates modules of file or directory that are included by import(or require)
// It locates modules by their absolute paths, so every relative path gets converted into absolute path.
// If the module is a file, then it gets bundled, otherwise files of extensions in the extensions option in the directory get bundled.
extensions: [".ts", ".tsx", ".js"],
plugins: [new TsconfigPathsWebpackPlugin()] // uses the paths option in tsconfig instead of using the resolve.modules option
},
plugins: [
new MiniCssExtractPlugin({ // won't be able to use style-loader
filename: "index.css", // relative to output.filename
}),
new OptimizeCssAssetsPlugin(), // if it's in optimization.minimizer property, webpack-dev-server won't apply it.
new HtmlWebpackPlugin({
filename: "index.html", // relative to output.filename
template: "src/index.html" // relative to context
})
],
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [
"babel-loader",
"ts-loader"
]
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
"babel-loader"
]
},
{
test: /\.s[ac]ss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"resolve-url-loader",
"sass-loader"
]
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader"
]
}
]
},
devtool: "source-map",
devServer: {
contentBase: outputPath,
watchContentBase: true,
compress: true,
port: 3000,
proxy: {
"/api": "http://localhost:8080"
},
hot: true,
stats: {
colors: true,
version: false,
hash: false,
assets: false,
timings: false,
children: false, // children are plugins that are applied to webpack like mini-css-extract-plugin
entrypoints: false,
cached: false,
cachedAssets: false,
exclude: [
/node_modules/,
/webpack/
]
}
}
};
return config;
};
export default factory;
I put the description in the code for readability. One thing you should be careful is the order of loader list on use. And also note that I use both babel-loader
and ts-loader
as you need so much further configuration without ts-loader
.
babel.config.json
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry",
"corejs": "3",
"targets": {
"browsers": [
">5%",
"not ie 11",
"not op_mini all"
]
}
}
],
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-syntax-dynamic-import", // you may delete this line
"@babel/plugin-transform-modules-commonjs"
]
Note that I don't use @babel/preset-typescript as I use ts-loader
for Webpack instead.
"useBuiltIns": "entry"
means your babel will use built in libraries implemented in browsers. I don't know if this works dynamically or if this pre-loads unimplemented features in the target browsers
.
You need to put import "core-js/stable"
and import "regenerator-runtime/runtime"
before any imports or codes that contain javascript codes in your entry
file to let babel loads polyfills.
import "./index.scss" // this is okay as it's just styles.
// replacements for deprecated @babel/polyfill
import "core-js/stable";
import "regenerator-runtime/runtime";
tsconfig.json
"compilerOptions": {
"watch": true,
"module": "commonjs", // for webpack, ts-node does not support any module syntax other than commonjs.
"allowSyntheticDefaultImports": true, // for webpack
"esModuleInterop": true, // for webpack
"moduleResolution": "node", // for webpack.config about @types
"target": "es6",
"jsx": "react",
// belows are in favor of your project
"baseUrl": ".",
"strict": true,
"alwaysStrict": false,
"isolatedModules": true,
"noImplicitUseStrict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"sourceMap": true,
},
"include": [
"./src/**/*",
"webpack.config.ts"
]
Upvotes: 1
Reputation: 873
The class is defined inside the namespace. See the documentation
And the class is not equal to the namespace.
class Foo ...
namespace nameOptional {
*Some inner classes are here*
}
export default Foo;
Upvotes: 0