gregberge / loadable-components

The recommended Code Splitting library for React βœ‚οΈβœ¨

Home Page:https://loadable-components.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Typescript: nodeExtractor.requireEntrypoint returns undefined

natterstefan opened this issue Β· comments

πŸ’¬ Questions and Help

Hi everybody,

first I want to thank you for the awesome piece of software you created. Now to my problem.

Problem

Based on the SSR example in this repo, I have created my own example here. The only difference: it is a TypeScript example.

I've got the problem, that

const { default: App } = nodeExtractor.requireEntrypoint('main')

always returns undefined (here). Not sure why this happens and how I can fix it.

In my webpack config I use both babel-loader and ts-loader together with typescript-loadable-components-plugin:

// webpack.config.js
module: {
    rules: [
      {
        test: /\.(ts)x?$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
          },
          {
            loader: 'ts-loader',
            options: {
              getCustomTransformers: (program) => ({
                before: [createLoadableComponentsTransformer(program, {})],
              }),
            },
          },
        ],
      },
    ],
  },

I noticed that I am also unable to use webpackChunkName (the bundles get ascending numbers, e.g. 0.js, 1.js) but that's another problem for another issue report acrazing/typescript-loadable-components-plugin#3.

Could you please help me? Thank you very much. I appreciate every type of support.

Expected Result

SSR in a typescript app works similar to the JS example.

Example Repo

Related Issues

  • #189: I read it but couldn't figure out what I need to do.

Hey @natterstefan πŸ‘‹,
Thank you for opening an issue. We'll get back to you as soon as we can.
Please, consider supporting us on Open Collective. We give a special attention to issues opened by backers.
If you use Loadable at work, you can also ask your company to sponsor us ❀️.

FTR: Looks like I fixed it by setting the proper value esnext in tsconfig.json. This is how my config looks now:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "esnext",
    "jsx": "react",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

Related comments and solutions:

Edit: Plus this change in the webpack config for the server bundle:

  output: {
    /**
     * required, or chunkExtractor.requireEntrypoint() does not work.
     * @see https://loadable-components.com/docs/api-loadable-server/#chunkextractorrequireentrypoint
     */
    libraryTarget: 'commonjs2',
  },

According to the solution, the problem was bound to the transformer which looks into imports and handles only loadable imported from loadable. However the transformation should not happen on this stage, so, no - it is not.

So what you did - kept the module system "as is", for babel and later webpack.

Why did it fix the problem? I have no idea πŸ˜…

Hi @theKashey,

FTR: I've also had to change the libraryTarget similar to the one in the example. Without this change App was still undefined. So it was the combination of both I guess.

Why did it fix the problem? I have no idea πŸ˜…

I have no idea too ^^. I was just happy to find a solution :D

@natterstefan,
I'm facing a similar kind of problem for a few days now and didn't got any solution.
I believe I'm doing something wrong with either my webpack config or the major implementation of loadable components.
I can't share much of my codebase
Kindly see my configurations
Webpack loaders

/* eslint-disable @typescript-eslint/no-var-requires */

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { paths } = require("./paths");
const path = require('path')
const { createLoadableComponentsTransformer } = require('typescript-loadable-components-plugin');

const cssRegex = /\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassExternalRegex = /\.external\.(scss|sass)$/;


const babelLoader = {
  test: /\.(ts|tsx)$/,
  exclude: /server/,
  use: [
    {
      loader: 'babel-loader',
      options: {
        babelrc: false,
        presets: [
          "@babel/env",
          "@babel/preset-react",
        ],
        plugins: [
          "@babel/proposal-class-properties",
          "@babel/proposal-object-rest-spread",
          "@babel/plugin-syntax-dynamic-import",
          "@babel/plugin-transform-runtime",
          "@loadable/babel-plugin"
        ],
      }
    },
    {
      loader: "ts-loader",
      options: {
        // disable type checker - we will use it in fork plugin
        transpileOnly: true,
        onlyCompileBundledFiles: true,
        getCustomTransformers: (program) => ({
          before: [createLoadableComponentsTransformer(program, {})],
        })
      }
    },
  ],
};



const baseStyleLoader = {
  test: cssRegex,
  use: getStyleLoaders(
    {
      importLoaders: 1,
      sourceMap: true
    }
  ),
  sideEffects: true
};

const scssLoaderClient = {
  test: sassRegex,
  exclude: sassExternalRegex,
  use: getStyleLoaders(
    {
      modules: {
        localIdentName: "[path]_[name]_[local]",
        exportLocalsConvention: "camelCase"
      },
      importLoaders: 3,
      sourceMap: true
    },
    'sass-loader'
  ),
  sideEffects: true
};

const sassExternalLoaderClient = {
  test: sassExternalRegex,
  use: getStyleLoaders(
    {
      importLoaders: 3,
      sourceMap: true
    },
    'sass-loader'
  )
}


const fileLoaderFontClient = {
  test: /\.(eot|otf|ttf|woff|woff2)(\?.*)?$/,
  loader: 'file-loader',
  options: {
    name: `[name].[ext]`,
    outputPath: paths.assetsPrefix ? `${paths.assetsPrefix}/fonts` : 'fonts',
    publicPath: `${paths.assetsPublicPath}fonts/`
  }
}

const cssLoaderServer = {
  test: cssRegex,
  use: getStyleLoaders(
    {
      modules: {
        localIdentName: "[path]_[name]_[local]",
        exportLocalsConvention: "camelCase",
        exportOnlyLocals: true,
      },
      importLoaders: 1,
      sourceMap: true,
    }
  ),
  sideEffects: true
}

const fileLoaderFontServer = {
  test: /\.(eot|otf|ttf|woff|woff2)(\?.*)?$/,
  loader: 'file-loader',
  options: {
    emitFile: false,
    name: `[name].[ext]`,
    outputPath: paths.assetsPrefix ? `${paths.assetsPrefix}/fonts` : 'fonts',
    publicPath: `${paths.assetsPublicPath}fonts/`
  }
}

const fileLoaderMediaClient = {
  test: /\.(jpg|svg|jpeg|png|gif|ico|webp)(\?.*)?$/,
  loader: 'file-loader',
  options: {
    name: `[name].[ext]`,
    outputPath: paths.assetsPrefix ? `${paths.assetsPrefix}/images` : 'images',
    publicPath: `${paths.assetsPublicPath}images/`
  }
};
const fileLoaderMediaServer = {
  test: /\.(jpg|svg|jpeg|png|gif|ico|webp)(\?.*)?$/,
  use: [
    {
      loader: 'file-loader',
      options: {
        emitFile: false,
        name: `[name].[ext]`,
        outputPath: paths.assetsPrefix ? `${paths.assetsPrefix}/images` : 'images',
        publicPath: `${paths.assetsPublicPath}images/`
      }
    }
  ]
};

const svgrWebpack = {
  test: /\.svg$/,
  use: [
    {
      loader: "@svgr/webpack"
    },
    {
      loader: "file-loader"
    }
  ],
  type: "javascript/auto",
  issuer: {
    and: [/\.(ts|tsx|js|jsx|md|mdx)$/]
  }
}


const client = [
  {
    test: /client\.tsx$/,
    exclude: /server/,
    loader: 'bundle-loader'
  },
  {
    test: /\.m?js$/,
    type: 'javascript/auto',
    resolve: {
      fullySpecified: false,
    },
  }
  babelLoader,
  baseStyleLoader,
  scssLoaderClient,
  sassExternalLoaderClient,
  fileLoaderFontClient,
  svgrWebpack,
  fileLoaderMediaClient
];
const server = [
  {
    test: /\.(ts|tsx)$/,
    exclude: /node_modules/,
    use: [
      {
        loader: 'babel-loader',
        options: {
          babelrc: false,
          presets: [
            "@babel/env",
            "@babel/preset-react",
          ],
          plugins: [
            "@babel/proposal-class-properties",
            "@babel/proposal-object-rest-spread",
            "@babel/plugin-syntax-dynamic-import",
            "@babel/plugin-transform-runtime",
            '@loadable/babel-plugin'
          ],
        }
      },
      {
        loader: "ts-loader",
        options: {
          // disable type checker - we will use it in fork plugin
          transpileOnly: true,
          onlyCompileBundledFiles: true,
          getCustomTransformers: (program) => ({
            before: [createLoadableComponentsTransformer(program, {})],
          })
        }
      }],
  },
  {
    test: cssRegex,
    use: getStyleLoadersSever(
      {
        modules: {
          localIdentName: "[path]_[name]_[local]",
          exportLocalsConvention: "camelCase",
          exportOnlyLocals: true,
        },
        importLoaders: 1,
        sourceMap: true,
      }
    ),
    sideEffects: true
  },
  {
    test: sassRegex,
    exclude: sassExternalRegex,
    use: getStyleLoadersSever(
      {
        modules: {
          localIdentName: "[path]_[name]_[local]",
          exportLocalsConvention: "camelCase",
          exportOnlyLocals: true
        },
        importLoaders: 3,
        sourceMap: true,
      },
      'sass-loader'
    ),
    sideEffects: true
  },
  {
    test: sassExternalRegex,
    use: getStyleLoadersSever(
      {
        importLoaders: 3,
        sourceMap: true,
        modules: {
          exportOnlyLocals: true,
        }
      },
      'sass-loader'
    )
  },
  {
    test: /\.(eot|otf|ttf|woff|woff2)(\?.*)?$/,
    use: [
      {
        loader: 'file-loader',
        options: {
          emitFile: false,
          name: `[name].[ext]`,
          outputPath: paths.assetsPrefix ? `${paths.assetsPrefix}/fonts` : 'fonts',
          publicPath: `${paths.assetsPublicPath}fonts/`
        }
      }
    ]
  },
  {
    test: /\.svg(\?.*)?$/,
    issuer: /\.tsx?$/,
    use: ['@svgr/webpack']
  },
  {
    test: /\.(jpg|svg|jpeg|png|gif|ico|webp)(\?.*)?$/,
    use: [
      {
        loader: 'file-loader',
        options: {
          emitFile: false,
          name: `[name].[ext]`,
          outputPath: paths.assetsPrefix ? `${paths.assetsPrefix}/images` : 'images',
          publicPath: `${paths.assetsPublicPath}images/`
        }
      }
    ]
  },
];

module.exports = {
  client,
  server,
};

Plugins

/* eslint-disable @typescript-eslint/no-var-requires */


const webpack = require('webpack');
const WebpackAssetsManifest = require('webpack-assets-manifest');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { paths, clientstringified, stringified } = require('./paths');
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
const LoadablePlugin = require('@loadable/webpack-plugin');
const CompressionPlugin = require("compression-webpack-plugin");


const fileNamePattern = paths.isProd ? '[name].[contenthash]' : '[name]';
const shared = [
    new LoadablePlugin(),
];

const manifestJSON = stringified(paths.clientAsset);

const client = [
    new CompressionPlugin({
        algorithm: 'gzip',
        test: /\.(js|css|html|svg)$/,
        threshold: 10240,
        minRatio: 0.8
    }),
    new webpack.IgnorePlugin({
        resourceRegExp: /^\.\/locale$/,
        contextRegExp: /moment$/
    }),
    new webpack.DefinePlugin(clientstringified),
    !paths.isProd ? new ForkTsCheckerWebpackPlugin({
        typescript: {
            configFile: paths.tsConfig
        }
    }) : f => f,
    new MiniCssExtractPlugin({
        filename: paths.assetsPrefix ? `${paths.assetsPrefix}/${fileNamePattern}.css` : `${fileNamePattern}.css`
    }),
    new HtmlWebpackPlugin(
        Object.assign(
            {
                title: 'Risor'
            },
            paths.isProd
                ? {
                    minify: {
                        removeComments: true,
                        collapseWhitespace: true,
                        removeRedundantAttributes: true,
                        useShortDoctype: true,
                        removeEmptyAttributes: true,
                        removeStyleLinkTypeAttributes: true,
                        keepClosingSlash: true,
                        minifyJS: true,
                        minifyCSS: true,
                        minifyURLs: true,
                    },
                }
                : {}
        )
    ),
    new WebpackAssetsManifest({
        // Options go here
    }),
    new NodePolyfillPlugin({
        excludeAliases: [
            "assert",
            "buffer",
            "console",
            "constants",
            "crypto",
            "domain",
            "events",
            "punycode",
            "querystring",
            "stream",
            "_stream_duplex",
            "_stream_passthrough",
            "_stream_transform",
            "_stream_writable",
            "string_decoder",
            "sys",
            "timers",
            "tty",
            "url",
            "util",
            "vm",
            "zlib",
        ],
    })
];

const server = [
    new webpack.DefinePlugin(manifestJSON),
];

module.exports = {
    shared,
    client,
    server,
};

*** Server Rules ***

module.exports = {
    target: 'node',
    optimization: {
        minimize: isProdEnv,
        minimizer: [new TerserPlugin()],
        splitChunks: {
            chunks: 'all',
            automaticNameDelimiter: "-",
            minChunks: 3
        }
    },
    externals: ['@loadable/component', nodeExternals()],
    externalsPresets: { node: true },
    context: paths.context,
    entry: paths.serverEntryPath,
    output: {
        globalObject: `typeof self !== 'undefined' ? self : this`,
        path: paths.serverOutputPath,
        publicPath: '/',
        filename: `server.js`,
        libraryTarget: 'commonjs2',
    },
    resolve: {
        extensions: [".wasm", ".mjs", ".js", ".jsx", ".tsx", ".ts", ".json", ".scss", ".css", ".svg", ".jpg", ".png", ".ico", 'webp'],
        alias: alias,
        fallback: {
            "http": require.resolve("stream-http"),
            "stream": require.resolve("stream-browserify")
        }
    },
    plugins: [
        ...plugins.shared,
        ...plugins.server
    ],
    module: {
        rules: [
            {
                test: /\.(ts|tsx)$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            babelrc: false,
                            presets: [
                                "@babel/env",
                                "@babel/preset-react",
                            ],
                            plugins: [
                                "@babel/proposal-class-properties",
                                "@babel/proposal-object-rest-spread",
                                "@babel/plugin-syntax-dynamic-import",
                                "@babel/plugin-transform-runtime",
                                '@loadable/babel-plugin'
                            ],
                        }
                    },
                    {
                        loader: "ts-loader",
                        options: {
                            // disable type checker - we will use it in fork plugin
                            transpileOnly: true,
                            onlyCompileBundledFiles: true,
                            getCustomTransformers: (program) => ({
                                before: [createLoadableComponentsTransformer(program, {})],
                              })
                        }
                    }],
            },
            {
                test: cssRegex,
                use: getStyleLoaders(
                    {
                        modules: {
                            localIdentName: "[path]_[name]_[local]",
                            exportLocalsConvention: "camelCase",
                            exportOnlyLocals: true,
                        },
                        importLoaders: 1,
                        sourceMap: true,
                    }
                ),
                sideEffects: true
            },
            {
                test: sassRegex,
                exclude: sassExternalRegex,
                use: getStyleLoaders(
                    {
                        modules: {
                            localIdentName: "[path]_[name]_[local]",
                            exportLocalsConvention: "camelCase",
                            exportOnlyLocals: true
                        },
                        importLoaders: 3,
                        sourceMap: true,
                    },
                    'sass-loader'
                ),
                sideEffects: true
            },
            {
                test: sassExternalRegex,
                use: getStyleLoaders(
                    {
                        importLoaders: 3,
                        sourceMap: true,
                        modules: {
                            exportOnlyLocals: true,
                        }
                    },
                    'sass-loader'
                )
            },
            {
                test: /\.(eot|otf|ttf|woff|woff2)(\?.*)?$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            emitFile: false,
                            name: `[name].[ext]`,
                            outputPath: paths.assetsPrefix ? `${paths.assetsPrefix}/fonts` : 'fonts',
                            publicPath: `${paths.assetsPublicPath}fonts/`
                        }
                    }
                ]
            },
            {
                test: /\.svg(\?.*)?$/,
                issuer: /\.tsx?$/,
                use: ['@svgr/webpack']
            },
            {
                test: /\.(jpg|svg|jpeg|png|gif|ico|webp)(\?.*)?$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            emitFile: false,
                            name: `[name].[ext]`,
                            outputPath: paths.assetsPrefix ? `${paths.assetsPrefix}/images` : 'images',
                            publicPath: `${paths.assetsPublicPath}images/`
                        }
                    }
                ]
            },
        ]
    }
};

And also here is my server.tsx

 const nodeExtractor = new ChunkExtractor({
      statsFile: path.resolve(__CLIENT_OUTPUT_PATH__, "loadable-stats.json"),
      entrypoints: ["client"],
      outputPath: __CLIENT_OUTPUT_PATH__
    });

   /**
     getting "self is not defined" 
  
   */
    const { default: App } = nodeExtractor.requireEntrypoint('client'); // ***issue is here  ***

    const result = await createApp(
//    ....
      },
      false,
      req.url
    );

Hi @abhishek-mittal,

I'm afraid I won't be of much help to you, as I'm currently no longer involved with the loadable-components setup in some of my projects. However, I hope that someone reading this can help you.

@natterstefan thanks for the acknowledgment, much appreciated. ✌️