taro
taro copied to clipboard
taro3.x 引入 lodash 错误
相关平台
支付宝小程序
小程序基础库: 无 使用框架: React
复现步骤
import React, { useState } from 'react'
import { View } from '@tarojs/components'
import { AtSearchBar } from "taro-ui"
// import { debounce } from 'lodash' 此方法引入,页面加载失败,报错为 TypeError: Cannot read property 'prototype' of undefined
import { debounce } from 'lodash/function' 此方法引入,页面正常加载,当触发onChange 事件时,报错为 TypeError: Cannot read property 'now' of undefined
import "taro-ui/dist/style/components/search-bar.scss"
import "taro-ui/dist/style/components/list.scss"
import "taro-ui/dist/style/components/icon.scss"
import './index.scss'
export default function Search() {
const [mine, setMine] = useState({ list: [] }),
[search, setSearch] = useState({ list: [], search: { value: '' }, disabled: false, fixed: true }),
[debounceOptions, setDebounceOptions] = useState({
wait: 800, options: {
'leading': true,
'trailing': false
}
})
const handleSearch = ((e) => {
return debounce((e, options) => {
onSearch(e, options)
}, debounceOptions.wait, {
'leading': false,
'trailing': true
})
})()
const onSearch = (e, options) => {
console.log('onChange', e, options)
}
return (
<View className="page">
<AtSearchBar
actionName='搜一下'
value={search.search.value}
disabled={search.disabled}
fixed={search.fixed}
onChange={handleSearch}
onActionClick={handleSearch}
/>
</View>
)
}
期望结果
期望无报错
实际结果
h5与微信小程序无报错,支付宝小程序报错为: import { debounce } from 'lodash' 此方法引入,页面加载失败,报错为 TypeError: Cannot read property 'prototype' of undefined or import { debounce } from 'lodash/function' 此方法引入,页面正常加载,当触发onChange 事件时,报错为 TypeError: Cannot read property 'now' of undefined
环境信息
👽 Taro v3.0.16
Taro CLI 3.0.16 environment info:
System:
OS: macOS 11.0.1
Shell: 3.2.57 - /bin/bash
Binaries:
Node: 12.0.0 - ~/.nvm/versions/node/v12.0.0/bin/node
Yarn: 1.19.1 - ~/.nvm/versions/node/v10.0.0/bin/yarn
npm: 6.9.0 - ~/.nvm/versions/node/v12.0.0/bin/npm
npmPackages:
@tarojs/components: 3.0.16 => 3.0.16
@tarojs/mini-runner: 3.0.16 => 3.0.16
@tarojs/react: 3.0.16 => 3.0.16
@tarojs/runtime: 3.0.16 => 3.0.16
@tarojs/taro: 3.0.16 => 3.0.16
@tarojs/webpack-runner: 3.0.16 => 3.0.16
babel-preset-taro: 3.0.16 => 3.0.16
eslint-config-taro: 3.0.16 => 3.0.16
react: ^16.10.0 => 16.14.0
taro-ui: ^3.0.0-alpha.3 => 3.0.0-alpha.3
import { debounce } from 'lodash/function'
目前通过在 app.js(app.tsx) 中 写入
Object.assign(global, {
Array: Array,
Date: Date,
Error: Error,
Function: Function,
Math: Math,
Object: Object,
RegExp: RegExp,
String: String,
TypeError: TypeError,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setInterval: setInterval,
clearInterval: clearInterval
})
处理支付宝小程序
引入 lodash
错误问题
我调试得知访问 Array.prototype 时报错,此 Array 来自于 root.Array,root 又来自于
var root = _freeGlobal || freeSelf || Function('return this')();
这个就无解了,要么篡改这个 lodash 的 root 的赋值
var root = {
Array: Array,
Date: Date,
Error: Error,
Function: Function,
Math: Math,
Object: Object,
RegExp: RegExp,
String: String,
TypeError: TypeError,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setInterval: setInterval,
clearInterval: clearInterval
};
要么就只能如你代码所说按需引入,不能全局引入。
Taro 3.6.6 依然可以复现,大佬们来修下呀
import { debounce } from 'lodash/function'
目前通过在 app.js(app.tsx) 中 写入
Object.assign(global, { Array: Array, Date: Date, Error: Error, Function: Function, Math: Math, Object: Object, RegExp: RegExp, String: String, TypeError: TypeError, setTimeout: setTimeout, clearTimeout: clearTimeout, setInterval: setInterval, clearInterval: clearInterval })
处理
支付宝小程序
引入lodash
错误问题
Taro 3.6.6 里对于 lodash
有效,对于 lodash-es
无效。
最后换成了 https://www.npmjs.com/package/@github/mini-throttle
尝试在小程序入口文件引入lodash-fix.js 参考:https://blog.51cto.com/u_13567403/4842786
const obj = {
Array: Array,
Date: Date,
Error: Error,
Function: Function,
Math: Math,
Object: Object,
RegExp: RegExp,
String: String,
TypeError: TypeError,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setInterval: setInterval,
clearInterval: clearInterval
}
Object.assign(global, obj)
if (typeof window === 'object' && typeof window.global === 'object') {
Object.assign(window.global, obj)
}
试试这样?
这是来自QQ邮箱的假期自动回复邮件。 您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。
`const obj = { Array: Array, Date: Date, Error: Error, Function: Function, Math: Math, Object: Object, RegExp: RegExp, String: String, TypeError: TypeError, setTimeout: setTimeout, clearTimeout: clearTimeout, setInterval: setInterval, clearInterval: clearInterval }
Object.assign(global, obj)
if (typeof window === 'object' && typeof window.global === 'object') { Object.assign(window.global, obj) } `
试试这样?
大兄弟这个可以
Taro3 版本下使用 lodash 或者 lodash-es 都会报错。
原因是 lodash/debounce 下引入的 now.js 文件有段代码:
var now = function() {
return root.Date.now();
};
root 在运行时发现是 undefined。 用underscore 平替 lodash / lodash-es 吧。 https://github.com/jashkenas/underscore underscore 下的 now.js 是这样实现的:
export default Date.now || function() {
return new Date().getTime();
};
这样不会有问题
微信小程序下也遇到了,[email protected],[email protected]
这是来自QQ邮箱的假期自动回复邮件。 您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。
基础库版本问题
项目中使用了 lodash-es
,在切换 微信小程序基础库版本 时报了这个错误( 真机基础库版本比较新,预览正常 ),微信开发者工具中测试了基础库 3.2.4
及以上版本不会报错。
TypeError: Cannot read property 'now' of undefined
at now ([email protected]_node_modules_lodash-es_now.js:20)
在 3.2.4
中,打断点调用 Function('return this')()
返回的是 Window
对象。
在 3.2.3
及以下版本中,返回的是空对象 {}
。
直接在控制台调用
Function('return this')()
时返回的是Window
对象。
微信小程序中使用 lodash 不会报错
关闭 sourceMap 后可以方便从控制台直接进入实际代码。
export default {
mini: {
enableSourceMap: false,
},
} satisfies UserConfigExport;
打开 vendors.js 可以看到 node_modules/lodash/lodash.js 中的 global
被替换成了 __webpack_require__.g
。( 参考 https://github.com/NervJS/taro/issues/14033#issuecomment-1605953773 )
If you are using a module which needs global variables in it, use
ProvidePlugin
instead ofglobal
.
// 编译前
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
// 编译后
var freeGlobal = typeof __webpack_require__.g == 'object' && __webpack_require__.g && __webpack_require__.g.Object === Object && __webpack_require__.g;
微信小程序中存在 globalThis
所以不会报错。如果 globalThis
不存在就会出现和题主一样的错误。
import { debounce } from 'lodash'
会引入完整的 lodash.js
,出错是因为下面这段代码:
var runInContext = (function runInContext(context) {
context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps));
/** Built-in constructor references. */
var Array = context.Array,
Date = context.Date,
Error = context.Error,
Function = context.Function,
Math = context.Math,
Object = context.Object,
RegExp = context.RegExp,
String = context.String,
TypeError = context.TypeError;
/** Used for built-in method references. */
var arrayProto = Array.prototype,
funcProto = Function.prototype,
objectProto = Object.prototype;
// ...
});
root
是空对象了,那么 context.Array
自然是 undefined
。而 lodash-es
中没有 runInContext
就没有这个问题。
好奇研究了一下 lodash-es
中的 global
为什么没有被替换。因为 DefinePlugin 能对 lodash-es
生效,就对比了 DefinePlugin 和 NodeStuffPlugin 的代码,发现 NodeStuffPlugin 中缺少下面这段代码:
normalModuleFactory.hooks.parser
.for(JAVASCRIPT_MODULE_TYPE_ESM)
.tap(PLUGIN_NAME, handler);
说明 NodeStuffPlugin 不解析 ESM 包 ( 参考 https://github.com/webpack/webpack/issues/14210#issuecomment-917419144 ),而 [email protected]
正好是 ESM 包 。
解决办法
-
lodash-es
降级为4.17.15
版本和最新版就差了两个补丁版本,差别不大。
这个版本 package.json 中没有
"type": "module"
,就可以正常替换global
为__webpack_require__.g
。 -
给
[email protected]
打补丁哪个库有问题改哪个库,不影响其他库。
当然,也可以给 NodeStuffPlugin 打补丁让其支持 ESM 包 。
node_modules/webpack/lib/NodeStuffPlugin.js
添加补丁文件 /patches/lodash-es+4.17.21.patch
# generated by patch-package 6.4.14 # # declared package: # lodash-es: 4.17.21 # diff --git a/node_modules/lodash-es/_freeGlobal.js b/node_modules/lodash-es/_freeGlobal.js index 5e383a1..4ed3071 100644 --- a/node_modules/lodash-es/_freeGlobal.js +++ b/node_modules/lodash-es/_freeGlobal.js @@ -1,4 +1,12 @@ /** Detect free variable `global` from Node.js. */ -var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; +/* webpack/runtime/global */ +var freeGlobal = (function() { + if (typeof globalThis === 'object') return globalThis; + try { + return this || new Function('return this')(); + } catch (e) { + if (typeof window === 'object') return window; + } +})(); export default freeGlobal;
应用补丁:
npx patch-package
-
使用 DefinePlugin 替换
global
全局替换,在修复
lodash-es
的同时还可能会导致其他原本正常运行的库出错。DefinePlugin 和 NodeStuffPlugin 一样基于
parser.hooks.expression
(SyncBailHook
)实现,所以global
被 DefinePlugin 处理了之后就不再走 NodeStuffPlugin 了,也就不再注入__webpack_require__.g
依赖。export default { mini: { webpackChain(chain, webpack) { chain.plugin('mini_define').use(webpack.DefinePlugin, [ { global: 'globalThis', }, ]); }, }, } satisfies UserConfigExport;
其中的
globalThis
看情况可替换成其他表达式。
上面这些方法目的都是为了让 global
统一指向 globalThis
,对于不支持 globalThis
的环境需要额外处理,比如:
- 修改 node_modules/webpack/lib/runtime/GlobalRuntimeModule.js ,善用 patch-package 。
- 自己构造一个
global
对象再用 ProvidePlugin 注入。( 参考 https://github.com/NervJS/taro/issues/8098#issuecomment-1667592039 和 https://github.com/NervJS/taro/issues/14033#issuecomment-1607154589 ) - 编写 Taro 插件提前注入一些代码。( 参考 https://github.com/NervJS/taro/issues/12979#issuecomment-1655119738 )
- 如果 Taro 层面能抹平这些差异自然是最省心的。
记得比较修改前后的 vendors.js 内容,查看具体替换了哪些代码。
lodash 简易版按需引入
export default {
alias: {
lodash: 'lodash-es',
},
} satisfies UserConfigExport;
相关问题
- https://github.com/NervJS/taro/issues/14033
- https://github.com/NervJS/taro/issues/6078
- https://github.com/NervJS/taro/issues/12979
- https://github.com/NervJS/taro/issues/14090
最后
以上分析主要针对 微信小程序 ,其他小程序环境没试过,但思路应该一样的。
虽然没做过支付宝小程序不太了解,但看 文档 目前应该已经支持 globalThis
了。
默认情况下,小程序代码中禁止访问
globalThis
和global
等全局上下文对象;这可能会破坏 core-js 的正常工作。因此请先通过
mini.project.json
中的globalObjectMode
配置项开启全局上下文对象,具体参考 globalObjectMode 的相关说明。
这是来自QQ邮箱的假期自动回复邮件。 您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。