Como permitir namespaces não declarativos no React com Typescript
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
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 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;