Having Vue Components as entry point instead of main.js - vue.js

I'm working with a Java backend and Jersey and want to have the possibility to have different small page app.
My idea was to have a frontend module in which I would have a folder /apps.
The folder apps would then contain multiple vue components (that will be the main apps).
Another folder /component contains the different components that will be used in the different apps.
The idea is to have a webpack that would create one js file per vue app !
I know that Webpack is not specially designed to have multiple entrypoint / multiple outputs but does anyone have any idea how I could have multiple entry points being the different apps-file.vue and having multiple .js files as output ?

I had a similar problem and this answer pointed me in the right direction: I followed the Vue CLI docs to add a pages configuration option in a vue.config.js file.
After some experimentation, I got to a solution I was happy with. I saved it in repo that describes step-by-step how to create a multi-page Vue app from scratch.
https://github.com/chriscalo/vue-multipage
Some of the main things I was looking for:
Uses the Vue CLI, which means you can avoid most webpack
configuration headaches.
Doesn't force you to create an entry point .js file for each app /
page.
With this approach, you just place a bunch of .vue files in the src/pages directory and it generates a separate Vue app for each one. (You can pretty easily change that folder name from pages to apps if you would like.)
Autogenerate pages config and entry points
The crux of the solution is a script that creates a src/entry/bar/index.js entry point file for every src/pages/bar.vue file found and also generates a src/entry/pages.config.js file that you can import into your vue.config.js file like so:
const pagesConfig = require("./src/entry/pages.config.js");
module.exports = {
pages: pagesConfig,
};
Here's the script:
const path = require("path");
const glob = require("fast-glob");
const fse = require("fs-extra");
const R = require("ramda");
const { stripIndent } = require("common-tags");
const pathGlob = processRelativePath("../src/pages/**/*.vue");
const vuePagesPromise = glob(pathGlob);
console.log(`Generating entry points`);
// Step 1: compute specifications for work to be done
const pagesConfigPromise = vuePagesPromise.then(pages => {
return pages.map(page => {
const { dir, name } = path.parse(page);
const entryRoot = path.relative("src/pages", dir);
const entryName = (
split(entryRoot, path.sep)
).concat(
ensureEndsWith([name], "index")
).join(path.sep);
const entryFilePath = path.join(
processRelativePath("../src/entry"), `${entryName}.js`
);
const importPath = path.relative("src", page);
const entryFileContent = entryPointContent(importPath);
return {
source: page,
entryName,
entryFilePath,
entryFileContent,
};
});
});
// Step 2: clear entry folder
const entryFolderPath = processRelativePath("../src/entry");
fse.removeSync(entryFolderPath);
console.log(`Cleared ${entryFolderPath}`);
// Step 3: create a corresponding entry point file for each page
pagesConfigPromise.then(config => {
config.forEach(page => {
fse.outputFileSync(page.entryFilePath, page.entryFileContent);
console.log(`Created ${page.entryFilePath}`);
});
});
// Step 4: create a pages.config.js
// module.exports = {
// "index": 'src/pages/index.js',
// "login/index": "src/pages/login.js",
// "profile/index": "src/pages/profile/index.js",
// "foo/index": 'src/pages/foo.js',
// "bar/index": 'src/pages/bar/index.js',
// };
const pagesConfigPath = processRelativePath("../src/entry/pages.config.js");
pagesConfigPromise
.then(config => {
// transforms each into something like:
// { "login/index": "src/pages/login.js" }
return config.map(page => ({
[page.entryName]: page.entryFilePath,
}));
})
.then(R.mergeAll)
.then(pageConfigContent)
.then(content => fse.outputFileSync(pagesConfigPath, content))
.then(() => console.log(`Created ${pagesConfigPath}`));
function pageConfigContent(config) {
return stripIndent`
module.exports = ${JSON.stringify(config, null, 2)};
`;
}
function processRelativePath(p) {
const pathToThisDir = path.relative(process.cwd(), __dirname);
return path.join(pathToThisDir, p);
}
// fixes split() behavior for empty string ("")
function split(string, separator) {
if (string.length === 0) {
return [];
} else {
return string.split(separator);
}
}
function ensureEndsWith(array, item) {
if (array.slice(-1)[0] === item) {
return array;
} else {
return array.concat([item]);
}
}
function entryPointContent(importPath) {
return stripIndent`
import Vue from "vue";
import page from "#/${importPath}";
new Vue({
render: h => h(page),
}).$mount('#app');
`;
}

Related

How to dynamically access a remote component in vue js with module federation

I am trying to build a vue js 2 microfrontend with module federation. I dont want to use static remote imports via the webpack.config.js like this
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1#http://localhost:3001/remoteEntry.js',
},
}),
],
};
I am looking for a way to dynamically import vue components into my host application. I tried this approach so far, but i only found examples that worked with angular or react.
The goal is to have multiple remote frontends that can automatically register somewhere, maybe in some kind of store. The host application then can access this store and get all of the registered remote applications (name, url, components). The host application then loads the components and should be able to use them. I remote import the component HelloDerp, the loading process is working fine but i dont know how to render it on my host application. I read the vue js doc about dynamic and async imports but i think that only works for local components.
What i've got so far in the host application:
<template>
<div id="app">
<HelloWorld />
<HelloDerp />
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
const HelloDerp = null;
export default {
name: "App",
components: {
HelloWorld,
HelloDerp,
},
mounted() {
var remoteUrlWithVersion = "http://localhost:9000/remoteEntry.js";
const element = document.createElement("script");
element.type = "text/javascript";
element.async = true;
element.src = remoteUrlWithVersion;
element.onload = () => {
console.log(`Dynamic Script Loaded: ${element.src}`);
HelloDerp = loadComponent("core", "./HelloDerp");
};
document.head.appendChild(element);
return null;
},
};
async function loadComponent(scope, module) {
// Initializes the shared scope. Fills it with known provided modules from this build and all remotes
await __webpack_init_sharing__("default");
const container = window[scope]; // or get the container somewhere else
// Initialize the container, it may provide shared modules
await container.init(__webpack_share_scopes__.default);
const factory = await window[scope].get(module);
const Module = factory();
return Module;
}
</script>
Sorry i almost forgot about this. Here's my solution.
Load Modules:
export default async function loadModules(
host: string,
ownModuleName: string,
wantedNames: string[]
): Promise<RemoteComponent[]> {
...
uiApplications.forEach((uiApplication) => {
const remoteURL = `${uiApplication.protocol}://${uiApplication.host}:${uiApplication.port}/${uiApplication.moduleName}/${uiApplication.fileName}`;
const { componentNames } = uiApplication;
const { moduleName } = uiApplication;
const element = document.createElement('script');
element.type = 'text/javascript';
element.async = true;
element.src = remoteURL;
element.onload = () => {
componentNames?.forEach((componentName) => {
const component = loadModule(moduleName, `./${componentName}`);
component.then((result) => {
if (componentName.toLowerCase().endsWith('view')) {
// share views
components.push(new RemoteComponent(result.default, componentName));
} else {
// share business logic
components.push(new RemoteComponent(result, componentName));
}
});
});
};
document.head.appendChild(element);
});
});
...
}
export default async function loadModule(scope: string, module: string): Promise<any> {
await __webpack_init_sharing__('default');
const container = window[scope]; // or get the container somewhere else
await container.init(__webpack_share_scopes__.default);
const factory = await window[scope].get(module);
const Module = factory();
return Module;
}
Add Modules to routes
router.addRoute({
name: remoteComponent.componentName,
path: `/${remoteComponent.componentName}`,
component: remoteComponent.component,
});

how to use rollup to parse cucumber feature files and backing step definition files

I have the following rollup plugin that will include .feature files
export const cucumberRollupPlugin: PluginImpl<CucumberOptions> = pluginOptions => {
let options: CucumberOptions = {
...{
include: '**/*.feature',
cwd: process.cwd(),
},
...pluginOptions,
};
let filter = createFilter(options);
let plugin: Plugin = {
name: 'bigtest-cucumber',
async transform(code, id) {
if (!filter(id)) {
return;
}
let parser = new GherkinParser({ code, uri: id, rootDir: options.cwd });
let result = await parser.parse();
let esm = dataToEsm(result, { namedExports: false });
// TODO: add sourcemap support
let transformResult: TransformResult = { code: esm };
return transformResult;
},
};
return plugin;
};
The problem I have is that to make the feature files work, there are step definition files that actually contain the functionality. So a feature file might look like this
Feature: Visit career guide page in career.guru99.com
Scenario: Visit career.guru99.com
Given: I browse to career.guru99.com
And a step definition file might look like this:
import { Given, When, Then from 'cucumber';
import assert from 'assert';
import{ driver } from '../support/web_driver';
Given(/^browse to web site "([^"]*)"$/, async function(url) {
return driver.get(url);
});
The problem I have with rollup is that there are no import statements for either the step definition files. The way cucumber-js works is that these files are found at runtime.
I think I need to generate an index.js that looks like this to cover the step definitions.
import from './step_definition_1';
import from './step_definition_2';
import from './step_definition_3';
Where would this fit in the rollup pipeline to generate this file so it can get pulled into the rollup pipeline.
You can use this.emitFile to manually process the .feature files and include them in the output. Call this.emitFile for each .feature file in the buildStart hook (each emitted file will get processed through the transform hook you wrote).
Here's an example that uses the globby package (which expands a glob to an array of file paths) to get the file path of each .feature file to pass to this.emitFile:
import globby from 'globby'
export const cucumberRollupPlugin: PluginImpl<CucumberOptions> = pluginOptions => {
let options: CucumberOptions = {
include: '**/*.feature',
cwd: process.cwd(),
...pluginOptions,
};
let filter = createFilter(options);
let plugin: Plugin = {
name: 'bigtest-cucumber',
async buildStart({ include, cwd }) {
const featureFilePaths = await globby(include, { cwd });
for (const featureFilePath of featureFilePaths) {
this.emitFile({
type: 'chunk',
id: featureFilePath
});
}
},
async transform(code, id) {
if (!filter(id)) {
return;
}
let parser = new GherkinParser({ code, uri: id, rootDir: options.cwd });
let result = await parser.parse();
let esm = dataToEsm(result, { namedExports: false });
// TODO: add sourcemap support
let transformResult: TransformResult = { code: esm };
return transformResult;
},
};
return plugin;
};
Let me know if you have any questions!
You need to use the this.emitFile method and befor that lookup the files via glob or anything else inside your plugin
{
plugins: [typescript(),{
name: "emit-additional-files",
async transform(code,id) {
//id === fileName
//code === fileContent
// inspect code or id here then use path.resolve() and fs.readdir to find additional files
this.emitFile()
}
}]
}

using mock data while on development with vue + vuex

I'm working on a Vue app which also uses vuex.
Everything is setup ad working correctly as expected but i'd like to improve it so that I can work on it without actually calling the API endpoints (mainly to avoid rate limit).
I created a mock folder and placed some file in there.
How do I manage to use those mock in development, and the real api endpoint on the build on production withouth making a mess in my code ?
I created a repo with as less as possible.
It includes vue + vuex, a single smart component in charge of reading from the store, and a dumb component do display it.
In poor words, I'm looking for a way to do change this:
const actions = {
async fetchTodos({ commit }) {
let response;
if (process.env.NODE_ENV === "development") {
response = { data: todos };
} else {
response = await axios.get("https://jsonplaceholder.typicode.com/todos");
}
commit("setTodos", response.data);
}
};
with something which would be easier to maintain and wouldn't increase the bundle size.
I thought about mocking the whole action object, which seemed to be ok, but how do i avoid to bundle my mock files at that point?
How do you manage your front end environment to avoid this kind of problem?
What I did is encapsulate the whole API in another class/object. That single point of entry then switches between the mock and real api:
// store.js
const api = require('./api');
const actions = {
async fetchTodos({ commit }) {
// you can use api.getTodos() instead or another naming convention
const response = await api.get('todos');
commit("setTodos", response.data);
},
};
// api.js
const realapi = require('./realapi');
const mockapi = require('./mockapi');
module.exports = process.env.NODE_ENV === 'production' ? realapi : mockapi;
// mockapi/index.js
const todos = loadTodos();
module.exports = {
async get(path) {
switch (path) {
case 'todos':
return { data: todos };
// etc.
}
}
};
// realapi/index.js
const root = 'https://jsonplaceholder.typicode.com/';
module.exports = {
get(path) {
return axios.get(root + path);
}
};
Builders like Webpack can optimize the build and remove the whole mock api part in production builds based on the environment.

How do I seperate ipcMain.on() functions in different file from main.js

I'm creating an electron app with vuejs as frontend. How can I create all the ipcMain.on() functions in a separate file from the main.js. I want this for a more clean code structure.
The app has to work offline so I need to store the data in a local database. So when I create an object in the frontend, I send it with ipcMain to the electron side. Electron can then write it to the local database.
I want something like this:
main.js:
import { app, protocol, BrowserWindow } from "electron";
import {
createProtocol,
installVueDevtools
} from "vue-cli-plugin-electron-builder/lib";
require("./ipcListeners.js");
ipcListeners.js:
import { ipcMain } from "electron";
ipcMain.on("asynchronous-message", (event, arg) => {
console.log(arg);
event.reply("asynchronous-reply", "pong");
});
ipcMain.on("random-message", (event, arg) => {
console.log(arg);
event.reply("random-reply", "random");
});
The problem here is that only the first ipcMain.on() functions works but the second,... doesn't
What I did in my project is, I arranged all the IPCs in different folders according to their categories and in every file, I exported all the IPCs like the example below
products.js
module.exports = {
products: global.share.ipcMain.on('get-products', (event, key) => {
getProducts()
.then(products => {
event.reply('get-products-response', products)
})
.catch(err => {
console.log(err)
})
})
}
then I created a new file which imported all the exported IPCs
index.js
const {products} = require('./api/guestIpc/products')
module.exports = {
products
}
then finally I imported this file into the main.js file.
main.js
const { app, BrowserWindow, ipcMain } = require('electron')
global.share = {ipcMain} #this is only for limiting the number of ipcMain calls in all the IPC files
require('./ipc/index') #require the exported index.js file
That's it, now all the external IPCs are working as if they were inside the main.js file
i don't know this going to help any way i am going to post what i did. now your implementation worked for me but still i had problem if i am requiring 100 files now in all those requires i have to import ipcMain repeatedly so that's going to be a performances issue so what i did is created global object by inserting electon and IpcMain then it's worked perfectly
my Main.js file
const { app, BrowserWindow } = require('electron')
const electron = require('electron');
const { ipcMain } = require('electron')
global.share= {electron,ipcMain};
function createWindow () {
// Create the browser window.
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
// and load the index.html of the app.
win.loadFile('./views/index.html')
// Open the DevTools.
win.webContents.openDevTools()
}
app.whenReady().then(createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
require('./test.js');
test.js
global.share.ipcMain.on('synchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.returnValue = 'pong'
})
this is my html call
const { ipcRenderer } = require('electron')
document.querySelector('.btn').addEventListener('click', () => {
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"
})
Now this one worked me perfectly so no more messy and long main.js resources

express-handlebars helper doesnt display return value

I try to register my own helper methods to express-handlebars to use it in views and partials. My goal is to create a for a search function in my navigation partial. But I dont even get my helper working on my index view.
I tried a lot of thinks..
App.js
const exphbs = require('express-handlebars')
const helpers = require('./public/helper/hbs-helper');
const express = require('express')
const app = express()
const hbs = exphbs.create({
layoutsDir: path.join(__dirname, 'views/layouts'),
partialsDir: path.join(__dirname, 'views/partials'),
helpers: helpers
})
app.enable('trust proxy')
app.engine('handlebars', hbs.engine)
app.engine('.hbs', exphbs({
extname: '.hbs'
}))
app.set('view engine', 'handlebars')
app.set('views', path.join(__dirname, 'views'))
hbs-helper.js
module.exports = {
sayHello: function(elem) {
return 'hello!'
}
}
index.hbs - here i tried everything, but not at the same time ;):
<p>{{sayHello}}</p>
<p>{{#sayHello}}</p>
<p>{{sayHello this}}</p>
First one gives me an empty p-tag
Second one says "Error: Parse error on line ..."
Third one says "Error: Missing helper: "sayHello""
It doesnt matter if put "elem" to the function definition, it still doesnt work.
I also tried to implement the given example from https://github.com/ericf/express-handlebars with foo and bar helper (not importing them with require(), I really did the same), but it doesnt work for me. It never displays any of the return values.
Do u guys have any ideas?
The Answer from Vivasaayi worked for me!
can't register handlebar helpers
Just use following code
helpers.js
let register = function(Handlebars) {
let helpers = {
sayHello: function(elem) {
return 'hello!'
}
};
if (Handlebars && typeof Handlebars.registerHelper === "function") {
for (let prop in helpers) {
Handlebars.registerHelper(prop, helpers[prop]);
}
} else {
return helpers;
}
};
module.exports.register = register;
app.js
const exphbs = require('express-handlebars')
const hbs = exphbs.create({
layoutsDir: path.join(__dirname, 'views/layouts'),
partialsDir: path.join(__dirname, 'views/partials')
})
require("./pathto/helper.js").register(hbs.handlebars);