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

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

Related

How do I require a function in a node modules without using a Module Bundler

I cannot find an accurate answer to this anywhere. I understand the browser doesn't recognise commonJS syntax hence why if require() is loaded into the browser a reference error occurs like this
let isEven = require('is-even') // ReferenceError: Can't find variable: require
console.log(isEven(6)); // this doesn't run
So I try to use ES6 modules like this:
import isEven from "is-even";
console.log(isEven(6));
I then get this error in the console:
TypeError: Module specifier, 'is-even' does not start with "/", "./", or "../".
My fix at the moment is to run a module bundler like Webpack which allows me to use both of these syntax's effectively and I have no problems. However I would rather find an alternative because I find it harder to fix bugs when using a module bundler as it can be quite difficult to locate the code causing the error. Is there an alternative other than using a module bundler to transform to older Javascript modules before served to the browser ?
import in a browser does not allow importing from a plain filename. You must at least construct some kind of path starting with "/", "./", or "../" or even a full URL as import in the browser does not have a "default" place to load a plain filename from. You must be explicit about what the path is.
And, keep in mind that import from a browser will make a request to your server and your server must be configured to provide that file to the browser when requested.
So, depending upon how you have your server configured, you may want something like this:
import isEven from "/is-even.js";
console.log(isEven(6));
Or, if your server handles scripts separately, you might even have this:
import isEven from "/scripts/is-even.js";
console.log(isEven(6));
And, then it is the web server's responsibility to know how to receive one of these requests, know where that actual script file is in the server's file system, get it and send it back to the browser.
Keep in mind that the main reason client-side code uses bundlers is that it is not efficient to have a web page that loads tons of script modules, each loaded separately because each script load is an http round-trip request to your server. Loading can benefit some from client-side caching, but you still don't want your web-site's home page to have to load 50 separate modules the first time a user hits your home page just to get all your scripts loaded into the browser.
So, it would generally not be efficient in client-side code to import a script just for one function like isEven(). This is why bundlers exist so that you can write the code in nice, modular, separately testable modules and then have the bundler go grab all the code that is needed for a particular client-side operation and collect it into one common script file that can then be loaded more efficiently into the client-side environment.
try this
export { isEven }; // module exports a function:
module imports the function from is-even.mjs
import { isEven } from './is-even.mjs';
console.log(is-even(6));

failed to load wasm application

I'm trying to host a website, and I use a .wasm file with .js scripts created by the wasm-pack tool.
I tested the project locally with npm and node.js and everything worked fine.
But Then I hosted it on a raspberry (apache2), and when I try to access it, I get in the following error:
Failed to load module script: The server responded with a non-JavaScript MIME type of "application/wasm". Strict MIME type checking is enforced for module scripts per HTML spec.
details
There are multiple files, but here is the idea:
my index.html loads the module bootstrap.js
// bootstrap.js content
import("./index.js").catch(e => console.error("Error importing `index.js`:", e));
my main code is in the index.js, which calls test_wasm_bg.js
And finally, test_wasm_bg.js loads the wasm file with this line:
// test_wasm_bg.js first line
import * as wasm from './test_wasm_bg.wasm';
Where is the problem?
What is the right way to load a web assembly file?
I finally found what is the right way to load a wasm application in a wasm-bindgen project!
In fact, everything was on this page
When you compile the project without wanting to run it with a bundler, you have to run wasm-pack build with a --target flag.
wasm-pack build --release --target web.
This creates a .js file (pkg/test_wasm.js in my example) with everything you need to load the wasm-application.
And then this is how you use the functions created by wasm-bindgen (index.js):
import init from './pkg/test_wasm.js';
import {ex_function, ex_Struct ...} from '../pkg/test_wasm.js';
function run {
// use the function ex_function1 here
}
init().then(run)
You include your index.js in your HTML file
<script type="module" src="./index.js"></script>
And then it work's !
Edit:
Now that's I understand the javascript ecosystem a bit more, I cab try to explain what I understand:
There are many ways to do imports in js, here is a list :
https://dev.to/iggredible/what-the-heck-are-cjs-amd-umd-and-esm-ikm
You don't need to know much about that, except that the default target of wasm-pack is a node style ecmascript module. This import will work in node.js, but not directly in the browser. So in node, you can import a function from a wasm file directly, like so:
import {ex_function} from "./test.wasm"
But these styles of import don't work in the browser right now. Maybe it will be possible in the future
But for now, your browser only knows about js modules. So if you try to import a .wasm file directly in the browser, it will throw a mime type error because it doesn't know how to deal with webassembly files.
The tool you use to go from ecmascipt modules (with a lot of nmp packages for example) to a single js file is called a web-bundler (webpack, rollup, snowpack ...). If you work on a big project with npm, you probably need one. Otherwise, the "--target web" will say to wasm-bindgen to instantiate the wasm module the right way (look at the pkg/test_wasm.js)

Load a JS dependency from browserify bundle

I have a single page app which comprises of a JS bundle based on Browserify and Coffeescript.
In a certain usecase, I need to create an adhoc page (Detached from the SPA) which needs to access a library (Kendo to be specific), which is part of the browserified bundle and the adhoc page would have some simple JS based on kendo.
The question is how do I load/access the library outside the Single Page Application (If I try loading it, the browser says that the library is not found)?
Using RequireJS could be an option as specified here. But, I dont want to use another library just for this purpose. I think there must be a way to "require" the library without requireJS because it is already working in the Single Page Application.
Please help.. Thanks!
Browserify rewrites your module paths like ../moduleA/file.js into an internal module id like 23 when packing.
Every require statement will also be rewritten, a statement like this:
var moduleA = require('../moduleA/file.js');
Becomes this:
var moduleA = require(23);
To get access to a particular library, you can do to things:
1) find the internal id via debugger and then require the module via it (this is quite fragile, because the internal id could change with every build)
2) package another file into your bundle with the following contents:
var kendo = require('kendo');
window.kendo = kendo;
Afterwards, you can simply access kendo as a page global.

What's the best way to concatenate vendor js files?

In my Angular JS app, I'm using a lot of third party packages, mainly maintained via Bower.
When I use Grunt to concatenate all of them into one mega file, I'm getting errors when I load my page, for example that
Uncaught ReferenceError: angular is not defined and
GET http://localhost:8080/theproj/v4/dist/app/bootstrap.css.map 404 (Not Found)
What is the best way to properly concatenate all these files to ensure that everything loads in the right order and doesn't cause problems?
First issue: A lot of times third party libraries must be loaded in a particular order. That looks like like it's the source of your first issue. Something is trying to use angular and it's getting loaded before the angular code. You should refactor your grunt task to use a pre-defined order for third party libraries.
Second issue: You probably are missing the .map file. This is a file used by Chrome dev tools to show you the original source for the css (sass or less). Either provide the map file, or delete the reference to it from bootstrap.css. Or just ignore the error, it's only an error when you have chrome dev tools open, and doesn't actually affect your application.
For the issue of the correct order for your javascript files, i had that problem in a larger project where noone really had a clue which was the correct order.
Luckily we found that the Google Closure Compiler does exactly this: https://github.com/google/closure-compiler
You give it all your js files and it analyzes them all and concatenates them in order
$ java -jar compiler.jar --js_output_file=out.js in1.js in2.js in3.js ...
There is even a grunt plugin for the connection: https://github.com/gmarty/grunt-closure-compiler
'closure-compiler': {
frontend: {
closurePath: '/src/to/closure-compiler',
js: 'static/src/frontend.js',
jsOutputFile: 'static/js/frontend.min.js',
maxBuffer: 500,
options: {
compilation_level: 'ADVANCED_OPTIMIZATIONS',
language_in: 'ECMASCRIPT5_STRICT'
}
}
},
Another way would be to change your javascripts into AMD or CommonJS modules, this way you don't have to worry about the correct order. RequireJS (http://requirejs.org/docs/start.html) is a possibility for AMD for example or Webpack (http://webpack.github.io/) ...or many many others.

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

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.

Categories