I have a simple application written in vanilla javascript and using Module Federation to wrap things up. So far, I've separated the javascript and the styling into two separate "apps":
├ src/
│ ├ lib/
│ │ └ myApp.js
│ ├ scss/
│ │ └ styles.scss
│ ├ index.js
│ └ styles.js
└ webpack.config.js
The index.js imports myApp.js that has all the logic and styles.js simply imports a SASS-file with all necessary styling like this:
import './scss/signing-widget.scss';
The ModuleFederationPlugin in webpack.config.js is setup as follows:
module.exports = {
entry: {
index: ['./src/index.js'],
styles: ['./src/styles.js'],
},
...
plugins: [
new ModuleFederationPlugin({
name: 'myApp',
filename: 'remoteEntry.js',
exposes: [
'./myApp': './src/index.js'
'./myAppStyles': './src/styles.js'
],
shared: [
require('./package.json').dependencies
],
})
],
...
And to implement and use myApp you need to do the following:
<html>
<head>
...
<script defer src="http://path-to-my-app/index.js"></script>
<script defer src="http://path-to-my-app/styles.css"></script>
<script defer src="http://path-to-my-app/remoteEntry.js"></script>
</head>
<body>
<my-app></my-app>
</body>
</html>
But, I only want to implement the app by only importing the remoteEntry.js like this:
<html>
<head>
...
<script defer src="http://path-to-my-app/remoteEntry.js"></script>
</head>
<body>
<my-app></my-app>
</body>
</html>
But I can't figure out how to do it and I've done a lot of research but I haven't found any example nor documentation on how to achieve this with ModuleFederationPlugin. Can someone help me on this matter?
Thanks in advance, Clydefrog
I ended up making my own "mounting" script. I'll share my solution to anyone who find this interesting or experiencing the same problem.
├ src/
│ ├ lib/
│ │ └ myApp.js
│ ├ scss/
│ │ └ styles.scss
│ ├ index.js
│ ├ mount.js <------ NEW MOUNT
│ └ styles.js
└ webpack.config.js
Instead of manipulating or change the rmeoteEntry.js to auto import modules, I created a simple javascript file (mount.js) that detects its own script-tag. Then it extracts the base URL and then iterates through each file that needs to get imported with the same base URL:
mount.js
(() => {
const parseUrlString = (url) => !url || url.length <= 0 ? '' : new URL(url).origin;
var startsWith = '^',
contains = '*',
endsWith = '$',
scriptElement = document.querySelector(`script[src${endsWith}="widgetMount.js"]`),
url = parseUrlString(scriptElement?.getAttribute('src')),
head = document.getElementsByTagName('head')[0];
[
'styles.css',
'index.js',
'remoteEntry.js',
].forEach((filename) => {
var newElement;
switch (filename.split('.')[1]) {
case 'css':
newElement = document.createElement('link');
newElement.setAttribute('rel', 'stylesheet');
newElement.href = `${url}/${filename}`;
break;
case 'js':
newElement = document.createElement('script');
newElement.setAttribute('defer', '');
// newElement.setAttribute('async', '');
newElement.type = 'module';
newElement.src = `${url}/${filename}`;
break;
}
head.appendChild(newElement);
});
})();
Adding it to webpack.config.js in my remote application:
module.exports = {
entry: {
...,
widgetMount: ['./src/mount.js'], // widget mount
...
},
...
plugins: [
new ModuleFederationPlugin({
...,
exposes: [
...,
'./myMount': './src/mount.js'
...
],
...,
})
],
...
}
Last but not least, adding the mount into my hosting application:
<html>
<head>
...
<script defer src="http://path-to-my-app/widgetMount.js"></script>
</head>
<body>
<my-app></my-app>
</body>
</html>
And Voilà:
<html>
<head>
...
<script defer src="http://path-to-my-app/widgetMount.js"></script>
<link rel="stylesheet" href="http://path-to-my-app/styles.css">
<script defer src="http://path-to-my-app/index.js" type="module"></script>
<script defer src="http://path-to-my-app/remoteEntry.js" type="module"></script>
</head>
<body>
<my-app></my-app>
</body>
</html>
Related
I'm building a website with subdomains. Each subdomain is mapped to a file-based page using middleware. Below is an example of how I'm mapping subdomains to pages:
app.com maps to /home
app.com/pricing maps to /home/pricing
subdomain1.app.com/dashboard maps to /_subdomains/subdomain1/dashboard
subdomain2.app.com/dashboard/home maps to /_subdomains/subdomain2/dashboard/home
app.com, subdomain1.app.com and subdomain1.app.com/dashboard/ and everything else are working fine, but when I try to access subdomain1.app.com/dashboard/home I'm getting 404 Not Found.
Here's my folder structure:
pages/
├── _subdomains/
│ └── [subdomain]/
│ ├── dashboard/
│ │ ├── home.tsx
│ │ └── index.tsx
│ └── index.tsx
├── home/
│ └── ...
└── _app.tsx
import { NextRequest, NextResponse } from 'next/server';
export const config = {
// I have a feeling this isn't matching /_subdomains/subdomain/dashboard/home
matcher: ['/', '/([^/.]*)', '/auth/([^/.]*)', '/_subdomains/:path*'],
};
export default async function middleware(req: NextRequest) {
const url = req.nextUrl;
const hostname = req.headers.get('host') || process.env.ROOT_DOMAIN;
if (!hostname) {
throw Error('Middleware -> No hostname');
}
const currentHost = hostname.replace(`.${process.env.ROOT_DOMAIN}`, '');
if (currentHost === process.env.ROOT_DOMAIN) {
url.pathname = `/home${url.pathname}`;
} else {
console.log(`/_subdomains/${currentHost}${url.pathname}`)
url.pathname = `/_subdomains/${currentHost}${url.pathname}`;
}
return NextResponse.rewrite(url);
}
I'm fairly certain it's the matcher that isn't working but I don't know why.
Matching /_subdomains/:path* should match /_subdomains/a/b/c according to the docs but it isn't working in this case. Or it could be another issue I'm not sure
Fixed by changing the matcher to
matcher: [
/*
* Match all paths except for:
* 1. /api routes
* 2. /_next (Next.js internals)
* 3. /fonts (inside /public)
* 4. /examples (inside /public)
* 5. all root files inside /public (e.g. /favicon.ico)
*/
"/((?!api|_next|fonts|examples|[\\w-]+\\.\\w+).*)",
],
Thanks to this
I'm trying to move from babel to swc for compiling and bundling a react component library but I have trouble with the configuration.
When I run npm run spack, I get the following error:
thread '<unnamed>' panicked at 'internal error: entered unreachable code: module item found but is_es6 is false: ExportNamed(NamedExport { span: Span { lo: BytePos(954874), hi: BytePos(954914), ctxt: #0 }, specifiers: [Named(ExportNamedSpecifier { span: Span { lo: BytePos(954883), hi: BytePos(954890), ctxt: #0 }, orig: Ident(Ident { span: Span { lo: BytePos(954883), hi: BytePos(954890), ctxt: #4141 }, sym: Atom('default' type=static), optional: false }), exported: Some(Ident(Ident { span: Span { lo: BytePos(954883), hi: BytePos(954890), ctxt: #194 }, sym: Atom('default' type=static), optional: false })), is_type_only: false })], src: Some(Str { span: Span { lo: BytePos(954898), hi: BytePos(954913), ctxt: #0 }, value: Atom('./FormControl' type=dynamic), raw: Some(Atom(''./FormControl'' type=dynamic)) }), type_only: false, asserts: None })', crates/swc_bundler/src/bundler/chunk/cjs.rs:142:29
What I get from this error is that he fails to bundle React components. I can't find the is_es6 configuration mentioned in the error so I'm not sure how to solve this. I tried rereading the doc of swc without any success. The module part of the config doesn't seem to solve my problem.
Here is my working tree:
.
├── jest.config.ts
├── package-lock.json
├── package.json
├── spack.config.js
└── src
├── components
│ ├── FiltersBar
│ │ ├── FiltersBar.test.tsx
│ │ ├── FiltersBar.tsx
│ │ ├── __snapshots__
│ │ │ └── FiltersBar.test.tsx.snap
│ │ └── index.ts
│ └── index.ts
├── index.ts
└── libraries
├── helpers
│ ├── helpers.test.ts
│ ├── helpers.ts
│ └── index.ts
└── index.ts
Here is my .swcrc file:
{
"jsc": {
"target": "es2021",
"parser": {
"syntax": "typescript"
}
},
"module": {
"type": "commonjs"
}
}
I'm pretty new to all this stuff so please bear with me :)
My gulp file pretty much looks like this (simplified)
function copyAssets () {
return src(paths.assets, { base: './' })
.pipe(cache('asset-files'))
.pipe(dest(paths.build))
}
// Some more task functions
const build = parallel(copyAssets, brewCoffee, buildTypescript)
module.exports = { build, watch: series(build, watchFiles) }
But now when I list it it looks like:
[15:29:30] Tasks for .....
[15:29:30] ├─┬ <parallel>
[15:29:30] │ └─┬ <parallel>
[15:29:30] │ ├── copyAssets
[15:29:30] │ ├── brewCoffee
[15:29:30] │ └── buildTypescript
[15:29:30] └─┬ <series>
[15:29:30] └─┬ <series>
[15:29:30] ├─┬ <parallel>
[15:29:30] │ ├── copyAssets
[15:29:30] │ ├── brewCoffee
[15:29:30] │ └── buildTypescript
[15:29:30] └── watchFiles
And running things like gulp build will say that the task does not exist.
If I wrap the series / paralel function in an anonymised function I do see it appear in the list (like so)
const build = () => parallel(copyAssets, brewCoffee, buildTypescript)
module.exports = { build, watch: () => series(build, watchFiles) }
But then when running gulp build I'm getting
Did you forget to signal async completion?
as an error.
I know at some point this did work (a number of months ago). But now for some reason it doesn't anymore. If I run gulp --version the output is:
CLI version: 2.2.0
Local version: 4.0.0
edit: Hmmm. This seems to work
module.exports = { build: () => build(), watch: () => series(build, watchFiles)() }
But I doubt that's how I should actually do this stuff....
edit2: No, still broken, it runs now, and waits until completion. And then says the same error as before: The following tasks did not complete: build
When I have used parallel (very briefly) in the past I had to reference it from gulp.
E.g. Change:
const build = () => parallel(copyAssets, brewCoffee, buildTypescript);
to:
const build = () => gulp.parallel(copyAssets, brewCoffee, buildTypescript);
The same with series here:
module.exports = { build, watch: gulp.series(build, watchFiles) }
Try this:
var srcPaths = {
app: [
'wwwroot/app/**/*.ts'
],
js: [
'node_modules/core-js/client/shim.min.js',
'node_modules/zone.js/dist/zone.js',
'node_modules/reflect-metadata/Reflect.js',
'node_modules/systemjs/dist/system.src.js',
'node_modules/typescript/lib/typescript.js',
'node_modules/ng2-bootstrap/bundles/ng2-bootstrap.min.js',
'node_modules/moment/moment.js'
],
}
gulp.task('watch', function () {
gulp.watch([srcPaths.app, srcPaths.js], gulp.series('js'));
});
It will work fine. Enjoy!
Thanks for your attention!
I have built a javascript file from my source code using webpack, I was mean to use it in brower, but it seems not working for me, so I post this for help
here is my project tree
project
├── dist
│ └── my-dist.js
├── index.js
├── lib
│ └── my-source.js
└── webpack.config.js
here is my-source.js
'use strict'
const somepackage = require("somepackage")
module.exports = MyPkg
function MyPkg(param) {
this.myprop = param
}
MyPkg.prototype.Afunc = function () {
consolg.log("hello from A ", this.myprop)
}
MyPkg.prototype.Bfunc = function (param) {
// B do some thing
}
here is my index.js
exports = module.exports = require('./lib/MyPkg');
here is my webpack.config.js
const path = require('path')
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-dist.js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['babel-preset-env']
}
}
}]
}
}
then i run "webpack" to build the file, it seems ok, and the "my-dist.js" file was created, so i try to use it like this:
<html>
<head>
</head>
<body>
<div>
<button onclick="func()">click here</button>
</div>
</body>
<script src="dist/my-dist.js"></script>
<script>
var pkg = new MyPkg('haha')
function func() {
pkg.Afunc()
}
</script>
</html>
but it throw error like "Uncaught ReferenceError: MyPkg is not defined", and i really don't know how to fix it, please give my a hand, thank you guys~
Edit x2:
Here's a GitHub Repo with further explanations:
https://github.com/superjose/webpack-simple-example
If I recall correctly, I had this issue in the past. Webpack the JavaScript from your entry file so that no other file or code has direct access to it.
Therefore, in your index.html this will not work
<script>
var pkg = new MyPkg('haha')
function func() {
pkg.Afunc()
}
</script>
What you need to do is to use EventListeners so you can target the elements.
So you have this button: (Add a class or an id to better identify it)
<button onclick="func()" id="js-pkg">click here</button>
Then, inside index.js:
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('js-pkg').addEventListener(MyPkg);
// This also works:
// document.querySelector('#js-pkg').addEventListener(MyPkg);
});
Note:
We add the 'DOMContentLoaded' event so we wait for the DOM to be loaded before making any operations. Not doing so, it may result in the button to be not defined, since it may not be parsed or rendered by the browser's engine
Edit: More detailed approach below
Suppose you have the following structure:
----index.html
----index.js
----Email/
---------email.js
----Validator/
---------validator.js
Whereas index.js is your main entry file (Where Webpack loads the JavaScript).
Email.js contents:
// Ficticious/non-existent npm package
import email from 'send-email'
// Custom and ficticious email class that will send emails
export class Email {
sendEmail(subject, message) {
// You would send the email somehow.
email.send('contact#support.com' this.subject, message);
}
}
Validator.js contents:
// Checks if the value is undefined or null
module.exports = function (value) {
return !!value
}
In your index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<input type="name" id="js-name" placeholder="message" />
<textarea id="js-msg" placeholder="Write us a message"></textarea>
<button id="js-send">Send us a message!!</button>
<!-- Awful Real-life example -->
<button id="js-validate">Validate Text!</button>
<script src="index.js"></script>
</body>
</html>
In index.js (Webpack's main file):
// We import the email class that we want to use
import { Email } from './Email/email.js'
// We also add validator
import validator from './Validator/validator.js'
/**
* Now the actual magic. Webpack scopes the variable and function names, changing
* the normal values, in order (I could be wrong with this) to avoid collisions with other functions, and variables.
*
* Nonetheless, JavaScript allow us to programatically attach those functions to the element via event listeners.
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
* This means that <input type="button" onclick="func()" /> the onclick() event is the same
* as element.addEventListener('click', func); Where element is the <input type="button" found via a document.getElementById/querySelector/querySelectorAll/getElementsByTagName/etc
* Therefore, even if Webpack scopes the variable and function names, we would be able to attach it since it resides inside Webpack's code.
* The first thing we do is to add a "DOMContentLoaded" event. This is similar to jQuery's $(document).ready(function() { }); We need for the HTML to be loaded in order for us to add the event listener. You didn't have that problem in the past because you would add it directly to the HTML.
**/
document.addEventListener('DOMContentLoaded', () => {
let sendEmail = document.getElementById('js-send');
let name = document.getElementById('js-name');
let email = new Email();
let validateBtn = document.getElementById('js-validate');
// We pass the functions without parenthesis.
// Note that validator is a function, while Email is a class.
// Email needs to be instantiated first, and then we assign
// the method that it calls.
validateBtn.addEventListener('click', validator);
sendEmail.addEventListener('click', email.sendEmail);
});
ok, just keep this in record.
I figure out a special way, here it's looks like:
the project tree is now looks like:
project
├── dist
│ └── my-dist.js
├── src
│ └── my-entry-build.js
├── index.js
├── lib
│ └── my-source.js
└── webpack.config.js
and I updated the "webpack.config.js" and the "my-entry-build.js", the others keep still
the new "webpack.config.js" is like:
const path = require('path')
module.exports = {
entry: './src/my-entry-build.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-dist.js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['babel-preset-env']
}
}
}]
}
}
the new "my-entry-build.js" is like:
const MyPkg = require('../index.js')
window.MyPkg = MyPkg
and everything done!
I started learning webpack and I have this small project
I'll be simplifying the content for brevity, hopefully it wont affect your understanding of the problem.
.
└── project
├── dist
│ ├── fonts
│ └── js
├── src
│ ├── app
│ │ ├── app.js
│ │ └── component
│ │ ├── component.css
│ │ ├── component.directive.js
│ │ └── component.html
│ └── fontello
├── index.html
├── package.json
└── webpack.config.js
I'm trying to keep everything inside my component encapsulated so I have:
component.directive.js
require('../../fontello/css/fontello.css');
require('./component.css');
var angular = require('angular');
...
module.exports = angular.module('directives.component', [])
.directive('component', component)
.name;
app.js
var angular = require('angular');
var component = require('./component/component.directive.js');
...
angular.module('app', [component])
and webpack.config.js
var webpack = require('webpack');
module.exports = {
context: __dirname + '/src',
entry: {
app: './app/app.js',
vendor: ['angular']
},
output: {
path: __dirname + '/dist',
filename: 'js/app.bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin("vendor", "js/vendor.bundle.js")
],
module: {
loaders: [{
test: /\.css$/,
loader: 'style-loader!css-loader',
exclude: ['./src/fontello']
}, {
test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)(\?[a-z0-9=&.]+)?$/,
loader: 'file-loader?name=fonts/[name].[ext]'
}, {
test: /\.html$/,
loader: 'raw'
}]
}
};
and I get for woff2/woff/ttf
404 Not Found. GET http://localhost:8080/fonts/fontello.woff2
what am I doing wrong?