gregberge / loadable-components

The recommended Code Splitting library for React ✂️✨
https://loadable-components.com
MIT License
7.66k stars 380 forks source link

Loadable Components raises error `SSR requires @loadable/babel-plugin, please install it` when using TypeScript `commonjs` modules. #726

Closed ludovic-lambert closed 3 years ago

ludovic-lambert commented 3 years ago

🐛 Bug Report

Dynamic import of some components randomly raise (node:12876) UnhandledPromiseRejectionWarning: Invariant Violation: loadable: SSR requires @loadable/babel-plugin, please install it when access any page of the app in Chrome (server is starting ok) ; while others don't. The plugin is obviously installed. Some components randomly raise the same error not when calling them but when trying to 'display source code' in Chrome. Some components just run fine.

To Reproduce

This component systematically raise this error when any page is called:

import React, { Component } from 'react'
import { IAppDictonary, IAppSetMeta, IAppUser } from '../Shared/App'
import { siteHomepage } from 'bnc-libs/lib/page'
import { Page } from '../../libs/page'

let pageMeta = new Page()
Object.assign(pageMeta, siteHomepage)

export interface IHomeProps extends IAppDictonary, IAppUser, IAppSetMeta { }

export interface IHomeStates { }

export class Home extends Component<IHomeProps, IHomeStates> {

  componentDidMount() {
    this.props.setMeta(pageMeta)
  }

  render() {
    const d = this.props.dictionary
    return (
      <div id="mainDiv" className="homepage">
        <div className="devBanner">
          <h2>{d.devInProgressTitle}</h2>
          <div className="mainDiv center bigMargin">
            <img
              id="tortoise"
              src='/i/ui/tortoise.webp' alt={d.devInProgressTitle} /><br />
            <p>{d.devInProgressText}</p>
          </div>
        </div>
      </div>
    )
  }
}

export default Home

Other similar components don't raise the error when displayed, but do when trying to display source code of the page in Chrome.

Expected behavior

No error.

System:

Config

"dependencies": {
    "@loadable/component": "^5.14.1",
    "@loadable/server": "^5.14.2",
    "bnc-libs": "^0.2.33",
    "cookie-parser": "^1.4.5",
    "express": "^4.17.1",
    "isomorphic-fetch": "^3.0.0",
    "lazysizes": "^5.3.1",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-router-dom": "^5.2.0"
  },
  "devDependencies": {
    "@babel/cli": "^7.13.10",
    "@babel/core": "^7.13.13",
    "@babel/plugin-proposal-class-properties": "^7.13.0",
    "@babel/preset-react": "^7.13.13",
    "@babel/preset-typescript": "^7.13.0",
    "@loadable/babel-plugin": "^5.13.2",
    "@loadable/webpack-plugin": "^5.14.2",
    "@types/express": "^4.17.11",
    "@types/isomorphic-fetch": "0.0.35",
    "@types/loadable__component": "^5.13.3",
    "@types/loadable__server": "^5.12.3",
    "@types/node": "^14.14.37",
    "@types/react": "^17.0.3",
    "@types/react-dom": "^17.0.3",
    "@types/react-router-dom": "^5.1.7",
    "babel-loader": "^8.2.2",
    "jshint": "^2.12.0",
    "ts-node": "^9.1.1",
    "ts-node-dev": "^1.1.6",
    "typescript": "^4.2.3",
    "webpack": "^5.28.0",
    "webpack-cli": "^4.5.0",
    "webpack-node-externals": "^2.5.2"
  }
}
open-collective-bot[bot] commented 3 years ago

Hey @ludovic-lambert :wave:, 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 :heart:.

theKashey commented 3 years ago

Well, can you put a breakpoint there and figure out why loadable complains?

https://github.com/gregberge/loadable-components/blob/3676e488a714068baca5cefad2aa98c0de0a8fab/packages/component/src/createLoadable.js#L107

If properties the code is looking for are not present - what is present instead?

ludovic-lambert commented 3 years ago

Hey Anton, thank you for your help. Result:

props.__chunkExtractor ChunkExtractor {
  namespace: '',
  stats: {
    hash: '0d67286f0f7b09676e9b',
    version: '5.28.0',
    publicPath: 'auto',
    outputPath: 'D:\\_code\\bnc-new\\bnc-app\\public\\js',
    assetsByChunkName: {
      main: [Array],
      'Site-Homepage': [Array],
      error: [Array],
      pagesearch: [Array],
      profile: [Array],
      confirm: [Array],
      forget: [Array],
      notif: [Array]
    },
    assets: [
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object]
    ],
    chunks: [
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object]
    ],
    entrypoints: { main: [Object] },
    namedChunkGroups: {
      main: [Object],
      'Site-Homepage': [Object],
      error: [Object],
      pagesearch: [Object],
      profile: [Object],
      confirm: [Object],
      forget: [Object],
      notif: [Object]
    },
    errors: [],
    errorsCount: 0,
    warnings: [],
    warningsCount: 0,
    children: [],
    generator: 'loadable-components'
  },
  publicPath: '/js',
  outputPath: 'D:\\_code\\bnc-new\\bnc-app\\public\\js', 
  statsFile: 'D:\\_code\\bnc-new\\bnc-app\\public\\js\\loadable-stats.json',
  entrypoints: [ 'main' ],
  chunks: [],
  inputFileSystem: {
    appendFile: [Function: appendFile],
    appendFileSync: [Function: appendFileSync],
    access: [Function: access],
    accessSync: [Function: accessSync],
    chown: [Function: chown],
    chownSync: [Function: chownSync],
    chmod: [Function: chmod],
    chmodSync: [Function: chmodSync],
    close: [Function: close],
    closeSync: [Function: closeSync],
    copyFile: [Function: copyFile],
    copyFileSync: [Function: copyFileSync],
    createReadStream: [Function: createReadStream],      
    createWriteStream: [Function: createWriteStream],    
    exists: [Function: exists],
    existsSync: [Function: existsSync],
    fchown: [Function: fchown],
    fchownSync: [Function: fchownSync],
    fchmod: [Function: fchmod],
    fchmodSync: [Function: fchmodSync],
    fdatasync: [Function: fdatasync],
    fdatasyncSync: [Function: fdatasyncSync],
    fstat: [Function: fstat],
    fstatSync: [Function: fstatSync],
    fsync: [Function: fsync],
    fsyncSync: [Function: fsyncSync],
    ftruncate: [Function: ftruncate],
    ftruncateSync: [Function: ftruncateSync],
    futimes: [Function: futimes],
    futimesSync: [Function: futimesSync],
    lchown: [Function: lchown],
    lchownSync: [Function: lchownSync],
    lchmod: undefined,
    lchmodSync: undefined,
    link: [Function: link],
    linkSync: [Function: linkSync],
    lstat: [Function: lstat],
    lstatSync: [Function: lstatSync],
    lutimes: [Function: lutimes],
    lutimesSync: [Function: lutimesSync],
    mkdir: [Function: mkdir],
    mkdirSync: [Function: mkdirSync],
    mkdtemp: [Function: mkdtemp],
    mkdtempSync: [Function: mkdtempSync],
    open: [Function: open],
    openSync: [Function: openSync],
    opendir: [Function: opendir],
    opendirSync: [Function: opendirSync],
    readdir: [Function: readdir],
    readdirSync: [Function: readdirSync],
    read: [Function: read],
    readSync: [Function: readSync],
    readv: [Function: readv],
    readvSync: [Function: readvSync],
    readFile: [Function: readFile],
    readFileSync: [Function: readFileSync],
    readlink: [Function: readlink],
    readlinkSync: [Function: readlinkSync],
    realpath: [Function: realpath] { native: [Function (anonymous)] },
    realpathSync: [Function: realpathSync] { native: [Function (anonymous)] },
    rename: [Function: rename],
    renameSync: [Function: renameSync],
    rm: [Function: rm],
    rmSync: [Function: rmSync],
    rmdir: [Function: rmdir],
    rmdirSync: [Function: rmdirSync],
    stat: [Function: stat],
    statSync: [Function: statSync],
    symlink: [Function: symlink],
    symlinkSync: [Function: symlinkSync],
    truncate: [Function: truncate],
    truncateSync: [Function: truncateSync],
    unwatchFile: [Function: unwatchFile],
    unlink: [Function: unlink],
    unlinkSync: [Function: unlinkSync],
    utimes: [Function: utimes],
    utimesSync: [Function: utimesSync],
    watch: [Function: watch],
    watchFile: [Function: watchFile],
    writeFile: [Function: writeFile],
    writeFileSync: [Function: writeFileSync],
    write: [Function: write],
    writeSync: [Function: writeSync],
    writev: [Function: writev],
    writevSync: [Function: writevSync],
    Dir: [class Dir],
    Dirent: [class Dirent],
    Stats: [Function: Stats],
    ReadStream: [Getter/Setter],
    WriteStream: [Getter/Setter],
    FileReadStream: [Getter/Setter],
    FileWriteStream: [Getter/Setter],
    _toUnixTimestamp: [Function: toUnixTimestamp],       
    F_OK: 0,
    R_OK: 4,
    W_OK: 2,
    X_OK: 1,
    constants: [Object: null prototype] {
      UV_FS_SYMLINK_DIR: 1,
      UV_FS_SYMLINK_JUNCTION: 2,
      O_RDONLY: 0,
      O_WRONLY: 1,
      O_RDWR: 2,
      UV_DIRENT_UNKNOWN: 0,
      UV_DIRENT_FILE: 1,
      UV_DIRENT_DIR: 2,
      UV_DIRENT_LINK: 3,
      UV_DIRENT_FIFO: 4,
      UV_DIRENT_SOCKET: 5,
      UV_DIRENT_CHAR: 6,
      UV_DIRENT_BLOCK: 7,
      S_IFMT: 61440,
      S_IFREG: 32768,
      S_IFDIR: 16384,
      S_IFCHR: 8192,
      S_IFLNK: 40960,
      O_CREAT: 256,
      O_EXCL: 1024,
      UV_FS_O_FILEMAP: 536870912,
      O_TRUNC: 512,
      O_APPEND: 8,
      F_OK: 0,
      R_OK: 4,
      W_OK: 2,
      X_OK: 1,
      UV_FS_COPYFILE_EXCL: 1,
      COPYFILE_EXCL: 1,
      UV_FS_COPYFILE_FICLONE: 2,
      COPYFILE_FICLONE: 2,
      UV_FS_COPYFILE_FICLONE_FORCE: 4,
      COPYFILE_FICLONE_FORCE: 4
    },
    promises: [Getter]
  }
}
ctor.requireSync undefined
theKashey commented 3 years ago

ctor.requireSync is not defined 🤷‍♂️

ludovic-lambert commented 3 years ago

I've seen that. So what?

theKashey commented 3 years ago
ludovic-lambert commented 3 years ago

From my undestanding, babel-plugin is used during compilation, not at run time. Do you mean the compilation raises no error but actually doesn't complete correctly?

I have no error during compilation and compiled App.js contains the following code:

const Homepage = (0,_loadable_component__WEBPACK_IMPORTED_MODULE_2__.default)({
  resolved: {},

  chunkName() {
    return "Site-Homepage";
  },

  isReady(props) {
    const key = this.resolve(props);

    if (this.resolved[key] !== true) {
      return false;
    }

    if (true) {
      return !!__webpack_require__.m[key];
    }

    return false;
  },

  importAsync: () => __webpack_require__.e(/*! import() | Site-Homepage */ "Site-Homepage").then(__webpack_require__.bind(__webpack_require__, /*! ../Site/Homepage */ "./src/client/Site/Homepage.tsx")),

  requireAsync(props) {
    const key = this.resolve(props);
    this.resolved[key] = false;
    return this.importAsync(props).then(resolved => {
      this.resolved[key] = true;
      return resolved;
    });
  },

  requireSync(props) {
    const id = this.resolve(props);

    if (true) {
      return __webpack_require__(id);
    }

    return eval('module.require')(id);
  },

  resolve() {
    if (true) {
      return /*require.resolve*/(/*! ../Site/Homepage */ "./src/client/Site/Homepage.tsx");
    }

    return eval('require.resolve')("../Site/Homepage");
  }

}, {
  ssr: true,
  fallback: /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(_Shared_Parts_SpinLoader__WEBPACK_IMPORTED_MODULE_14__.default, null)
});

Is this not generated by the babel-plugin?

Also, once executed client-side, all dynamic imports, ie const X= loadable(() => import('X'), { ssr: true, fallback: <SpinLoader /> }), are working fine. The error occurs only during server-side rendering.

BTW, I'm surprised to see .tsxextensions in compiled .js but I don't know how this is resolved.

theKashey commented 3 years ago

Ok. So now we know that problem occurs on the server-side, not the client-side. However, it is still unclear for which compilation target the given piece of code belongs. Obviously not to the one with the missing requireSync.

👉 babel plugin is required both for client and the server-side. If it will be missing - no error will occur during the compilation as long as the "your code" is correct by design (not generating any typescript errors). It will be just not consumable by loadable-components.

ludovic-lambert commented 3 years ago

Any piece of code containing const X= loadable(() => import('../X'), { ssr: true, fallback: <Spinloader /> }), with X called during the initial request raise this error, on server-side only. Everything is installed correctly according to the documentation, and particularly @lodable/babel-plugin. Which makes the error message meaningless. The reason why requireSync is missing is beyong my app as I don't know where does this var comes from, for sure not from my code. Meanwhile the code works like a charm in SSR without Loadable-components.

Here are all my dependencies :

"dependencies": {
    "@loadable/component": "^5.14.1",
    "@loadable/server": "^5.14.2",
    "bnc-libs": "^0.2.33",
    "cookie-parser": "^1.4.5",
    "express": "^4.17.1",
    "isomorphic-fetch": "^3.0.0",
    "lazysizes": "^5.3.1",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-router-dom": "^5.2.0"
  },
  "devDependencies": {
    "@babel/cli": "^7.13.10",
    "@babel/core": "^7.13.13",
    "@babel/plugin-proposal-class-properties": "^7.13.0",
    "@babel/preset-react": "^7.13.13",
    "@babel/preset-typescript": "^7.13.0",
    "@loadable/babel-plugin": "^5.13.2",
    "@loadable/webpack-plugin": "^5.14.2",
    "@types/express": "^4.17.11",
    "@types/isomorphic-fetch": "0.0.35",
    "@types/loadable__component": "^5.13.3",
    "@types/loadable__server": "^5.12.3",
    "@types/node": "^14.14.37",
    "@types/react": "^17.0.3",
    "@types/react-dom": "^17.0.3",
    "@types/react-router-dom": "^5.1.7",
    "babel-loader": "^8.2.2",
    "jshint": "^2.12.0",
    "ts-node": "^9.1.1",
    "ts-node-dev": "^1.1.6",
    "typescript": "^4.2.3",
    "webpack": "^5.28.0",
    "webpack-cli": "^4.5.0",
    "webpack-node-externals": "^2.5.2"
  }

Here is my babel config file :

{
    "presets": [
      "@babel/preset-typescript",
      "@babel/preset-react"
    ],
    "plugins": [
      "@babel/plugin-proposal-class-properties",
      "@loadable/babel-plugin"
    ]
}

Here is my webpack config :

const path = require('path');
const nodeExternals = require('webpack-node-externals');
const webpack = require('webpack');
const LoadablePlugin = require('@loadable/webpack-plugin');

const devTools = process.env.NODE_ENV == 'production' ? false : 'source-map';

const serverApp = {
  mode: process.env.NODE_ENV,
  entry: './src/server/index.ts',
  devtool: devTools,
  target: 'node',
  module: {
    rules: [{
      test: /\.tsx?/,
      exclude: /node_modules/,
      use: 'babel-loader'
    }],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  externals: [nodeExternals()],
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bnc-app-server.js'
  }
};

const clientApp = {
  mode: process.env.NODE_ENV,
  devtool: devTools,
  entry: './src/client/index.tsx',
  output: {
    path: path.resolve(__dirname, 'public/js'),
    filename: 'bnc-app-client.js'
  },
  module: {
    rules: [{
      test: /\.tsx?$/,
      exclude: /node_modules/,
      use: 'babel-loader'
    }]
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js']
  },
  plugins: [
    new LoadablePlugin(),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
      'process.env.ALERT': JSON.stringify(process.env.ALERT),
      'process.env.DOMAIN_NAME': JSON.stringify(process.env.DOMAIN_NAME),
      'process.env.API_URI': JSON.stringify(process.env.API_URI),
      'process.env.SITE_LINK': JSON.stringify(process.env.SITE_LINK),
      'process.env.SITE_NAME': JSON.stringify(process.env.SITE_NAME)
    })
  ]
};

module.exports = [clientApp, serverApp];

Here is my typescript config :

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es6",
    "sourceMap": true,
    "jsx": "react",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": false,
    "forceConsistentCasingInFileNames": false,
    "declaration": false,
    "isolatedModules": true,
    "removeComments": true,
    "resolveJsonModule": true
  }
}

Is there any other trace I can log?

speedy250 commented 3 years ago

I am also experiencing this issue. Loading endpoints that use Loadable are throwing this strange "SSR requires @loadable/babel-plugin"

Any progress made here @ludovic-lambert ?

I have almost the exact same dependencies, but am also using Razzle

ludovic-lambert commented 3 years ago

@vancityhuddy Unfortunately, no. My code just works nicely without loadable-components, and crashes with it. The error message is meaningless as the plugin is installed. Except getting deeply into loadable-components source code, which is beyond my skills, I don't know what to do.

theKashey commented 3 years ago

Beyond mine as well. It should work. There are just a few lines that can work weirdly, and if you make me a minimal reproduction of your problem/setup - I should be able to help you.

denchiklut commented 3 years ago

the problem is in tsconfig.json when you use module: commonjs!

if you will try to change it to es2020 for example the error will be gone

theKashey commented 3 years ago

Bingo. Absolutely correct @denchiklut: babel plugin looks for the variable with name "loadable" ( https://github.com/gregberge/loadable-components/blob/main/packages/babel-plugin/src/index.js#L39) and CJS transformation "renames" it.

denchiklut commented 3 years ago

@theKashey is there any chance to fix '@loadable/babel-plugin' to work with commonjs?

ghost commented 3 years ago

Having this problem as well. Still not figured it out. I am using CommonJS also and appears undefined same issue with transformation and renaming. Any idea how to fix this ?

theKashey commented 3 years ago

Can I double-check where commonjs is coming from? All tools are expected not to change module system, and only the last one (webpack) to do expected transform.

So:

Don't forget to undo this for Jest/another testing framework, running without webpack.

jjblumenfeld commented 3 years ago

We are experiencing the same issue. We are using commonJS modules and are doing SSR using our own expressJS server on node v12.

denchiklut commented 3 years ago

@theKashey is there any way to make this plugin work with commonjs?

theKashey commented 3 years ago

you have to use import in order to have code splitting with webpack. So the input is always expected to be non commonjs.

denchiklut commented 3 years ago

you have to use import in order to have code splitting with webpack. So the input is always expected to be non commonjs.

Yeah and on client I use esmodules but my express server where I use ChunkExtractor I use commonjs

ludovic-lambert commented 3 years ago

you have to use import in order to have code splitting with webpack. So the input is always expected to be non commonjs.

Actually the code itself shall be es6 to use import while node module could be anything like commonjs, meanwhile compilation target could be yet something else.

In typescript configuration parameters are clearly different:

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es6"
  }
}
theKashey commented 3 years ago

Exactly. Compilation target (for babel) can be anything - it's all about the "source code". In case of TS - it shall just do "a check".

One of the simplest ways to handle this - use babel-loader for ts + ForkTSCheckPlugin. Or, as pointed above, amend default tsconfig. PS: Ideally add jsx: "preserve" as well, to remove all "real transformations" from TS.

stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.