跳到主要内容

RsPack Typescript SWC React

2025年01月09日
柏拉文
越努力,越幸运

一、认识


基于 RsPack 配置的核心部分是使用 RsPack 内置的 swc-loaderJavaScriptTypeScriptJSXTSX 文件进行高效编译。swc-loader 提供了高性能的转译能力,同时支持最新的 JavaScript/TypeScript 语法以及 React 17+ 的自动 JSX 转换。配置中的动态导入支持使得项目能够按需加载模块,提升性能。

通过合理配置 RsPackmodule.rulesTypeScripttsconfig.json,我们能够确保开发环境的高效构建和生产环境的性能优化,同时保持良好的开发体验和代码质量。

针对 tsconfig.json 的相关配置如下

{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"noEmit": true,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*", "definitions/**/*"],
"exclude": ["node_modules", "dist"]
}
  • jsx: "react-jsx": 启用 React 17+ 自动 JSX 转换模式,配合 Webpack 中的 runtime: "automatic",确保 JSX 语法能够正确转换。

针对 swc-loader 的相关配置如下:

{
test: /\.(jsx|tsx)$/,
type: "javascript/auto",
exclude: [/node_modules/],
use: [
{
loader: "builtin:swc-loader",
options: {
sourceMap: true,
jsc: {
parser: {
tsx: true,
syntax: "typescript",
},
externalHelpers: true,
transform: {
react: {
useBuiltins: false,
runtime: "automatic",
throwIfNamespace: true,
},
},
},
},
},
],
}
  • options.jsc.parser.tsx: true: 启用对 TSX 文件的解析。TSXTypeScript 中的 JSX 扩展语法,常用于 React 开发。

  • options.jsc.parser.syntax: typescript: 指示 swc 解析器使用 TypeScript 语法进行解析。这使得它能够理解 .ts.tsx 文件。

  • options.jsc.parser.dynamicImport: true: 启用对动态导入语法(import())的支持。这对于实现代码拆分(Code Splitting)是必需的。

  • options.jsc.transform.react.runtime: automatic: 这表示启用 React 17+ 新的 JSX 转换方式,自动引入 React,不再需要显式地在每个文件中引入 React。这种转换方式大大简化了 React 代码,并提高了开发效率。

二、准备


安装相关依赖如下

# Css 相关依赖 
pnpm add css-loader style-loader sass-loader sass less less-loader postcss-loader postcss-preset-env autoprefixer -D

# React 相关依赖
pnpm add react react-dom -D

# Webpack 相关依赖
pnpm add cross-env @rspack/cli @rspack/core webpack-merge -D

# Typescript 相关依赖
pnpm add @swc/cli @swc/core typescript swc-loader @types/react @types/react-dom -D

三、配置


3.1 definitions

definitions/style.d.ts

declare module "*.css" {
const content: { [className: string]: string };
export default content;
}

declare module "*.scss" {
const content: { [className: string]: string };
export default content;
}

declare module "*.less" {
const content: { [className: string]: string };
export default content;
}

declare module "*.module.css" {
const content: { [className: string]: string };
export default content;
}

declare module "*.module.scss" {
const content: { [className: string]: string };
export default content;
}

declare module "*.module.less" {
const content: { [className: string]: string };
export default content;
}

3.2 package.json

"scripts": {
"build": "cross-env NODE_ENV=production rspack build"
}

3.3 tsconfig.json

{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"noEmit": true,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*", "definitions/**/*"],
"exclude": ["node_modules", "dist"]
}

3.4 rspack.config.js

const Path = require("path");
const rspack = require("@rspack/core");
const { merge } = require("webpack-merge");

const devMode = process.env.NODE_ENV !== "production";

const cssLoader = (modules = false) => {
return [
devMode ? "style-loader" : rspack.CssExtractRspackPlugin.loader,
{
loader: "css-loader",
options: {
modules: modules
? {
namedExport: false,
}
: false,
},
},
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: ["autoprefixer", ["postcss-preset-env", {}]],
},
},
},
];
};

const sassLoader = (modules = false) => [...cssLoader(modules), "sass-loader"];
const lessLoader = (modules = false) => [...cssLoader(modules), "less-loader"];

const cssRules = [
{
test: /\.css$/,
exclude: /\.module\.css$/,
use: cssLoader(),
},
{
type: "javascript/auto",
test: /\.module\.css$/,
use: cssLoader(true),
},
];

const sassRules = [
{
test: /\.(sa|sc)ss$/,
exclude: /\.module\.(sa|sc)ss$/,
use: sassLoader(),
},
{
type: "javascript/auto",
test: /\.module\.(sa|sc)ss$/,
use: sassLoader(true),
},
];

const lessRules = [
{
test: /\.less$/,
exclude: /\.module\.less$/,
use: lessLoader(),
},
{
type: "javascript/auto",
test: /\.module\.less$/,
use: lessLoader(true),
},
];

/** @type {import('@rspack/cli').Configuration} */
const config = {
entry: {
main: Path.resolve(process.cwd(), "src/index.tsx"),
},
output: {
clean: true,
path: Path.resolve(process.cwd(), "dist"),
filename: devMode ? "[name]/[name].js" : "[name]/[name].[contenthash:8].js",
chunkFilename: devMode
? "[name]/[name].js"
: "[name]/[name].[contenthash:8].js",
},
experiments: {
css: false,
},
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx"],
alias: {
"@": Path.resolve(__dirname, "../", "src"),
},
},
module: {
rules: [
{
test: /\.(js|ts)$/,
type: "javascript/auto",
exclude: [/node_modules/],
use: [
{
loader: "builtin:swc-loader",
options: {
sourceMap: true,
jsc: {
parser: {
syntax: "typescript",
},
externalHelpers: true,
},
},
},
],
},
{
test: /\.(jsx|tsx)$/,
type: "javascript/auto",
exclude: [/node_modules/],
use: [
{
loader: "builtin:swc-loader",
options: {
sourceMap: true,
jsc: {
parser: {
tsx: true,
syntax: "typescript",
},
externalHelpers: true,
transform: {
react: {
useBuiltins: false,
runtime: "automatic",
throwIfNamespace: true,
},
},
},
},
},
],
},
{
type: "asset",
test: /\.(png|jpg|jpeg|gif|svg)$/,
parser: {
dataUrlCondition: {
maxSize: 1 * 1024,
},
},
generator: {
filename: "images/[name].[hash:8].[ext]",
},
},
{
type: "asset",
test: /\.(woff|woff2|eot|ttf|otf)$/,
parser: {
dataUrlCondition: {
maxSize: 1 * 1024,
},
},
generator: {
filename: "fonts/[name].[hash:8].[ext]",
},
},
...cssRules,
...sassRules,
...lessRules,
],
},
plugins: [
new rspack.HtmlRspackPlugin({
filename: "index.html",
template: Path.resolve(process.cwd(), "public", `index.html`),
}),
...(devMode
? []
: [
new rspack.CssExtractRspackPlugin({
filename: "[name]/[name].[contenthash:8].css",
chunkFilename: "[name]/[name].[contenthash:8].css",
}),
]),
],
};

module.exports = async function () {
const module = await import("@unocss/webpack/rspack");
const { UnoCSSRspackPlugin } = module;

return merge(config, {
plugins: [
UnoCSSRspackPlugin(Path.resolve(process.cwd(), "./uno.config.ts")),
],
optimization: {
realContentHash: true,
},
});
};

四、使用


4.1 index.tsx

import App from "./App";
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById("root"));
root.render(<App />);

4.2 App.tsx

import 'uno.css'
import "./App.scss"
import city1 from "./images/city1.png";
import AppStyle from "./App.module.scss";

function App() {
return (
<div className="app">
App 页面
<div className={AppStyle.div1}></div>
<img src={city1}/>
<div className="m-1"></div>
<div className="p-2"></div>
<div className='text-ellipsis-2 w-100'>敷设电缆;范德萨范德萨;林凤娇了;附件都说了;就发了;三等奖发了发;啦束带结发;拉数据; 发;苏妲己;啊;发的酸;拉法基;了发的酸;浪费啊;减肥的;是佛i额文峰街道舒服了扩大升级</div>
<span className='text-ellipsis-2-inline w-100'>敷设电缆;范德萨范德萨;林凤娇了;附件都说了;就发了;三等奖发了发;啦束带结发;拉数据; 发;苏妲己;啊;发的酸;拉法基;了发的酸;浪费啊;减肥的;是佛i额文峰街道舒服了扩大升级</span>
</div>
);
}

export default App;