strothj / react-app-rewire-typescript-babel-preset

Add TypeScript support to Create React App using @babel/preset-typescript
https://react-app-rewire-typescript-babel-preset.netlify.com
MIT License
52 stars 6 forks source link

wip: Add updated Storybook example for react-scripts@2.0.0 #27

Closed seniorquico closed 6 years ago

seniorquico commented 6 years ago

The Webpack configuration files for Storybook appear to have been split up across a few different packages. It's not quite as straightforward as the first time I looked at the source of the configs for these examples.

This PR is my first stab at changes to get the Storybook example working with the latest react-scripts@2.0.0 to address #26. I'd like to try to dig through the new Webpack configuration files in the new Storybook packages and the latest react-scripts@2.0.0 to see if there are other important preset/plugin differences.

strothj commented 6 years ago

Totally agree with removing the monorepo example. Thanks for the work.

strothj commented 6 years ago

Hello,

Would you mind rebasing this?

What do you think about reusing the Babel loader from react-scripts:

const babelLoader = getReactScriptsBabelLoader(env);
config.module.rules.unshift(babelLoader);
function getReactScriptsWebpackConfig(env) {
  const reactAppRewired = require("react-app-rewired");
  const rewiredConfigOverrides = require("react-app-rewired/config-overrides");
  const craWebpackConfig = require(path.join(
    reactAppRewired.paths.scriptVersion,
    `config/webpack.config.${env === "PRODUCTION" ? "prod" : "dev"}`
  ));
  const rewiredWebpackConfig = rewiredConfigOverrides.webpack(
    craWebpackConfig,
    env === "PRODUCTION" ? "production" : "development"
  );
  return rewiredWebpackConfig;
}

function getReactScriptsBabelLoader(env) {
  const { getLoader } = require("react-app-rewired");
  const reactScriptsWebpackConfig = getReactScriptsWebpackConfig(env);
  const babelLoader = getLoader(reactScriptsWebpackConfig.module.rules, rule =>
    Boolean(
      rule.test && rule.test.toString() === /\.(ts|tsx|js|jsx)$/.toString()
      // Later versions of react-scripts add support for JavaScript Modules:
      // rule.test.toString() === /\.(ts|tsx|js|mjs|jsx)$/.toString(),
    )
  );
  return babelLoader;
}
seniorquico commented 6 years ago

I'm not versed enough in the various packages/changes to have a strong opinion on reusing the loader in react-scripts. I did a quick check, and Storybook v4 doesn't appear to use the loader from react-scripts.

https://github.com/storybooks/storybook/blob/2ef180d4886bee84b3203445fc3cdc0bcde47812/lib/core/src/server/config/webpack.config.dev.js#L70-L80 https://github.com/storybooks/storybook/blob/master/app/react/src/server/framework-preset-react.js

Do you have some insight to elaborate on the pros/cons of using the loader from react-scripts vs sticking closer to Storybook's configurations? e.g., if this was switched to the loader from react-scripts, would users be likely to encounter build issues after updating to Storybook v4 and/or a version of CRA with built-in support for TypeScript (in reference to their PR 4837)?

strothj commented 6 years ago

I was working on migrating a project to the new build in PR 4837 and came up with this in the process:

This is using the latest Storybook alpha that uses Babel 7.

The findMonoRepo is the old code from here: https://github.com/facebook/create-react-app/blob/22353ecf02db21f7bff6a0c088ef7f0631f29fd6/packages/react-dev-utils/workspaceUtils.js That's only important if you want monorepo support. Just posting it here in case someone else finds it useful. I haven't had a chance to test this extensively yet.

import { findMonorepo } from "@secret-project/lib-build-tools";
import assert from "assert";
import path from "path";
import { Configuration, RuleSetLoader, RuleSetRule } from "webpack";

export default function(
  _baseConfig: Configuration,
  _env: "DEVELOPMENT" | "PRODUCTION",
  defaultConfig: Configuration,
): Configuration {
  const scriptRule = getScriptRule(defaultConfig);

  // Allow transpiling of source files from source directory of monorepo
  // packages directly. With this we can import uncompiled source directly
  // from monorepo packages.
  addMonorepoSourcePathsToScriptRule(scriptRule);

  const babelOptions = getBabelLoaderOptions(scriptRule);

  // Add TypeScript support.
  scriptRule.test = /\.[jt]sx?$/;
  babelOptions.presets.push(require.resolve("@babel/preset-typescript"));
  defaultConfig.resolve!.extensions!.unshift(".tsx", ".ts");

  // Support for Create React App v2 SVG Component imports.
  babelOptions.plugins.push([
    require.resolve("babel-plugin-named-asset-import"),
    {
      loaderMap: {
        svg: {
          ReactComponent: "@svgr/webpack?-prettier,-svgo![path]",
        },
      },
    },
  ]);

  return defaultConfig;
}

function getScriptRule(config: Configuration) {
  const scriptRule = config.module!.rules.find(
    rule => rule.test!.toString() === String(/\.jsx?$/),
  );
  if (!scriptRule) throw new Error("Could not locate script loader.");
  return scriptRule;
}

function addMonorepoSourcePathsToScriptRule(scriptRule: RuleSetRule) {
  if (!scriptRule.include || !Array.isArray(scriptRule.include)) {
    throw new Error("Expected include to be string array.");
  }

  const monorepoPkgPaths = findMonorepo(path.resolve(__dirname, "..")).pkgs;
  const monorepoSourcePaths = monorepoPkgPaths.map(p => path.join(p, "src"));
  scriptRule.include.push(...monorepoSourcePaths);
  scriptRule.exclude = [/[/\\\\]node_modules[/\\\\]/];
}

function getBabelLoaderOptions(scriptRule: RuleSetRule) {
  let babelLoader: RuleSetLoader;
  try {
    babelLoader = (scriptRule.use as RuleSetLoader[]).find(
      loader => loader.loader === "babel-loader",
    )!;
    assert(babelLoader);
  } catch (e) {
    throw new Error("Could not locate babel loader.");
  }

  return babelLoader.options as Record<string, any[]>;
}
strothj commented 6 years ago

Thank you for the help. I'm going to close this because Create React App will be incorporating the TypeScript features.