Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/nutui-taro-demo/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ const config = {
sass: {
resource: [
path.resolve(__dirname, '../../../', fileStr),
path.resolve(__dirname, '../../../', themeStr),
],
},
defineConstants: {},
Expand Down
2 changes: 1 addition & 1 deletion packages/nutui-taro-demo/src/app.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@import '../../../src/styles/jd-font.scss';
@import '../../../src/styles/theme-default.scss';

body {
font-size: 14px;
Expand Down
40 changes: 38 additions & 2 deletions scripts/build-taro.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const require = createRequire(import.meta.url)
const pxToScalePxInComponentScss = require('./px-to-scale-px-in-component-scss.cjs')

// PostCSS plugin: remove .nut-rtl selectors for mini-program builds
const stripRtlPlugin = {
postcssPlugin: 'strip-rtl',
Rule(rule) {
if (rule.selector && rule.selector.includes('.nut-rtl')) {
rule.remove()
}
},
}
const dist = 'release/taro/dist'
const filePath = resolve(__dirname, '../package.json')
const packageJson = JSON.parse(readFileSync(filePath, 'utf8'))
Expand Down Expand Up @@ -289,6 +299,11 @@ async function buildAllCSS(themeName = '') {
}
},
})

// generate mini-program variant without RTL rules
const fullCss = await readFile(join(__dirname, `../${dist}/${themeStylePath}.css`), { encoding: 'utf8' })
const miniResult = await postcss([stripRtlPlugin]).process(fullCss, { from: undefined })
await dest(`${dist}/${themeStylePath}.mini.css`, miniResult.css)
}

async function buildThemeCSS() {
Expand Down Expand Up @@ -400,29 +415,41 @@ async function buildCSS(themeName = '') {
await dest(join(`${dist}/es`, cssPath, `${themeDir}/${base}`), postcssRes.css)
await dest(join(`${dist}/cjs`, cssPath, `${themeDir}/${base}`), postcssRes.css)

const code = sass.compileString(variables + '\n' + postcssRes.css.replaceAll('../../../../', '../../'), {
// strip CSS entity blocks (:root/page {}) from variables before per-component compile
// these blocks belong in the global style.css entry, not repeated in each component
const variablesSassOnly = variables.toString().replace(/:root\s*,?\s*\npage\s*\{[^}]*\}\n?/g, '')
const code = sass.compileString(variablesSassOnly + '\n' + postcssRes.css.replaceAll('../../../../', '../../'), {
loadPaths: [loadPath],
})
await dest(join(`${dist}/es`, cssPath, `${themeDir}/style.css`), code.css)
await dest(join(`${dist}/cjs`, cssPath, `${themeDir}/style.css`), code.css)

// strip .nut-rtl rules for mini-program — no RTL needed in weapp/jd/tt etc.
const miniCssResult = await postcss([stripRtlPlugin]).process(code.css, { from: undefined })
await dest(join(`${dist}/es`, cssPath, `${themeDir}/style.mini.css`), miniCssResult.css)
await dest(join(`${dist}/cjs`, cssPath, `${themeDir}/style.mini.css`), miniCssResult.css)

const jsContent = []
const cssContent = []
const miniContent = []
atRules.forEach((rule) => {
rule = rule.replaceAll('\'', '')
if (rule.indexOf('../styles/') > -1) {
const ext = extname(rule)
jsContent.push(`import '../../${rule}${ext ? '' : '.scss'}';`)
miniContent.push(`import '../../${rule}${ext ? '' : '.scss'}';`)
} else if (rule.startsWith('../') || rule.startsWith('./')) {
const base = basename(rule)
const ext = extname(base)
const name = base.replace(ext, '')
jsContent.push(`import '../../${name}/${themeDir}';`)
cssContent.push(`import '../../${name}/${themeDir}/css';`)
miniContent.push(`import '../../${name}/${themeDir}/mini';`)
}
})
jsContent.push(`import './${base}';`)
cssContent.push(`import './style.css';`)
miniContent.push(`import './style.mini.css';`)

await dest(
join(`${dist}/cjs`, cssPath, `${themeDir}/index.js`),
Expand All @@ -436,6 +463,13 @@ async function buildCSS(themeName = '') {
join(`${dist}/cjs`, cssPath, `${themeDir}/css.js`),
cssContent.join('\n'),
)

// 写 mini 文件(小程序专用,无 RTL)
await dest(join(`${dist}/es`, cssPath, `${themeDir}/mini.js`), miniContent.join('\n'))
await dest(
join(`${dist}/cjs`, cssPath, `${themeDir}/mini.js`),
miniContent.join('\n'),
)
}
}

Expand Down Expand Up @@ -489,8 +523,10 @@ async function buildHarmonyCSS(themeName = '') {
return result
})
const themeDir = themeName ? `style-${themeName}` : 'style'
// strip CSS entity blocks from variables — same as buildCSS
const variablesSassOnly = variables.toString().replace(/:root\s*,?\s*\npage\s*\{[^}]*\}\n?/g, '')
const code = sass.compileString(
variables + '\n' + postcssRes.css.replaceAll('../../../../', '../../'),
variablesSassOnly + '\n' + postcssRes.css.replaceAll('../../../../', '../../'),
{
loadPaths: [loadPath],
},
Expand Down
15 changes: 10 additions & 5 deletions src/sites/sites-react/doc/docs/taro/start-react.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ module.exports = {
customStyleName: (name) =>
`@nutui/nutui-react-taro/dist/es/packages/${name.toLowerCase()}/style`,
// customStyleName: (name) => `@nutui/nutui-react-taro/dist/es/packages/${name.toLowerCase()}/style/css`
// mini-program only: removes RTL styles for smaller bundle
// customStyleName: (name) => `@nutui/nutui-react-taro/dist/es/packages/${name.toLowerCase()}/style/mini`
},
'nutui-react',
],
Expand All @@ -212,11 +214,14 @@ Taro config/index.js
```js
{
sass: {
data: '@import "@nutui/nutui-react-taro/dist/styles/variables.scss";'
// JMAPP Theme
// data: `@import '@nutui/nutui-react-taro/dist/styles/variables-jmapp.scss';`
// JRKF Theme
// data: `@import '@nutui/nutui-react-taro/dist/styles/variables-jrkf.scss';`
// resource only injects pure Sass variables/functions, no CSS entity output
resource: [
path.resolve(__dirname, 'node_modules/@nutui/nutui-react-taro/dist/styles/variables.scss'),
// JMAPP Theme
// path.resolve(__dirname, 'node_modules/@nutui/nutui-react-taro/dist/styles/variables-jmapp.scss'),
// JRKF Theme
// path.resolve(__dirname, 'node_modules/@nutui/nutui-react-taro/dist/styles/variables-jrkf.scss'),
],
}
}
```
Expand Down
15 changes: 10 additions & 5 deletions src/sites/sites-react/doc/docs/taro/start-react.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ module.exports = {
`@nutui/nutui-react-taro/dist/es/packages/${name.toLowerCase()}/style`,
// 自动加载 css 样式文件
// customStyleName: (name) => `@nutui/nutui-react-taro/dist/es/packages/${name.toLowerCase()}/style/css`
// 自动加载 css 样式文件(小程序专用,已移除 RTL 样式,体积更小)
// customStyleName: (name) => `@nutui/nutui-react-taro/dist/es/packages/${name.toLowerCase()}/style/mini`

// JMAPP 主题
// 自动加载 scss 样式文件
Expand All @@ -233,11 +235,14 @@ module.exports = {
```js
{
sass: {
data: '@import "@nutui/nutui-react-taro/dist/styles/variables.scss";'
// JMAPP 主题
// data: `@import '@nutui/nutui-react-taro/dist/styles/variables-jmapp.scss';`
// JRKF 主题
// data: `@import '@nutui/nutui-react-taro/dist/styles/variables-jrkf.scss';`
// resource 仅注入纯 Sass 变量/函数,不含实体 CSS 输出
resource: [
path.resolve(__dirname, 'node_modules/@nutui/nutui-react-taro/dist/styles/variables.scss'),
// JMAPP 主题
// path.resolve(__dirname, 'node_modules/@nutui/nutui-react-taro/dist/styles/variables-jmapp.scss'),
// JRKF 主题
// path.resolve(__dirname, 'node_modules/@nutui/nutui-react-taro/dist/styles/variables-jrkf.scss'),
],
}
}
```
Expand Down
161 changes: 160 additions & 1 deletion src/sites/sites-react/doc/docs/taro/theme-react.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default defineConfig({

#### Webpack 方式

```javascript
````javascript
{
test: /\.(sa|sc)ss$/,
use: [
Expand All @@ -64,4 +64,163 @@ export default defineConfig({
}
]
}

### Taro 项目:使用 sass.resource 的性能说明

在 Taro 项目中,通常会通过 `config/index.js` 的 `sass.resource` 来注入全局 SCSS 变量:

```javascript
// config/index.js
const config = {
sass: {
resource: [
path.resolve(__dirname, '../src/styles/variables.scss'),
],
},
}
````

**注意**:`sass.resource` 会将文件内容注入到项目中**每一个** SCSS 文件的头部参与编译。因此,应**只将包含纯 Sass 变量/函数定义的文件**放入 `sass.resource`——即只包含 `$variable`、`@mixin`、`@function` 等编译期定义,不含任何实体 CSS 输出(如 `:root {}` 块或 `@font-face` 声明)。

`theme-default.scss`(主题 CSS 变量文件)全部内容为 `:root, page {}` CSS 自定义属性声明,属于**运行时 CSS**,无需在每个组件编译时重复注入。应将其移至全局入口样式(如 `app.scss`)中一次性引入:

```scss
/* app.scss —— 全局引入一次,而非通过 sass.resource 重复注入 */
@import '../../src/styles/theme-default.scss';
```

```javascript
// config/index.js —— sass.resource 只保留纯 Sass 变量/函数文件
const config = {
sass: {
resource: [
path.resolve(__dirname, '../src/styles/variables.scss'),
// 不要在这里加入 theme-default.scss
],
},
}
```

这样可以避免 `theme-default.scss` 的 380+ 行 CSS 被重复编译到每个 SCSS 文件中,显著减少编译产物体积。

## 暗黑模式

NutUI-React (Taro 版) 原生支持暗黑模式。组件库在暗黑模式下使用一套独立调优的色彩变量。

### 方案 A:跟随 APP 主动切换(推荐)

如果你的小程序/应用需要由用户在设置中手动切换亮色/暗黑模式(不强制跟随系统),可以使用**类名切换方案**。

#### 1. 引入样式

首先,在项目入口或全局样式文件中引入组件库的默认样式和暗黑样式。你可以根据项目配置选择引入 CSS 或 SCSS:

##### 引入 CSS 样式(适用于普通 JS/TS 项目):

参考方式二。

##### 引入 SCSS 样式(适用于配置了 Sass 预处理器的项目):

参考方式二。

#### 2. 挂载类名

在小程序的根页面容器(如 Taro 的 `View` 标签或全局包裹页面组件)上挂载 `.nut-theme-dark` 类名:

`isDark` 的值,需要桥接 APP 的实现。

```tsx
import React, { useState } from 'react'
import { View, Button } from '@tarojs/components'

function App() {
const [isDark, setIsDark] = useState(false)

return (
<View
className={isDark ? 'nut-theme-dark' : ''}
style={{ minHeight: '100vh' }}
>
<Button onClick={() => setIsDark(!isDark)}>
当前模式:{isDark ? '暗黑模式' : '普通模式'}
</Button>
{/* 你的其他组件 */}
</View>
)
}
```

#### 3. 配合 ConfigProvider 进行定制

如果你需要更灵活地在 Taro/React 逻辑中控制暗黑模式,或者对暗黑色彩变量进行定制,可以结合 `ConfigProvider` 使用:

```tsx
import React, { useState } from 'react'
import { View, Button } from '@tarojs/components'
import { ConfigProvider } from '@nutui/nutui-react-taro'

// 自定义暗黑模式下的主色调
const darkTheme = {
nutuiColorPrimary: '#ff0f23',
nutuiColorBackgroundOverlay: '#1f2226',
}

function App() {
const [isDark, setIsDark] = useState(false)

return (
<ConfigProvider
theme={isDark ? darkTheme : undefined}
className={isDark ? 'nut-theme-dark' : ''}
>
<View style={{ minHeight: '100vh' }}>
<Button onClick={() => setIsDark(!isDark)}>切换模式</Button>
{/* 组件库在暗黑模式下会自动识别 .nut-theme-dark,且通过 theme 属性定制的变量会自动覆盖 */}
</View>
</ConfigProvider>
)
}
```

---

### 方案 B:跟随系统媒体查询

如果你的应用希望**完全自动跟随手机操作系统**的亮暗模式设置(例如微信小程序的深色模式),无需手动提供开关,可使用**系统媒体查询方案**。

#### 1. 编写全局 SCSS

在全局 SCSS 样式文件中利用媒体查询动态注入暗黑变量。对于小程序环境,样式需要作用在 `page` 节点上以覆盖整页背景:

```scss
@media (prefers-color-scheme: dark) {
page {
// 导入组件库自带的暗黑变量映射
@import '@nutui/nutui-react-taro/dist/styles/theme-dark.scss';
}
}
```

#### 2. 引入配置

确保你的小程序(以微信小程序为例)在 `app.json` 或 `app.config.ts` 中配置支持暗黑模式:

```json
{
"darkmode": true
}
```

---

### 方案对比与选用建议

| 方案 | 适用场景 | 实现原理 | 优点 |
| --- | --- | --- | --- |
| **方案 A (跟随 APP)** | 应用内需要提供“深色/浅色”的手动切换开关,不强绑定系统。 | 挂载类名 `.nut-theme-dark` 或结合 `ConfigProvider` | 灵活度最高,支持局部容器应用暗黑,可通过 JS/TS 灵活控制。 |
| **方案 B (跟随系统)** | 应用不需要手动开关,纯粹期望手机系统是深色模式时,小程序自动呈现深色。 | `@media (prefers-color-scheme: dark)` | 无需 JS 介入,完全由宿主小程序容器自动触发渲染。 |

```


```
Loading