Webpack import module at runtime without knowing which during build - javascript

I am building a jsFiddle-like application and I want the user to be able to enter the URL of a js module that I can then import in the runtime.
So somewhere in the code I do something like import(userModuleUrl).then(...)
I want to use webpack to bundle the app, but now webpack hijacks the original import ES6 function and is not very happy not knowing about the userModuleUrl at compile time.
I can’t use externals because I can’t know ahead of time all the urls the user may import.
I only wish there was a way for webpack to not interpret that line, or to give me a function that’s equivalent to the raw ES import

I ended up doing it this way:
const importFunc = new AsyncFunction('url','return import(url);');
const script = await importFunc(url);
you'll need to allow your app to run eval though... Which is fine for a jsFiddle-like app.

Related

According to whether the package exists in the window or not load the package dynamically

Does any build tool support this loading strategy, such as webpack or rollup.js.
Build every dependency to a single bundle, and when loading these dependencies, firstly search it in window['package'], if exist, use it. Otherwise dynamic load dependencies bundle to use.
Such app dependency is React, ReactDOM and UiLib.
The built result is:
React -> a.js
ReactDOM -> b.js
UiLib -> c.js
my code -> d.js
if window.React exist but window.ReactDOM and window.UiLib does not exist. d.js should dynamically load b.js and c.js and use window.React.
I know I can config React to externals, but this is a microapp used in many different apps, I'm not sure which packages exist in every global.
Nope. It is not possible directly. For a bundler, it is a binary choice between bundle or not to bundle.
Why? When a bundler encounters a library via import statements like - import React from 'react', it needs to know what global object it should substitute whenever it encounters react package across the entire application dependency graph. This must happen at compile-time. Additionally, loading a library with dynamic decision at runtime means you are introducing an asynchronous behavior in your code which your components or application cannot handle readily.
There are two form factors - a library and application. As far as library is considered, this is the only way to teach bundler (either bundle it or leave it via externals).
At an application level, you can write your own code to partially achieve what you seek with help of CDN. For this, you use externals and tell Webpack, for example, that react will be available as global React object on window namespace.
Now before your library is getting consumed, you have to add a dynamic code where to check for presence of React object.
function async initialize() {
if (!window.React) {
const React = await import(/* webpackIgnore: true */ 'https://unpkg.com/react#18/umd/react.development.js')
window.React = React;
initializeMicroapp();
} else {
initializeMicroapp();
return Promise.resolve();
}
}
Your initialize function for microapp is async and returns a promise. This is usually the pattern to go ahead with shell + micro-frontends.
On a side note, you can use module federation approach which is actually meant to solve exactly similar use-case. With module federation, you can teach Webpack that if the host/shell provides a library, then Webpack should simply ignore its bundled copy of that library while serving only other necessary code. However, I advice caution as it is a very specific pattern and neither de-facto nor de-jure at this point. It is recommended when you are having sufficient scale and many independent teams working on same product space.

How to use a capacitor plugin meant to be imported in a vanilla js app

I'm currently pulling hairs trying to figure out how to go about this.
So, I'm working in a vanilla JS environment with no webpack setup served with capacitor and want to use this capacitor-plugin: https://github.com/CodetrixStudio/CapacitorGoogleAuth
However, to use this plugin I have to import the package into my client code.
Here's what I've tried:
Unpkg type="module": however browser support in mobile isn't that great. And this app will be served to a ton of users
Using browserify + esmify to bundle the plugins code into something I could import with a <script> tag into my index.html. Didn't work
My last thought is to setup webpack to bundle everything for me, similar to the browserify approach and import that. However before I go through with all of that I wanted to reach out here to see if you guys had any other ideas.
Is there a way to access this plugin from window maybe?
so I figured out the way to go about this by following this article: https://medium.com/#SmileFX/a-complete-guide-building-a-capacitorjs-application-using-pure-javascript-and-webpack-37d00f11720d
Basically you have a www/js directory (vanilla js), and a src directory (ES6/import code goes). You then configure webpack to output in your www/js/ directory.
Note: Any variable you want accessible to your vanilla js code must be explicitly stored in the window object.
Example
./src/toBeWebpacked.js
import Module from "your-module"
window.doSomething = () => Module.doSomething()
./www/js/vanilla.js
const useModuleCode = () => {
// use code from webpacked ES6 JavaScript here
return window.doSomething();
}

Implement static data assets in NPM module for browser use

I am making a small NPM package which would essentially serve as a convenient data import in a React app. My package currently has one exported method, getSystems(), which returns an array of objects. The module code reads the data file using fs/promises.readFileSync which obviously is not available in a browser environment.
How can I bundle the data in my package so that it can be used in a React app?
Here is what the module is doing:
import { decode } from "#msgpack/msgpack";
import { readFileSync } from "fs";
import path from "path";
const file = readFileSync(path.join(__dirname, "../assets/systems.dat"));
const systems = decode(file);
export const getSystems = () => systems;
This works in node. I suppose what I need is a way to configure the build in such a way that the data is included in the output JS files.
Full code here.
Disclaimer'ish: I understand this is usually done via an API instead of bloating the application code with data (which is what I'm doing here, I suppose). This is kind of a learning and testing thing. And also I got sick of copying data files around in Dockerfiles.. :)
Turns out if I use JSON files as source and just import them, the module works in browsers. While the webpack bundle gets pretty big, gzip appears to do a great job compressing the embedded data, so I guess this is the way to go.

Typescript modules in client side - What is the best way?

I am building a web application.
I use typescript for the client side and golang for the server side.
I use typescript 3.9.2.
I want to compile the .ts files to .js, and use modules.
The default compiling compile it to CommonJS module loader.
Then in the browser it has some exports.__... in the second line.
I searched and it basicly said it want a variable called exports, which I don't have. In this case, I don't like the main solution to define a mock exports in other script tag.
I changed the compilerOption to "module":"ES6"
and then it compile and loaded well (after changing the type of the script to module), but the browser can't find the module i want to import.
The code goes like this:
use.ts
import * as fun from 'funcs'
let bar = fun.foo()
funcs.ts
export function foo() :boolean{
return true;
}
use.js
import * as fun from 'funcs'
var bar = fun.foo()
funcs.js
export function foo(){
return true;
}
And now the browser can't find /funcs, which it needs for the use.js. When i change manualy the first line in use.js to
import * as fun from 'funcs.js'
It works.
What can i do to make everything automatic?
What is the best practice here?
ES Module imports need to have the .js extension, but Typescript's modules don't and right now there does not appear to be a built-in option to add them automatically, but somebody did write a script to add the extensions to the imports, or you can add them to the imports manually.
There is some discussion on the TypeScript Github about this and related issues. They are considering adding a module resolution option to the compiler for the browser.

How do you get webpack to *actually* ignore an external and rely on the browser to import?

I'm trying to get webpack to ignore an import, so that its imported by the browser using a native ES6 import statement, not webpack. I'm trying to get ffmpeg.js to import directly as it crashes webpack when it tries to bundle it, as the files are too large.
Following the answer here (How to exclude a module from webpack, and instead import it using es6), I have my code in the local tree as /ffmpeg/ffmpeg-mpeg.js and verified my dev server can access as http://localhost:8080/ffmpeg/ffmpeg-webm.js
I then import via:
import ffmpeg from '/ffmpeg/ffmpeg-webm.js';
And add that to the externals section of my webpack config:
externals: {
'/ffmpeg/ffmpeg-webm.js': 'ffmpeg',
},
The result is an link that looks like this
webpack:///external "ffmpeg"
containing:
module.exports = ffmpeg;
Which then fails with "Uncaught Error: Cannot find module ?" (In fact that error is hardcoded in the generated file)
So that seems to assume there is a global ffmpeg option and then maps that module to that, but instead I want it leave the line completely untouched by webpack and leave it to the browser.
Whats the correct way to do that? The exclude rule thats downvoted on that page doesn't work either.
Edit:
You can use this:
import(/* webpackIgnore: true */'/ffmpeg/ffmpeg-webm.js').then(({default: ffmpeg}) => {
//Do what you want to do with ffmpeg
});
Which will prevent webpack from compiling the import (so it will be a regular ES6 import)
Original answer:
You forgot to include the external script in your page.
Also since you pointed out that your file is very big, I'd recommend to include it defered
So you need to add
<script src="/ffmpeg/ffmpeg-webm.js" defer></script>
To the head of your app and you would then import it slightly differently using the import function with a callback
import('/ffmpeg/ffmpeg-webm.js').then(ffmpeg => {
//Do what you want to do with ffmpeg
});
Small note: the externals key does not need to be the path of your file, it's just the name you will use when importing, so rename it if you are getting confused with the path
module.export = {
//...
externals: {
"ffmpeg-webm": "ffmpeg"
}
}
//Then import
import('ffmpeg-webm').then(ffmpeg => {
//Do what you want to do with ffmpeg
});
Alternatively for node js, instead of using externals you could use
const ffmpeg = __non_webpack_require__('/ffmpeg/ffmpeg-webm.js')
Just keep in mind that this will transform it as a normal require that only works with node js

Categories