Can you feed a Webpack magic comment with a specified variable? - javascript

A colleague and I have been trying to write a function for dynamically importing components into our Vue app. We're bundling our code with Webpack 5. At the moment our function looks like this:
const dynamicImport = (pathName, request, resolve, canTryAgain) => {
import(/* webpackChunkName: '[request]' */ `${pathName}/${request}`)
.then(component => resolve(component.default))
.catch(() => {
if (canTryAgain) {
dynamicImport(pathName, request, resolve, false);
} else {
location.reload();
}
});
};
Our hope was that the [request] placeholder in the magic comment would take our request parameter, and name the bundle resulting from this function after whatever we feed that parameter. However, Webpack just takes the entire line that we feed into the import statement ${pathName}/${request} and uses it for naming our bundles. This results in really long and not very elegant file names to use in our frontend. Any idea of how to still feed the import statement two variables/parameters, and have the magic comment only use one of them for naming the bundle?

I don't have the exact solution, but I can point you in some sort of directly. This would likely be managed on the babel-plugin side of things.
Use the loadable-components library as an example. There is the main component library (which would include code similar to what you wrote above), then there is the webpack side of things where transformations occur in a babel plugin.
Loadable Components:
https://loadable-components.com/
Loadable Components Repo:
https://github.com/gregberge/loadable-components/
Babel Chunk Name Functions:
https://github.com/gregberge/loadable-components/blob/main/packages/babel-plugin/src/properties/chunkName.js

Related

How to import a module from the static using dynamic import of es6?

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

Webpack 4 how to require sources as separated chunks and load them on demand

I have noticed that when I am importing source in the old way, like this: require('./my-script') it injects the code of the script into the main bundle.js.
And when I am importing like this: import('./my-script')
it created a separate new file and I even can name it with:
import(/* name.js */ './my-script');
That is nice, but in the new way of using import instead of require - how can I send parameters to the imported function?
For example, in require I could do require('./my-script')('something');
However this cannot be achieved using import
How can I pass parameters to an imported function and make it as a separate chunk in webpack?
This is the my-script.js (for example only):
// my-script.js
module.exports = str => {
return `${str} was returned`;
}
import(name) is used for code splitting (creating separate chunks) and will return a promise for the module's exports once the chunk has been retrieved asynchronously.
With your example, it can be used with something like the following:
import('./my-script').then(myScript => myScript.default('hello'))
The Dynamic imports section provides a more complete example and describes how import() is used for code splitting and here in Magic Comments are options listed which you can use to control how the chunk is created and when it's retrieved so you can tweak the user experience.

Is it possible to have a truly dynamic import() in webpack?

I've been trying to use the import() function to import something which is dynamic at runtime. I would think that as long as I create an entry for the file, webpack could be smart enough to import() the correct module, but that doesn't seem to be the case.
Does anyone know of a way to chunk off an entry and use the import() syntax, feeding it a variable, and have it work at runtime?
A simple example of the root problem is as follows:
// works
import( './a.js' ).then(() => console.log('it worked'));
// something is a dynamic variable that changes at runtime
const something = './a.js';
// does not work, even with this simplistic example
import( something ).catch(() => console.log('it did not work'));
It does not work because, although it is called "dynamic import" it is does not follow what the word means. The idea on "dynamic" import is to be able to import something dynamically at runtime, but here it is caveat: the module to be imported has to be known.
Since webpack does static analysis to do the lazy loading on these import() statements, everything has to be known and predictable, otherwise webpack would not be able to create async chunks on the fly. That is why adding a variable to the import does not work.
Yeah, this bit of webpack is weird. Truly dynamic import doesn't work.
We can put string template inside the import statement, but putting variable name raises Dependency warning.
This is the closest I could achieve - put if conditions or switch case or a string template and then write the import in it.
Something like -
const getModule = (filename) => {return import(`directoryPath/${fileName}`);}
OR
const something = 'a.js';
if (something === 'a.js') {
return import(`./${something}`); // promise
} else if (something === 'b.js'){
.........
}
There's this thing with dynamic imports - webpack bundles all possible files in these conditions which is not cool.

How does the JavaScript Import work under the hood?

How does the import statement actually work in JavaScript? I read the documentation and it says that it places exported code within the scope of the file. Does that mean that the code is copied over into the file where I type import?
For example - in library.js I have:
export {export function getUsefulContents(url, callback) {
getJSON(url, data => callback(JSON.parse(data)));
}
In main.js I have:
import { getUsefulContents} from 'library.js';
getUsefulContents('http://www.example.com',
data => { doSomethingUseful(data); });
This allows me to call getUsefulContents() in main.js. My question is, does main.js now have the contents that I exported from library.js?
Is using import the same as just having physically defined getUsefulContents() in main.js?
function getUsefulContents(url, callback) {
getJSON(url, data => callback(JSON.parse(data)));
}
getUsefulContents('http://www.example.com',
data => { doSomethingUseful(data); });
The main reason why I ask is because I want to know if using import will have an effect on main.js file size? Or is something else going on under the hood?
Thank you!
Depends on how you are using main.js. If you run it through a bundler, then the bundler will probably include library.js into main.js to pack it up into one file. In that case, the only advantage would be maintainability and ease of development because you are focused on the file you are working on. If you are simply running the import statement and deploying your application, the import statement won't affect the file size of main.js.
It just namespacing it within the file that you are importing it to so
any code from useful-library.js is included in the file, I visualize it this way
import { usefulCodeFromLibrary } from './useful-library.js';
((usefulCodeFromLibrary)=>{
// My excellent code
// Imported code doing it's job
usefulCodeFromLibrary.someHelperFunction()
}()

Creating a reusable RequireJs library

Given the following simplified scenario, how could I best construct my reusable component so that it is correctly consumable by another application, such that foo.js prints 23?
Reusable Component:
/home.js
/main.js
/stuff
foo.js
--
/home.js:
define([], function () { return 23; }
/stuff/foo.js:
define(['home'], function (home) { console.log(home); } // prints 23
Consumer Application:
/app.js
/main.js
/home.js
/views
template.html
/bower_components
/myReusableComponent
/home.js
/main.js
/stuff
foo.js
--
/home.js:
define([], function () { return 17; }
/bower_components/myReusableComponent/home.js:
define([], function () { return 23; }
/bower_components/myReusableComponent/stuff/foo.js:
define(['home'], function (home) { console.log(home); } // now prints 17
Is there a consumer application requirejs config that sets the baseUrl of any module in /myReusableComponent to '/myReusableComponent'? My reusable component should not have/need access to the root level of the consumer application anyway.
I have looked into the r.js optimizer, but it just outputs a bunch of define('stuff/foo', [], function ())... what happens if the consumer application has a stuff/foo.js too?
The only solution I have found so far is forcing the use of relative paths in all of my modules: define(['../home'], function (home) { console.log(home); } but I am hoping there is a more elegant way to solve this problem.
All feedback is very appreciated!
If you want to produce a library that is going to be usable in different applications, then you should use use relative paths when one module of your library refers to another, because this makes it more likely that whoever uses your library will be able to do so without having to do modify their RequireJS configuration.
Some clashes can be eliminated by the judicious use of map or paths. Ultimately, however, there are cases that cannot be solved (or at least not be solved as the user wants it) without having access to the unoptimized modules so you should distribute your library as an optimized bundle and provide the possibility to load it as a collection of unoptimized modules.

Categories

Resources