Browse Source

fix(frontend): revert to @vue/cli-service v4 + patch remove-scoped.js via postinstall

v5 (webpack 5) breaks @dcloudio/uni-mp-weixin (needs webpack/lib/GraphHelpers).
v4 alone had remove-scoped.js crash on empty selector (spaces access).

Fix:
- Keep @vue/cli-service ~4.5.0, postcss 7.0.39 pinned
- Add postinstall-fix.js to patch remove-scoped.js selector.first guard
- Add node-sass as devDependency (needed by uni-app sass-loader)
- Restore .postcssrc.js, postcss.config.js, vue.config.js
- Document build constraints in AGENTS.md
liaoxg 3 months ago
parent
commit
382c8b3886

+ 9 - 0
cfc-frontend/.postcssrc.js

@@ -0,0 +1,9 @@
+// PostCSS config for uni-app
+const path = require('path');
+module.exports = {
+  plugins: {
+    'autoprefixer': {
+      overrideBrowserslist: ['> 1%', 'last 2 versions', 'not dead']
+    }
+  }
+}

+ 47 - 2
cfc-frontend/AGENTS.md

@@ -64,12 +64,56 @@ pages.json 中定义 5 个 TabBar 页面(TabBar 文案,非五维维度名)
 - 四级地址选择: `components/address-picker.vue` - 省市区街道联动
 - 四级地址选择: `components/address-picker.vue` - 省市区街道联动
 - 双角色界面: 根据用户角色(parent/child)显示不同UI
 - 双角色界面: 根据用户角色(parent/child)显示不同UI
 
 
+## BUILD SYSTEM
+
+### 依赖约束(2026-07-23 修复后锁定)
+
+| 包 | 版本 | 原因 |
+|----|------|------|
+| `@vue/cli-service` | `~4.5.0` | v5 使用 webpack 5,`@dcloudio/uni-mp-weixin` 依赖 `webpack/lib/GraphHelpers`(webpack 4 特有)|
+| `@vue/cli-plugin-babel` | `~4.5.0` | 与 cli-service v4 匹配 |
+| `postcss` (override + dependency) | `7.0.39` | `remove-scoped.js` 在 PostCSS 8 下会走 v8 插件路径,但 `spaces` 属性访问有兼容问题 |
+| `node-sass` | (devDependency) | `@dcloudio/vue-cli-plugin-uni` 内置的 sass-loader 需要 |
+| `copy-webpack-plugin` | `^5.1.2` | 与 cli-service v4 的 webpack 4 兼容 |
+
+### postinstall 自动补丁
+
+`npm install` 后会自动运行 `postinstall-fix.js`,修补 `remove-scoped.js` 中的 `spaces` 崩溃:
+
+```js
+// 问题: selector.first 可能为 undefined
+// 修改前: selector.first.spaces.before = '';
+// 修改后: if (selector.first) { selector.first.spaces.before = ''; }
+```
+
+### 已知编译错误处理
+
+| 错误 | 原因 | 处理 |
+|------|------|------|
+| `Cannot read properties of undefined (reading 'spaces')` in `remove-scoped.js` | PostCSS 节点没有 `spaces` 属性 | postinstall-fix.js 自动补丁 |
+| `Cannot find module 'webpack/lib/GraphHelpers'` | `@vue/cli-service v5` 的 webpack 5 不支持 | 保持 `@vue/cli-service ~4.5.0` |
+| `Cannot find module 'node-sass'` | uni-app sass-loader 需要 | `npm install node-sass --save-dev` |
+| `getPreCompileOptions` DevTools 内部错误 | `dist/dev/mp-weixin/` 缺少 `project.config.json` | 先 `npm run build:mp-weixin` 生成 dist |
+| `Unexpected token` in `.vue` | JS 语法错误,常见于方法被截断/拼接错误 | 检查 v-on:click/方法调用是否正确闭合 |
+
+### 修改 `remove-scoped.js` 的风险
+
+该文件在 `node_modules/@dcloudio/vue-cli-plugin-uni/` 内,每次 `npm install` 会通过 `postinstall-fix.js` 自动恢复补丁。如需手动执行:
+
+```bash
+node postinstall-fix.js
+```
+
+**不要**将 `@vue/cli-service` 升级到 v5,除非:
+- `@dcloudio/uni-mp-weixin` 确认移除了 `webpack/lib/GraphHelpers` 依赖
+- 或者找到替代方法处理独立分包插件
+
 ## COMMANDS
 ## COMMANDS
 
 
 ```bash
 ```bash
-npm install             # 安装依赖
+npm install             # 安装依赖(自动运行 postinstall-fix.js)
 npm run dev:mp-weixin   # 开发模式 (微信小程序)
 npm run dev:mp-weixin   # 开发模式 (微信小程序)
-npm run build:mp-weixin # 构建生产版本
+npm run build:mp-weixin # 构建生产版本(仅生成 dist,不在 DevTools 里编译)
 ```
 ```
 
 
 ## NOTES
 ## NOTES
@@ -78,3 +122,4 @@ npm run build:mp-weixin # 构建生产版本
 - 后端API地址: 配置在 `config/index.js` 的 `baseUrl`
 - 后端API地址: 配置在 `config/index.js` 的 `baseUrl`
 - 地址数据: 使用后端街道API获取四级地址数据
 - 地址数据: 使用后端街道API获取四级地址数据
 - 调试工具: 微信开发者工具需配置合法域名
 - 调试工具: 微信开发者工具需配置合法域名
+- **不要在 WeChat DevTools 里直接编译源码**,先 `npm run build:mp-weixin` 生成 `dist/dev/mp-weixin/`,然后在 DevTools 中导入该目录

File diff suppressed because it is too large
+ 307 - 329
cfc-frontend/package-lock.json


+ 10 - 5
cfc-frontend/package.json

@@ -10,11 +10,12 @@
     "dev:h5": "cross-env NODE_ENV=development UNI_PLATFORM=h5 UNI_INPUT_DIR=. UNI_CLI_CONTEXT=. vue-cli-service uni-serve",
     "dev:h5": "cross-env NODE_ENV=development UNI_PLATFORM=h5 UNI_INPUT_DIR=. UNI_CLI_CONTEXT=. vue-cli-service uni-serve",
     "build:h5": "cross-env NODE_ENV=production UNI_PLATFORM=h5 UNI_INPUT_DIR=. UNI_CLI_CONTEXT=. vue-cli-service uni-build",
     "build:h5": "cross-env NODE_ENV=production UNI_PLATFORM=h5 UNI_INPUT_DIR=. UNI_CLI_CONTEXT=. vue-cli-service uni-build",
     "build:mp-weixin": "cross-env NODE_ENV=development UNI_PLATFORM=mp-weixin UNI_INPUT_DIR=. UNI_CLI_CONTEXT=. vue-cli-service uni-build",
     "build:mp-weixin": "cross-env NODE_ENV=development UNI_PLATFORM=mp-weixin UNI_INPUT_DIR=. UNI_CLI_CONTEXT=. vue-cli-service uni-build",
+    "postinstall": "node postinstall-fix.js",
     "postbuild": "node -e \"try{require('fs').unlinkSync('dist/dev/mp-weixin/custom-tab-bar/index.vue')}catch(e){};try{const c=require('./dist/dev/mp-weixin/project.config.json');c.setting=c.setting||{};c.setting.lazyCodeLoading='requiredComponents';require('fs').writeFileSync('./dist/dev/mp-weixin/project.config.json',JSON.stringify(c,null,2))}catch(e){}\""
     "postbuild": "node -e \"try{require('fs').unlinkSync('dist/dev/mp-weixin/custom-tab-bar/index.vue')}catch(e){};try{const c=require('./dist/dev/mp-weixin/project.config.json');c.setting=c.setting||{};c.setting.lazyCodeLoading='requiredComponents';require('fs').writeFileSync('./dist/dev/mp-weixin/project.config.json',JSON.stringify(c,null,2))}catch(e){}\""
   },
   },
   "devDependencies": {
   "devDependencies": {
-    "@babel/core": "^7.29.7",
-    "@babel/preset-env": "^7.29.7",
+    "@babel/core": "^7.16.0",
+    "@babel/preset-env": "^7.16.0",
     "@dcloudio/uni-app": "^2.0.2-5010420260703001",
     "@dcloudio/uni-app": "^2.0.2-5010420260703001",
     "@dcloudio/uni-cli-i18n": "^2.0.2-5010420260703001",
     "@dcloudio/uni-cli-i18n": "^2.0.2-5010420260703001",
     "@dcloudio/uni-cli-shared": "^2.0.2-5010420260703001",
     "@dcloudio/uni-cli-shared": "^2.0.2-5010420260703001",
@@ -24,12 +25,13 @@
     "@dcloudio/uni-mp-weixin": "^2.0.2-5010420260703001",
     "@dcloudio/uni-mp-weixin": "^2.0.2-5010420260703001",
     "@dcloudio/uni-template-compiler": "^2.0.2-5010420260703001",
     "@dcloudio/uni-template-compiler": "^2.0.2-5010420260703001",
     "@dcloudio/webpack-uni-pages-loader": "^2.0.2-5010420260703001",
     "@dcloudio/webpack-uni-pages-loader": "^2.0.2-5010420260703001",
-    "@vue/cli-plugin-babel": "^5.0.9",
-    "@vue/cli-service": "^5.0.9",
+    "@vue/cli-plugin-babel": "~4.5.0",
+    "@vue/cli-service": "~4.5.0",
     "@vue/compiler-sfc": "2.7.16",
     "@vue/compiler-sfc": "2.7.16",
     "babel-jest": "^26.6.3",
     "babel-jest": "^26.6.3",
-    "copy-webpack-plugin": "^14.0.0",
+    "copy-webpack-plugin": "^5.1.2",
     "jest": "^26.6.3",
     "jest": "^26.6.3",
+    "node-sass": "^9.0.0",
     "vue": "2.7.16",
     "vue": "2.7.16",
     "vuex": "^3.6.2",
     "vuex": "^3.6.2",
     "yalc": "^1.0.0-pre.53"
     "yalc": "^1.0.0-pre.53"
@@ -37,10 +39,13 @@
   "dependencies": {
   "dependencies": {
     "@dcloudio/vue-cli-plugin-uni": "^2.0.2-5000720260410001",
     "@dcloudio/vue-cli-plugin-uni": "^2.0.2-5000720260410001",
     "@dcloudio/webpack-uni-mp-loader": "^2.0.2-5010520260709001",
     "@dcloudio/webpack-uni-mp-loader": "^2.0.2-5010520260709001",
+    "postcss": "7.0.39",
     "regenerator-runtime": "^0.14.1",
     "regenerator-runtime": "^0.14.1",
     "yargs-parser": "^22.0.0"
     "yargs-parser": "^22.0.0"
   },
   },
   "overrides": {
   "overrides": {
+    "postcss": "7.0.39",
+    "postcss-selector-parser": "5.0.0",
     "@vue/compiler-sfc": "2.7.16",
     "@vue/compiler-sfc": "2.7.16",
     "@vue/babel-plugin-jsx": "npm:@vue/babel-plugin-jsx@^1.1.1"
     "@vue/babel-plugin-jsx": "npm:@vue/babel-plugin-jsx@^1.1.1"
   }
   }

+ 9 - 0
cfc-frontend/postcss.config.js

@@ -0,0 +1,9 @@
+// PostCSS config for uni-app mp-weixin build
+// Fixed: ensure compat with @vue/component-compiler-utils (PostCSS 7.x)
+module.exports = {
+  plugins: [
+    require('postcss-import'),
+    require('postcss-url'),
+    require('autoprefixer')
+  ]
+}

+ 51 - 0
cfc-frontend/postinstall-fix.js

@@ -0,0 +1,51 @@
+#!/usr/bin/env node
+/**
+ * Postinstall fix: patch remove-scoped.js to guard unsafe `.spaces` access.
+ *
+ * Root cause: remove-scoped.js in @dcloudio/vue-cli-plugin-uni accesses
+ * `selector.first.spaces.before` without checking if `selector.first` exists.
+ * When postcss-selector-parser produces an empty selector (e.g. from certain
+ * CSS edge cases), this crashes with:
+ *   "TypeError: Cannot read properties of undefined (reading 'spaces')"
+ *
+ * The fix adds a guard before accessing .spaces on selector.first.
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+const filePath = path.join(
+  __dirname,
+  'node_modules',
+  '@dcloudio',
+  'vue-cli-plugin-uni',
+  'packages',
+  '@vue',
+  'component-compiler-utils',
+  'dist',
+  'stylePlugins',
+  'remove-scoped.js'
+);
+
+if (!fs.existsSync(filePath)) {
+  console.log('remove-scoped.js not found, skipping patch');
+  process.exit(0);
+}
+
+let content = fs.readFileSync(filePath, 'utf8');
+
+// Fix line 56: selector.first.spaces.before = '';
+// Guard: if (selector.first && selector.first.spaces)
+if (content.includes("selector.first.spaces.before = '';")) {
+  content = content.replace(
+    /selector\.first\.spaces\.before = '';/,
+    "if (selector.first) { selector.first.spaces.before = ''; }"
+  );
+  fs.writeFileSync(filePath, content, 'utf8');
+  console.log('✅ remove-scoped.js: patched selector.first guard');
+} else if (content.includes("if (selector.first) { selector.first.spaces.before = ''; }")) {
+  console.log('remove-scoped.js: already patched, skipping');
+} else {
+  console.log('remove-scoped.js: unexpected format, manual check needed');
+  console.log('Look for: selector.first.spaces.before');
+}

+ 11 - 0
cfc-frontend/vue.config.js

@@ -0,0 +1,11 @@
+// vue.config.js - disable thread-loader for compatibility
+module.exports = {
+  parallel: false, // Ensure we use the correct source directory
+  pages: undefined,
+  configureWebpack: {
+    resolve: {
+      extensions: ['.vue', '.js', '.json'], // Ensure .vue files resolve correctly
+    },
+    node: false,
+  },
+}

Some files were not shown because too many files changed in this diff