From ac58fa9ae8df5d48940c2ee11102b4659bbd599e Mon Sep 17 00:00:00 2001 From: hanyuxinting Date: Sat, 22 Aug 2026 00:36:07 +0800 Subject: [PATCH 1/3] perf(taro-demo): avoid duplicating theme-default.scss via sass.resource Move theme-default.scss from sass.resource to a single global @import in app.scss, preventing 380+ lines of CSS from being compiled into every SCSS file. Update theme customization docs with sass.resource usage guidelines. Co-Authored-By: Claude --- packages/nutui-taro-demo/config/index.js | 1 - packages/nutui-taro-demo/src/app.scss | 2 +- .../sites-react/doc/docs/taro/theme-react.md | 161 +++++++++++++++++- 3 files changed, 161 insertions(+), 3 deletions(-) diff --git a/packages/nutui-taro-demo/config/index.js b/packages/nutui-taro-demo/config/index.js index 47df7565b0..d58f4ca7ae 100644 --- a/packages/nutui-taro-demo/config/index.js +++ b/packages/nutui-taro-demo/config/index.js @@ -131,7 +131,6 @@ const config = { sass: { resource: [ path.resolve(__dirname, '../../../', fileStr), - path.resolve(__dirname, '../../../', themeStr), ], }, defineConstants: {}, diff --git a/packages/nutui-taro-demo/src/app.scss b/packages/nutui-taro-demo/src/app.scss index 9049abed34..f007aeeeee 100644 --- a/packages/nutui-taro-demo/src/app.scss +++ b/packages/nutui-taro-demo/src/app.scss @@ -1,4 +1,4 @@ -@import '../../../src/styles/jd-font.scss'; +@import '../../../src/styles/theme-default.scss'; body { font-size: 14px; diff --git a/src/sites/sites-react/doc/docs/taro/theme-react.md b/src/sites/sites-react/doc/docs/taro/theme-react.md index 0a6b733c82..1a69e202e3 100644 --- a/src/sites/sites-react/doc/docs/taro/theme-react.md +++ b/src/sites/sites-react/doc/docs/taro/theme-react.md @@ -51,7 +51,7 @@ export default defineConfig({ #### Webpack 方式 -```javascript +````javascript { test: /\.(sa|sc)ss$/, use: [ @@ -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 ( + + + {/* 你的其他组件 */} + + ) +} +``` + +#### 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 ( + + + + {/* 组件库在暗黑模式下会自动识别 .nut-theme-dark,且通过 theme 属性定制的变量会自动覆盖 */} + + + ) +} +``` + +--- + +### 方案 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 介入,完全由宿主小程序容器自动触发渲染。 | + +``` + + ``` From 20caed4a6ed1a5d1f19dda37e6e2d1e44ad3820e Mon Sep 17 00:00:00 2001 From: hanyuxinting Date: Sat, 22 Aug 2026 00:54:00 +0800 Subject: [PATCH 2/3] perf(build): strip :root block from per-component style.css + fix sass.resource docs - buildCSS/buildHarmonyCSS: strip the :root,page{} scale-vars block from variables.scss before compiling each component's style.css. The block belongs in the global style.css entry only, not repeated across 106 components (~41KB total saving in per-component output). - docs: replace deprecated sass.data with sass.resource in taro start guides (zh-CN and en-US), consistent with Taro v3+ config API. Co-Authored-By: Claude --- scripts/build-taro.mjs | 9 +++++++-- .../sites-react/doc/docs/taro/start-react.en-US.md | 13 ++++++++----- src/sites/sites-react/doc/docs/taro/start-react.md | 13 ++++++++----- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/scripts/build-taro.mjs b/scripts/build-taro.mjs index bf7446d930..5d64a0ea5c 100644 --- a/scripts/build-taro.mjs +++ b/scripts/build-taro.mjs @@ -400,7 +400,10 @@ 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) @@ -489,8 +492,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], }, diff --git a/src/sites/sites-react/doc/docs/taro/start-react.en-US.md b/src/sites/sites-react/doc/docs/taro/start-react.en-US.md index 6b306a0105..50f0e4407a 100644 --- a/src/sites/sites-react/doc/docs/taro/start-react.en-US.md +++ b/src/sites/sites-react/doc/docs/taro/start-react.en-US.md @@ -212,11 +212,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'), + ], } } ``` diff --git a/src/sites/sites-react/doc/docs/taro/start-react.md b/src/sites/sites-react/doc/docs/taro/start-react.md index d7e58c23dc..d7ee69352d 100644 --- a/src/sites/sites-react/doc/docs/taro/start-react.md +++ b/src/sites/sites-react/doc/docs/taro/start-react.md @@ -233,11 +233,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'), + ], } } ``` From 31b73e2832973779864ec7eb3de1af2ff7b59dd8 Mon Sep 17 00:00:00 2001 From: hanyuxinting Date: Sat, 22 Aug 2026 00:57:50 +0800 Subject: [PATCH 3/3] perf(build): generate style.mini.css without RTL rules for mini-program Add stripRtlPlugin (PostCSS) to strip .nut-rtl selectors when building mini-program style outputs: - buildCSS: emit style.mini.css + mini.js per component alongside style.css - buildAllCSS: emit style.mini.css global entry alongside style.css - docs: add mini customStyleName option in babel-plugin-import examples RTL rules are unused in weapp/jd/tt environments. Stripping them saves ~25.5KB (~7%) from the full bundle and proportionally from per-component on-demand loading. Co-Authored-By: Claude --- scripts/build-taro.mjs | 31 +++++++++++++++++++ .../doc/docs/taro/start-react.en-US.md | 2 ++ .../sites-react/doc/docs/taro/start-react.md | 2 ++ 3 files changed, 35 insertions(+) diff --git a/scripts/build-taro.mjs b/scripts/build-taro.mjs index 5d64a0ea5c..3dde7b6a7b 100644 --- a/scripts/build-taro.mjs +++ b/scripts/build-taro.mjs @@ -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')) @@ -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() { @@ -409,23 +424,32 @@ async function buildCSS(themeName = '') { 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`), @@ -439,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'), + ) } } diff --git a/src/sites/sites-react/doc/docs/taro/start-react.en-US.md b/src/sites/sites-react/doc/docs/taro/start-react.en-US.md index 50f0e4407a..8849eca58a 100644 --- a/src/sites/sites-react/doc/docs/taro/start-react.en-US.md +++ b/src/sites/sites-react/doc/docs/taro/start-react.en-US.md @@ -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', ], diff --git a/src/sites/sites-react/doc/docs/taro/start-react.md b/src/sites/sites-react/doc/docs/taro/start-react.md index d7ee69352d..19ec70d837 100644 --- a/src/sites/sites-react/doc/docs/taro/start-react.md +++ b/src/sites/sites-react/doc/docs/taro/start-react.md @@ -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 样式文件