How to keep all functions after webpack build? - javascript

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'
}
};

Related

Javascript - How to use a variable across two functions in webpack?

I have to migrate old, plain HTML with plain JS files to webpack but I have troubles when I have a variable declared outside functions and used between multiple functions below them.
One of the example:
// import stuff
var recorder;
var blobs = [];
function recordStart() {
recorder = new MediaRecorder(...);
blobs = []
recorder.ondataavailable = (event) => {
console.log("recording");
if (event.data) blobs.push(event.data);
};
recorder.onstop = function(){...};
recorder.start();
}
function recordEnd() {
recorder.stop();
}
$('#capture_button').on('touchstart mousedown', function() {
recordStart();
})
$('#capture_button').on('touchend mouseup', function() {
recordEnd();
})
However, everytime recordEnd is called, JS console always throw undefined error on recorder object, as if the variable never even touched at all by the recordStart function.
Is there something that I do wrong here? I just learn webpack for a week so please bear with me if this is a rookie mistake.
PS. jQuery runs fine, if I run console.log() in them they would fire properly.
Edit: I forgot to mention this issue happens only after I run npx webpack on it with this configuration:
const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
optimization: {
minimize: false,
},
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|svg|jpg|jpeg|gif|glb|gltf|usdz)$/i,
type: 'asset/resource',
}
]
},
plugins: [
new CopyPlugin({
patterns: [
{from: "src/images", to: "images"},
{from: "src/maps", to: "maps"},
{from: "src/models", to: "models"}
]
})
]
};
It runs well before I migrate to webpack, but after I bundled with it and include the dist/bundle.js in the HTML in dist/index.html the undefined error happens.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webpack Title</title>
<script src="./bundle.js"></script>
</head>
<body>
...
</body>
</html>
One of the main selling points of modules is to give your code an explicit dependency chain, and to avoid global variables. For variables that you want to be able to access in other modules, you should export them, and import them where they're needed.
export let recorder;
// do stuff
// assign to recorder
and in the other module
import { recorder } from './theFirstModule';
function recordEnd() {
recorder.stop();
}
The other (bad) option is to make recorder explicitly global, so it'll be accessible anywhere - but that defeats the purpose of using modules and makes code harder to reason about, so I wouldn't recommend it.
Instead of doing
var recorder;
, instead, wherever you assign to recorder, assign to window.recorder. (But if I were you I'd really try working within the module system first)

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 to access Method of a Class in HTML Element using TypeScript? [duplicate]

I'm new to webpack and similar tools. I wanted to reorganize my project. Currently all my JS-code lives in App.js. So before splitting it into modules and improving, I wanted just to set up the workflow for copying it.
This is the content of webpack.config.js:
const path = require('path');
module.exports = {
mode: 'development',
entry: {
App: "./app/assets/scripts/App.js"
},
output: {
path: path.resolve(__dirname, './app/temp/scripts'),
filename: '[name].js',
},
module: {
rules: [{
loader: 'babel-loader',
query: {
presets: ['es2015']
},
test: /\.js$/,
include: [
path.resolve(__dirname, "app")
],
exclude: [
path.resolve(__dirname, "node_modules")
],
}],
},
};
which I learned at this video course. But afterwards not all functions work. For instance, the functions called by event listeners work:
function initOnClickForVersionBtns() {
$('#btn_soprano').click(function () {
voice = 1;
loadFile();
});
$('#btn_basset').click(function () {
voice = 2;
loadFile();
});
}
But those called from HTML do not:
Score
Note that I am still referencing few others js files from HTML:
<script src="../javascript/basic-events.js" type="text/javascript">/**/</script>
<script src="../javascript/bootstrap.min.js" type="text/javascript">/**/</script>
<script src="../javascript/jquery-3.3.1.min.js" type="text/javascript">/**/</script>
I'd like it to change later, but currently I thought it should not be a problem. Maybe it is?
Webpack wraps all code in an IIFE, for more predictable behavior and to avoid global pollution, among other things. In the bundled code, the place where your module's functions are defined is not the top level of the <script>.
Inline attribute event handlers may only reference global variables., and are quite a bad idea in nearly all cases. While you could fix it by explicitly assigning the function to window, eg:
window.switchToScore = function() {
// ...
instead of
function switchToScore() {
// ...
It would be much better to remove the inline attribute event handlers completely, and attach listeners with Javascript, just like you're doing with
$('#btn_soprano').click(function () {
voice = 1;
loadFile();
});

Packaging code for AWS Lambda

I am trying to package code for AWS Lambda. Lambda has various restrictions, such as using Node 6.10, and not having a build step, like AWS EB does. I also am using NPM modules, so these will need to be bundled with the AWS Lambda handler.
Here is what I would like to do:
Define and use NPM modules (pure JS modules only)
Transpile all code (including NPM modules) to a JS version that Node 6.10 supports
Statically link all NPM modules into one big JS file
Upload that single file to AWS Lambda
For example, suppose I have an NPM module foo (node_modules/foo/index.js):
export default { x: 1 };
and I have my own code ('index.js'):
import foo from 'foo';
export const handler = (event, context, callback) => {
console.log(foo); // Will appear in CloudWatch logs
callback(null, 'OK');
};
The output would be something like this ('dist/bundle.js'):
var foo = { x: 1 };
exports.handler = function(event, context, callback) {
console.log(foo);
callback(null, 'OK');
};
I should be able to upload and run bundle.js on AWS Lambda without further modification.
How can I achieve this using existing JS tools?
You can use serverless with serverless-webpack
Then you deploy your bundle with serverless deploy
It turns out that this is possible, but it requires some tricky configuration to achieve. I have created a boiler-plate repo for others to use.
Here are the important bits...
First, you need a .babelrc that targets Node.js 6.10:
{
"presets": [
[
"env", {
"targets": {
"node": "6.10"
},
"loose": false,
"spec": true
}
]
]
}
Next, you need to configure Webpack to generate a commonjs library targetting node:
const path = require('path');
const webpack = require('webpack');
const debug = process.env.NODE_ENV !== 'production';
module.exports = {
context: __dirname,
entry: [ 'babel-polyfill', './index.js' ],
output: {
path: path.join(__dirname, 'out'),
filename: 'index.js',
libraryTarget: 'commonjs'
},
devtool: debug ? 'source-map' : false,
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
babelrc: true,
compact: !debug
}
}
}
],
},
target: 'node',
plugins: [
new webpack.DefinePlugin({ 'global.GENTLY': false })
]
};
Note that you do not want to ignore the node_modules folder, since that would prevent static-linking.
The babel-polyfill plugin is also crucial if you want to use modern JS features.
Your actual handler code should have a named export that matches what you have set in the AWS console:
export const handler = (event, context, callback) => callback(null, 'OK');
Do not do it like this!
// Bad!
export default {
handler: (event, context, callback) => callback(null, 'OK'),
};
When packaging the code, make sure you add index.js to the top level of the zip:
zip -j bundle.zip ./out/index.js

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