Unexpected undefined while import with Webpack - javascript

I have a problem that has never happened to me before: I'm compiling a little basic starter browser web app (with React) using Webpack + Babel 7.
I've got three different file:
withAuth.js The Auth High Order Component
NavBar.js The NavBar Component
Login.js The Login Form
If I import the withAuth HOC in the NavBar is everything alright, but if I import the withAuth component in the Login.js file it return undefined
/** withAuth.js */
console.log('withAuth Loaded');
const withAuth = Child => ChildProps => (
<AuthContext.Consumer>
{ authClient => <Child {...ChildProps} authClient={authClient} }
</AuthContext.Consumer>
)
export { withAuth };
/** NavBar.js */
import { withAuth } from 'HOC/Auth';
console.log('NavBar Loaded', withAuth); // <- My HOC
const NavBarComponent = (authClient) => { /* ... My Code ... */ }
const NavBar = withAuth(NavBarComponent);
export default NavBar;
/** Login.js */
import { withAuth } from 'HOC/Auth';
console.log('Login Loaded', withAuth); // <- undefined ??
const LoginFormComponent = (authClient) => { /* ... My Code ... */ }
const LoginForm = withAuth(LoginFormComponent);
// /|\
// |
// Will produce an Error, withAuth is Undefined
This is my Webpack Configuration:
/** webpack.config.js */
module.exports = {
entry: { core: 'index.js' },
resolve: {
alias: {
HOC: './path/to/hoc/folder'
}
},
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all'
}
},
plugins: [ /* Various Plugin */ ],
module: {
rules: [ /* My Rules */ ]
}
}
Any one know why my HOC is undefined?
Edit:
I've placed Console Log in the tree file. The result are:
'Login Loaded' - undefined
'withAuth Loaded'
'NavBar Loaded' - function() { }
Edit 2:
This is the files structure:
app/
|-high-order-component/
| |-auth/
| |-withAuth.js
|
|-layout-component/
| |-navbar/
| |-index.js
|
|-pages/
|-auth/
|-login.js

Resolved
After much testing and research throughout the afternoon I came to the solution of the problem. As I said in the question, mine is a larger project and I only partially wrote its structure because I thought the problem was located in those three files.
In reality, the problem was a Circular Dependency problem and not a Webpack configuration problem.
In my project I have a module called 'Route' that store all Path and all Component for Path, so I can build the React Router using Array Map function. That module has a function that allow me to Route through path and that can return me a path string to a Component.
My problem was due to the fact that this module is often called in the project and this has created a Circular Dependency.
Webpack doesn't show the Circular Dependency during compiling, but I found useful adding a plugin, called CircualDependencyPlugin. This plugin will break Webpack compiling when a Circual Dependency will be found.
Splitting the Route module into two files solved my problem.

Related

How to publish Next.js app as npm package?

I have a web app built on Next.js. I want to publish this app as npm package so it can be used in other projects.
I tried to find resources and help using google but did not find any useful information.
Is it possible? if yes how can I achieve this?
Thanks
I have almost the same need. I created a blog using next js pages and i want to share these pages for 2 next js projects.
Until now, i've been able to make it using vite / rollup builder this way (simplified):
NPM package:
// node_modules/#namespace/next-blog/pages/ssr/BlogArticle.tsx
export SSRBlogArticle = getServerSideProps(...) {...}
export BlogArticlePage: NextPage = (SSRProps) => <Blog {..props} />
Using Package in my main next js app
// ./src/pages/blog.tsx
import { SSRBlogArticle, BlogArticlePage } from '#namespace/next-blog'
export getServerSideProps = SSRBlogArticle
const BlogPage: NextPageWithLayout = BlogArticlePage
// some layout
BlogPage.getLayout = (page) => <Layout>{page}</Layout>
export default BlogPage
The problem is about the usage of process.ENV and useRouter. next/link seems not to work...
Here is my vite configuration file :
import { defineConfig } from 'vite'
import react from '#vitejs/plugin-react'
import dts from 'vite-plugin-dts'
import gql from 'vite-plugin-simple-gql'
import tsconfigPaths from 'vite-tsconfig-paths'
import * as path from 'path'
import pkg from './package.json'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
tsconfigPaths(),
gql(),
dts({
insertTypesEntry: true,
}),
],
resolve: {
alias: [{ find: '#', replacement: path.resolve(__dirname, 'src') }],
},
build: {
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
name: 'NextBlog',
formats: ['es', 'cjs'],
fileName: (format) => `index.${format}.js`,
},
rollupOptions: {
external: [
...Object.keys(pkg.dependencies),
...Object.keys(pkg.peerDependencies),
],
},
},
})
So i would like to add more questions to the original one :
Can we read env file from npm packages? process.ENV reading main app env file ?
Why useRouter is not working properly?
Am i doing it right? Is it good practice?
Thanks for your help :)
Edit
Process.env
I've managed to find out why all process.env were removed from build, vitejs removes them and replace with {}. Solution is :
define: {
// keep process.env* vars in code bundle
'process.env': 'process.env',
},
useRouter
Still impossible to understand the issue with it...
Here one example error on useRouter in main app when clicking on next/link:
Uncaught TypeError: Cannot read properties of null (reading 'push')
at linkClicked (index.es.js?4bcb:3731:1)
at onClick (index.es.js?4bcb:3830:1)
at HTMLUnknownElement.callCallback (react-dom.development.js?ac89:4161:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js?ac89:4210:1)
at invokeGuardedCallback (react-dom.development.js?ac89:4274:1)
at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js?ac89:4288:1)
at executeDispatch (react-dom.development.js?ac89:9038:1)
at processDispatchQueueItemsInOrder (react-dom.development.js?ac89:9070:1)
at processDispatchQueue (react-dom.development.js?ac89:9083:1)
at dispatchEventsForPlugins (react-dom.development.js?ac89:9094:1)
at eval (react-dom.development.js?ac89:9285:1)
at batchedUpdates$1 (react-dom.development.js?ac89:26096:1)
at batchedUpdates (react-dom.development.js?ac89:3988:1)
at dispatchEventForPluginEventSystem (react-dom.development.js?ac89:9284:1)
at dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay (react-dom.development.js?ac89:6462:1)
at dispatchEvent (react-dom.development.js?ac89:6454:1)
at dispatchDiscreteEvent (react-dom.development.js?ac89:6427:1)
For now, the only work around i'm thinking off is using native a tags...
Edit 2
I have finally found out the solution : to make router object not null inside of my package, i have to pass it from main nextjs app to page's package. Inside of the package, i had to wrap my components with RouterContext:
// package next page
import { RouterContext } from 'next/dist/shared/lib/router-context' // next 12
const MyPackagePage = (props) => {
<RouterContext.Provider value={props.router}>
<MyComponents ... />
</RouterContext.Provider>
}
// main next app
const Page = (props) => {
const router = useRouter()
return MyPackagePage({ router, ...props })
}
Now works fine. Less easy to integrate and more boilerplate code, but at least it's possible de export next pages in npm packages.
You can use semantic-release npm package.
In your package.json file you should add follow configuration
"release": {
"branches": [
"master"
],
"plugins": [
"#semantic-release/commit-analyzer",
"#semantic-release/release-notes-generator",
[
"#semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md"
}
],
"#semantic-release/npm",
"#semantic-release/github",
[
"#semantic-release/git",
{
"assets": [
"CHANGELOG.md",
"package.json",
"package-lock.json"
]
}
]
]
}
And your project should be public and should have "private": false in package.json.

to add .default or not?: dynamic imports in react router 3 for webpack code-splitting

Recently I'm working on upgrading my project from webpack 3 (v. 3.3.0) to webpack 4 (v. 4.44.2). Compiling and building worked perfectly, but it turned out to render nothing on the screen.
After I compared the parameters passed to RouterContext between two projects of webpack 3 and of webpack 4, I narrowed down the root cause to dynamic imported components.
The following script is how I use react-router 3 (v. 3.0.5) getComponent to import component, which is workable before I upgrade to webpack 4:
const loadRoute = callback => module => callback(null, module);
const forceToReload = (err) => {
Logger.error(err);
window.location.reload();
};
export default (loadRoute, forceToReload) => (
<Route name="Subscribe" path={appUtils.getRoutePath('/sample')}
getComponent={(location, callback) => import('./index.jsx').then(loadRoute(callback)).catch(forceToReload)}/>
);
Fortunately, after days of trials & errors, I finally made my project render correctly.
In webpack 4 version, I have to append .default to module in loadRoute as follows:
const loadRoute = callback => module => callback(null, module.default);
The Question is
Even though the problem is solved, I still wonder if someone can provide an explanation about when to add .default. Does webpack 4 compile differently on dynamic import? Any ideas?
After days of research, I finally found the answer to my question, hoping I can somehow help those with the same question in the future.
Webpack official website (Code-Splitting: Dynamic Import) has a description as follows:
The reason we need default is that since webpack 4, when importing a CommonJS module, the import will no longer resolve to the value of module.exports, it will instead create an artificial namespace object for the CommonJS module.
Out of curiosity, I did some experiment logging out things I dynamic imports:
// exportObject.js
export default {a: 1, b: 2};
export const c = 3;
// exportString.js
export default "TEST_STRING";
// exportComponent.js
import React, {Component} from "react";
export default class exportComponent extends Component {
constructor(props) {
super(props);
this.a = 1;
this.b = 2;
}
}
// exportFunction.js
export default () => ({a: 1, b: 2});
// index.js
componentDidMount() {
import('./exportObject').then(module => {
console.group('Object');
console.warn('module', module);
console.warn('module.a:', module.a);
console.warn('module.b:', module.b);
console.warn('module.default', module.default);
console.warn('module.c', module.c);
console.groupEnd();
});
import('./exportString').then(module => {
console.group('String');
console.warn('module', module);
console.warn('module.default', module.default);
console.groupEnd();
});
import('./exportComponent').then(module => {
console.group('Component');
console.warn('module', module);
console.warn('module.default', module.default);
console.groupEnd();
});
import('./exportFunction').then(module => {
console.group('Function');
try {
console.warn('module()', module());
} catch (e) {
console.warn('module()', e);
}
try {
console.warn('module.default()', module.default());
} catch (e) {
console.warn('module.default()', e);
}
console.groupEnd();
});
}
This is the result from Webpack 3 Project:
While from Webpack 4 Project:
As you can see, non-objects have different results. This finding matches the description of this article (which is provided by webpack official)
It’s not that problematic when exporting an object. But you’ll get into trouble when using module.exports with non-objects.
I use export default in my code rather than module.exports, then why I still got the result?
Actually, Babel#6 will do the transform as below: (reference)
Babel#6 transforms the following file
export default 'foo'
into
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = 'foo';
Now, things are crystal clear.
The packages I used was:
node: 11.15.0
babel-core: "^6.23.1",
babel-loader: "7.1.4",
webpack: "4.44.2",
webpack-cli: "^4.2.0",

Dynamic Imports: Am I missing something?

I have a React project that uses Webpack as a bundler, and I'm splitting my bundle into two chunks -- the main codebase main.js, and the vendor bundle vendor.js.
After building these bundles, main.js ends up being 45kb and vendor.js is 651kb.
One specific vendor library is 225kb and seems to be the worst offendor in the vendor imports.
I am importing this library in a page component at the top of the file:
import React from 'react';
import { ModuleA, ModuleB } from 'heavyPackage'; // 225kb import
...
const Page = ({ setThing }) => {
...
};
To try and have this heavy import loaded in a separate bundle, I tried to instead import these modules using a dynamic import.
Inside the Page component, the modules weren't actually used until a particular function was called, so I tried to import the modules within that scope rather than at the top of the file:
import React from 'react';
...
const Page = ({ setThing }) => {
...
const handleSignIn = async () => {
const scopedPackage = await import('heavyPackage');
const { moduleA, moduleB } = scopedPackage;
// use moduleA & moduleB normally here
};
};
For some reason I figured Webpack would intelligently pick up on what I'm trying to do here and separate this heavy package into its own chunk that is downloaded only when needed, but the resulting bundles were the same -- a main.js that was 45kb and a vendor.js that was 651kb. Is my line of thinking here correct and possibly my Webpack configuration is off, or am I thinking of dynamic imports in the wrong way?
edit I have Webpack configured to split the bundle using splitChunks. Here is how I have this configured:
optimization: {
chunkIds: "named",
splitChunks: {
cacheGroups: {
commons: {
chunks: "initial",
maxInitialRequests: 5,
minChunks: 2,
minSize: 0,
},
vendor: {
chunks: "initial",
enforce: true,
name: "vendor",
priority: 10,
test: /node_modules/,
},
},
},
},
Update for React 18: The code below is no longer required to split chunks/dynamically load components. Instead, you can use React.lazy with Suspense, which achieves similar results (this only works for React components, therefore any node_module imports would need to be imported within this dynamically loaded component):
const ProfilePage = React.lazy(() => import('./ProfilePage')); // Lazy-loaded
<Suspense fallback={<Spinner />}>
<ProfilePage />
</Suspense>
#Ernesto's answer offers one way of code splitting by using react-loadable with the babel-dynamic-import plugin, however, if your Webpack version is v4+ (and has a custom Webpack config set to SplitChunks by all), then you'll only need to use magic comments and a custom React component.
From the docs:
By adding [magic] comments to the import, we can do things such as name our chunk or select different modes. For a full list of these magic comments see the code below followed by an explanation of what these comments do.
// Single target
import(
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
'module'
);
// Multiple possible targets
import(
/* webpackInclude: /\.json$/ */
/* webpackExclude: /\.noimport\.json$/ */
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
/* webpackPrefetch: true */
/* webpackPreload: true */
`./locale/${language}`
);
Therefore, you can create a reusable LazyLoad component like so:
import React, { Component } from "react";
import PropTypes from "prop-types";
class LazyLoad extends Component {
state = {
Component: null,
err: "",
};
componentDidMount = () => this.importFile();
componentWillUnmount = () => (this.cancelImport = true);
cancelImport = false;
importFile = async () => {
try {
const { default: file } = await import(
/* webpackChunkName: "[request]" */
/* webpackMode: "lazy" */
`pages/${this.props.file}/index.js`
);
if (!this.cancelImport) this.setState({ Component: file });
} catch (err) {
if (!this.cancelImport) this.setState({ err: err.toString() });
console.error(err.toString());
}
};
render = () => {
const { Component, err } = this.state;
return Component ? (
<Component {...this.props} />
) : err ? (
<p style={{ color: "red" }}>{err}</p>
) : null;
};
}
LazyLoad.propTypes = {
file: PropTypes.string.isRequired,
};
export default file => props => <LazyLoad {...props} file={file} />;
Then in your routes, use LazyLoad and pass it the name of a file in your pages directory (eg pages/"Home"/index.js):
import React from "react";
import { Route, Switch } from "react-router-dom";
import LazyLoad from "../components/LazyLoad";
const Routes = () => (
<Switch>
<Route exact path="/" component={LazyLoad("Home")} />
<Route component={LazyLoad("NotFound")} />
</Switch>
);
export default Routes;
On that note, React.Lazy and React-Loadable are alternatives to having a custom Webpack config or Webpack versions that don't support dynamic imports.
A working demo can be found here. Follow installation instructions, then you can run yarn build to see routes being split by their name.
Oki then, look! you have yow webpack config with the splitChunks property, also you need to add a chunkFilename property in side of the output object from webpack.
If we take for example the one generated by CRA
// The build folder.
path: isEnvProduction ? paths.appBuild : undefined,
// Add /* filename */ comments to generated require()s in the output.
pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files.
filename: isEnvProduction
? 'static/js/[name].[contenthash:8].js'
: isEnvDevelopment && 'static/js/bundle.js',
// TODO: remove this when upgrading to webpack 5
futureEmitAssets: true,
// THIS IS THE ONE I TALK ABOUT
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
// webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
// We inferred the "public path" (such as / or /my-project) from homepage.
publicPath: paths.publicUrlOrPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: isEnvProduction
? info =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
// Prevents conflicts when multiple webpack runtimes (from different apps)
// are used on the same page.
jsonpFunction: `webpackJsonp${appPackageJson.name}`,
// this defaults to 'window', but by setting it to 'this' then
// module chunks which are built will work in web workers as well.
globalObject: 'this',
},
Once you have that on yow webpack. next thing is to install a npm i -D #babel/plugin-syntax-dynamic-import and add it to your babel.config.js
module.exports = api =>
...
return {
presets: [
.....
],
plugins: [
....
"#babel/plugin-syntax-dynamic-import",
....
]
}
then last thing npm install react-loadable
create a folder called: containers. in it place all the containers
inside index.js do some like:
The loadable object have two properties
export const List = Loadable({
loader: () => import(/* webpackChunkName: "lists" */ "./list-constainer"),
loading: Loading,
});
loader: component to dynamically import
loadinh: component to display until the dynamic component is loaded.
and for last on you Router set each loadable to a route.
...
import { Lists, List, User } from "../../containers";
...
export function App (): React.ReactElement {
return (
<Layout>
<BrowserRouter>
<SideNav>
<nav>SideNav</nav>
</SideNav>
<Main>
<Header>
<div>Header</div>
<div>son 2</div>
</Header>
<Switch>
<Route exact path={ROUTE_LISTS} component={Lists} />
<Route path={ROUTE_LISTS_ID_USERS} component={List} />
<Route path={ROUTE_LISTS_ID_USERS_ID} component={User} />
<Redirect from="*" to={ROUTE_LISTS} />
</Switch>
</Main>
</BrowserRouter>
</Layout>
);
}
so then when you bundle yow code we get some like:

Alternative for "component: () => import()" in VueJS routing

I downloaded a template online to better understand VueJS and also create a web app. However I have a problem with routing. There is a function in my router's index.js that imports a path. The import syntax seems to be buggy due to some webpack issues. I tried a lot of different things but couldn't fix the bug so I want to find a workaround for that import syntax
This is my code for router's index.js
import Vue from 'vue'
import VueAnalytics from 'vue-analytics'
import Router from 'vue-router'
import Meta from 'vue-meta'
// Routes
import paths from './paths'
// import views from './views'
function route (path, view, name) {
return {
name: name || view,
path,
component: () => import(
`../views/${view}.vue`
)
}
}
Vue.use(Router)
// Create a new router
const router = new Router({
mode: 'history',
routes: paths.map(path => route(path.path, path.view, path.name)).concat([
{ path: '*', redirect: '/home' }
]),
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
}
if (to.hash) {
return { selector: to.hash }
}
return { x: 0, y: 0 }
}
})
Vue.use(Meta)
// Bootstrap Analytics
// Set in .env
// https://github.com/MatteoGabriele/vue-analytics
if (process.env.GOOGLE_ANALYTICS) {
Vue.use(VueAnalytics, {
id: process.env.GOOGLE_ANALYTICS,
router,
autoTracking: {
page: process.env.NODE_ENV !== 'development'
}
})
}
export default router
When i try to build it, I get an error saying:
ERROR in ./src/router/index.js
Module build failed: SyntaxError: C:/djangoProjects/martin - Copy/martin/src/router/index.js: Unexpected token (15:21)
The syntax error is on line (15:21), in the route function on the component: () => import( line and exactly on import. Fixing this issue is a pain so I was wondering if there is a workaround for it without using the import syntax?
If I remember correctly you'll need a plugin for babel that can handle dynamic imports.
Check out:
https://babeljs.io/docs/en/babel-plugin-syntax-dynamic-import
Run npm install #babel/plugin-syntax-dynamic-import
Create or open .babelrc
{
"plugins": ["#babel/plugin-syntax-dynamic-import"]
}

How to use Webpack to load a static file with a relative path with React?

I'm trying to create a map component in React using the Tangram Library.
I got it working with Webpack alone but it started bugging out when I used react in the mix.
I've tried using various loaders such as a raw loader, a yaml loader and so forth, but none of them have worked thus far.
The map component looks as follows:
// -- Import dependencies --
import React from 'react';
import { Map } from 'react-leaflet';
// -- Import Tangram --
import Tangram from 'tangram';
import yaml from 'js-yaml';
import data from '!!raw-loader!./scene.yaml';
export default class LeafletMap extends React.Component {
componentDidMount() {
const layer = Tangram.leafletLayer({
scene: data
});
layer.addTo(this.map.leafletElement);
}
render() {
return (
<Map center={[40.70532, -74.00976]} zoom={15} ref={(ref) => { this.map = ref }} />
);
}
}
How can I actually load the scene.yaml so that the Tangram library makes use of it ?
In the end it responds with a 404 as the file isn't found.
The solution was, that the static files weren't being copied to the bundle built by webpack.
I solved it by using the CopyPlugin in the webpack config and copying the files to a folder respecting the relative path name, like so:
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
entry: './main.js',
output: {
filename: './bundle.js'
},
plugins: [
new CopyPlugin([
{ from: 'src', to: 'src' },
]),
],
};

Categories

Resources