Loadable-components: SSR Loadable can not detect code splitting with typescript, ts-loader

Created on 19 Apr 2019  ·  9Comments  ·  Source: gregberge/loadable-components

💬 Questions and Help

Problem

typescript project like App.jsx can not code split with @loadable/babel-plugin.

To Reproduce

I'm forked this project and copied examples/server-side-rendering-ts.
Example js code was transformed to ts or tsx.

After yarn build:webpack, there is only one js file main.js in public/dist/node and public/dist/web. It seems like @loadable/component not working properly.

https://github.com/DylanJu/loadable-components/tree/master/examples/server-side-rendering-ts/public/dist

If yarn start, you can see an error "Invariant Violation: loadable: SSR requires @loadable/babel-plugin, please install it"

Expected behavior

Output of yarn build:webpack is letters-A.js, letters-B.js, letters-C.js.... etc and main.js like server-side-rendering.

Details

For typescript project, I changed some config

  1. Added ts-loader in webpack
  2. Transformed .js to .ts, .tsx (except minor issue about preventing uglify remove)
  3. Changed babel target from src to ts-output because babel can not read typescript directly.
    ts-output is compiled files from src.

I guess my problem is 'Loadable detection'.
Seeing ts-output/client/App.js in here
(https://github.com/DylanJu/loadable-components/tree/master/examples/server-side-rendering-ts/ts-output/client)

App.tsx
const A = loadable(() => import('./letters/A'))

compiled App.js
const A = component_1.default(() => Promise.resolve().then(() => __importStar(require('./letters/A'))));

It seems keyword loadable is gone. So @ladable/babel-plugin can not detect code splitting. It's just my opinion.

Please help me!

help wanted 🆘 question ❓ typings 🔧

Most helpful comment

@neoziro @theKashey

It is solved by myself. The problem is because typescript compile as I guessed.
Using webpack, babel and typescript same time, A.tsx is compiled in order ts-loader, babel-loader, webpack.
In my case, ts-loader changed loadable based on typescript config.

If configure tsconfig.json like this

"compilerOptions": {
    "module": "commonjs",
}

It change App.tsx
const A = loadable(() => import('./letters/A'))

to
const A = component_1.default(() => Promise.resolve().then(() => __importStar(require('./letters/A'))));

Then @loadable/babel-plugin can't detect loadable

My Solution

tsconfig.json

"compilerOptions": {
    "module": "esnext",
    "moduleResolution": "node",
}

It don't change loadable(() => import('something') so the detection is enable compile normally.
I wish help someone like me.
And I think it's needed a guide to this for someone who use typescript.

All 9 comments

Hello @DylanJu, I am not Typescript compliant. @theKashey help!

@neoziro @theKashey

It is solved by myself. The problem is because typescript compile as I guessed.
Using webpack, babel and typescript same time, A.tsx is compiled in order ts-loader, babel-loader, webpack.
In my case, ts-loader changed loadable based on typescript config.

If configure tsconfig.json like this

"compilerOptions": {
    "module": "commonjs",
}

It change App.tsx
const A = loadable(() => import('./letters/A'))

to
const A = component_1.default(() => Promise.resolve().then(() => __importStar(require('./letters/A'))));

Then @loadable/babel-plugin can't detect loadable

My Solution

tsconfig.json

"compilerOptions": {
    "module": "esnext",
    "moduleResolution": "node",
}

It don't change loadable(() => import('something') so the detection is enable compile normally.
I wish help someone like me.
And I think it's needed a guide to this for someone who use typescript.

I would suggest (having babel coming next):

"compilerOptions": {
    "module": "esnext",
    "moduleResolution": "node",
    "jsx": "preserve", // <--- this is important
    "target": "es2016" or "esnext" // <-- this might be important for arrow functions
}

@theKashey
Thanks your suggest.
I'm using "target": "esnext", "jsx": "react", it's not make any problem but I'll follow your suggest.
Thanks again.
I'm ok this issue can be closed.

but I hope that the official README show your suggest for typescript user.

@DylanJu I have the same issue when converting to TypeScript (The system works ok with normal JS).
The compiled files now are named as 0.js, 1.js, etc., instead of A.js, B.js, where A and B are the name of loadable components.

My tsconfig.json:

{
    "compilerOptions": {
        "outDir": "./dist/",
        "esModuleInterop": true,
        "sourceMap": true,
        "noImplicitAny": true,
        "module": "esnext",
        "moduleResolution": "node",
        "target": "esnext",
        "jsx": "react"
    }
}

and here is the webpack config:

module.exports = {
    entry: './client/index.tsx',
    output: {
        path: path.join(__dirname, 'dist'),
        // filename: '[name].[contenthash].js',
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader'],
            },
            {
                test: /\.ts(x?)$/,
                exclude: /node_modules/,
                use: 'ts-loader',
            },
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
            },
        ],
    },
    resolve: {
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
    },
    ...
}

Can you help me?

Never mind. The order of loaders are important.
I changed the loaders to

{
    test: /\.ts(x?)$/,
    exclude: /node_modules/,
    use: ['babel-loader', 'ts-loader'], // The orders are important
},

and it works perfectly now!

@phuoc-ng what are your configs for ts and babel? Could you share these plz?

@dakiesse Yes, sure.

I have been using loadable-components for a few sites such as this vs that, CSS Layout, HTML DOM with the same setup as following.
All those sites are open source, so you can take a look at them or fork one of them.

Here is an example

"dependencies": {
        "@loadable/component": "^5.12.0",
},
"devDependencies": {
        "@loadable/babel-plugin": "^5.12.0",
        "@types/loadable__component": "^5.10.0", 
        "babel-loader": "^8.0.6",
        "ts-loader": "^6.2.1",
}
{
    "plugins": ["@loadable/babel-plugin"],
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react"
    ]
}
module.exports = {
    ...
    module: {
        rules: [
            ...
            {
                test: /\.ts(x?)$/,
                exclude: /node_modules/,
                // The order of loaders are very important
                // It will make the @loadable/component work
                use: ['babel-loader', 'ts-loader'],
            },
            ...
        ],
    },
    ...
};
const App = () => {
    return (
        <Router>
            <RouteSwitch>
                <Route
                    path='/:slug'
                    render={(props) => <PostLoader slug={props.match.params.slug as string} />}
                />
            </RouteSwitch>
        </Router>
    );
};

PostLoader.tsx will use @loadable/component to load a page located in the ../posts folder:

import loadable, { LoadableComponent } from '@loadable/component';
import React from 'react';

import Spinner from './Spinner';

interface PostLoaderProps {
    slug: string;
}

const loadDetails = /* #__LOADABLE__ */ (props: PostLoaderProps) => import(`../posts/${props.slug}`);

const PostLoader: LoadableComponent<PostLoaderProps> = loadable(loadDetails, {
    fallback: <Spinner />,
});

export default PostLoader;

I hope it helps you.

Thanks, @phuoc-ng, worked for me.

Was this page helpful?
0 / 5 - 0 ratings