Como permitir namespaces não declarativos no React com Typescript

Sep 28 2020

Acabei de entrar no ambiente React integrado ao Typescript. Portanto, não entendo totalmente como isso realmente funciona internamente e espero ver as respostas não omitidas entre as soluções.

Eu encontrei uma solução que eu não queria https://stackoverflow.com/a/62084097/10694438, está fazendo downgrade da versão do typescript e parece funcionar com a versão <4. Mas não quero fazer downgrade dele.

O erro ocorre quando executo o script npm run buildquando index.tsx contém um elemento personalizado cujo arquivo de classe contém o namespace correspondente, como

class Foo ... {
    ...
}
namespace Foo {
    *Some inner classes of Foo are here*
}
export default Foo;

A mensagem é Namespace not marked type-only declare. Non-declarative namespaces are only supported experimentally in Babel.

Eu tentei descobrir em https://babeljs.io/docs/en/babel-plugin-transform-typescript#impartial-namespace-support. Tentei fazer o .babelrcarquivo e colocá- "plugins": [["@babel/plugin-transform-typescript", {"allowNamespaces": true}]]lo, mas não funcionou.

Meu package.json é assim atualmente (algumas coisas não relevantes foram omitidas):

"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",
},

Respostas

1 ChangdaePark Sep 29 2020 at 15:12

Tentei encontrar mais soluções, mas parece que não há uma solução clara quando você está no boilerplate do create-react-app, pois outro problema ocorre se eu resolver de alguma forma. então estou deixando esta solução que é configurar o projeto por conta própria, sem usar create-react-app, o que eu queria, mas não pude tentar por causa de minha falta de conhecimento sobre React e Webpack. Realmente apreciohttps://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"
}

Deixe-me descrever sobre as dependências fora do código para aqueles que desejam copiar e colar.

*-loaders são módulos que resolvem importações em seus arquivos de origem. Como o webpack entende apenas arquivos javascript, você precisa fornecer a ele um carregador que entenda os arquivos importados de extensões específicas.

html-webpack-pluginpega as saídas js / css dos carregadores e as insere no modelo html que você especificar e gera o arquivo html output.pathdo webpack.config de modo que os estilos e o javascript incluído estejam ativos na página. Este é um substituto do style-loader, pois style-loadernão funciona com minimizadores CSS como mini-css-extract-plugin.

Para saber o motivo pelo qual você usa resolve-url-loader, verifique aqui .

optimize-css-assets-webpack-plugin só funciona com um minimizador css, pois funciona ao conectar um minimizador.

Alguns carregadores precisam de um transpiler correspondente a cada extensão sasse typescriptsão essas instâncias.

tsconfig-paths-webpack-pluginpermite que o webpack use a pathsopção em seu tsconfig.

Embora ele vai trabalhar sem core-jsuma vez que está incluído no talvez @babel/preset-env(eu não me lembro claramente, mas é como utilizar a versão do incorporado core-jsé uma forma obsoleta), é porque se você definir useBuiltInsa opção como quer usageou entryem @babel/preset-envem babel.config.json, @babel/preset-envvai reclamar que você' d melhor definir a versão de core-js.

ts-node será necessário para webpack.config.ts.

@babel/plugin-syntax-dynamic-import pode ser omitido se você não usar importações dinâmicas.

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;

Coloquei a descrição no código para facilitar a leitura. Uma coisa que você deve ter cuidado é a ordem de uso da lista do carregador . E também observe que eu uso tanto babel-loadere ts-loadercomo você precisa de muito mais configuração sem 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"
]

Observe que eu não uso @ babel / preset-typescript como uso ts-loaderpara Webpack.

"useBuiltIns": "entry"significa que o seu babel usará bibliotecas integradas implementadas em navegadores. Não sei se isso funciona dinamicamente ou se pré-carrega recursos não implementados no target browsers.

Você precisa colocar import "core-js/stable"e import "regenerator-runtime/runtime"antes de quaisquer importações ou códigos que contenham códigos javascript em seu entryarquivo para permitir que o babel carregue 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"
]
A.R.SEIF Sep 28 2020 at 15:56

A classe é definida dentro do namespace. Veja a documentação
E a classe não é igual ao namespace.

class Foo ...
namespace nameOptional {
    *Some inner classes are here*
}
export default Foo;