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. βοΈ