Opinionated Rstack CLI presets — the Rstack counterpart of the sxzz toolchain split (@sxzz/eslint-config + @sxzz/prettier-config + tsdown-preset-sxzz), with tree-shakeable subpaths and a unified define.extends entry.
- Tool subpaths:
rslint/rslib/fmt/test/staged/commitlint/tsconfig/tsconfig/app - Unified
rstack()→RstackConfigfordefine.extends([...]), including Rsbuild sites viaapp sideEffects: false+ distinct ESM bundles- Optional peers: install only the tools you use
- Lazy factories so
rs lintdoes not load the Rslib graph
pnpm add -D @heyq02/rstack-config rstack @rslint/core @rslib/core
# Optional — commit message lint (commit-msg hook)
pnpm add -D @commitlint/cli// rstack.config.ts
import { define } from 'rstack';
import { rstack } from '@heyq02/rstack-config/rstack';
define.extends([
rstack({
lib: { entry: 'index' },
lint: { typescript: true, rstest: true },
}),
]);
// Optional project overrides (merge on top of the shared preset):
define.fmt({ printWidth: 100 });Setting app registers define.app() and leaves the library build off. Lint keeps Node rules for config files, and turns off node/no-missing-import so Rsbuild extensionless imports (./mount) are not reported.
// rstack.config.ts
import { define } from 'rstack';
import { rstack } from '@heyq02/rstack-config/rstack';
define.extends([
rstack({
app: {
source: { entry: { index: './src/index.tsx' } },
},
lint: { react: true },
}),
]);// tsconfig.json
{
"extends": "@heyq02/rstack-config/tsconfig/app",
"include": ["src"]
}Pass lib as well only when the same project should also build a library. Pass node: false on lint to drop Node rules entirely.
import { define } from 'rstack';
import { lint } from '@heyq02/rstack-config/rslint';
import { nodeLib } from '@heyq02/rstack-config/rslib';
import { fmt } from '@heyq02/rstack-config/fmt';
import { test } from '@heyq02/rstack-config/test';
import { staged } from '@heyq02/rstack-config/staged';
define.lib(() => nodeLib());
define.lint(() => lint({ typescript: true }));
define.fmt(fmt());
define.test(test());
define.staged(staged());Rstack CLI has no define.commitlint(), so commit message lint stays a one-liner config file (same shape as rspack-plugin-git-revision):
// commitlint.config.js
export { default } from '@heyq02/rstack-config/commitlint';With options:
import { commitlint } from '@heyq02/rstack-config/commitlint';
export default commitlint({
headerMaxLength: 100,
// types: ['feat', 'fix', ...],
});Wire the hook (once per repo):
# .rstack/hooks/commit-msg
pnpm exec commitlint --edit "$1"And "prepare": "rs hooks" in package.json.
Shareable tsconfig JSON (strict + bundler resolution). Path fields like include / rootDir stay in the consumer so they resolve against the project root:
// tsconfig.json
{
"extends": "@heyq02/rstack-config/tsconfig",
"compilerOptions": {
"rootDir": "src"
},
"include": ["src"]
}Browser and Rsbuild sites extend the DOM + JSX config instead (lib includes DOM, jsx is react-jsx):
{
"extends": "@heyq02/rstack-config/tsconfig/app",
"compilerOptions": {
"types": ["@rsbuild/core/types", "node"]
},
"include": ["src", "rsbuild.config.ts", "rstack.config.ts"]
}./tsconfig.app.json is the same file.
Override when needed (tests, extra types, wider include):
{
"extends": "@heyq02/rstack-config/tsconfig",
"compilerOptions": {
"rootDir": ".",
"types": ["rstack/types", "node"]
},
"include": ["src", "tests", "rstack.config.ts"]
}| Subpath | Role | Inspired by |
|---|---|---|
@heyq02/rstack-config/rslint |
lint() Rslint flat-config |
@sxzz/eslint-config |
@heyq02/rstack-config/fmt |
fmt() for rs fmt / define.fmt |
@sxzz/prettier-config |
@heyq02/rstack-config/rslib |
lib() / nodeLib() |
tsdown-preset-sxzz |
@heyq02/rstack-config/test |
test() for rs test |
Rstack-native |
@heyq02/rstack-config/staged |
staged() for rs staged |
Rstack-native |
@heyq02/rstack-config/commitlint |
commitlint() for @commitlint/cli |
Conventional Commits |
@heyq02/rstack-config/tsconfig |
shareable tsconfig.json |
Node library defaults |
@heyq02/rstack-config/tsconfig/app |
site tsconfig (DOM + JSX) |
Rsbuild / browser |
@heyq02/rstack-config/rstack |
rstack() → define.extends shared cfg |
Rstack define.extends |
There is no root . export — pick a subpath.
| Option | Default | Description |
|---|---|---|
app |
omitted | Rsbuild options for define.app(), or false. Setting it defaults lib to false |
lib |
{} |
LibOptions or false to omit. false when app is set |
node |
true |
Use nodeLib instead of lib |
lint |
Node library lint defaults | lint options or false. With app, node/no-missing-import is off |
fmt |
{} |
fmt() options or false |
test |
{} |
test() options or false |
staged |
{} |
staged() options or false |
| Option | Default | Description |
|---|---|---|
typescript |
auto (typescript) |
true / false / { typeChecked, strict, stylistic, projectService } |
javascript |
true |
JavaScript recommended + opinionated rules |
unicorn |
true |
Unicorn recommended + overrides |
imports |
true |
Import recommended + no-default-export, etc. |
sortImports |
true |
true / false / Oxfmt-like options → import/order |
node |
true (module) |
true / 'module' / 'script' / 'mixed' |
promise |
true |
Promise recommended |
react |
auto (react) |
React + React Hooks (+ jsx-a11y) |
jest |
auto (jest) |
Jest recommended |
rstest |
auto (rstest) |
Rstest recommended |
unusedImports |
true |
eslint-plugin-unused-imports |
regexp |
true |
eslint-plugin-regexp |
deMorgan |
true |
eslint-plugin-de-morgan |
eslintComments |
true |
@eslint-community/eslint-plugin-eslint-comments |
ignores |
[] |
Extra global ignore patterns |
| Option | Default | Description |
|---|---|---|
entry |
'index' |
'index' / 'shallow' / 'all' / custom source.entry |
inlineDeps |
[] |
Bundle instead of externalize |
publint |
true |
rsbuild-plugin-publint |
attw |
true |
rsbuild-plugin-arethetypeswrong with ESM-only defaults. Needs TypeScript JS API (typescript@npm:@typescript/typescript6 when using TS 7 / tsgo). |
rsdoctor |
false |
@rsdoctor/rspack-plugin on tools.rspack. true runs when RSDOCTOR=true; 'ci-only' runs when CI is set. CI writes dist/.rsdoctor/rsdoctor-data.json for rsdoctor-action and does not start the client server. |
Rstack CLI has no separate Rsdoctor entry. Install the plugin and opt in on the Rslib preset:
pnpm add -D @rsdoctor/rspack-pluginimport { define } from 'rstack';
import { nodeLib } from '@heyq02/rstack-config/rslib';
define.lib(() =>
nodeLib({
rsdoctor: true,
}),
);RSDOCTOR=true pnpm exec rs libIn CI, rsdoctor: 'ci-only' (or rsdoctor: true with RSDOCTOR=true) writes brief JSON to dist/.rsdoctor/rsdoctor-data.json after the build. rsdoctor-action reads that file, compares it with main, and comments on the pull request.
fmt({
singleQuote: true,
sortPackageJson: true,
ignorePatterns: ['.github/**'],
});
test({ strictCoverage: true });
staged({ formatAll: true, lintFix: true });
commitlint({ headerMaxLength: 100 });- import { heyq02 } from '@heyq02/rslint-config';
+ import { lint } from '@heyq02/rstack-config/rslint';
- import { lib, nodeLib } from '@heyq02/rslib-config';
+ import { lib, nodeLib } from '@heyq02/rstack-config/rslib';
+ import { rstack } from '@heyq02/rstack-config/rstack';
+ define.extends([rstack()]);pnpm install
pnpm run build
pnpm run lint
pnpm run typecheck
pnpm run test
pnpm run format # automd + rs fmt
pnpm run check # rs check + typecheck
pnpm release # global bumpp: bump, commit, tag, pushpnpm releaseUses your globally installed bumpp to bump, commit, tag, and push. The v* tag runs .github/workflows/release.yml (GitHub Release via changelogithub + npm publish via sxzz/workflows). Every push/PR also gets a preview package from .github/workflows/release-commit.yml (pkg-pr-new).
Configure npm Trusted Publishing for this GitHub repo so OIDC publish works without NPM_TOKEN.
Published under the MIT license.
Made by @heyq02 and community 💛
🤖 auto updated with automd