Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

taro3.x 引入 lodash 错误 #8098

Open
JiaLe1 opened this issue Nov 23, 2020 · 14 comments
Open

taro3.x 引入 lodash 错误 #8098

JiaLe1 opened this issue Nov 23, 2020 · 14 comments
Assignees
Labels
F-react Framework - React T-alipay Target - 编译到支付宝小程序 V-3 Version - 3.x

Comments

@JiaLe1
Copy link

JiaLe1 commented Nov 23, 2020

相关平台

支付宝小程序

小程序基础库: 无
使用框架: 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 
@taro-bot2 taro-bot2 bot added F-react Framework - React T-alipay Target - 编译到支付宝小程序 V-3 Version - 3.x labels Nov 23, 2020
@JiaLe1
Copy link
Author

JiaLe1 commented Nov 23, 2020

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

@everlose
Copy link

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

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

@wenfangdu
Copy link
Contributor

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

@wenfangdu
Copy link
Contributor

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
Copy link
Contributor

@codMeing
Copy link

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

@jackple
Copy link

jackple commented Aug 7, 2023

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

试试这样?

@codMeing
Copy link

codMeing commented Aug 7, 2023 via email

@agileago
Copy link
Collaborator

agileago commented Aug 9, 2023

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

试试这样?

大兄弟这个可以

@specialCoder
Copy link

specialCoder commented Dec 11, 2023

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

这样不会有问题

@NervJS NervJS deleted a comment from codMeing May 2, 2024
@yuuk
Copy link
Contributor

yuuk commented Jul 4, 2024

微信小程序下也遇到了,taro@3.6.32,lodash-es@4.17.21

@codMeing
Copy link

codMeing commented Jul 4, 2024 via email

@anyesu
Copy link
Contributor

anyesu commented Jul 31, 2024

基础库版本问题


项目中使用了 lodash-es ,在切换 微信小程序基础库版本 时报了这个错误( 真机基础库版本比较新,预览正常 ),微信开发者工具中测试了基础库 3.2.4 及以上版本不会报错。

TypeError: Cannot read property 'now' of undefined
  at now (._node_modules_.pnpm_lodash-es@4.17.21_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 。( 参考 #14033 (comment)

  • 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 包 ( 参考 webpack/webpack#14210 (comment) ),而 lodash-es@4.17.20 正好是 ESM 包

解决办法


  • lodash-es 降级为 4.17.15 版本

    和最新版就差了两个补丁版本,差别不大。

    这个版本 package.json 中没有 "type": "module" ,就可以正常替换 global__webpack_require__.g

  • lodash-es@4.17.21 打补丁

    哪个库有问题改哪个库,不影响其他库。

    当然,也可以给 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 的环境需要额外处理,比如:

记得比较修改前后的 vendors.js 内容,查看具体替换了哪些代码。

lodash 简易版按需引入


export default {
  alias: {
    lodash: 'lodash-es',
  },
} satisfies UserConfigExport;

相关问题


最后


以上分析主要针对 微信小程序 ,其他小程序环境没试过,但思路应该一样的。

虽然没做过支付宝小程序不太了解,但看 文档 目前应该已经支持 globalThis 了。

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

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

支付宝小程序 - globalObjectMode

@codMeing
Copy link

codMeing commented Jul 31, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
F-react Framework - React T-alipay Target - 编译到支付宝小程序 V-3 Version - 3.x
Projects
None yet
Development

No branches or pull requests

10 participants