I'm currently developing a web application and am in the process of refactoring my code so it's bundled by WebPack.
In my HTML I have a button that calls the sendMessage() function when clicked,
<button type="button" class="btn btn-default" onclick="sendMessage(document.getElementById('claim').value)">Submit</button>
In the first version of my code, the sendMessage() function was defined in a .js file that I imported directly in the HTML,
<script src="my_js/newsArticleScript.js"></script>
The sendMessage() function is directly declared in the file, it's not inside any class or module but it calls other functions defined on the same file, so I don't want to separate it.
But now it's being bundled by WebPack. Here is my config file (the homepageScript.js is for another page):
const path = require('path')
module.exports = {
entry: {
homepage: './src/homepageScript.js',
newspage: './src/newsArticleScript.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist/my_js')
}
}
I changed the script import on the HTML to import the bundled version.
And now, when I press the button I get the error
[Error] ReferenceError: Can't find variable: sendMessage
I've read about it, and I learned that webpack does not expose the functions globally. I have tried to export the function, but kept getting the same error.
Can anyone help me with this? Essentially what I need is to understant how to configure webpack or change the JS so it can be accessed by the HTML.
I'm new to JS so I'm pretty sure the way my app is designed is not the best, so any tips on improvements (maybe I should use modules, or there are better ways of calling JS function upon clicking a button) would be welcome as well. Thank you :)
Generally using text-based event handlers isn't a good idea. That said, you have a few options, with increasing amounts of changes.
For texual onchange handlers to work, sendMessage would have to be a global. So the quickest fix would be to change your code to do that. If for instance you have
function sendMessage(arg){}
in your code, you'd add an additional
window.sendMessage = sendMessage;
after it to expose it as a global variable.
A more modern approach would be to remove the onchange from the button and have the button labeled with an ID in the HTML, e.g. id="some-button-id".
Then in your JS code, you could do
var button = document.querySelector("#some-button-id");
button.addEventListener("change", function(){
sendMessage(document.getElementById('claim').value);
});
to add the change handler function using JS code, instead of with HTML attributes.
Related
tl:dr;
class ModuleInBundleA extends ModuleInBundleC { … }
window.moduleInBundleB.foo(new ModuleInBundleA())
class ModuleInBundleB {
public foo(bar: ModuleInBundleA|ModuleInBundleC|number) {
if (bar instanceof ModuleInBundleA || bar instanceof ModuleInBundleC) {
// always false
…
}
}
}
Details:
I'm trying to start using TypeScript + Webpack 4.41.6 on the project that has mostly old codebase. Basically I want to package several small modules onto bundles to migrate softly without moving whole project onto new js stack.
I found out that Webpack can do this with code splitting, and package shared code into bundles on it's own with some configuration. However I can't really control what will be in every bundle unless I build every bundle separately and then only share types, using my own modules as external libraries and that's bit frustrating.
Maybe on this point you can say that I'm doing something wrong already and I would like to hear how can I achieve my goal of using bundles just as vanilla javascript (controlling defer/async on my own and using script tag on my own as well), and I don't really want to pack everything as an independent package with own configuration, types export and so on.
Hope you got overall context. Closer to the point.
I have the following function, that is bundled to it's own chunk called modal-manager.js.
public showModal (modal: ModalFilter|AbstractModal|number) {
let modalId: number;
console.log(modal);
console.log(typeof modal);
console.log(modal instanceof ModalFilter);
console.log(modal instanceof AbstractModal);
if (modal instanceof AbstractModal) {
modalId = modal.getId();
} else {
modalId = modal;
}
...
};
(Originally it had no ModalFilter as ModalFilter inherits AbstractModal but I included it for demonstration purposes)
The abstract modal is bundled automatically to modal-utils.js as it's shared between modules.
Next, I have another big bundle called filter.js. This one literally creates instance of ModalFilter const modalFilter = new ModalFilter(...). I think it's work mentioning that instance of modalFilter declared to the global window variable. The trouble is that filter.js calls modal.js code (through window.modalFilter.showModal(modalFilter)) with no problems whatsoever, but I see the following result of console.log:
ModalFilter {shown: false, loading: false, closing: false, html: init(1), id: 0, …}
modal.bundle.23e2a2cb.js:264 object
modal.bundle.23e2a2cb.js:265 false
modal.bundle.23e2a2cb.js:266 false
I disabled mapping to get more into code and see this:
ModalManager.prototype.showModal = function (modal) {
var modalId;
console.log(modal);
console.log(typeof modal);
console.log(modal instanceof _builder_component_modal_filter__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"]);
console.log(modal instanceof _modal_abstract__WEBPACK_IMPORTED_MODULE_0__[/* default */ "a"]);
if (modal instanceof _modal_abstract__WEBPACK_IMPORTED_MODULE_0__[/* default */ "a"]) {
modalId = modal.getId();
}
else {
modalId = modal;
}
this.modals[modalId].show();
this.scrollLock(modalId);
};
With my understanding of how javascript works, instanceof should check the object-creator function. As code chunks separated (modal.js has no same code with modal-utils.js) the creator function should be the same. However, getting more to the details I see that webpackJsonp can be really tricky and calling them from kind-of independent environments, still it should be the same environment where FilterModal, AbstractModal is called. The ModalManager could have own environment I believe. But code called is 100% the same. Could that webpackJsonp bundle-arrays be the source of the problem? If so, how can I avoid that and make modal.js bundle understand that both filter.js and others reference the same AbstractModal from modal-utils.js?
If I'm doing it wrong, is there a simple way to start bundling small and efficient scripts build with TypeScript and Webpack (or other tools)?
Also, I see the externals feature of Webpack, but haven't figured out how to use that in my case. In general, I'm ok with current set up except instanceof issue. The reason I want to avoid multiple builds is that I'll probably have dozens of smaller bundles that shared across different modules and having dozen of npm packages for each seems excessive.
Prefacing this with; I don't know the answer to the exact issue that you are facing in regards to the instanceOf part of your question. This is aimed at the "how did you do it" part.
Approx. 4 weeks ago we also changed from a .js to .ts implementation with about 1-2 hunderd .js files. Obviously we didn't want to migrate these all at once over to .ts as the effort was too high.
What we ended up doing was identifying .js scripts which needed to run on specific pages and added these into webpack as entry files. Then for all of the other suporting scripts, if we required their contents in our new .ts files, we actually created a large index/barrel file for them all, imported them in and then webpack will automatically include these in the correct scope alongside their respective .ts files.
What does this look like?
legacy.index.ts: For every single supporting .js file that we wanted to reference in any way in .ts.
var someFile_js = require("someFile.js");
export { someFile_js };
This then allowed us to import and use this in the .ts files:
import { someFile_js } from './legacy.index';
In reply to #tonix. To load a defined list:
webpack.config
const SITE_INDEX = require('./path-to-js-file/list.js')
module.exports = {
entry: SITE_INDEX
...
}
list.js
{
"filename1": "./some-path/filename1.js"
"filename2": "./some-path/filename2.ts"
}
How to include an entire file into my bundle main.js?
ES6 can import/export functions and classes. But what if i want to include the whole content from another file into my bundle main.js? how to do it?
I came across the query on Stackoverflow: Managing jQuery plugin dependency in webpack.
I'm not sure about this question though. Those options given there seem to target injecting implicit globals, configuring this, disabling AMD, to include large dists. I don't think this is what i want.
Let's say i have two files in src directory
1- rough.js
const rgh = "qwerty"
2- index.js
import './rough.js' //something like this
console.log (rgh)
Now what i expect in bundle.js is
const rgh = "query";
console.log(rgh)
I just want all the content inside one of my file to get all transported to index.js for webpack to bundle them
Those options given there seem to target injecting implicit globals,
configuring this, disabling AMD, to include large dists. I don't think
this is what i want.
To understand this you need to understand what webpack is doing for you. Web pack takes a series of Javascript files (and more importantly their contents) and parses these into one file. That's what it does from a file point of view, but if you ignore the file and think about what it does from a code point of view, it takes each one of the imported objects and makes them available to other objects depending upon the rules you define in your code (using import and export). You can think of this from a closure point of view something like this:
if you have some code like:
import a from 'a.js';
export default b(){
console.log(a.test());
}
This will be turned into something like, in one js file:
var a = (function() {
var testStr = "test";
function test(){
return testStr;
}
return {test:test};
})();
var b = (function(a) {
console.log(a.test());
})(a);
So you can see that the file isn't really important. What's important is the scope. b can use a because it is injected into it's scope (In this instance as a IIFE).
In the above example a and b are in the global scope but testStr isn't.
So when your talking about "importing my file", you need to forget about that and think about what objects in that file you want to import how. Any variables "in that file" declared directly var a = ....; are in the global scope. So it sounds like what you want to do is import the objects in that file into the global scope.
you just need to import that file in main.js
like this way
I'm trying to add dynamic import into my code to have a better performance on the client-side. So I have a webpack config where is bundling js files. On SFCC the bundled files are in the static folder where the path to that files is something like this: /en/v1569517927607/js/app.js)
I have a function where I'm using dynamic import of es6 to call a module when the user clicks on a button. The problem is that when we call for that module, the browser doesn't find it because the path is wrong.
/en/lazyLoad.js net::ERR_ABORTED 404 (Not Found)
This is normal because the file is on /en/v1569517927607/js/lazyLoad.js.
There is a way to get it from the right path? Here is my code.
window.onload = () => {
const lazyAlertBtn = document.querySelector("#lazyLoad");
lazyAlertBtn.addEventListener("click", () => {
import(/* webpackChunkName: "lazyLoad" */ '../modules/lazyLoad').then(module => {
module.lazyLoad();
});
});
};
I had the same problem and solved it using the Merchant Tools > SEO > Dynamic Mapping module in Business Manager.
There you can use a rule like the following to redirect the request to the static folder:
**/*.bundle.js i s,,,,,/js/{0}.bundle.js
All my chunk files are named with the <module>.bundle pattern.
Here you can find more info :
https://documentation.b2c.commercecloud.salesforce.com/DOC1/topic/com.demandware.dochelp/content/b2c_commerce/topics/search_engine_optimization/b2c_dynamic_mappings.html
Hope this helps.
I believe you'll likely need to do some path.resolve() magic in either your import statement or your webpack.config.js file as is shown in the accepted answer to this question: Set correct path to lazy-load component using Webpack - ES6
We did it in a different way. That required two steps
From within the template file add a script tag that creates a global variable for the static path. Something like
// inside .isml template
<script>
// help webpack know about the path of js scripts -> used for lazy loading
window.__staticPath__ = "${URLUtils.httpsStatic('/')}";
</script>
Then you need to instruct webpack to know where to find chunks by changing __webpack_public_path__ at runtime
// somewhere in your main .js file
// eslint-disable-next-line
__webpack_public_path__ = window.__staticPath__ + 'js/';
Optional step:
You might also want to remove code version from your __staticPath__ using replace (at least we had to do that)
__webpack_public_path__ = window.__staticPath__.replace('{YOUR_CODE_VERSION_GOES_HERE}', '') + 'js/';
I am totally new to webpack (i previously built my apps by including tons of css / js files by "hand") and am now trying to understand how namespaces work when working with the named tools.
i have an app.js
require('./bootstrap');
require('./helperFunctions');
/* ... more, unrelated stuff */
webpack.mix.js is untouched from the original file delivered with the laravel 5.5 sample project
let mix = require('laravel-mix');
mix.js('resources/assets/js/app.js', 'public/js')
.sass('resources/assets/sass/app.scss', 'public/css');
my helperFunctions.js is a simple js file with some helpful functions i want to use throughout my project:
function foo_bar(A, B) {
return A - B;
}
/* more functions, following the same structure... */
but everytime i try to use one of the functions defined in the helperFunctions file i find that they are "undefined", even in the app.js file directly after the 'require' happens.
after inspecting the generated app.js file i found that my functions are encapsulated in an anonymous function function(module, exports) { /* my File contents go here */ }, resulting in them being unavailable to the rest of the scripts.
while i understand that this is propably there to reduce polluting the global namespace, i dont understand how i am supposed to define global objects (such as data Storage objects for vue) or functions such as helper functions.
can anybody explain how this is supposed to work, or link me to a ressource explaining this for someone who never worked with an asset compiler (if this is even the right terminus).
cheers
// Edit: i think i found a solution, after stumbling over this:
https://nodejs.org/api/modules.html#modules_modules
I editted the helper functions file to something like this:
module.exports = {
foo_bar(A, B) {
return (A - B);
},
/* ... more functions ... */
}
And imported it wherever i need it like this:
import HelperFunctions from './helperFunctions'
var result = HelperFunctions.foo_bar(5, 8);
However, this only works in files which are pre packed using webpack. is registering the component under window.HelperFunctions the only way to make them available in dynamically generated <script></script> tags throughout the website?
Registering your helper methods on the window object, as you kind of suggested, is a simple and easy to understand approach, so that's what I would choose to do if I wanted these methods to be available globally.
window.myHelperMethod = function () { console.log('ayo, this works!') }
I have a file call scripts.js and it merely do var main = require('./main');
and my main.js is at the same level, it has
module.exports = {
console.log("hello");
}
I do webpack in my terminal I got unexpected token?
You can not include function executions into a javascript object (you are exporting an object), you need to export a function to achieve this
module.exports = function() {
console.log("hello");
}
And from your file where you require the export
var main = require('./main')(); // Add () to execute the function that you obtained
UPDATE:
You are executing console.log() in your main.js file, think that you can simply require some script without exporting nothing, and that file should be executed. For example you can put console.log("hello") without all the module.exports thing and the code should be executed, but when you check the value of your main variable (in the file where you do the require), you probably would not find anything useful.
You can also export a function(like in the code i send before) and then execute that function later, there's many ways to aproach this, I recommend you to google a bit about how module export works and how can you use it
You can check more about module.exports here https://www.sitepoint.com/understanding-module-exports-exports-node-js/, you are using it for the browser, but this examples for node should be usefull anyways