Snowpack 下使用 AntV/L7 require方法未定义问题

在Web前端的 React 项目中使用 Snowpack 作为编译工具(Webpack 的替代工具),并在项目中引用到了 AntV/L7 库。使用时遇到两个错误。

第一个错误,编译错误

命令行报错

解决办法

在 snowpack.config.js 配置文件中增加 polyfill 配置

snowpack.config.js

其它解决方法,参考 https://www.snowpack.dev/#node.js-polyfills

问题原因

@antv/l7-core 库依赖了 merge-json-schemas 库,merge-json-schemas 中使用了 node 内置库 assert,导致编译失败。

代码位置:https://github.com/goodeggs/merge-json-schemas/blob/445e937377adb6a04f4e53bcc2059448af642089/src/index.js#L9

第二个错误,运行时错误

编译通过后,打开浏览器

浏览器控制台错误

点击跟踪错误代码位置,查看报错代码

错误代码位置

解决办法

在浏览器全局定义一个高阶方法 require(注意此时全局当中不应当存在名为 require 的方法,如果存在则需要自行兼容)

function require(libName) {
    if (libName == "load-styles") {
        // https://www.npmjs.com/package/load-styles/v/2.0.0
        return function loadStyles(css, doc) {
            // default to the global `document` object
            if (!doc) doc = document;

            var head = doc.head || doc.getElementsByTagName("head")[0];

            // no <head> node? create one...
            if (!head) {
                head = doc.createElement("head");
                var body = doc.body || doc.getElementsByTagName("body")[0];
                if (body) {
                    body.parentNode.insertBefore(head, body);
                } else {
                    doc.documentElement.appendChild(head);
                }
            }

            var style = doc.createElement("style");
            style.type = "text/css";
            if (style.styleSheet) {
                // IE
                style.styleSheet.cssText = css;
            } else {
                // the world
                style.appendChild(doc.createTextNode(css));
            }
            head.appendChild(style);

            return style;
        };
    }
    throw Error(
        `requrie ${libName} failed, require syntax is not supported`
    );
}

以下是问题的定位及解决方法的思考过程

问题原因

通过出现问题的上下文,逐步回溯寻找问题原因。

  1. 错误代码位置在哪?
    从浏览器的错误代码上下文推测,出错代码在L7的库中,似乎想要通过 commonjs 语法来引入一个 load-styles 库,特征字符串是 require('load-styles')。然后到项目的 node_modules/@antv/ 目录下搜索特征字符串,发现出现多处。且都是为了引入 require('load-styles') 。

  2. require('load-styles') 代码为什么会报错?
    Snowpack在编译项目时,使用了 ES Module 方式管理模块,而L7的发布代码在ES Module模块中使用了 require 语法!?这是不正确的

  3. require('load-styles') 是做什么用的?
    因为node_modules目录下并非源码,不容易查看,所以到 github 上搜索 L7 源码: https://github.com/antvis/L7(L7的文档尚不完善,没有直接跳转连接)。找到后,看到先浏览 package.json 配置,发现存在 load-styles@2.0.0 库。于是先去查看 load-styles 库的功能,github上未找到该库,但是在npm上找到,意外的是该库为6年前发布的。下载后查看源码,代码很少,功能就是把一个字符创建为CSS标签并插入网页中。L7中的require('load-styles') 就是为了把一段CSS文本内容作为style标签插入网页。

到这里基本确定了问题的原因:L7库中使用一个6年前的方式来将一段CSS文本变成一个style标签,然后在生成ES Module模块时,load-styles代码被错误的转义成 commonjs 语法。Snowpack在编译时不会对 ES Module 做处理,因为它认为 ES Module 已经是目标格式,不需要编译,因此也忽略了 ES Module 中错误的 require 语法,所以代码被发送给浏览器,在浏览器执行时才报出运行时错误。

原因到这里已经可以解释我遇到的问题,那么,更深层次的原因是什么?为什么会L7会产生 require('load-styles') 这样的语法,其它库则不会?

  1. 目标代码是怎么包含require('load-styles') 的?
    以 @antv/l7-component 库为例,通过 import "xxx.css" 的方式引入样式文件 查看代码
引入样式文件

发布到npm的编译后代码目录结构如下

.
├── CHANGELOG.md
├── LICENSE.md
├── es
│   ├── index.js
│   ├── ...
├── lib
│   ├── index.js
│   ├── ...
└── package.json

因为使用的 Snowpack 采用 ES Module 方式,所以引用的是 es/index.js 文件。查看该文件中的编译结果文件,看到模块中含有 require 语法,即最初遇到的错误。


npm库中代码

import css的编译结果不应该是require方式,所以问题出在编译过程

  1. 自己编译,查看问题是否存在?
    下载源码查看编译过程(代码库太大导致从 github 下载总失败,后来从https://gitee.com/antv/L7 下载到了源码)。
yarn install
npm run build

自己编译后查看编译结果


packages/component/es/indes.js

问题复现,接着查看编译过程。顺序查找较慢,所以还是通过特征代码查找。

  1. 编译过程是怎么进行的?

首先确定特征代码 require('load-styles'),特征库是 load-styles。然后通过 yarn.lock 文件查找 load-styles 的依赖关系,发现只有一处依赖(好兆头)


babel插件库

只有 babel-plugin-transform-import-styles 库使用了 load-styles,从名字看,这是用于转换 import css 的语法的 babel 插件库。因为是babel插件,所以到 babel.config.js 配置项中查找(babel的配置文件可能有不同的名字,但包含babel

babel.config.js

需要根据条件选择是否使用 transform-import-styles 插件(babel插件在使用时可以忽略掉 babel-plugin- 前缀,所以不能直接搜索完整名称,呵呵呵)。然后查看条件变量 isCDNBundle 来源

babel.config.js

isCDNBundle 变量来自于环境变量 bundle 参数,环境变量参数来自于编译命令参数


l7/components/package.json

所以在编译l7-components的ES Module模块时 isCDNBundle 变量为空,所以 babel-plugin-transform-import-styles 插件生效了。然后查看插件转换 import css文件的方法 源码

babel-plugin-transform-import-css/index.js
babel-plugin-transform-import-css/helpers.js

babel-plugin-transform-import-css 采用 babel 模板替换的方法,将 import 的 CSS 文件内容附加在 require('load-styles')(/* CSS */) 模板中。因此最终的ES Module编译结果中出现了 require 语句,导致错误。

解决思路

  • 思路一:修改打包、编译的配置,兼容ES Module中包含 require 的语法

不可行 。当前项目打包、编译依赖3个配置文件:babel.config.js、tsconfig.js、snowpack.config.js,尝试修改后都不能解决问题。原因是因为,无论哪个项目配置型都是针对项目中自己编写的代码的,不会处理 node_modules 中的文件,node_modules 中的文件被认为编译好的 ES Module,Snowpack 直接复制过来使用,不做处理。所以修改配置项都是不起作用的。

  • 思路二:对 node_modules 中的文件进行预处理

不可行。Snowpack的优势就是默认采用 ES Module。如果对 node_modules 中的模块逐个编译就无法使用 Snowpack。虽然Snowpack中可以配置 Webpack 插件,但那时在 production模式下做代码合并优化时使用的。

  • 思路三:采用 Webpack 编译

可行,但不想,不想退回 Webpack是因为技术框架选型的惯性,一旦采用了哪种方案,就不会轻易变动。

  • 思路四:修改 @antv/l7 代码中的编译错误

不可行。首先 @antv/l7 是有组织的官方库,修正其代码就意味着要提交pull request,然后是漫长的等待,正在开发中的项目不能承受这种等待,提交PR然后等待修复的时间,不如想其它方案。

  • 思路五:自己fork @antv/l7 代码库修改问题

成本高。@antv/l7 的代码库下 有多个package,而且相互依赖,有多个地方使用了 load-styles ,因此fork并维护多个package成本过高。

  • 思路六:hotfix,使用某种补丁方法规避掉这个错误

值得一试。常用的比如 try catch、monkey patch、proxy 等等 magic ways ,可以解决一些不好处理的问题,只不过这种解决方式本身就是一种问题。所以最终打算使用这种方式来解决运行时报错问题。

解决方式

运行时错误有多处,但都是同一种错误,require('load-styles') 未定义导致。L7 使用这个方法只有一个目的,就是把一段 CSS 文本变成一个 style 标签插入 html 中。因此,在L7代码运行前实现一个全局方法,能够将 require('load-styles')('/* 这是一段CSS 文本*/') 正确的运行并生成style标签即可。

function require(){
     return function(css) {
        var style = document.createElement("style");
        style.appendChild(document.createTextNode(css));
        document.head.appendChild(style)
    }
}

重新编译运行,页面可以正常显示。


AntV/L7地图

通过上述方法即可解决 Snowpack 的打包项目中引用 @antv/l7 项目的问题。虽然可以正常使用,但代码还可以做些优化,首先是创建style标签的功能,既然原本使用的是 load-styles 项目,这里就直接把 load-styles 源码直接复制过来,这样与原本的代码就完全一致了;其次是错误提醒,全局 require 方法是为了解决 require('load-style') 问题的,因此出现其他引用库时要抛出异常提示。最终的代码版本,参看上开始的“解决办法”内容。

(虽然最终解决,但问题根源是 @antv/l7 代码的编译问题。而且这个破事,花了我5个小时)

参考文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341