Why my function is not present in bundle.js? - javascript

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.

Related

Webpack - Bundling multiple js files with respect to resusable methods and variable from one another

I am trying to bundle around 10+ javascript files of my application which are loaded as scripts in the index.html file. They are properly sequenced as per their dependencies with one another.
<script src="/js/main.js"></script>
<script src="/js/user.js"></script>
<script src="/js/contact.js"></script>
...
The code inside each file looks like this:
// main.js
const _main = {};
_main.MethodOne = function(){
...
}
// user.js
const _user = {};
_user.MethodTwo = function(){
// Can access the method from main.js file
_main.MethodOne();
}
// contact.js
const _contact = {};
_contact.MethodThree = function(){
// Can access the methods from main.js & user.js file
_user.MethodTwo();
_main.MethodOne();
}
Now, when bundling them through webpack, the constant variables _main, _contact & _user gets renamed to some random letters. However, the names used to invoke those methods from other files remain the same in each case i.e. _user.MethodTwo(), _main.MethodOne() doesn't change.
This is my webpack.config.js
entry: {
vendors: [
'./public/js/colorpicker.js',
...
],
app: [
'./public/js/main.js',
'./public/js/user.js',
'./public/js/contact.js'
]
},
mode: 'production',
output: {
filename: 'js/[name].[contenthash].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
publicPath: 'auto',
assetModuleFilename: 'img/[hash][ext][query]'
},
I read the webpack documentation however didn't get any clue about this problem.
Your best bet is to move to the module approach by using import and export.
so instead of defining global variables like you do, each file would export the corresponding variable. If a file needs a variable from another file, it simply has to import it.
// main.js
export const _main = {};
_main.MethodOne = function(){
...
}
// user.js
import {_main} from "./main.js";
export const _user = {};
_user.MethodTwo = function(){
// Can access the method from main.js file
_main.MethodOne();
}
// contact.js
import {_main} from "./main.js";
import {_user} from "./user.js";
const _contact = {};
_contact.MethodThree = function(){
// Can access the methods from main.js & user.js file
_user.MethodTwo();
_main.MethodOne();
}
Read more about import/export here

How to keep all functions after webpack build?

Webpack checks the usage of functions and remove (as dead code) the "unused" functions. But if I'm using the function inside HTML, the same function will be removed if none scripts calls it.
For example, I have the script.js:
function doSomething() {
console.log("clicked button");
}
function explicitUsedFunction() {
console.log("Hey, function called!");
}
explicitUsedFunction();
And index.html:
<html>
<head>
<title>Test</title>
<script src="script.js"></script>
</head>
<body>
<button onclick="doSomething()">Button</button>
</body>
</html>
doSomething function is used by onclick button event.
here is my webpack.config.js:
const path = require('path');
const TerserMinimizer = require('terser-webpack-plugin');
module.exports = {
mode: 'production',
entry: ["./script.js"],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
optimization: {
minimize: true,
minimizer: [
new TerserMinimizer({
terserOptions: {
keep_classnames: true,
keep_fnames: true
}
})
]
}
};
I'm using TerserPlugin to keep function name (because HTML will not be modified). So, the bundle.js file will be:
!function explicitUsedFunction(){console.log("Hey, function called!")}();
doSomething function was removed, the question is, how can I keep all declared functions in bundle.js using Webpack?
Some points about answer need be understood:
the example above is just for easy code reading, I don't will use addEventListener to the button (because if I have about 20 different buttons (using the function) is not a helpful answer addEventListener to all buttons)
I'm not using import/export keyword because is just a simple javascript file imported by script tag, the use of import/export keyword causes "SyntaxError: Invalid token"
After a lot of fights with webpack, I found a simple solution.
I need 2 things: send all function to minified file and make event functions acessible on window's scope. A just added the following line for every function I need:
function doSomething() {
console.log("clicked button");
}
function explicitUsedFunction() {
console.log("Hey, function called!");
}
explicitUsedFunction();
/*NEW LINE HERE:*/
window.doSomething = doSomething;
with this simple change I tell to webpack that the function was used and I dont need Terser anymore (webpack.config.js):
const path = require('path');
module.exports = {
mode: 'production',
entry: ["./script.js"],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
};

Webpack + babel, ReferenceError: class is not defined

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!

Exporting Typescript function in Webpack bundle built with gulp, empty object in browser

I'm trying to build a js bundle from typescript with exports that I can call directly from an inline script, but I don't seem to be able to get it to work.
I have a Typescript source file like this:
export namespace Init {
export function index() {
// do some things
}
}
export function blahblah() { console.log('blahblah') }
This sits alongside some other sources in the same folder src/ts/*.ts. My webpack.config.js looks like this:
'use strict';
module.exports = {
context: __dirname,
output: {
filename: 'bundle.js',
library: 'bitthicket',
},
devtool: 'sourcemap',
resolve: {
extensions: [ '.ts', '.tsx', '.js' ]
},
module: {
rules: [
{ test: /\.tsx?$/, exclude: /node_modules/, loader: 'ts-loader' }
]
}
}
And finally, my gulpfile.js looks like this:
var gulp = require('gulp')
var gutil = require('gulp-util')
var plumber = require('gulp-plumber')
var watch = require('gulp-watch')
var sass = require('gulp-sass')
var ts = require('gulp-typescript')
var clean = require('gulp-clean')
var webpack = require('webpack')
var webpack_config = require('./webpack.config.js')
var webpack_stream = require('webpack-stream')
let _sass = () =>
gulp.src('src/css/**/*.scss')
.pipe(plumber())
.pipe(sass())
.pipe(gulp.dest('static/css'))
.on('end', () => gutil.log('_sass: done'))
let _webpack = () =>
gulp.src('src/ts/**/*.ts')
.pipe(plumber())
.pipe(webpack_stream(webpack_config, webpack))
.pipe(gulp.dest('static/js'))
.on('end', () => gutil.log('_webpack: done'))
gulp.task('build:sass', _sass)
gulp.task('clean:sass', () => gulp.src('static/css/**/*').pipe(clean()))
gulp.task('watch:sass', () => watch('src/css/**/*.scss', () => _sass()))
gulp.task('build:webpack', _webpack)
gulp.task('clean:ts', () => gulp.src('static/js/**/*').pipe(clean()))
gulp.task('watch:webpack', () => watch('src/ts/**/*.ts', () => _webpack()))
gulp.task('watch', ['watch:sass', 'watch:webpack'])
gulp.task('clean', ['clean:sass', 'clean:ts'])
gulp.task('build', ['build:sass', 'build:webpack'])
gulp.task('default', ['clean', 'build'])
In an html file:
<script src="bundle.js" async></script>
<script>window.onload = function() { console.log(bitthicket); }</script>
What's happening, as I understand it, is that webpack is getting the piped list of sources as entry files, as though they were placed on the commandline like webpack {src list} bundle.js. But I've set my library output option, and the bitthicket variable does get set, but it's an empty object. What happened to Init.index?
Edit: I added the export keyword to the Init namespace, as well as a plain function to init.ts to see what would happen - but the resulting object still doesn't have those functions.
Here's the resulting webpack bootstrapper argument:
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
exports.__esModule = true;
var _ = __webpack_require__(2);
var Init;
(function (Init) {
function index() {
Nav.attachNavbarScrollWatcher();
document.addEventListener('scroll', function (event) {
var articles = document.querySelectorAll('.featured column.summary article');
var visibleArticle = _.find(articles, function (el) { return Util.isElementInViewport(el); });
console.log("{0} just became visible", visibleArticle);
});
}
Init.index = index;
})(Init = exports.Init || (exports.Init = {}));
function blahblah() {
console.log('blahblah');
}
exports.blahblah = blahblah;
/***/ }),
As you can see, exports.blahblah and exports.Init.index are assigned in this block - but the object I get back in my browser looks like this:
The real answer to my question is
Do you even module, bro?
I just flat-out misunderstood the relationship between what typescript calls namespaces and modules, and what webpack thinks modules are.
After this exercise in futility, I'm not even sure what the point of Typescript namespaces are anymore, unless you have a multi-stage build process where you use tsc and tsconfig.json to create multiple transpiled bundles that will later be re-bundled by some other loader like Webpack.
Anyway, to the point:
Typescript "namespaces" mean nothing at all to Webpack (more specifically, ts-loader: see TypeStrong/ts-loader#193). Webpack is not able to walk namespace dependencies, and so you wind up with undefined symbols in the bundle, which means the webpackBootstrap function also had no idea how to load things in the right order. The entry point in my bundle was kinda like this:
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
__webpack_require__(1); // <-- actual entry point, exports are lost
__webpack_require__(2);
exports = __webpack_require__(4); // <-- not entry point, nothing is exported here anyway
/***/ }),
...
Webpack (and ts-loader) require a form of modules that it understands in order to make the bootstrapper load things correctly.
I'm still using a Typescript namespace for structure, but I'm exporting it and using import to indicate dependencies on other Typescript modules:
import * as _ from 'lodash'
import * as Nav from './nav'
import * as Util from './util'
export namespace Init {
// ...
}
The main thing is the expression of the dependencies as es2015-style import/exports.

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