Importing files from a either src/ or lib/ - javascript

I have a package (let's call it preset) that imports a component (component). Both are maintained by me. For backwards compatibility, preset additionally has to import a specific function from component, which is only available from a specific file (not the one in the main field). To do this, I use the require('component/lib/file.js') syntax.
However, file.js has side-effects. This is fine normally, but for Webpack builds I also have lib-mjs (with ES module syntax for tree shaking). This causes file.js to be loaded twice, once indirectly in lib-mjs via component, and once directly in lib.
A few options:
I could move file.js out of lib/lib-mjs altogether. However, this complicates the build process and messes up the repo structure.
I could export the specific function anyway. However, since component follows the same pattern with a lot of different components, I'd have to change that in a lot of places.
I could remove the side-effect to some init function. However, this would be a breaking change and involve quite some work.
I could make sure the side-effect only happens if it hasn't happened already. However, that and the previous change still has the file loaded twice. On top of that, it would require some semi-global state, I don't think I can pull off duck-typing for this one.
Is there some option like require(require.resolve('component') + '/file.js') that actually works for both Node and Webpack?

Related

Webpack combining multiple dynamic imports with different webpackExports into 1 chunk

Using Webpack 5, in one entry file I have multiple dynamic imports setup like so: import(/* webpackExports: ["moduleName"] */ 'package name'); each of these imports are done under different conditions. Likely on a page load only one of the 5 or so imports should actually be called. These imports are spread over multiple different packages.
The problem is for some reason Webpack is setting all these imports into one, rather larger, chunk instead of individual chunks as I would expect them to be.
Just to make sure there wasn't some cross dependency conflict or something I tried clearing the imported modules out to be practically nothing for testing purposes. I've also tried using webpackChunkName. Nothing has worked.
Why would webpack be making these one chunk instead of multiple? How can I fix this?
Thanks.
The issue is related to "state". Conditional loading does not mean that we can have multiple states for the same module. Issues with side effects and live bindings force webpack (or any loader) to aggregate all the exports of a module that are being used, conditionally or not, into one chunk.
Therefore, it's a limitation with tree shaking of dynamically imported modules.
I have written more about this here: https://blog.hotstar.com/how-to-dynamically-import-esmodules-and-tree-shake-them-too-aa24ee4885f5

How do you import jQuery plugins with Webpack?

I'm using Webpack v4, and I have jQuery plugins which I currently load into our app with the webpack-merge-and-include-globally webpack plugin (and then manually load these into the html file with a <script> tag) but I would like to be able to move this into our main app code, so that webpack can be aware of them. There's been issues where some dependancies/classes are loaded twice, once in the merge-plugin mentioned above, and again in the Webpack dynamic imports.
So far its been hit and miss trying to get jQuery plugins to load and properly attached to the jQuery object.
Is there a recommended way to import jQuery plugins, as its not like a normal JavaScript ES6 class which you can just prefix with export class ClassName or export default class ClassName, because the plugin is wrapped in an IIFE (Immediately Invoked Function Expression).
A few potential solutions:
Option 1 - jQuery plugin that does not need its own global
import 'myapp/jquery-plugins/MyjQueryPlugin';
This one seemed potentially too good to be true / too easy, as it did not require new loaders.
Add the import statement without a name, just the script path. This should self execute the script, similar to how it would on a load from HTML.
Note that this would not work with a script that needs to be Global. For that, use the expose-loader in option 2 to specify what globals to expose.
As I found that each plugin/library/class I had to work with required a different way of handling it, I will also mention some other methods I found useful, and may be useful to others:
Option 2 - globals, such as jQuery
import 'expose-loader?exposes[]=$&exposes[]=jQuery!jquery';
This would import and execute it as it went, so that the next import line could use the global variable straight away.
Option 3 - alternative for globals
import MyClass from './views/MyClass';
window.MyClass = MyClass;
You can import, and then set on the window yourself, but if you have several import statements, and the latter depend on the first one already being available, then this wont work, as its not yet defined. See option 2. This is my least favorite, but worth mentioning as a fallback.
Option 4
Use the [webpack-merge-and-include-globally][1] plugin, but this is the one I was trying to avoid/reduce the use of, as its outside of "webpack's awareness."
Others
There's also other loaders like raw-loader, which you can use as normal with a !! in the import, or with a .then promise to control execution after load.
import('raw-loader!someScript.js').then(rawModule => eval.call(null, rawModule.default))
This is the example given by the docs in script-loader as an alternative, as script-loader is deprecated.

How to build JS app with all the assets merged into single file component but not wrapped into webpack modules

Is it possible to build AngularJS/Angular/VueJS/*JS based app, with all the assets (fonts, images, styles, templates), merged into single file component/app, but not wrapped into any webpack module (like webpackJsonp) and without dependency on *.map.js files?
And by saying "not wrapped into webpackJsonp module" I mean the final output file, should be just transpiled/compiled pure ES5 code, inside IIFEs (Immediately invoked function expressions).
The code shouldnt be wrapped inside webpack methods, but should be standard, minified, uglified, ES5 *.min.js.
Just like the jQuery in the old days was, so something like that: (function(t) {this._init(t)})().
Of course, inside should be also packed templates (html) and styles (both usually as a strings), also images and fonts (the last two's usually as a base64 code), but nothing wrapped inside webpackJsonp or any of it's methods.
I'm asking in context of my current work: I'm writing reusable account registration form component in VueJS 2.6.x, which I'd like to import and load on 3rd party websites.
But it must also work on IE11, so using Web Components is out of question, that's why I'm transpiling it with Babel.
Regarding the vendors part, I'm currently compiling vendors scripts as a vendor.[hash].js file, but knowing how to merge it with final output app.js during compilation, would be a plus.
Is it possible to achieve it and if not, at least something very close to that?

dynamically load modules in Meteor node.js

I'm trying to load multiple modules on the fly via chokidar (watchdog) using Meteor 1.6 beta, however after doing extensive research on the matter I just can't seem to get it to work.
From what I gather require by design will not take in anything other than static strings, i.e.
require("test/string/here")
Since if I try:
var path = "test/string/here"
require(path)
I just get Error: Cannot find module, even though the strings are identical.
Now the thing is I'm uncertain how to go on about this, am I really forced to either use import or static strings when using meteor or is there some workaround this?
watchdog(cmddir, (dir) => {
match = "." + regex_cmd.exec(dir);
match = dir;
loader.emit("loadcommand", match)
});
loader.on('loadcommand', (file) => {
require(file);
});
There is something intrinsically weird in what you describe.
chokidar is used to watch actual files and folders.
But Meteor compiles and bundles your code, resulting in an app folder after build that is totally different from your project structure.
Although Meteor now supports dynamic imports, the mechanism is internal to Meteor and does not rely on your actual project files, but on Meteor built ones.
If you want to dynamically require files like in Node, including with dynamically generated module path, you should avoid import and require statements, which are automatically replaced by Meteor built-in import mechanism. Instead you would have to make up your own loading function, taking care of the fact that your app built folder is different from your project folder.
That may work for example if your server is watching files and/or folders in a static location, different from where your app will be running.
In the end, I feel this is a sort of XY problem: you have not described your objective in the first place, and the above issue is trying to solve a weird solution that does not seem to fit how Meteor works, hence which may not be the most appropriate solution for your implicit objective.
#Sashko does a great job of explaining Meteor's dynamic imports here. There are also docs
A dynamic import is a function that returns a promise instead of just importing statically at build time. Example:
import('./component').then((MyComponent) => {
render(MyComponent);
});
The promise runs once the module has been loaded. If you try to load the module repeatedly then it only gets loaded once and is immediately available on subsequent requests.
afaict you can use a variable for the string to import.

RequireJS loads resources I don't want to

I'm using RequireJS in my app, but don't quite well understand all aspects of it's work.
I have main.js file, where dependencies are described. I have Backbone.Router component up and running, which triggers different application classes (class which is responsible to create main view). Some code you can see here.
What I can see with requireJS: even if some view has not been yet 'required' (mean explicitly call to require('./subviews/view)), it's still loaded and inside it loads all templates (I use requireJS text plugin). If I'm adding new application but it's subviews are not ready, but I never used the application - nonexisting subviews are still loaded and I'm getting 404 errors.
Not sure I explained everything clearly, but hope you got point.
Looks like you are using the CommonJS sugared form of define(). Note that this is just a convenience for wrapping Node/CommonJS code, but AMD modules do not operate like Node modules.
An AMD loader will scan for require('') calls in the factory function of the module, and make sure to load all the modules. Otherwise, synchronous access to the that module, by doing a require('./apps/DashboardApp');, would fail in the browser, since file IO is by default async network IO.
If you want to delay loading of some scripts, then you need to use the callback-form of require:
require(['./apps/DashboardApp'], function (DashboardApp) {
});
but this call is an async call, so you would have to adjust your module's public API accordingly.
So basically, if you want to do on-demand loading of a dependency, the callback form of require is needed, given the async nature of file IO in the browser.
Because RequireJS loads all required dependencies. By taking quick look at your code I see that you load routing module and routing has:
var ViewManager = require('ViewManager');
That means it will load ViewManager, dependecies that are specified by ViewManager and other dependencies that those modules need. Essentially when you include require(...), it's the same as specifying dependency. This will be transformed by RequireJS into
define(['ViewManager'], ...)

Categories

Resources