如何在低版本 ios(<=15) 让你的代码也跑起来

283 阅读5分钟

通过这篇文章,你可以掌握以下技能

  1. webpack 打包编译 es6 语法
  2. rollup 打包编译 es6 语法
  3. 正则表达式“后行断言”(lookbehind)语法转换为前瞻断言(lookahead)

下面结合实际案例一步步解决遇到的问题

低版本 ios 不支持 es6 语法

报错 & 定位问题

报错内容

null;

没错,就是这么抽象的报错,什么信息都没有。经经验丰富的同事的提醒,这可能是因为存在 es6 语法导致的,低版本的 ios 不支持 es6 语法

看看打包好的 chunk,发现确实存在 class 语法

企业微信截图_3cfc2923-4a98-4b93-9559-b83872ae8a7b.png

而被转为 es5 的 class 的写法是这样的

var Emitter = /** @class */function () {
  function Emitter(options) {
    this.disposed = false;
    this.options = options;
  }

解决方法,把 node_modules 下的代码在打包时也转为 es5

因为之前只针对项目 src 下的代码进行了转换,所以需要在打包时也转换 node_modules 下的代码

但是这样会导致打包时间变长

// 原配置
{
  test: /\.(ts|js)x?$/,
  loader: 'babel-loader',
  include: srcPath,
  exclude: [/node_modules/],
  options: {
    rootMode: 'upward',
    plugins: [],
  },
}

// 修改后的配置
{
  test: /\.(ts|js)x?$/,
  loader: 'babel-loader',
  include: [srcPath, /node_modules/],
  options: {
    rootMode: 'upward',
    presets: ['@babel/preset-env'],
  },
}

转换不完的 class

增加了对 node_modules 里代码的转换后,打包产物的 class 语法变少了,但是里面还是有 class

企业微信截图_e913bb0c-08fc-40f9-b22a-30d71e39f570.png

是一个叫 slide-template-selector 的包没有被转换成功,去 node_modules 里面看看它的 package.json

"source": "src/index.tsx",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",

发现它导出的 moduleindex.mjs ,而我们上面 webpack test 的规则 /\.(ts|js)x?$/ 没有覆盖到 mjs 文件,需要调整一下

// 更新的 webpack 配置
{
  test: /\.(ts|js|mjs)x?$/,
  loader: 'babel-loader',
  include: [srcPath, /node_modules/],
  options: {
    rootMode: 'upward',
    presets: ['@babel/preset-env'],
  },
}

再次打包看看

企业微信截图_74bda30a-021e-4e25-b665-349b0b05a4d8.png

纳尼!怎么还是会有 class 语法!看看又是哪个包没有被转换成功

转换不完的 class (续)

企业微信截图_7565ec34-7b35-4e08-a776-ea8f848971a5.png

找到了,是这个叫 marked 的包,但是项目里面并没有显式的依赖这个包。排查发现是依赖的第三方包依赖了这个包,并把其打包进了其代码中,但是没有进行 es5 转换处理。

很幸运,这个包是我们大仓内的包

问题定位:依赖包使用的 rollup 进行打包,但在打包时把 marked 作为内部模块时,没有对 marked 进行 es5 转换

// 原 rollup 打包配置
nodeExternals({
  exclude: [
    // marked 是 es6 的产物,sdk 打包进去转换一下
    'marked',
  ],
}),
babel({
  babelHelpers: 'runtime',
  extensions: ['.js', '.cjs', '.ts'],
  exclude: /node_modules(?!\/marked)/,
  presets: ['@babel/preset-env', '@babel/preset-typescript', '@babel/preset-react'],
  plugins: ['@babel/plugin-transform-runtime'],
}),

// 修复后的配置,只需要把 exclude 的配置删除
nodeExternals({
  exclude: [
    // marked 是 es6 的产物,sdk 打包进去转换一下
    'marked',
  ],
}),
babel({
  babelHelpers: 'runtime',
  extensions: ['.js', '.cjs', '.ts'],
  presets: ['@babel/preset-env', '@babel/preset-typescript', '@babel/preset-react'],
  plugins: ['@babel/plugin-transform-runtime'],
}),

好的,让我们再来检查一次!

企业微信截图_b11532f4-45b7-4d98-90b8-323acdff743d.png

皇天不负有心人,终于没有 class 了。看来有些事只要努力了还是会有结果的

不仅仅是 class

让我们再次满怀期望的打开页面。发现,还是白屏...

打开 vconsole 看看,发现报错信息变了。看来情况是在往好的方向发展的

SyntaxError: Unexpected token '='. Expected an opening '(' before a method's parameter list.\n\t/index_js.js:16141:0\n

经过排查发现,打包产物里面还存在 => 箭头函数,这个问题同样是上面 rollup 打包的第三方包的问题,需要对其进行 es5 转换

最后一难

在处理了所有的 webpack 和 rollup 打包编译 es6 的问题后,想着这回总没问题了吧。但是,还有高手!可能这就是低版本 ios 的魅力吧

SyntaxError: Invalid regular expression: invalid group specifier name\n\t/index_js.js:11926:0\n

联网搜索一下,发现低版本的 ios 连有些正则语法也是不支持的 stackoverflow.com/questions/5…

问一下 chatgpt:

在低版本的 Safari 浏览器中,正则表达式不支持“后行断言”(lookbehind),这是你提供的正则表达式中使用的功能。为了在不支持后行断言的环境中实现相同的功能,我们可以采用一种替代方法,即先使用一个不包含后行断言的正则表达式进行匹配,然后在匹配结果中进一步检查条件。(省略函数方法解决)

如果你希望使用正则表达式来解决这个问题,但又不能使用后行断言(lookbehind),我们可以通过使用前瞻断言(lookahead)和一些智能的模式匹配来达到类似的效果。这种方法依赖于匹配整个 URL 的结构,并确保不是从 wenku. 开始。

正则表达式解决方案

我们可以构建一个正则表达式,它检查 yourwebsite.com 出现的位置,并确保这个位置前面不是紧跟着 wenku.。这可以通过确保 yourwebsite.com 前面是开始、一个非单词字符(如点号或斜杠),或者完全没有 wenku. 这个词来实现。

const regex = /^(?!.*\bwenku\.yourwebsite\.com\b).*yourwebsite.com/;

解释

^:从字符串的开始位置开始匹配。

(?!.*\bwenku.docs.qq.com\b):这是一个负向前瞻断言,确保字符串中不包含完整的 wenku.yourwebsite.com。

.*yourwebsite.com:匹配任意字符(除了换行符),后跟 yourwebsite.com。 JavaScript 示例

function testUrl(url) {
	const regex = /^(?!.*\bwenku\.yourwebsite\.com\b).*yourwebsite\.com/;
	return regex.test(url);
}

// 测试用例
console.log(testUrl("https://yourwebsite.com")); // true
console.log(testUrl("https://wenku.yourwebsite.com")); // false
console.log(testUrl("https://something.yourwebsite.com")); // true
console.log(testUrl("https://yourwebsite.com/some/path")); // true
console.log(testUrl("https://wenku.yourwebsite.com/some/path")); // false

这个正则表达式应该能够满足你的需求,即在不使用后行断言的情况下,匹配包含 yourwebsite.com 但不是 wenku.yourwebsite.com 的 URL。这种方法在大多数不支持 ES2018 后行断言的 JavaScript 环境(包括旧版 Safari)中都应该有效。

总结

低版本 ios ,诗人我吃。其实这些问题在 ie<=11 版本也是会遇到的,所以解决方案也是通用的。