总会遇到那么几个库是ES6的代码, 怎么办?|七日打卡

1,198 阅读2分钟

背景

前几天,笔者把项目里的query-string升级到v6的版本,为了里面一些新特性(比如,字符串的'1'转成数字1)。今天,测试同事和我说,移动端低版本的浏览器跑不起来,我看了报错,发现是编辑完的代码里有箭头函数,心想简单,把query-string纳入babel的编译范围即可。结果,还是有箭头函数。我又检查了一遍,结果是query-string的依赖库也有用es6代码写的。

这怎么办?总不能让笔者一个一个把依赖库检查一遍吧。。。。

这是笔者原先的babel-loader配置:

const babelLoader = {
  test: /\.(js|jsx|ts|tsx|mjs)$/,
  exclude: [/node_modules\/(?![(braft\-editor)|(query\-string)])/, /bower_components/],
  include: [paths.appSrc, resolveApp('node_modules/braft-editor'), resolveApp('node_modules/query-string')],
  use: ['happypack/loader?id=babel-loader'],
};

怎么处理es6代码写的依赖库?

当然,还是使用babel-loader,但是每次改完 exclude后,还要去 include 里改,而且可能依赖库的依赖库也有es6的代码,太麻烦了!

笔者之前使用umi,umi里有个配置字段 nodeModulesTransform, 可以指定编译node_modules目录下的一些依赖库,使用起来就很便捷。

先定义哪些直接的依赖库要单独编译

const nodeModulesTransform = {
  type: 'none', // none 不编译node_modules下的;all,编译node_modules下的所有库
  exclude: ['braft-editor', 'query-string'],
};

all 的逻辑比较简单,就是编译node_modules下的所有文件,这篇就不讲这部分的实现了,主要讲none。(下面的都是自己琢磨的,并没有去参考 umi 这部分的实现, 如果有看过 umi的别杠我 = =)

获取依赖库的所有dependencies

由于依赖库的 dependencies 可能也存在 es6 代码,所以宁愿牺牲一点时间,也要用babel把这些dependencies也编译一遍

const path = require('path');
const process = require('process');
function getDeps(moduleName, depMap) {
  try {
    const modulePkg = require(path.join(process.cwd(), `node_modules/${moduleName}/package.json`));
    if(!('dependencies' in modulePkg)) return [];
    const dependencies = modulePkg.dependencies;
    let deps = [];
    for (var key in dependencies) {
      if (key in depMap) continue; // 防止有依赖库重复引用或者循环引用,所以加个hash检查下
      deps = deps.concat(getDeps(key, depMap));
      deps.push(key);
      depMap[key] = 1;
    }
    return deps;
  } catch (e) {
    console.log(e);
    console.log(`${path.join(process.cwd(), `node_modules/${moduleName}/package.json`)} don't exist`);
  }
}

获取直接依赖库,以及直接依赖库的所有子依赖库

const getAllDeps = (() => {
  let deps;
  return modules => {
    if (deps) return deps;
    if (modules.length === 0) return [];
    let res = modules.slice();
    let depMap = {}; // 依赖库字典
    for (var i = 0, len = modules.length; i < len; i++) {
    	depMap[modules[i]] = 1;
    }
    for (var i = 0, len = modules.length; i < len; i++) {
      res = res.concat(getDeps(modules[i], depMap));
    }
    res = res.filter(n => {
      if (n.indexOf('babel') > -1) return false;
      if (['regenerator-runtime', 'core-js', 'promise', 'object-assign', 'setimmediate'].includes(n)) {
        return false;
      }
      return true;
    });

    deps = res;
    return deps;
  };
})();

因为最终提供给 babel-loaderexcludeinclude 所涉及的第三方模块是相同的,所以笔者使用 闭包 做了一个 缓存代理

这里获取所有依赖库的方法,是使用的递归,有可能会爆栈。感兴趣的同学可以使用 广度优先搜索 算法来优化上述代码:)

提供给 babel-loaderexcludeinclude

function getNodeModulesTransformExclude() {
  const modules = getAllDeps(nodeModulesTransform.exclude).map(n => {
    return `(${n.replace(/\-/g, '\\-')})`;
  });
  if (modules.length === 0) {
    return /node_modules/;
  }
  return new RegExp(`node_modules\\/(?![${modules.join('|')}])`);
}

function getNodeModulesTransformInclude() {
  return getAllDeps(nodeModulesTransform.exclude).map(n => `node_modules/${n}`);
}

新的 babel-loader配置如下:

const babelLoader = {
  test: /\.(js|jsx|ts|tsx|mjs)$/,
  exclude: [getNodeModulesTransformExclude(), /bower_components/],
  include: [paths.appSrc, ...getNodeModulesTransformInclude().map(n => resolveApp(n))],
  use: ['happypack/loader?id=babel-loader'],
};

以后再遇到某个库使用了es6代码,只需要修改 nodeModulesTransform 中的 exclude 这一处地方即可。


tips: 这几天都在学 Python, 昨天计划今天讲 Pythonif 语句,但是这部分内容比较简单, 打算和后面看的 字典 一起讲。