使用monorepo(Lerna + Yarn Workspaces)对共享代码进行ReactJS linting(eslint)和transpiling(babel)错误

时间:2018-03-19 00:14:38

标签: javascript reactjs webpack babel create-react-app

我正在使用纱线工作区,lerna和2个不同的项目构建monorepo:

  • ux - 使用create-react-app
  • 构建的React组件库
  • auth - 一个React应用程序,它使用使用create-react-app构建的ux库然后弹出(弹出是为了尝试解决当前的linting和transpiling错误)。

我的monorepo设置是:

project
  |-------- node_modules
  |-------- lerna.json
  |-------- package.json
  |-------- packages
                |----------- auth
                             |------- node_modules
                             |------- src
                             |------- packages.json
                |----------- ux
                             |------- node_modules
                             |------- src
                             |------- packages.json

lerna.json:

{
  "lerna": "2.9.0",
  "npmClient": "yarn",
  "useWorkspaces": true,
  "packages": [
    "packages/*"
  ],
  "version": "3.0.0"
}

Lerna root package.json:

{
    "name": "myorganization",
    "version": "3.0.0",
    "license": "UNLICENSED",
    "workspaces": ["packages/*"],
    "private": true,
    "scripts": {
        "clean": "lerna clean -f --yes && rm -rf node_modules",
        "reset": "yarn run clean && yarn",
        "auth": "cd packages/auth && yarn start"
    },
    "devDependencies": {
        "lerna": "^2.9.0"
    }
}

ux package.json:(ux未被弹出)

{
  "name": "@myorganization/ux",
  "version": "3.0.0",
  "author": "Me",
  "private": true,
  "license": "UNLICENSED",
  "dependencies": {
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-scripts": "2.0.0-next.9754a231"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "browserslist": {
    "development": [
      "last 2 chrome versions",
      "last 2 firefox versions",
      "last 2 edge versions"
    ],
    "production": [
      ">1%",
      "last 4 versions",
      "Firefox ESR",
      "not ie < 11"
    ]
  },
  "devDependencies": {
    "eslint": "^4.19.0",
    "eslint-plugin-react": "^7.7.0"
  }
}

auth package.json:

{
  "name": "@myorganization/auth",
  "version": "3.0.0",
  "private": true,
  "author": "Me",
  "license": "UNLICENSED",
  "dependencies": {
    "@myorganization/ux": "^3.0.0",
    "@babel/core": "7.0.0-beta.38",
    "@babel/runtime": "7.0.0-beta.38",
    "autoprefixer": "7.2.5",
    "babel-core": "7.0.0-bridge.0",
    "babel-eslint": "8.2.1",
    "babel-jest": "22.1.0",
    "babel-loader": "8.0.0-beta.0",
    "babel-preset-react-app": "4.0.0-next.9754a231",
    "case-sensitive-paths-webpack-plugin": "2.1.1",
    "chalk": "2.3.0",
    "css-loader": "0.28.9",
    "dotenv": "4.0.0",
    "dotenv-expand": "4.0.1",
    "eslint-config-react-app": "3.0.0-next.9754a231",
    "eslint-loader": "1.9.0",
    "eslint-plugin-flowtype": "2.41.0",
    "eslint-plugin-import": "2.8.0",
    "eslint-plugin-jsx-a11y": "6.0.3",
    "extract-text-webpack-plugin": "3.0.2",
    "file-loader": "1.1.6",
    "fs-extra": "5.0.0",
    "html-webpack-plugin": "2.30.1",
    "identity-obj-proxy": "3.0.0",
    "jest": "22.1.2",
    "object-assign": "4.1.1",
    "postcss-flexbugs-fixes": "3.2.0",
    "postcss-loader": "2.0.10",
    "promise": "8.0.1",
    "raf": "3.4.0",
    "react": "^16.2.0",
    "react-dev-utils": "6.0.0-next.9754a231",
    "react-dom": "^16.2.0",
    "react-router-dom": "^4.2.2",
    "style-loader": "0.19.1",
    "svgr": "1.6.0",
    "sw-precache-webpack-plugin": "0.11.4",
    "thread-loader": "1.1.2",
    "uglifyjs-webpack-plugin": "1.1.6",
    "url-loader": "0.6.2",
    "webpack": "3.10.0",
    "webpack-dev-server": "2.11.0",
    "webpack-manifest-plugin": "1.3.2",
    "whatwg-fetch": "2.0.3"
  },
  "scripts": {
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "test": "node scripts/test.js --env=jsdom"
  },
  "browserslist": {
    "development": [
      "last 2 chrome versions",
      "last 2 firefox versions",
      "last 2 edge versions"
    ],
    "production": [
      ">1%",
      "last 4 versions",
      "Firefox ESR",
      "not ie < 11"
    ]
  },
  "jest": {
    "collectCoverageFrom": [
      "src/**/*.{js,jsx,mjs}"
    ],
    "setupFiles": [
      "<rootDir>/config/polyfills.js"
    ],
    "testMatch": [
      "<rootDir>/src/**/__tests__/**/*.{js,jsx,mjs}",
      "<rootDir>/src/**/?(*.)(spec|test).{js,jsx,mjs}"
    ],
    "testEnvironment": "node",
    "testURL": "http://localhost",
    "transform": {
      "^.+\\.(js|jsx|mjs)$": "<rootDir>/node_modules/babel-jest",
      "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
      "^(?!.*\\.(js|jsx|mjs|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
    },
    "transformIgnorePatterns": [
      "'[/\\\\]node_modules[/\\\\](?!' + paths.allLernaModules.join('|') + ').*\\.(js|jsx|mjs)$'",
      "^.+\\.module\\.css$"
    ],
    "moduleNameMapper": {
      "^react-native$": "react-native-web",
      "^.+\\.module\\.css$": "identity-obj-proxy"
    },
    "moduleFileExtensions": [
      "web.js",
      "mjs",
      "js",
      "json",
      "web.jsx",
      "jsx",
      "node"
    ]
  },
  "babel": {
    "presets": [
      "react-app"
    ]
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "devDependencies": {
    "eslint": "^4.19.0",
    "eslint-plugin-react": "^7.7.0"
  }
}

然后我更改了create-react-app创建的config/paths以获取引用的包:

"use strict";

const path = require("path");
const fs = require("fs");
const url = require("url");

// Make sure any symlinks in the project folder are resolved:
// https://github.com/facebookincubator/create-react-app/issues/637
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);

const envPublicUrl = process.env.PUBLIC_URL;

function ensureSlash(path, needsSlash) {
  const hasSlash = path.endsWith("/");
  if (hasSlash && !needsSlash) {
    return path.substr(path, path.length - 1);
  } else if (!hasSlash && needsSlash) {
    return `${path}/`;
  } else {
    return path;
  }
}

const getPublicUrl = appPackageJson =>
  envPublicUrl || require(appPackageJson).homepage;

// We use `PUBLIC_URL` environment variable or "homepage" field to infer
// "public path" at which the app is served.
// Webpack needs to know it to put the right <script> hrefs into HTML even in
// single-page apps that may serve index.html for nested URLs like /todos/42.
// We can't use a relative path in HTML because we don't want to load something
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
function getServedPath(appPackageJson) {
  const publicUrl = getPublicUrl(appPackageJson);
  const servedUrl =
    envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : "/");
  return ensureSlash(servedUrl, true);
}

// config after eject: we're in ./config/
module.exports = {
  dotenv: resolveApp(".env"),
  appPath: resolveApp("."),
  appBuild: resolveApp("build"),
  appPublic: resolveApp("public"),
  appHtml: resolveApp("public/index.html"),
  appIndexJs: resolveApp("src/index.js"),
  appPackageJson: resolveApp("package.json"),
  appSrc: resolveApp("src"),
  yarnLockFile: resolveApp("yarn.lock"),
  testsSetup: resolveApp("src/setupTests.js"),
  appNodeModules: resolveApp("node_modules"),
  publicUrl: getPublicUrl(resolveApp("package.json")),
  servedPath: getServedPath(resolveApp("package.json"))
};

module.exports.lernaRoot = path
  .resolve(resolveApp("."), "../")
  .endsWith("packages")
  ? path.resolve(resolveApp("."), "../../")
  : resolveApp(".");

module.exports.appLernaModules = [];

module.exports.appLernaNodeModules = path.join(
  module.exports.lernaRoot,
  "node_modules"
);

fs.readdirSync(module.exports.appLernaNodeModules).forEach(folderName => {
  if (folderName === "react-scripts") return;
  if (folderName.substr(0, 1) === ".") return;

  let fullName = path.join(module.exports.appLernaNodeModules, folderName);

  if (folderName.substr(0, 1) === "@" && fs.lstatSync(fullName).isDirectory()) {
    fs.readdirSync(fullName).forEach(subFolderName => {
      let subFullName = path.join(fullName, subFolderName);

      if (fs.lstatSync(subFullName).isSymbolicLink()) {
        let src = fs.realpathSync(subFullName);
        module.exports.appLernaModules.push(src);
      }
    });
  }

  if (fs.lstatSync(fullName).isSymbolicLink()) {
    module.exports.appLernaModules.push(fs.realpathSync(fullName));
  }
});

并将create-react-app原始 webpack.config.dev 更改为:

"use strict";

const autoprefixer = require("autoprefixer");
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
const InterpolateHtmlPlugin = require("react-dev-utils/InterpolateHtmlPlugin");
const WatchMissingNodeModulesPlugin = require("react-dev-utils/WatchMissingNodeModulesPlugin");
const eslintFormatter = require("react-dev-utils/eslintFormatter");
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
const getClientEnvironment = require("./env");
const paths = require("./paths");

// Webpack uses `publicPath` to determine where the app is being served from.
// In development, we always serve from the root. This makes config easier.
const publicPath = "/";
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
const publicUrl = "";
// Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl);

// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
const postCSSLoaderOptions = {
  // Necessary for external CSS imports to work
  // https://github.com/facebookincubator/create-react-app/issues/2677
  ident: "postcss",
  plugins: () => [
    require("postcss-flexbugs-fixes"),
    autoprefixer({
      flexbox: "no-2009"
    })
  ]
};

console.log(paths.appLernaModules.concat(paths.appSrc));


// This is the development configuration.
// It is focused on developer experience and fast rebuilds.
// The production configuration is different and lives in a separate file.
module.exports = {
  // You may want 'eval' instead if you prefer to see the compiled output in DevTools.
  // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
  devtool: "cheap-module-source-map",
  // These are the "entry points" to our application.
  // This means they will be the "root" imports that are included in JS bundle.
  // The first two entry points enable "hot" CSS and auto-refreshes for JS.
  entry: [
    // We ship a few polyfills by default:
    require.resolve("./polyfills"),
    // Include an alternative client for WebpackDevServer. A client's job is to
    // connect to WebpackDevServer by a socket and get notified about changes.
    // When you save a file, the client will either apply hot updates (in case
    // of CSS changes), or refresh the page (in case of JS changes). When you
    // make a syntax error, this client will display a syntax error overlay.
    // Note: instead of the default WebpackDevServer client, we use a custom one
    // to bring better experience for Create React App users. You can replace
    // the line below with these two lines if you prefer the stock client:
    // require.resolve('webpack-dev-server/client') + '?/',
    // require.resolve('webpack/hot/dev-server'),
    require.resolve("react-dev-utils/webpackHotDevClient"),
    // Finally, this is your app's code:
    paths.appIndexJs
    // We include the app code last so that if there is a runtime error during
    // initialization, it doesn't blow up the WebpackDevServer client, and
    // changing JS code would still trigger a refresh.
  ],
  output: {
    // Add /* filename */ comments to generated require()s in the output.
    pathinfo: true,
    // This does not produce a real file. It's just the virtual path that is
    // served by WebpackDevServer in development. This is the JS bundle
    // containing code from all our entry points, and the Webpack runtime.
    filename: "static/js/bundle.js",
    // There are also additional JS chunk files if you use code splitting.
    chunkFilename: "static/js/[name].chunk.js",
    // This is the URL that app is served from. We use "/" in development.
    publicPath: publicPath,
    // Point sourcemap entries to original disk location (format as URL on Windows)
    devtoolModuleFilenameTemplate: info =>
      path.resolve(info.absoluteResourcePath).replace(/\\/g, "/")
  },
  resolve: {
    // This allows you to set a fallback for where Webpack should look for modules.
    // We placed these paths second because we want `node_modules` to "win"
    // if there are any conflicts. This matches Node resolution mechanism.
    // https://github.com/facebookincubator/create-react-app/issues/253
    modules: ["node_modules", paths.appNodeModules].concat(
      // It is guaranteed to exist because we tweak it in `env.js`
      process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
    ),
    // These are the reasonable defaults supported by the Node ecosystem.
    // We also include JSX as a common component filename extension to support
    // some tools, although we do not recommend using it, see:
    // https://github.com/facebookincubator/create-react-app/issues/290
    // `web` extension prefixes have been added for better support
    // for React Native Web.
    extensions: [".web.js", ".mjs", ".js", ".json", ".web.jsx", ".jsx"],
    alias: {
      // Support React Native Web
      // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
      "react-native": "react-native-web"
    },
    plugins: [
      // Prevents users from importing files from outside of src/ (or node_modules/).
      // This often causes confusion because we only process files within src/ with babel.
      // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
      // please link the files into your node_modules/ and let module-resolution kick in.
      // Make sure your source files are compiled, as they will not be processed in any way.
      new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson])
    ]
  },
  module: {
    strictExportPresence: true,
    rules: [
      // Disable require.ensure as it's not a standard language feature.
      { parser: { requireEnsure: false } },

      // First, run the linter.
      // It's important to do this before Babel processes the JS.

      {
        test: /\.(js|jsx|mjs)$/,
        enforce: "pre",
        use: [
          {
            options: {
              formatter: eslintFormatter,
              eslintPath: require.resolve("eslint")
            },
            loader: require.resolve("eslint-loader")
          }
        ],
        include: paths.appLernaModules.concat(paths.appSrc)
      },
      {
        // "oneOf" will traverse all following loaders until one will
        // match the requirements. When no loader matches it will fall
        // back to the "file" loader at the end of the loader list.
        oneOf: [
          // "url" loader works like "file" loader except that it embeds assets
          // smaller than specified limit in bytes as data URLs to avoid requests.
          // A missing `test` is equivalent to a match.
          {
            test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
            loader: require.resolve("url-loader"),
            options: {
              limit: 10000,
              name: "static/media/[name].[hash:8].[ext]"
            }
          },
          // Process application JS with Babel.
          // The preset includes JSX, Flow, and some ESnext features.
          {
            test: /\.(js|jsx|mjs)$/,
            include: paths.appLernaModules.concat(paths.appSrc),
            use: [
              // This loader parallelizes code compilation, it is optional but
              // improves compile time on larger projects
              require.resolve("thread-loader"),
              {
                loader: require.resolve("babel-loader"),
                options: {
                  // This is a feature of `babel-loader` for webpack (not Babel itself).
                  // It enables caching results in ./node_modules/.cache/babel-loader/
                  // directory for faster rebuilds.
                  cacheDirectory: true,
                  highlightCode: true
                }
              }
            ]
          },
          // Process any JS outside of the app with Babel.
          // Unlike the application JS, we only compile the standard ES features.
          {
            test: /\.js$/,
            use: [
              // This loader parallelizes code compilation, it is optional but
              // improves compile time on larger projects
              require.resolve("thread-loader"),
              {
                loader: require.resolve("babel-loader"),
                options: {
                  babelrc: false,
                  compact: false,
                  presets: [
                    require.resolve("babel-preset-react-app/dependencies")
                  ],
                  cacheDirectory: true,
                  highlightCode: true
                }
              }
            ]
          },
          // "postcss" loader applies autoprefixer to our CSS.
          // "css" loader resolves paths in CSS and adds assets as dependencies.
          // "style" loader turns CSS into JS modules that inject <style> tags.
          // In production, we use a plugin to extract that CSS to a file, but
          // in development "style" loader enables hot editing of CSS.
          // By default we support CSS Modules with the extension .module.css
          {
            test: /\.css$/,
            exclude: /\.module\.css$/,
            use: [
              require.resolve("style-loader"),
              {
                loader: require.resolve("css-loader"),
                options: {
                  importLoaders: 1
                }
              },
              {
                loader: require.resolve("postcss-loader"),
                options: postCSSLoaderOptions
              }
            ]
          },
          // Adds support for CSS Modules (https://github.com/css-modules/css-modules)
          // using the extension .module.css
          {
            test: /\.module\.css$/,
            use: [
              require.resolve("style-loader"),
              {
                loader: require.resolve("css-loader"),
                options: {
                  importLoaders: 1,
                  modules: true,
                  localIdentName: "[path]__[name]___[local]"
                }
              },
              {
                loader: require.resolve("postcss-loader"),
                options: postCSSLoaderOptions
              }
            ]
          },
          // Allows you to use two kinds of imports for SVG:
          // import logoUrl from './logo.svg'; gives you the URL.
          // import { ReactComponent as Logo } from './logo.svg'; gives you a component.
          {
            test: /\.svg$/,
            use: [
              {
                loader: require.resolve("babel-loader"),
                options: {
                  cacheDirectory: true
                }
              },
              require.resolve("svgr/webpack"),
              {
                loader: require.resolve("file-loader"),
                options: {
                  name: "static/media/[name].[hash:8].[ext]"
                }
              }
            ]
          },
          // "file" loader makes sure those assets get served by WebpackDevServer.
          // When you `import` an asset, you get its (virtual) filename.
          // In production, they would get copied to the `build` folder.
          // This loader doesn't use a "test" so it will catch all modules
          // that fall through the other loaders.
          {
            // Exclude `js` files to keep "css" loader working as it injects
            // its runtime that would otherwise be processed through "file" loader.
            // Also exclude `html` and `json` extensions so they get processed
            // by webpacks internal loaders.
            exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
            loader: require.resolve("file-loader"),
            options: {
              name: "static/media/[name].[hash:8].[ext]"
            }
          }
        ]
      }
      // ** STOP ** Are you adding a new loader?
      // Make sure to add the new loader(s) before the "file" loader.
    ]
  },
  plugins: [
    // Makes some environment variables available in index.html.
    // The public URL is available as %PUBLIC_URL% in index.html, e.g.:
    // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    // In development, this will be an empty string.
    new InterpolateHtmlPlugin(env.raw),
    // Generates an `index.html` file with the <script> injected.
    new HtmlWebpackPlugin({
      inject: true,
      template: paths.appHtml
    }),
    // Add module names to factory functions so they appear in browser profiler.
    new webpack.NamedModulesPlugin(),
    // Makes some environment variables available to the JS code, for example:
    // if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
    new webpack.DefinePlugin(env.stringified),
    // This is necessary to emit hot updates (currently CSS only):
    new webpack.HotModuleReplacementPlugin(),
    // Watcher doesn't work well if you mistype casing in a path so we use
    // a plugin that prints an error when you attempt to do this.
    // See https://github.com/facebookincubator/create-react-app/issues/240
    new CaseSensitivePathsPlugin(),
    // If you require a missing module and then `npm install` it, you still have
    // to restart the development server for Webpack to discover it. This plugin
    // makes the discovery automatic so you don't have to restart.
    // See https://github.com/facebookincubator/create-react-app/issues/186
    new WatchMissingNodeModulesPlugin(paths.appNodeModules),
    // Moment.js is an extremely popular library that bundles large locale files
    // by default due to how Webpack interprets its code. This is a practical
    // solution that requires the user to opt into importing specific locales.
    // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
    // You can remove this if you don't use Moment.js:
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
  ],
  // Some libraries import Node modules but don't use them in the browser.
  // Tell Webpack to provide empty mocks for them so importing them works.
  node: {
    dgram: "empty",
    fs: "empty",
    net: "empty",
    tls: "empty",
    child_process: "empty"
  },
  // Turn off performance hints during development because we don't do any
  // splitting or minification in interest of speed. These warnings become
  // cumbersome.
  performance: {
    hints: false
  }
};

OBS:修复上面代码中的console.log()命令,以显示正在加载的内容。

正在运行,我得到以下结果:

$ npm start

> @myorganization/auth@3.0.0 start D:\project\packages\auth
> node scripts/start.js

[ 'D:\\project\\packages\\auth',
  'D:\\project\\packages\\ux',
  'D:\\project\\packages\\auth\\src' ]
Starting the development server...

Failed to compile.

../ux/src/atoms/index.js
Module build failed: Error: No ESLint configuration found.

在webpack.config.dev中评论eslint步骤,我得到了:

$ npm start

> @myorganization/auth@3.0.0 start D:\project\packages\auth
> node scripts/start.js

[ 'D:\\project\\packages\\auth',
  'D:\\project\\packages\\ux',
  'D:\\project\\packages\\auth\\src' ]
Starting the development server...

Failed to compile.

../ux/src/atoms/ActivityItem/ActivityItem.js
Syntax error: D:\project\packages\ux\src\atoms\ActivityItem\ActivityItem.js: Support for the experimental syntax 'classProperties' isn't currently enabled (34:20):

  32 | class ActivityItem extends Component {
  33 |
> 34 |   static propTypes = {
     |                    ^
  35 |     showHeader: PropTypes.bool,
  36 |     timestamp: PropTypes.number,
  37 |     username: PropTypes.string,

Add @babel/plugin-proposal-class-properties (https://git.io/vb4SL) to the 'plugins' section of your Babel config to enable transformation.

简而言之,共享代码既没有被转换,也没有被打印,这打破了整个项目。整个项目使用ES6完成。

1 个答案:

答案 0 :(得分:0)

从react-scripts 2.0开始,他们删除了对monorepos(https://github.com/facebook/create-react-app/issues/1333)的支持。但是,如果滚动到问题页面的底部,则会看到一个指向react-workspaces的链接。我没有尝试过,但是看起来很有希望。 (https://github.com/react-workspaces/react-workspaces-playground