Webpack + babel, ReferenceError: class is not defined - javascript

I am writing my first JS library and wanted to learn to use babel and webpack.
The problem I am having is that the class (entry point?) that I'd like to instantiate in my index.htm file causes the browser to complain that it's "not defined".
This is my webpack config:
const path = require('path');
module.exports = {
entry: path.resolve(__dirname, './src/js/FormrEditor.js'),
output: {
filename: 'formr-editor.js',
path: path.resolve(__dirname, 'dist/js'),
},
devtool: "source-map",
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: '/node_modules/',
query: {
presets: ['#babel/env']
}
}
]
},
};
And the js:
import {Toolbox, ToolboxItem} from "./Toolbox";
export default class FormrEditor{
constructor(element, config){
/** Find the element to use as the container */
if(element instanceof Element)
this.container = element;
else
this.container = document.getElementById(element);
this.container.classList.add("feditor")
this.buildEditorDom();
}
buildEditorDom()
{
let form = document.createElement("div");
form.classList.add("feditor-form");
let toolbox = document.createElement("div");
toolbox.classList.add("feditor-toolbox");
let handle = document.createElement("div");
handle.classList.add("feditor-toolbox-handle");
let testItem = document.createElement("div");
testItem.classList.add("feditor-toolbox-item");
toolbox.appendChild(handle);
toolbox.appendChild(testItem);
this.container.appendChild(form);
this.container.appendChild(toolbox);
this.toolbox = new Toolbox(toolbox);
this.form = form;
}
}
So in the index.htm file I am doing:
<script src="formr-editor.js"></script>
<script>
import FormrEditor from "./formr-editor";
var formr = FormrEditor(document.getElementById('editor'), {});
</script>
And that's when it complains that FormrEditor isn't defined...
A bit more info:
I am serving index.htm out of a "demo" folder, in which I have symlinks to both formr-editor.js (the output of webpack) and the css.
I have also tried var formr = new FormrEditor(document.getElementById('editor'), {}); without the import as per MarcRo and I still get the same error.
I have tried both export and export default on the class and nothing...

There are a few things you might want to check.
You cannot use ES6 imports in the browser like this - actually the import statement is entirely unnecessary.
Your <script src="formr-editor"> seems like it has the wrong path. Your webpacks output is generating a file at dist/js/formr-editor.js. if you want to import the untranspiled src file in your Browser use <script src="src..." type="module"> - make sure the untranspiled file is also accessible.
How are you serving public assets? Is your formr-editor.js file (generated by webpacks) accessible?
You want to call your FormrEditor class with the new keyword.
Note: you can check in your Browsers dev-tools whether your Browser manages to load the respective files!

Related

How to configure webpack 5 to bundle the module Web Worker script correctly which imports other modules?

As per the documentation, I am not using worker-loader, I am trying to use the native way suggested by the webpack-5 documentation.
Below is the usage of the worker script in the main thread.
const worker = new window.Worker(
new URL("../workers/listOperation.worker.js", import.meta.url),
{
type: "module",
},
);
worker.postMessage({ list: hugeList, params: reqData });
worker.onerror = err => console.error(err);
worker.onmessage = e => {
const { list } = e.data;
// Usage of `list` from the response
worker.terminate();
};
return worker;
It works fine if there are no imports used in the script. But when I import any node modules (e.g. loadash/get) or any other function/constants from local, it does not work as the output webWorker bundle file doesn't transpile and bundle the imported code. It keeps the "import" statement as it is.
Below is the worker script (listOperation.worker.js)
import get from "lodash/get";
import { ANY_CONSTANT } from "../constants"; // This is some local constant
addEventListener("message", e => {
const { list, params } = e.data;
// Here I have some usage of `get` method from `lodash/get` and ANY_CONSTANT
self.postMessage({
list: list,
});
});
Webpack outputs the bundle file like below which won't be usable by the browser, if I put the /\.worker.js$/ pattern in the the exclude of babel-loader rule.
import get from "lodash/get";import{ANY_CONSTANT}from"../constants";addEventListener("message",(e=>{const{list:s,params:t,.......
And even if I don't put the /\.worker.js$/ pattern in the the exclude of babel-loader rule, the output bundle still doesn't include the implementation of get from lodash/get or the value of the constant. It just outputs it in cjs using require.
Also, I made use of asset module so that I can put the output file inside a directory, not directly in the root of the dist folder. Configuration changes in my webpack.config looks like this.
module.exports = {
entry: {...},
module: {
rules: [
{
test: /\.js$/,
exclude: [/(node_modules)/, /\.worker.js$/],
use: {
loader: "babel-loader", // This uses the config defined in babel.config.js
},
},
{
test: /\.worker.js$/,
exclude: /(node_modules)/,
type: "asset/resource",
generator: {
filename: "js/workers/[hash][ext][query]",
},
},
],
},
}

Why my function is not present in bundle.js?

I have a simple JS web app where I have a function in my main.js which handle my button onclick event.
index.html:
<input type="button" class="btn btn-dark" value="Submit" onclick="onSubmitButtonPressed()">
...
<script src="./dist/bundle.js"></script>
main.js:
function onSubmitButtonPressed() {
// some DOM manipulating stuff
}
webpack.config.js
var path = require('path');
module.exports = {
entry: './main.js',
output: {
path: path.join(__dirname, '/dist'),
filename: 'bundle.js',
publicPath: '/dist'
},
devServer: {
port: 8000,
}
}
I am using webpack and seems like my onSubmitButtonPressed() function is not generated into bundle.js
Additional info:
I tried to export my function like this:
module.exports = {
onSubmitButtonPressed: onSubmitButtonPressed(),
}
After this my function is generated into bundle.js like this, but not working:
e.exports = {
onSubmitButtonPressed: function () {
const e = document.getElementById("input-text").value;
r.push(e);
let t = document.createElement("LI");
t.innerHTML = e, t.className = "list-group-item", n.appendChild(t)
}()
}
When I don't use my bundle.js only the main.js then everything is working fine.
Based on the below suggestion (original issue solved), I updated my
webpack.config.js with:
optimization: {
minimize: false
}
I have my onSubmitButtonPressed() function in my bundle.js:
...
function onSubmitButtonPressed() {
const inputText = document.getElementById('input-text').value;
items.push(inputText)
let child = document.createElement("LI");
child.innerHTML = inputText;
child.className = 'list-group-item';
itemsUl.appendChild(child);
console.log('Called');
}
window.onSubmitButtonPressed = onSubmitButtonPressed
...
And I also add this line in order to make my function to make it globally available:
window.onSubmitButtonPressed = onSubmitButtonPressed
Webpack is tree shaking your function as it's not called or referenced throughout your entrypoint. Also, even if it wasn't tree shaken, webpack is a module bundler, so it would scope your function if it weren't declared on the global scope (i.e. window.myFunction = function(arg) { return arg }).
Tree shaking is a term commonly used in the JavaScript context for dead-code elimination. It relies on the static structure of ES2015 module syntax, i.e. import and export. The name and concept have been popularized by the ES2015 module bundler rollup.
Here's a solution that seems to correspond with your use-case.
This is because webpack treats function onSubmitButtonPressed as a local (not global) function.
Use window.onSubmitButtonPressed = onSubmitButtonPressed at the end of the file to make it globally available.

How do I get treeshaking to work for a file that exports multiple react components?

I have a file where I'm exporting multiple consts with arrays of children called icons.js.
In another react file, lets call it CloseButton.js. I'm only importing
import { cross } from './icons.js';
and when I run webpack with production mode enabled, all the other icons appear to be imported as well (the icons.js const exports amount to close to 100kB or so, but a single line shouldnt be larger than 1kB) to the transpiled CloseButton.js.
I am using webpack 4.30.0 with #babel/preset-env and #babel/preset-react.
webpack.config.js
const config = {
entry: './CloseButton.js',
output: {
filename: 'CloseButton.js',
},
plugins: [],
module: {
rules: [
{
test: /\.js/,
use: {
loader: 'babel-loader',
options: {
presets: [['#babel/preset-env', {
modules: false
}], '#babel/preset-react']
}
}
}
]
},
mode: 'production'
};
module.exports = config;
I tested to run the same setup but only exports strings from icons.js, and then the code was properly excluding dead code.
Does anybody know if there's a way to only export "cross" from the icons.js file without creating a separate file for each react component defined in icons.js?
I've tested removing all references of consts being exported as React components from icons.js and that works, but that doesn't let me export the icons.
I figured out the issue, basically I was setting the react components directly on the export const myicon = [<path></path>, <path />]; Instead it needs to of course be wrapped with a function call.
such as: ```export const myicon = ()=> {
return ([, ])
}```

Dynamically load/import split vendor chunks/bundles via Webpack

I have a simple sample application that is structured thusly:
/dist
index.html
app.bundle.js
moduleA.bundle.js
moduleB.bundle.js
vendors~app~moduleA~moduleB.bundle.js
[...sourcemaps]
/node_modules
[...]
/src
index.js
moduleA.js
moduleB.js
package.json
webpack.config.js
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test Dependency Pulls</title>
</head>
<body>
<script type="text/javascript" src="app.bundle.js"></script>
</body>
</html>
src/index.js
import _ from 'Lodash';
import printA from './moduleA.js';
import printB from './moduleB.js';
function component() {
var element = document.createElement('div');
var btn = document.createElement('button');
element.innerHTML = _.join(['Hello', 'webpack', '4'], ' ');
btn.innerHTML = 'printA. Click me and check the console.';
btn.onclick = printA;
element.appendChild(btn);
btn = document.createelement('button');
btn.innerHTML = 'printB. Click me and check the console.';
btn.onclick = printB;
element.appendChild(btn);
return element;
}
document.body.appendChild(component());
src/moduleA.js
import printB from './moduleB.js';
export default function printA() {
console.log('AAA I get called from moduleA.js!');
}
src/moduleB.js
import _ from 'Lodash';
export default function printB() {
console.log('BBB I get called from moduleB.js!');
}
/webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: {
app: './src/index.js',
moduleA: './src/moduleA.js',
moduleB: './src/moduleB.js',
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
splitChunks: {
chunks: 'all'
}
}
}
When I pull in app.bundle.js, I expect the vendor bundle to be auto-pulled as well, since it is a dependency for app.js. Currently, this is not happening - the vendor bundle is not loaded. I'm not even seeing an attempt in the network tab.
How do I tell webpack to automatically load dependencies of a bundle?
Webpack bundling/dependency management does not work exactly in that way. You need to manually add a <script> tag to the html for each bundle (entry).
However, you may want to look into using:
html-webpack-plugin:
https://www.npmjs.com/package/html-webpack-plugin
https://webpack.js.org/plugins/html-webpack-plugin
which will automatically inject the bundle references to your html.
html-webpack-template:
https://github.com/jaketrent/html-webpack-template
may also help with additional customization/features.

Unexpected "Uncaught TypeError: XXX is not a constructor" errors with Babel and ES6

I am giving a try to Webpack, and am giving a try to the instructions in this tutorial, give or take a few custom things.
This is simple code, really, but I'm quite puzzled about this error, and feel this is something silly that I missed.
I defined two ES6 classes, each corresponding to a Handlebars template, and my app's entrypoint is supposed to replace the placeholder HTML in the index file by their contents:
Entrypoint:
import './bloj.less'
// If we have a link, render the Button component on it
if (document.querySelectorAll('a').length) {
require.ensure([], () => {
const Button = require('./Components/Button.js');
const button = new Button('9gag.com');
button.render('a');
}, 'button');
}
// If we have a title, render the Header component on it
if (document.querySelectorAll('h1').length) {
require.ensure([], () => {
const Header = require('./Components/Header.js');
new Header().render('h1');
}, 'header');
}
Index:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h1>My title</h1>
<a>Click me</a>
<script src="build/bloj.js"></script>
</body>
</html>
Button:
import $ from 'jquery';
import './Button.less';
export default class Button {
constructor(link) {
this.link = link;
}
onClick(event) {
event.preventDefault();
alert(this.link);
}
render(node) {
const text = $(node).text();
var compiled = require('./Button.hbs');
// Render our button
$(node).html(
compiled({"text": text, "link": this.link})
);
// Attach our listeners
$('.button').click(this.onClick.bind(this));
}
}
Header:
import $ from 'jquery';
import './Header.less';
export default class Header {
render(node) {
const text = $(node).text();
var compiled = require('./Header.hbs');
// Render the header
$(node).html(
compiled({"text": text})
);
}
}
Sadly, it does not work, and I get both these errors when displaying the page:
Uncaught TypeError: Header is not a constructor
Uncaught TypeError: Button is not a constructor
What could I be missing?
Here is my webpack configuration:
var path = require('path');
var webpack = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');
var ExtractPlugin = require('extract-text-webpack-plugin');
var production = process.env.NODE_ENV === 'production';
var appName = 'bloj';
var entryPoint = './src/bloj.js';
var outputDir = './build/';
var publicDir = './build/';
// ************************************************************************** //
var plugins = [
//new ExtractPlugin(appName + '.css', {allChunks: true}),
new CleanPlugin(outputDir),
new webpack.optimize.CommonsChunkPlugin({
name: 'main',
children: true,
minChunks: 2
})
];
if (production) {
plugins = plugins.concat([
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.MinChunkSizePlugin({
minChunkSize: 51200 // 50ko
}),
new webpack.optimize.UglifyJsPlugin({
mangle: true,
compress: {
warnings: false // Suppress uglification warnings
}
}),
new webpack.DefinePlugin({
__SERVER__: false,
__DEVELOPMENT__: false,
__DEVTOOLS__: false,
'process.env': {
BABEL_ENV: JSON.stringify(process.env.NODE_ENV)
}
})
]);
}
module.exports = {
entry: entryPoint,
output: {
path: outputDir,
filename: appName + '.js',
chunkFilename: '[name].js',
publicPath: publicDir
},
debug: !production,
devtool: production ? false : 'eval',
module: {
loaders: [
{
test: /\.js/,
loader: "babel",
include: path.resolve(__dirname, 'src'),
query: {
presets: ['es2015']
}
},
{
test: /\.less/,
//loader: ExtractPlugin.extract('style', 'css!less')
loader: "style!css!less"
},
{
test: /\.html/,
loader: 'html'
},
{
test: /\.hbs/,
loader: "handlebars-template-loader"
}
]
},
plugins: plugins,
node: {
fs: "empty" // Avoids Handlebars error messages
}
};
What could I be missing?
Babel assigns default exports to the default property. So if you use require to import ES6 modules, you need to access the default property:
const Button = require('./Components/Button.js').default;
I realize that you already have an answer. However I had a similar issue to which I found an answer. Starting my own question and answering it seems weird.
So I'm just going to leave this here.
I had the same error as you got. However, I managed to solve it by changing my
export default {Class}
to
export default Class
I don't know why I wrapped the Class in an object but I remember having seen it somewhere so I just started using it.
So instead of the default returning a Class it returned an object like this {Class: Class}.
This is completely valid yet it will break webpack+babel.
EDIT: I've since come to know why this probably breaks babel+webpack. The export default is meant to only have 1 export. A javascript-object can contain many properties. Which means it can have more than 1 export. (See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export).
For multiple exports use: export {definition1, definition2}.
Use-case: I've used this in a situation where I've created a library which exported different types of an editor (while the underlying code was the same, the appearance of the editor changes depending on which export you use).
You can just put export var __useDefault = true; just after exporting your Class.
export default class Header {
...
}
export var __useDefault = true;
I was able to fix this by adding babel-plugin-add-module-exports to the .babelrc file
npm install babel-plugin-add-module-exports --save-dev
{
"presets": ["#babel/env"],
"plugins": ["add-module-exports"]
}
this adds
module.exports = exports.default;
to the last line when compiling the class with babel.
Although this is not the cause of your particular issue, I ran into a very similar problem when trying to rip babel out of an existing node app that was using ES6's import and export syntax, so this post is to help out anyone else struggling with this in the future.
Babel will resolve any circular dependencies between one module and another, so you can use ES6's import and export with reckless abandon. However, if you need to get rid of babel and use native node, you will need to replace any import and exports with require. This can reintroduce a latent circular reference issues that babel was taking care of in the background. If you find yourself in this situation, look for an area in your code that looks like this:
File A:
const B = require('B');
class A {
constructor() {
this.b = new B();
}
}
module.exports = A;
File B:
const A = require('A'); // this line causes the error
class B {
constructor() {
this.a = new A();
}
}
module.exports = B;
There are several different ways to resolve this issue depending on how you structured your code. The easiest way is probably to pass B a reference to A instead of creating a new instance of class A. You could also dynamically resolve the reference when loading A. There are a myriad of other alternatives, but this is a good place to get started.
It's not the problem in this particular question, but for some reasons, babel does not hoist classes in the same file.
So if you declare your class Token at the top of the file, and write later new Token(), it will run.
If you declare your class after the constructor call, you will have the xxx is not a constructor error
I had the same error message and discovered that the cause was circular import statements. That is: I had two files that imported each other, wherein one file contained an export default class that contained a method that was dependent upon an export function from the other file.
My solution was to move one of the dependencies (functions) out of the class and into a utils.js file, which was a more appropriate place for it anyway!
This is the way I am using / importing my classes:
Utils.class.js
export default class Utils {
somefunction(val) {
return val
}
}
Using Utils into my controllers:
import {default as U} from '../helpers/Utils.class';
const Utils = new U();
console.log(Utils.somefunction(123));

Categories

Resources