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

React库打包时如何做到不同环境使用不同的代码实现 #84

Open
jyzwf opened this issue Feb 17, 2021 · 2 comments
Open

React库打包时如何做到不同环境使用不同的代码实现 #84

jyzwf opened this issue Feb 17, 2021 · 2 comments

Comments

@jyzwf
Copy link
Owner

jyzwf commented Feb 17, 2021

导言

在阅读react源码的过程中,遇到了这样的一个模块:
image

即该文件引用必须由特定的打包来实现。我们知道,react 这个库不只是有 react,还有 react-native、react-server ,还有一些测试环境的运行等。所以由于各个环境的不一样,打包时候的一些实现就不一样。那这一步是在哪里实现的?

react 库的打包跑的是如下命令:

"scripts": {
    "build": "node ./scripts/rollup/build.js"
  }

可以看出,其打包用的是 rollup ,而非 webpack。

打包简介

在看打包前,先看看 scripts/rollup/bundles.js 文件,该文件定义了各个react周边包的一个打包方式,看下 react-dom 的一个打包配置:

{
  // 打包的类型
    bundleTypes: [
      UMD_DEV,
      UMD_PROD,
      UMD_PROFILING,
      NODE_DEV,
      NODE_PROD,
      NODE_PROFILING,
      FB_WWW_DEV,
      FB_WWW_PROD,
      FB_WWW_PROFILING,
    ],
  // 模块类型
    moduleType: RENDERER,
  // 包入口
    entry: 'react-dom',
  // 包被引用时的全局变量名
    global: 'ReactDOM',
  // 包依赖的其他文件
    externals: ['react'],
  }

然后我们来看 build.js 里的主入口:

async function buildEverything() {
  if (!argv['unsafe-partial']) {
    await asyncRimRaf('build');
  }
  // Run them serially for better console output
  // and to avoid any potential race conditions.
  let bundles = [];
  // eslint-disable-next-line no-for-of-loops/no-for-of-loops
  // bundle 即为上面的那一个个配置
  for (const bundle of Bundles.bundles) {
    bundles.push(
      [bundle, UMD_DEV],
      [bundle, UMD_PROD],
      [bundle, UMD_PROFILING],
      [bundle, NODE_DEV],
      [bundle, NODE_PROD],
      [bundle, NODE_PROFILING],
      [bundle, FB_WWW_DEV],
      [bundle, FB_WWW_PROD],
      [bundle, FB_WWW_PROFILING],
      [bundle, RN_OSS_DEV],
      [bundle, RN_OSS_PROD],
      [bundle, RN_OSS_PROFILING]
    );
    if (__EXPERIMENTAL__) {
     // ......
    }
  }
    
  // ......
  // eslint-disable-next-line no-for-of-loops/no-for-of-loops
  for (const [bundle, bundleType] of bundles) {
    // 开始使用 rollup 进行打包
    await createBundle(bundle, bundleType);
  }
  // ......
}

createBundle 里面就是获取 rollup 打包的一个配置,然后进行 打包,而我们一开始的疑问就是通过rollup 的插件来实现的:

function getPlugins(
  entry,
  externals,
  updateBabelOptions,
  filename,
  packageName,
  bundleType,
  globalName,
  moduleType,
  pureExternalModules
) {
    // ......
  // 获取当前环境的真正代码实现
  const forks = Modules.getForks(bundleType, entry, moduleType);
  
    // ......
    
  return [
    // ......
    // Shim any modules that need forking in this environment.
    // 这里 react 自己实现了一个 rollup plugin ,作用就是将一开始的 SchedulerHostConfig.js 文件,根据打包环境来找到对应的真正实现
    useForks(forks),
   // ......
  ].filter(Boolean);
}

getForks

// Hijacks some modules for optimization and integration reasons.
function getForks(bundleType, entry, moduleType) {
  const forksForBundle = {};
  Object.keys(forks).forEach(srcModule => {
    const dependencies = getDependencies(bundleType, entry);
    const targetModule = forks[srcModule](
      bundleType,
      entry,
      dependencies,
      moduleType
    );
    if (targetModule === null) {
      return;
    }
    forksForBundle[srcModule] = targetModule;
  });
  return forksForBundle;
}

而上面的 forks 即是一份路径映射表,如下可以看出,该映射表可以根据打包类型,入口,依赖等返回最终的路径文件:

{
'scheduler/src/SchedulerHostConfig': (bundleType, entry, dependencies) => {
    if (
      entry === 'scheduler/unstable_mock' ||
      entry === 'react-noop-renderer' ||
      entry === 'react-noop-renderer/persistent' ||
      entry === 'react-test-renderer'
    ) {
      return 'scheduler/src/forks/SchedulerHostConfig.mock';
    }
    return 'scheduler/src/forks/SchedulerHostConfig.default';
  },
}

use-forks-plugin

刚才说到,react 自己实现了一个 rollup 插件来实现如上路径的拦截,这就是 use-forks-plugin 插件了:

// 做一个缓存
let resolveCache = new Map();
function useForks(forks) {
  let resolvedForks = new Map();
  Object.keys(forks).forEach(srcModule => {
    // 这里就是做上述的 forks 的一个 映射便于下面取数
    const targetModule = forks[srcModule];
    resolvedForks.set(
      require.resolve(srcModule),
      // targetModule could be a string (a file path),
      // or an error (which we'd throw if it gets used).
      // Don't try to "resolve" errors, but cache
      // resolved file paths.
      typeof targetModule === 'string'
        ? require.resolve(targetModule)
        : targetModule
    );
  });
  return {
    name: 'scripts/rollup/plugins/use-forks-plugin',
    // 这里从这里开始拦截
    resolveId(importee, importer) {
      if (!importer || !importee) {
        return null;
      }
      if (importee.startsWith('\u0000')) {
        // Internal Rollup reference, ignore.
        // Passing that to Node file functions can fatal.
        return null;
      }
      let resolvedImportee = null;
      let cacheKey = `${importer}:::${importee}`;
      if (resolveCache.has(cacheKey)) {
        // Avoid hitting file system if possible.
        resolvedImportee = resolveCache.get(cacheKey);
      } else {
        try {
          resolvedImportee = resolveRelatively(importee, importer);
        } catch (err) {
          // Not our fault, let Rollup fail later.
        }
        if (resolvedImportee) {
          resolveCache.set(cacheKey, resolvedImportee);
        }
      }
      if (resolvedImportee && resolvedForks.has(resolvedImportee)) {
        // We found a fork!
        const fork = resolvedForks.get(resolvedImportee);
        if (fork instanceof Error) {
          throw fork;
        }
        return fork;
      }
      return null;
    },
  };
}

至此我们就知道了,react 是如何做到根据根据不同的打包环境找到对应的代码实现的了,关于如果写 rollup 插件可以参考如下文章:
Rollup插件
Rollup.js插件开发

最后

如有不当或者见解,欢迎交流^_^

@mehwww
Copy link

mehwww commented Feb 22, 2021

又有空写博客了?:smirk:

@jyzwf
Copy link
Owner Author

jyzwf commented Feb 23, 2021

@mehwww 哈哈,过年没事写写

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants