taro icon indicating copy to clipboard operation
taro copied to clipboard

taro3.x 引入 lodash 错误

Open JiaLe1 opened this issue 4 years ago • 10 comments

相关平台

支付宝小程序

小程序基础库: 无 使用框架: 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 

JiaLe1 avatar Nov 23 '20 03:11 JiaLe1

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 错误问题

JiaLe1 avatar Nov 23 '20 04:11 JiaLe1

我调试得知访问 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
};

要么就只能如你代码所说按需引入,不能全局引入。

everlose avatar May 15 '23 11:05 everlose

Taro 3.6.6 依然可以复现,大佬们来修下呀

wenfangdu avatar May 17 '23 09:05 wenfangdu

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 无效。

wenfangdu avatar May 17 '23 09:05 wenfangdu

最后换成了 https://www.npmjs.com/package/@github/mini-throttle

wenfangdu avatar May 17 '23 09:05 wenfangdu

尝试在小程序入口文件引入lodash-fix.js 参考:https://blog.51cto.com/u_13567403/4842786

codMeing avatar Jun 30 '23 09:06 codMeing

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)
}

试试这样?

jackple avatar Aug 07 '23 10:08 jackple

这是来自QQ邮箱的假期自动回复邮件。   您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。

codMeing avatar Aug 07 '23 10:08 codMeing

`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) } `

试试这样?

大兄弟这个可以

agileago avatar Aug 09 '23 06:08 agileago

Taro3 版本下使用 lodash 或者 lodash-es 都会报错。 image 原因是 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();
};

这样不会有问题

specialCoder avatar Dec 11 '23 15:12 specialCoder

微信小程序下也遇到了,[email protected][email protected]

yuuk avatar Jul 04 '24 08:07 yuuk

这是来自QQ邮箱的假期自动回复邮件。   您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。

codMeing avatar Jul 04 '24 08:07 codMeing

基础库版本问题


项目中使用了 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.4

3.2.3 及以下版本中,返回的是空对象 {}

直接在控制台调用 Function('return this')() 时返回的是 Window 对象。

3.2.3

微信小程序中使用 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 )

  • node.global

    If you are using a module which needs global variables in it, use ProvidePlugin instead of global.

// 编译前
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 生效,就对比了 DefinePluginNodeStuffPlugin 的代码,发现 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 的同时还可能会导致其他原本正常运行的库出错。

    DefinePluginNodeStuffPlugin 一样基于 parser.hooks.expressionSyncBailHook )实现,所以 globalDefinePlugin 处理了之后就不再走 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 了。

默认情况下,小程序代码中禁止访问 globalThisglobal 等全局上下文对象;这可能会破坏 core-js 的正常工作。

因此请先通过 mini.project.json 中的 globalObjectMode 配置项开启全局上下文对象,具体参考 globalObjectMode 的相关说明。

支付宝小程序 - globalObjectMode

anyesu avatar Jul 31 '24 12:07 anyesu

这是来自QQ邮箱的假期自动回复邮件。   您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。

codMeing avatar Jul 31 '24 12:07 codMeing