通过这篇文章,你可以掌握以下技能
- webpack 打包编译 es6 语法
- rollup 打包编译 es6 语法
- 正则表达式“后行断言”(lookbehind)语法转换为前瞻断言(lookahead)
下面结合实际案例一步步解决遇到的问题
低版本 ios 不支持 es6 语法
报错 & 定位问题
报错内容
null;
没错,就是这么抽象的报错,什么信息都没有。经经验丰富的同事的提醒,这可能是因为存在 es6 语法导致的,低版本的 ios 不支持 es6 语法
看看打包好的 chunk,发现确实存在 class 语法
而被转为 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
是一个叫 slide-template-selector 的包没有被转换成功,去 node_modules 里面看看它的 package.json
"source": "src/index.tsx",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
发现它导出的 module
是 index.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'],
},
}
再次打包看看
纳尼!怎么还是会有 class 语法!看看又是哪个包没有被转换成功
转换不完的 class (续)
找到了,是这个叫 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'],
}),
好的,让我们再来检查一次!
皇天不负有心人,终于没有 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 版本也是会遇到的,所以解决方案也是通用的。