requirejs r.js optimized builds of components over a main library - javascript

I'd like to get some best practice advice on componetising sub modules into a larger project. In development mode, it works fine as require resolves all deps on the fly - but it needs to be produced for a GWT app now that apparently has difficulty consuming async stuff via callbacks.
Structure is as follows (this builds fine individually and creates the files as required):
main (builds all shared libs resources etc)
component A (requires main to be there but excluded in own build)
... component N
To be used in an app that loads like so:
<script src='require.js'></script>
<script src='main-lib-min.js'></script>
<script src='component-a-min.js'></script>
<script src='component-n-min.js'></script>
<!-- client to all these files is a single app to kick off: -->
<script src='app.js'></script>
main is defined as a require wrap and fires an event when done.
components are a collection of modules in a define() wrap.
the problem is, app.js should not reference anything until all references have been resolved. and component-A may have references to exports of main-lib so it should not be evaluated before the previous script has loaded.
in chrome, we are seeing parallel downloading and reference errors. need that to be blocking instead. since the r.js build has pre-packaged everything, the factory should be able to just return any module defined above it.
i.e. doing:
var foo = require('foo'); ought to be fine.
if app.js is created as:
require(['foo'], function(foo){
});
then it works.
does anyone have experience in producing this kind of a component build / module pattern? would you include all modules from the main-lib build config as modules: [] so they wait for each other? what if you want the client (i.e. the html file) to be the one adding the modules?
would you change the loading? is there a GWT loader that will be more appropriate to ensure sync-like behaviour? performance is not an issue.

Related

Using a JS bundler with a dummy entry point just for bundling purposes

I have a legacy Java app using JS inside JSPs. We statically import JS dependencies from filesystem into our JSP files, some of them are old JS dependencies, some of them are newer and use the import/export JS feature.
For the module-based dependencies I'd like to bundle them. Most if not all bundlers I am familiar with, like webpack/esbuild uses an entry point to analyze the usage of dependencies and then bundles all dependencies and all app code used from entry-point onwards and puts them all in one big file
My JSP page spit JS code like so
<script type="module">
// do some stuff...
import { RFB } from 'rfb.js' // dozen of js files
// referenced, that is why
// i want to bundle them
// into just one js file...
// do some stuff with RFB
</script>
Then in the a dummy entrypoint I'd like to do that same import in a separate esbuild project for the purpose of bundling the module in one js file.
The idea is to use a dummy entry point that imports the elements I know I use in my JSP application and use that project (webpack/esbuild) for the purpose of using its bundling functionality. However, the bundle usually has this format
1. (() => {
2. // - Dependencies 1, 2, N goes here
3. // - import/export statements removed.
4. //
5. // - entry-point code
6. // - app code
7. })();
Of course if I import this file into my JSP file I can't access the dependencies. I was thinking of doing one of these 2 things:
I could add in the entry-point something like: window.ClassIAmInterested = ClassIAmInterested then that class will be available in the global scope of my JSP page.
I could remove the lines 1 and 7 of the bundle and then everything would be available in the global scope.
And although either of the 2 options might work I don't fully understand the implications of doing it. I know I am trying to use the bundlers in a way they are not designed, since I explicitly want to use the entry point as a dummy code that will just import whatever I know I use in my JSP pages. So I need clarity regarding if what I am trying to do is ok or if there is a better way of doing it.

Can I monkeypatch a function in another webpack bundle at runtime?

I am writing a plugin for a third-party web application, whose code I can see but can't modify. I'm running it in Chrome. The main webapp and the plugin are both (separate) webpack bundles. At runtime when the page loads, the webapp fetches the plugin bundles from the same server, and initialises them.
My objective is to make my plugin patch/wrap a function in the third-party application, in the module webapp/utils/target.tsx, such that calls to that function from within the webapp have my modified behaviour. Something like this:
// somehow import the `target` module (this is the problem, see below...)
oldFunc = target.targetFunc;
target.targetFunc = function targetFunc(args) {
// do extra stuff here
return oldFunc(args);
}
But I don't know how to import the target module or whether this is possible. Specifically:
I can't just import target, because application/webapp is not a dependency of my plugin
Plugins are meant to access limited entrypoints that get attached to window by the webapp, so they have no direct dependency on the webapp
I don't think I can add application/webapp as a dependency because
it's not a published package (perhaps I can add it as a github link?) and
I don't want webpack to include it in the bundle, so I think I'd have to specify it as an external dependency, but I don't know how to do that...
I can't modify application to do any extra things in its webpack (like exposing target in a different way)
I thought perhaps I could import it dynamically at runtime:
import(/*webpackIgnore: true*/ '/application/webapp/utils/target').then({
...
})
This gives me the error Expected a JavaScript module script but the server responded with a MIME type of "text/html".
If it helps, when the page is fully loaded and the app has loaded my plugin, in Chrome developer tools under Sources -> Page, I see a tree structure like this:
- localhost:port
- .
- com.mydomain.myplugin
- <modules for my plugin>
- application
- webapp
- .
- <other modules>
- utils
- target.tsx
- <other files>
- webpack
Meanwhile the original html page source seems to load the webapp via this tag in the header:
<script defer="defer" src="/static/main.c4e2eaf1d8c47b01fa6c.js"></script>
The Chrome devtools say "Source map detected".
Is it even possible to do what I'm trying to do?
From what i understand, you can import the module object, edit with the Object. library and return the edited module. If you don't understand nothing but knows the core, these references should helpful to solve this.
https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Statements/import
https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Statements/export
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties

Instantiate an object defined in module A and use its methods in module B javascript

I'm working in a web development project.
Right now i'm using a 3rd party library to instantiate an object of that library in a file called, let's say, fileA.js
so i do:
import libraryExport from "./librarymain.js"
var object = libraryExport( ... );
export default object;
Now, in fileB.js i want to use the methods that the instantiated object has, for example:
import object from "fileA.js"
object.methodOfTheLibrary();
However, when im running this in my browser console i always get "methodOfTheLibrary is not a function", which means, from my point of view, that the library is not being imported properly in fileB.js
Note: I'm using webpack to bundle all of my files and everything was compiling and bundling just fine until i came with issues. I usually know my way around C++ in an advanced way but for JS i just still don't fully understand how to solve these kind of import issues.
Thank you for any help
It's generally* recommended that you avoid using an imported module directly in the body of another module. (One of your own modules, that is... as you'll see in a moment, third-party modules are generally fine to use.)
The big issue is load order. If your fileA also imports some other module, which imports another module which then imports fileB, then fileB will attempt to run and, since fileA is still trying to load dependencies, object will not actually have been instantiated yet.
You'll either need to carefully review your dependencies to look for just such a loop and then eliminate it or, if possible, restructure fileA to wrap your code in a function that can be called once the entry point has finished loading (which will guarantee that all other modules have been resolved):
// fileB.js
import object from "fileA.js"
export function init() {
object.methodOfTheLibrary();
}
// main.js
import init from "fileB.js";
init();
* "generally" meaning that there are plenty of perfectly acceptable situations where it's fine. You just have to mindful of the pitfalls and mitigate against those situations.

How can I use Gulp and Browserify for my javascript app?

I am finally trying to bring a modern build system to my app, and I'm hoping someone can help. I think I need a few paradigm shifts.
So this is how my app is structured:
/src
/components
/Base
/App.jsx
/Pages.jsx
/...
/Page1
/Page1Component1.jsx
/Page1Component2.jsx
/...
/Page2
/Page2Component1.jsx
/Page2Component2.jsx
/...
/...
/libs
/bootstrap.js
/jquery.js
/react.js
/...
/scripts
/index.js
/utils.js
/styles
/main.css
/html
/index.html
Right now I have gulp set up to do this:
Make a new folder /dest to put everything
Combine everything in /scripts, name it main.js, put it in dest
Combine everything in /libs, name it libs.js, put it in dest
Combine everything in /components, run it through babel, name it comps.js, put it in dest
Copy the one /html file and one /styles file into dest
Then here is how the app runs:
Open index.html
That page requests main.js
main.js requests libs.js and comps.js
Everything works
But here is the issue I'm running into: A lot of stuff here relies on other stuff being global. index.js waits for comps.js and libs.js to load, then calls ReactDOM.render(<App />...), which means both ReactDOM and App need to be global.
Now I'm trying to add something that needs require(), and I try to use Browserify for it. But Browserify takes the code that needs the require and wraps it up in a way that, I believe, makes nothing global.
I realize that I need to turn my app into actual modules, instead of just a bunch of files that concatenate and call each other. And I know that avoiding global variables will be a good thing in the long run. But I'm having a really hard time figuring out how.
For example, I have >50 React modules. It seems wrong to add module.exports to every single one of those, and then import them all to the main file. Further, some of the things in /lib are libraries that don't export as modules, they're made to be run in the <head> tag, like Google Charts.
So I guess my questions are:
Where should my module exports be, and how do they fit into my gulp tasks? Do I concatenate then export?
How do I deal with libraries that aren't modules?
Is my app really poorly laid out, and I just need to restructure from scratch?
Thanks, and sorry about the rambley question.
First, there's nothing wrong with your file structure.
Second, the best thing you can do is follow the "one module, one file" rule. That does mean adding module.exports or export default to every single file. That's just good JavaScript. But it doesn't mean importing them all into your main file, which brings us to:
Third, think in modularity. Files should require or import precisely what they need and nothing they don't. For example, if your App uses Page1 and Page1 uses Page1Component1, then that's how your imports should work:
App -> Page1 -> Page1Component1
-> Page1Component2
-> Page2 -> Page2Component1
-> ...
This ensure separation of concerns and protects your code from easy-to-trigger errors later on (like those from nested dependency changes). And your build system should generate one file (but you can tackle performance later if needed with chunking and so forth).
And you're correct that in this kind of structure, using Browserify or Webpack will ensure that nothing is global - and that's a good thing (though I will note that you can tell them explicitly to expose components, which is sometimes necessary for libraries).
And that leaves libraries that you don't control that you can't import. This does not apply to Bootstrap, jQuery, or React, which all have require-able modules from NPM. But assuming that you have a library you didn't mention that is not available through NPM, you can still include it globally in your HTML with a script tag and tell Browserify or Webpack to expose it for requiring.

Struggling to setup Jasmine with RequireJS. Module's relative paths in define break when calling from test framework.

I have a project with which I have just recently introduced RequireJS. Before RequireJS, I had been testing my project with Jasmine. I am now trying to update Jasmine's SpecRunner so that it can handle RequireJS.
I found this article on unit testing with RequireJS and Jasmine which I have used to get me started. I've hit a snag with how I am loading modules. Each modules defines other modules it has a dependency on, but each module's load path is relative to the location where requireJS was loaded.
I load requireJS in two different locations: background.html and SpecRunner.html. Background is for the main application and SpecRunner is for testing. The problem is that my modules fail to load when called from SpecRunner because their relative paths change.
Example:
js/third_party/jquery-1.7.2.min.js
js/background/player.js
js/background/playlists.js
tests/SpecRunner.html
tests/spec/PlayerSpec.js
background.html
With this folder hierarchy and files I have two scenarios. One in which the code runs properly and one in which there is an issue:
Good Scenario
background.html loads require.js and executes targeting player.js as the loading point.
player.js defines playlists.js as a dependency.
playlists.js is loaded.
player.js is loaded.
Bad Scenario
SpecRunner.html loads require.js and executes targeting player.js as the loading point.
player.js defines playlists.js as a dependency.
playlists.js fails to load. GET file://tests/playlists.js
The file location is incorrect. playlists.js is not under /tests, it is under js/background!
I'm not sure how I should handle this? It seems like being extremely explicit with path names is my only way out, but that goes against all of the requireJS examples.
Paths in require are relative to the data-main location or relative to the location of the function that called require(). In your good scenario this is js/background/, and in your bad scenario this is tests/. In the blog you linked they use require.paths to make this work properly:
<!--
Since we are in the "test" directory, not in the standard
"js" directory, we need to define the path prefix' for the
RequireJS modules so that the modules can be found from the
tests running in the Spec directory.
-->
<script type="text/javascript">
var require = {
paths: {
"domReady": "../js/lib/require/domReady",
"model": "../js/model"
}
};
</script>
In your case, you'll have to set up paths like
'player': '../js/background/player.js',
'playlists': '../js/background/playlists.js'
and explicitly require player and your spec in your test runner as they do in the blog post.

Categories