首页 > 解决方案 > React Loadable JSON mapping file with components being referenced from multiple locations

问题描述

I'm using React Loadable to code split my components, but the problem is that certain components are referenced from multiple locations. For example, I have this component CarsModule, which is referenced in multiple areas like this:

const CarsModule = Loadable({
  loader: () => import(/* webpackChunkName: "CarsModule" */ '../components/CarsModule'),
  loading() {
    return <div>Loading...</div>
  }
});

const CarsModule = Loadable({
  loader: () => import(/* webpackChunkName: "CarsModule" */ '../../../../components/CarsModule'),
  loading() {
    return <div>Loading...</div>
  }
});

const CarsModule = Loadable({
  loader: () => import(/* webpackChunkName: "CarsModule" */ '../../components/CarsModule'),
  loading() {
    return <div>Loading...</div>
  }
});

const CarsModule = Loadable({
  loader: () => import(/* webpackChunkName: "CarsModule" */ '../../../../components/CarsModule'),
  loading() {
    return <div>Loading...</div>
  }
});

What ends up happening in the dist/react-loadable.json file is that CarsModule looks like this:

"../../../../components/CarsModule": [
  {
    "id": 822,
    "name": "./dist/components/CarsModule/index.js",
    "file": "0-78a53ff810acc75b6fde.js",
    "publicPath": "http://localhost:3000/dist/0-78a53ff810acc75b6fde.js"
  }
],

When one of the pages goes to request it during server side rendering that imports it like this '../components/CarsModule', it gets undefined for the module (since the bundle's key is this "../../../../components/CarsModule"). I used the webpackChunkName to try and name those keys in the react-loadable.json file but no luck :(

I have chunk hashing going on inside my webpack prod file which looks like this:

module.exports = {
  devtool: 'source-map',
  context: path.resolve(__dirname, '..'),
  entry: {
    main: [
      'babel-polyfill',
      './dist/app/defaultClient.js'
    ],
  },
  output: {
    path: assetsPath,
    filename: '[name]-[chunkhash].js',
    chunkFilename: '[name]-[chunkhash].js',
    publicPath: STATIC_HOST ? `${STATIC_HOST}/dist/` : `http://localhost:3000/dist/`,
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: 'babel-loader',
      },
      {
        test: /\.scss$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [
            {
              loader: 'css-loader',
              options: {
                modules: true,
                importLoaders: 2,
                localIdentName: '[folder]-[local]',
                minimize: true,
              },
            },
            {
              loader: 'postcss-loader',
              options: {
                plugins: () => [require('autoprefixer')()],
              },
            },
            {
              loader: 'sass-loader',
              options: {
                outputStyle: 'expanded',
                sourceMap: false,
              },
            },
          ],
        }),
      },
      {
        test: /\.woff2?(\?v=\d+\.\d+\.\d+)?$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10000,
            mimetype: 'application/font-woff',
          },
        },
      },
      {
        test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10000,
            mimetype: 'application/octet-stream',
          },
        },
      },
      {
        test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
        use: 'file-loader',
      },
      {
        test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10000,
            mimetype: 'image/svg+xml',
          },
        },
      },
      {
        test: webpackIsomorphicToolsPlugin.regular_expression('images'),
        use: {
          loader: 'url-loader',
          options: {
            limit: 10240,
          },
        },
      },
    ],
  },
  resolve: {
    modules: ['node_modules', 'src'],
    extensions: ['.json', '.js', '.jsx'],
  },
  plugins: [
    new CleanPlugin([assetsPath], { root: projectRootPath }),

    // css files from the extract-text-plugin loader
    new ExtractTextPlugin({
      filename: '[name]-[chunkhash].css',
      allChunks: true,
    }),

    new webpack.DefinePlugin({
      'process.env': exportedClientAppConfigProperties,
      __CLIENT__: true,
      __DEVTOOLS__: false,
    }),

    // ignore dev config
    new webpack.IgnorePlugin(/\.\/dev/, /\/config$/),

    // optimizations
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false,
      },
    }),

    new ReactLoadablePlugin({ filename: `./dist/react-loadable.json` }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),

    webpackIsomorphicToolsPlugin,
  ],
};

However, there is no CarsModule.js file that is created. How can I solve this issue with multiple components importing the same module from different locations if react-loadable.json is going to use the import location as a key?

If I remove these other files that are doing different imports from my project, the react-loadable.json file will be generated referencing the CarsModule like so:

"../components/CarsModule": [
  {
    "id": 822,
    "name": "./dist/components/CarsModule/index.js",
    "file": "0-78a53ff810acc75b6fde.js",
    "publicPath": "http://localhost:3000/dist/0-78a53ff810acc75b6fde.js"
  }
],

However, this isn't a very plausible solution because other components still need to reference that CarsModule. Any ideas what I'm doing wrong? Thanks in advance!

Here are my versions of react-loadable & webpack:

"react-loadable": "^5.4.0"
"webpack": "^3.5.6"

Here is my .babelrc file:

{
  "presets": [
    ["env", {
      "targets": {
        "node": "current",
        "browsers": ["last 2 versions", "safari >= 7"]
      }
    }],
    "stage-0",
    "react"
  ],
  "plugins": [
    "dynamic-import-webpack",
    "react-loadable/babel",
    "syntax-async-functions",
    "syntax-dynamic-import",
    "transform-decorators-legacy",
    ["module-resolver", {
      "root": ["./src/"],
    }],
    ["transform-runtime", {
      "helpers": false,
      "polyfill": true
    }]
  ]
}

标签: webpackcode-splittingreact-loadablecode-splitting-async

解决方案


是否将模块解析器别名添加到您的.babelrc帮助中?

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ],
  "plugins": [
    [
      "module-resolver",
      {
        "root": [
          "."
        ],
        "alias": {
          "components": "./components",
        }
      }
    ],
    "react-loadable/babel"
  ]
}

这样每个加载器都可以使用相同的路径字符串。

const CarsModule = Loadable({
  loader: () => import(/* webpackChunkName: "CarsModule" */ 'components/CarsModule'),
  loading() {
    return <div>Loading...</div>
  }
});

推荐阅读