webpack compile javascript for browser - javascript

Thanks for your attention!
I have built a javascript file from my source code using webpack, I was mean to use it in brower, but it seems not working for me, so I post this for help
here is my project tree
project
├── dist
│   └── my-dist.js
├── index.js
├── lib
│   └── my-source.js
└── webpack.config.js
here is my-source.js
'use strict'
const somepackage = require("somepackage")
module.exports = MyPkg
function MyPkg(param) {
this.myprop = param
}
MyPkg.prototype.Afunc = function () {
consolg.log("hello from A ", this.myprop)
}
MyPkg.prototype.Bfunc = function (param) {
// B do some thing
}
here is my index.js
exports = module.exports = require('./lib/MyPkg');
here is my webpack.config.js
const path = require('path')
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-dist.js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['babel-preset-env']
}
}
}]
}
}
then i run "webpack" to build the file, it seems ok, and the "my-dist.js" file was created, so i try to use it like this:
<html>
<head>
</head>
<body>
<div>
<button onclick="func()">click here</button>
</div>
</body>
<script src="dist/my-dist.js"></script>
<script>
var pkg = new MyPkg('haha')
function func() {
pkg.Afunc()
}
</script>
</html>
but it throw error like "Uncaught ReferenceError: MyPkg is not defined", and i really don't know how to fix it, please give my a hand, thank you guys~

Edit x2:
Here's a GitHub Repo with further explanations:
https://github.com/superjose/webpack-simple-example
If I recall correctly, I had this issue in the past. Webpack the JavaScript from your entry file so that no other file or code has direct access to it.
Therefore, in your index.html this will not work
<script>
var pkg = new MyPkg('haha')
function func() {
pkg.Afunc()
}
</script>
What you need to do is to use EventListeners so you can target the elements.
So you have this button: (Add a class or an id to better identify it)
<button onclick="func()" id="js-pkg">click here</button>
Then, inside index.js:
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('js-pkg').addEventListener(MyPkg);
// This also works:
// document.querySelector('#js-pkg').addEventListener(MyPkg);
});
Note:
We add the 'DOMContentLoaded' event so we wait for the DOM to be loaded before making any operations. Not doing so, it may result in the button to be not defined, since it may not be parsed or rendered by the browser's engine
Edit: More detailed approach below
Suppose you have the following structure:
----index.html
----index.js
----Email/
---------email.js
----Validator/
---------validator.js
Whereas index.js is your main entry file (Where Webpack loads the JavaScript).
Email.js contents:
// Ficticious/non-existent npm package
import email from 'send-email'
// Custom and ficticious email class that will send emails
export class Email {
sendEmail(subject, message) {
// You would send the email somehow.
email.send('contact#support.com' this.subject, message);
}
}
Validator.js contents:
// Checks if the value is undefined or null
module.exports = function (value) {
return !!value
}
In your index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<input type="name" id="js-name" placeholder="message" />
<textarea id="js-msg" placeholder="Write us a message"></textarea>
<button id="js-send">Send us a message!!</button>
<!-- Awful Real-life example -->
<button id="js-validate">Validate Text!</button>
<script src="index.js"></script>
</body>
</html>
In index.js (Webpack's main file):
// We import the email class that we want to use
import { Email } from './Email/email.js'
// We also add validator
import validator from './Validator/validator.js'
/**
* Now the actual magic. Webpack scopes the variable and function names, changing
* the normal values, in order (I could be wrong with this) to avoid collisions with other functions, and variables.
*
* Nonetheless, JavaScript allow us to programatically attach those functions to the element via event listeners.
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
* This means that <input type="button" onclick="func()" /> the onclick() event is the same
* as element.addEventListener('click', func); Where element is the <input type="button" found via a document.getElementById/querySelector/querySelectorAll/getElementsByTagName/etc
* Therefore, even if Webpack scopes the variable and function names, we would be able to attach it since it resides inside Webpack's code.
* The first thing we do is to add a "DOMContentLoaded" event. This is similar to jQuery's $(document).ready(function() { }); We need for the HTML to be loaded in order for us to add the event listener. You didn't have that problem in the past because you would add it directly to the HTML.
**/
document.addEventListener('DOMContentLoaded', () => {
let sendEmail = document.getElementById('js-send');
let name = document.getElementById('js-name');
let email = new Email();
let validateBtn = document.getElementById('js-validate');
// We pass the functions without parenthesis.
// Note that validator is a function, while Email is a class.
// Email needs to be instantiated first, and then we assign
// the method that it calls.
validateBtn.addEventListener('click', validator);
sendEmail.addEventListener('click', email.sendEmail);
});

ok, just keep this in record.
I figure out a special way, here it's looks like:
the project tree is now looks like:
project
├── dist
│ └── my-dist.js
├── src
│ └── my-entry-build.js
├── index.js
├── lib
│ └── my-source.js
└── webpack.config.js
and I updated the "webpack.config.js" and the "my-entry-build.js", the others keep still
the new "webpack.config.js" is like:
const path = require('path')
module.exports = {
entry: './src/my-entry-build.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-dist.js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['babel-preset-env']
}
}
}]
}
}
the new "my-entry-build.js" is like:
const MyPkg = require('../index.js')
window.MyPkg = MyPkg
and everything done!

Related

"Uncaught ReferenceError: printMe is not defined" at onload method in HTML

I'm currently trying to get started using webpack and ran into "Uncaught ReferenceError: printMe is not defined" error when trying to call my "init" method from HTML using the onload tag despite exporting the methods. My files are as follows:
print.js (located in /resources/static):
export function printMe() {
console.log("Hi");
}
index.js (located in /resources/static):
import {printMe} from "./print";
export function component() {
const element = document.createElement('div');
const btn = document.createElement('button');
btn.innerHTML = 'Click me and check the console!';
btn.onclick = printMe;
element.appendChild(btn);
return element;
}
export function init() {
console.log("init ran");
}
document.body.appendChild(component());
webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
index: './resources/static/index.js',
},
devtool: 'inline-source-map',
plugins: [
new HtmlWebpackPlugin({
title: 'Output Management',
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean:true,
},
};
index.html (located in /dist):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Output Management</title>
<meta name="viewport" content="width=device-width, initial-scale=1"><script defer src="index.bundle.js"></script></head>
<body onload="init()">
</body>
</html>
index.bundle.js (located in /dist) I'm not going to include this file as I don't believe it to be necessary to the solution, although is important to note that it is actually there. Any help is appreciated
Even though you say export, I think that webpack isn't including it in your bundle since you never use the function in the JavaScript code. You'll either need a plugin to monitor your HTML, or use addEventListener on the body to add an "onLoad" event handler.
The simplest way to do this is with the event listener (don't include parentheses after init; see Why is the method executed immediately when I use setTimeout?):
document.body.addEventListener('load', init);

HTML error using functions from bundle.js

I've created a bundle.js file using webpack to organize all my javascript code. I was expecting to use it in my html file but having troubles using the function. An error always returns when I try to call the function index.html:9 Uncaught TypeError: App.testButton is not a function at setup.
I had the impression that if I configured the webpack with the library output specified, I would be able to access the functions via the specified library name.
What am I doing wrong?
App.js
import testButton from './testButton.js';
console.log("I'm the entry point");
testButton();
testButton.js
function testButton()
{
console.log("Test Button");
};
export default testButton;
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="dist/bundle.js"></script>
<script>
function setup()
{
console.log("Initializing a webpage");
App.testButton()
}
</script>
</head>
<body onload="setup();">
</body>
</html>
webpack.config.js
const path = require('path');
module.exports = {
entry: './app.js',
mode: 'development',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
libraryTarget: 'var',
library: 'App'
},
};
I found this to work for me. I needed to modify App.js to include babel so I could use ES5 which allowed me to use require to populate module.exports.
App.js
require('babel-register')({
presets: ['env']
});
module.exports = {
testButton: require('./testButton.js'),
testButton2: require('./testButton2.js')
};
testButton.js
export function testButton()
{
console.log("Test Button");
};
testButton2.js
export function testButton()
{
console.log("Test Button 2");
};
index.html
function setup()
{
App.testButton.testButton();
App.testButton2.testButton();
}
</script>
Output in Console
Test Button
test Button 2

Can Webpack build multiple HTML files from SCSS and Pug?

I've read quite a few tutorials on webpack, but it seems more for creating web apps then for what I'm trying to do so I didn't want to waste any more time if the following isn't possible.
I'm creating websites on a third-party eCommerce system and they have a system of coding out templates that can be used to change the structure of their website. Below is an example of one of their templates I would be creating (although there are many types & variations I will need to make, not just a few).
My idea to streamline the creation of these templates is to create a bunch of pug components and place them in the components/ directory. Outside of the components directory I want to make higher level pug templates that utilize the components. Once these have been created, I would build it with NPM and the template files need to be converted to HTML and placed within the /dist folder.
Is this hard to do with webpack?
Structure of the project:
src/
..components/
....header/
......header1.pug
......header1.scss
..navcustom-template.pug
..customfooter-template.pug
..non-template-specific.scss
and once built:
dist/
..navcustom-template.html
..customfooter-template.html
..non-template-specific.css
src/
..components/
....header/
......header1.pug
......header1.scss
..navcustom-template.pug
..customfooter-template.pug
..non-template-specific.scss
Sample of a template:
<!--
Section: NavCustom
-->
<style>
//Template Speific CSS Imports Here
</style>
<script type="text/javascript">
//Template Speific JS Imports Here
</script>
<header>
<div class="col-md-4">
// Social Media Code
</div>
<div class="col-md-4">
// Logo Code
</div>
<div class="col-md-4">
// Call to Action Code
</div>
</header>
<nav>
</nav>
You can use these packages (--save-dev for all):
raw-loader to load the Pug files
pug-html-loader to read the Pug files
html-webpack-pug-plugin to generate HTML from Pug
html-webpack-plugin (standard HTML plugin loader)
Then configure Webpack similar to the following, where index.js is a dummy file you should create if you don't already have an entry. Each Pug template you need to process is written in a separate HtmlWebpackPlugin object at the bottom.
var HtmlWebpackPlugin = require('html-webpack-plugin');
var HtmlWebpackPugPlugin = require('html-webpack-pug-plugin');
module.exports = {
entry: ['./src/index.js'],
output: {
path: __dirname + '/dist',
publicPath: '/'
},
module: {
rules: [
{
test: /\.pug$/,
use: [
"raw-loader",
"pug-html-loader"
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/navcustom-template.pug',
filename: 'navcustom-template.html'
}),
new HtmlWebpackPlugin({
template: 'src/customfooter-template.pug',
filename: 'customfooter-template.html'
}),
new HtmlWebpackPugPlugin()
]
}
All Pug mixins (your src/components files) will be included in the output. This example is tested and working.
Edit: To dynamically process all Pug files in the src directory use this config:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPugPlugin = require('html-webpack-pug-plugin');
const fs = require('fs');
let templates = [];
let dir = 'src';
let files = fs.readdirSync(dir);
files.forEach(file => {
if (file.match(/\.pug$/)) {
let filename = file.substring(0, file.length - 4);
templates.push(
new HtmlWebpackPlugin({
template: dir + '/' + filename + '.pug',
filename: filename + '.html'
})
);
}
});
module.exports = {
entry: ['./src/index.js'],
output: {
path: __dirname + '/dist',
publicPath: '/'
},
module: {
rules: [
{
test: /\.pug$/,
use: [
"raw-loader",
"pug-html-loader"
]
}
]
},
plugins: [
...templates,
new HtmlWebpackPugPlugin()
]
}
This uses fs.readdirSync to get all Pug files in a directory. The synchronous version is used (as opposed to fs.readdir) because the module.exports function will return before the files are processed.
in 2022 is appeared the Pug plugin for Webpack that compiles Pug to static HTML, extracts CSS and JS from their source files defined directly in Pug.
It is enough to install the pug-plugin only:
npm install pug-plugin --save-dev
webpack.config.js
const path = require('path');
const PugPlugin = require('pug-plugin');
module.exports = {
output: {
path: path.join(__dirname, 'dist/'),
filename: 'assets/js/[name].[contenthash:8].js'
},
entry: {
// Note: a Pug file is the entry-point for all scripts and styles.
// All scripts and styles must be specified in Pug.
// Define Pug files in entry:
index: './src/views/index.pug', // => dist/index.html
'navcustom-template': './src/navcustom-template.pug', // => dist/navcustom-template.html
'customfooter-template': './src/customfooter-template.pug', // => dist/customfooter-template
// etc ...
},
plugins: [
// enable using Pug files as entry-point
new PugPlugin({
extractCss: {
filename: 'assets/css/[name].[contenthash:8].css' // output filename of CSS files
},
})
],
module: {
rules: [
{
test: /\.pug$/,
loader: PugPlugin.loader, // the Pug loader
},
{
test: /\.(css|sass|scss)$/,
use: ['css-loader', 'sass-loader']
},
],
},
};
Of cause, the entry can be dynamically generated like in the answer above.
In your Pug file use the source files of styles and scripts via require():
html
head
//- add source SCSS styles using a path relative to Pug file
link(href=require('./styles.scss') rel='stylesheet')
body
h1 Hello Pug!
//- add source JS/TS scripts
script(src=require('./main.js'))
Generated HTML:
<html>
<head>
<link href="/assets/css/styles.f57966f4.css" rel="stylesheet">
</head>
<body>
<h1>Hello Pug!</h1>
<script src="/assets/js/main.b855d8f4.js"></script>
</body>
</html>
Just one Pug plugin replaces the functionality of many plugins and loaders used with Pug:
pug-html-loader
html-webpack-pug-plugin
html-webpack-plugin
mini-css-extract-plugin
resolve-url-loader
svg-url-loader
posthtml-inline-svg

Importing hash-set module not working for typescript/systemjs?

I'm working on an aurelia project, using typescript to create the javascript. Now I tried to add another custom library, 'hash-set' (using jspm install npm:hash-set --save). However I can't seem to actually use this package (using systemjs as loader).
My document structure is like:
\
dist\
src\
app.html
app.js
main.js
jsp_packages\
npm\
hash-set#1.0.1\
node_modules\
index.html
config.js
package.json
tsconfig.json
The important files (I think, please state in the comments if I miss something):
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
</head>
<body aurelia-app="src/main">
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script>
SystemJS.import('aurelia-bootstrapper');
</script>
</body>
</html>
app.ts
This is compiled to app.js as prebuilt step. Using es2015 as target configuration.
import {hashSet} from 'hash-set';
export class App {
public myText: string;
hashFn(value) {
return value.toString();
}
constructor() {
alert("oh");
const h = hashSet;
const StringSet = hashSet(this.hashFn);
alert('oh2');
}
}
config.js
System.config({
defaultJSExtensions: true,
transpiler: false,
paths: {
"*": "dist/*",
"github:*": "jspm_packages/github/*",
"npm:*": "jspm_packages/npm/*"
},
meta: {
"bootstrap": {
"deps": [
"jquery"
]
}
},
map: { /*lots of aurelia and other library stuff*/
"hash-set": "npm:hash-set#1.0.1"
}
}
});
And it's also listed in package.json # {"jspm":{"dependencies":"hash-set": "npm:hash-set#^1.0.1"}}}
Now when I try to run above code (typescript compiles to app.js as prebuilt step), the app.js/app.ts loads, as expected. (frankly removing the hash-set specific code makes everything work as expected).
However during construction "oh" is shown, but "oh2" is never. Debugging over the code shows that "hashSet" is "undefined". Which leads me to believe that that systemjs isn't including the hash-set correctly?
Am I missing something?
EDIT: digging into the generated js (app.js) file I notice something weird:
define(["require", "exports", "hash-set"], function (require, exports, hash_set_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class App {
hashFn(value) {
return value.toString();
}
constructor() {
alert('oh');
const h = hash_set_1.hashSet;
const StringSet = hash_set_1.hashSet(this.hashFn);
alert('oh2');
}
}
exports.App = App;
});
//# sourceMappingURL=app.js.map
While debugging, hash_set_1 is actually of the type I expect hash_set_1.hashSet to be. Actually manually editing the javascript to not use hash_set_1.hashSet but rather just hash_set_1 works.
Trying
import hashSet from 'hash-set'; (notice lack of {}) changes the generated javascript offending line to const StringSet = hash_set_1.default(this.hashFn); which is still not correct (default isn't defined either).
If you look at the code, you'll see it is exported as:
module.exports = function hashSet(hashFn) {
Doing import { hashSet } from 'hash-set'; cannot work because the export would have to be module.exports.hashSet = ....
It should work if you do:
import hashSet = require("hash-set");

Calling webpacked code from outside (HTML script tag)

Suppose that I have class like this (written in typescript) and I bundle it with webpack into bundle.js.
export class EntryPoint {
static run() {
...
}
}
In my index.html I will include the bundle, but then I would also like to call that static method.
<script src="build/bundle.js"></script>
<script>
window.onload = function() {
EntryPoint.run();
}
</script>
However, the EntryPoint is undefined in this case. How would I call the bundled javascript from another script then?
Added: Webpack config file.
It seems that you want to expose the webpack bundle as a library. You can configure webpack to expose your library in the global context within a variable of your own, like EntryPoint.
I don't know TypeScript so the example uses plain JavaScript instead. But the important piece here is the webpack configuration file, and specifically the output section:
webpack.config.js
module.exports = {
entry: './index.js',
output: {
path: './lib',
filename: 'yourlib.js',
libraryTarget: 'var',
library: 'EntryPoint'
}
};
index.js
module.exports = {
run: function () {
console.log('run from library');
}
};
Then you will be able to access your library methods like you expect:
<script src="lib/yourlib.js"></script>
<script>
window.onload = function () {
EntryPoint.run();
};
</script>
Check the gist with the actual code.
I managed to get this working without any further webpack.config.js modifications, by simply using the import statement which i called from my main/index.js file:
import EntryPoint from './EntryPoint.js';
window.EntryPoint = EntryPoint;
For reference, here's my weback.config.js file.
Initially I tried accomplishing the same using require, however it assigned the module wrapper to window.EntryPoint as opposed to the actual class.
In my circumstance I was able to call a function from within the bundled JavaScript from another script by writing the function to the window when creating it.
// In the bundled script:
function foo() {
var modal = document.createElement('div');
}
// Bind to the window
window.foo = foo;
// Then, in the other script where I want to reference the bundled function I just call it as a normal function
<button onClick="window.foo()">Click Me</button>
I wasn't able to use Babel so this worked for me.
I had a similar challenge, I wanted to create a bundle for multiple pages within a journey and wanted each page to have it's own entry point into the code, and without a separate bundle for each page.
Here's my approach, which is very similar to Kurt Williams but from a slightly different angle, also without changing webpack config:
JourneyMaster.js
import { getViewData } from './modules/common';
import { VIEW_DATA_API_URL } from './modules/constants';
import { createLandingPage, createAnotherPage } from './modules/components/pageBuilder';
window.landingPageInit = () => {
getViewData(VIEW_DATA_API_URL).then(viewData => {
createLandingPage(viewData);
});
};
window.anotherPageInit = () => {
getViewData(VIEW_DATA_API_URL).then(viewData => {
createAnotherPage(viewData);
});
};
// I appreciate the above could be one liners,
// but readable at a glance is important to me
Then an example of how I call these methods at the end of the html page:
<script src="/js/JourneyMaster.js"></script>
<script>window.landingPageInit();</script>
WEBPACK.CONFIG.JS
1.USING UMD
module.exports={
mode:'development',
entry:'./yourentry.js',
output:{
path:path.resolve(__dirname,"dist"),
filename:'main.js',
publicPath:'/dist/',
libraryTarget:'umd',
library:'rstate',
umdNamedDefine: true,
libraryExport: 'default'
}
}
index.html
<script src="dist/main.js"></script>
<script>
window.onload = function () {
rstate()=>{}
</script>
main.js
export default function rstate(){
console.log("i called from html")
}
2.USING VAR
module.exports={
mode:'development',
entry:'./yourentry.js',
output:{
path:path.resolve(__dirname,"dist"),
filename:'main.js',
publicPath:'/dist/',
libraryTarget:'var',
library: 'EntryPoint'
}
}
index.html
<script>
window.onload = function () {
EntryPoint.rstate()=>{}
</script>
main.js
module.exports={
rstate=function(){
console.log("hi module")
}
}
3.USING AMD as library we use like(for those who want to make lib)
define(['jquery', './aux-lib.js'], function ($) { ..(1).. });
Many of the answers so far work, it would only be necessary to clarify that Webpack will not recognize the library until it is built once declared.
You should use npm run build right after creating your library,
before continuing to work with npm start.
At least that's how it works for me, using only webpack.
Maybe this is some impostor syndrome on my part, but I think 'real' coders will cringe at my answer. Regardless, I found this solution to be the best fitting for being pragmatic about my time with my hobby project:
Chane your JS function declaration from:
function renderValue(value) {
to:
global.renderValue = function(value) {
Of course, you'll want to require('path/to/your_custom_js') as you would any file.
I found this answer here:
https://www.fastruby.io/blog/rails/webpack/from-sprockets-to-webpacker.html
This took me forever to figure out as the accepted answer wasn't working for me. Just make sure the function name is the same as the library in the config and it's bundled with the config specified -- npx webpack --config webpack.config.js --mode=development -- hopefully this saves people a few hours.
index.js (function to be bundled) >>
function EntryPoint() {
console.log('called from bundle');
}
module.exports = EntryPoint;
webpack.config.js >>
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
libraryTarget: 'var',
library: 'EntryPoint'
},
};
start.html (where the bundled function is called) >>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Azure SDK Storage Example</title>
<script type="text/javascript" src="./dist/main.js"></script>
</head>
<body>
<h1>Azure SDK Storage Example</h1>
</body>
</html>
<script>
EntryPoint();
</script>
App.ts:
namespace mytypescript.Pages {
export class Manage {
public Initialise() {
$("#btnNewActivity").click(() => {
alert("sdc'");
});
}
}
}
mypage.html:
<input class="button" type="button" id="btnNewActivity" value="Register New Activity" />
<script type="text/javascript">
var page = new mytypescript.Pages.Manage();
page.Initialise();
</script>

Categories