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);
Related
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,
});
Within my Express 4.17.1 app, I have the following template setup (below). I have read sources on pulling out routes into their own module files. But how can I take my nunjucks configuration code below and have it called into my app.js as a separate file and still use my template filters? Just trying to unclutter.
Thanks!
App.js
const express = require('express');
const nunjucks = require('nunjucks');
const env = new nunjucks.Environment();
const path = require('path');
//...other stuff...
// VIEWS | NUNJUCKS
app.set('view engine', 'njk');
app.set('views', [path.join(__dirname, 'views'), path.join(__dirname, 'views/shared/')]);
// NUNJUCKS - CONFIGURATION & CUSTOM FILTERS
function setUpNunjucks(expressApp) {
const env = nunjucks.configure([path.join(__dirname, 'views'), path.join(__dirname, 'views/shared/')], {
autoescape: true, //default
throwOnUndefined: true, //for dev testing only
trimBlocks: true,
lstripBlocks: true,
noCache: false, //default
express: app
});
//custom Nunjucks filter to pass back into template!
env.addFilter('shorten', function (str, count) {
return str.slice(0, count || 5);
});
//an object looper. then do stuff with it
//apply the following inside the template form
env.addFilter('param', function (obj, mparam) {
for (const [key, value] of Object.entries(obj)) {
//example of whatever is needed...
if (mparam == value.param) {
return `${value.value}`;
}
}
});
}
setUpNunjucks(app);
//...other stuff...
//LISTEN
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Sever started on port ${PORT}`);
});
So I'm starting to use Node.js. I saw the video with Ryan Dahl on Nodejs.org and heard he recommended Express-js for websites.
I downloaded the latest version of Express, and began to code. I have a fully fledged static view up on /, but as soon as I try sending parameters, I get errors like this:
Cannot GET /wiki
I tried following the guide on expressjs.com but the way one uses routes has changed in the latest version, which makes the guide unusable.
Guide:
app.get('/users/:id?', function(req, res, next){
var id = req.params.id;
if (id) {
// do something
} else {
next();
}
});
Generated by Express:
app.get('/', routes.index);
My problem arises when I try and add another route.
app.get('/wiki', routes.wiki_show);
I've tried a bunch of approaches, but I keep getting the Cannot GET /wiki (404) error.
routes/index.js looks like this:
exports.index = function(req, res) {
res.render('index', { title: 'Test', articles: articles, current_article: current_article, sections: sections })
};
The only thing I did there was add some parameters (arrays in the same file) and this i working. But when I copy the contents and change exports.index to exports.wiki or exports.wiki_show I still get the Cannot GET /wiki error.
Can anyone explain to me what I'm missing here? - Thanks.
So, after I created my question, I got this related list on the right with a similar issue: Organize routes in Node.js.
The answer in that post linked to the Express repo on GitHub and suggests to look at the 'route-separation' example.
This helped me change my code, and I now have it working. - Thanks for your comments.
My implementation ended up looking like this;
I require my routes in the app.js:
var express = require('express')
, site = require('./site')
, wiki = require('./wiki');
And I add my routes like this:
app.get('/', site.index);
app.get('/wiki/:id', wiki.show);
app.get('/wiki/:id/edit', wiki.edit);
I have two files called wiki.js and site.js in the root of my app, containing this:
exports.edit = function(req, res) {
var wiki_entry = req.params.id;
res.render('wiki/edit', {
title: 'Editing Wiki',
wiki: wiki_entry
})
}
The route-map express example matches url paths with objects which in turn matches http verbs with functions. This lays the routing out in a tree, which is concise and easy to read. The apps's entities are also written as objects with the functions as enclosed methods.
var express = require('../../lib/express')
, verbose = process.env.NODE_ENV != 'test'
, app = module.exports = express();
app.map = function(a, route){
route = route || '';
for (var key in a) {
switch (typeof a[key]) {
// { '/path': { ... }}
case 'object':
app.map(a[key], route + key);
break;
// get: function(){ ... }
case 'function':
if (verbose) console.log('%s %s', key, route);
app[key](route, a[key]);
break;
}
}
};
var users = {
list: function(req, res){
res.send('user list');
},
get: function(req, res){
res.send('user ' + req.params.uid);
},
del: function(req, res){
res.send('delete users');
}
};
var pets = {
list: function(req, res){
res.send('user ' + req.params.uid + '\'s pets');
},
del: function(req, res){
res.send('delete ' + req.params.uid + '\'s pet ' + req.params.pid);
}
};
app.map({
'/users': {
get: users.list,
del: users.del,
'/:uid': {
get: users.get,
'/pets': {
get: pets.list,
'/:pid': {
del: pets.del
}
}
}
}
});
app.listen(3000);
Seems that only index.js get loaded when you require("./routes") .
I used the following code in index.js to load the rest of the routes:
var fs = require('fs')
, path = require('path');
fs.readdirSync(__dirname).forEach(function(file){
var route_fname = __dirname + '/' + file;
var route_name = path.basename(route_fname, '.js');
if(route_name !== 'index' && route_name[0] !== "."){
exports[route_name] = require(route_fname)[route_name];
}
});
You could also organise them into modules. So it would be something like.
./
controllers
index.js
indexController.js
app.js
and then in the indexController.js of the controllers export your controllers.
//indexController.js
module.exports = function(){
//do some set up
var self = {
indexAction : function (req,res){
//do your thing
}
return self;
};
then in index.js of controllers dir
exports.indexController = require("./indexController");
and finally in app.js
var controllers = require("./controllers");
app.get("/",controllers.indexController().indexAction);
I think this approach allows for clearer seperation and also you can configure your controllers by passing perhaps a db connection in.
No one should ever have to keep writing app.use('/someRoute', require('someFile')) until it forms a heap of code.
It just doesn't make sense at all to be spending time invoking/defining routings. Even if you do need custom control, it's probably only for some of the time, and for the most bit you want to be able to just create a standard file structure of routings and have a module do it automatically.
Try Route Magic
As you scale your app, the routing invocations will start to form a giant heap of code that serves no purpose. You want to do just 2 lines of code to handle all the app.use routing invocations with Route Magic like this:
const magic = require('express-routemagic')
magic.use(app, __dirname, '[your route directory]')
For those you want to handle manually, just don't use pass the directory to Magic.
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');
`;
}
Has anyone gotten Aurelia Validation to work with the i18n Plugin for multi-lingual error messages? My app won't even start when I add in the code from the Aurelia documentation http://aurelia.io/hub.html#/doc/article/aurelia/validation/latest/validation-basics/12.
Here's my main.js:
import environment from './environment';
import {I18N} from 'aurelia-i18n';
import XHR from 'i18next-xhr-backend';
import {ValidationMessageProvider} from 'aurelia-validation';
//Configure Bluebird Promises.
//Note: You may want to use environment-specific configuration.
Promise.config({
warnings: {
wForgottenReturn: false
}
});
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.feature('resources')
.plugin('aurelia-validation');
aurelia.use.plugin('aurelia-i18n', (instance) => {
// register backend plugin
instance.i18next.use(XHR);
// adapt options to your needs (see http://i18next.com/docs/options/)
instance.setup({
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
lng : 'en',
ns: ['translation'],
defaultNS: 'translation',
attributes : ['t','i18n'],
fallbackLng : 'en',
debug : false
});
});
// Straight from Aurelia Documentation
const i18n = aurelia.container.get(i18n);
ValidationMessageProvider.prototype.getMessage = function(key) {
const translation = i18n.tr(`errorMessages.${key}`);
return this.parser.parseMessage(translation);
};
// Straight from Aurelia Documentation
ValidationMessageProvider.prototype.getDisplayName = function(propertyName) {
return i18n.tr(propertyName);
};
if (environment.debug) {
aurelia.use.developmentLogging();
}
if (environment.testing) {
aurelia.use.plugin('aurelia-testing');
}
aurelia.start().then(() => aurelia.setRoot());
}
The error I get is vendor-bundle.js:3394 Error: key/value cannot be null or undefined. Are you trying to inject/register something that doesn't exist with DI?(…)
If I delete the two sections marked // Straight from Aurelia Documentation, it works fine (but only in one language).
If you see an error in my code, please point it out. Or, if you have a working example using aurelia-validation and aurelia-i18n working together, please pass on a link. Thanks!
Ran into this issue as well. It appears that the line
// Straight from Aurelia Documentation
const i18n = aurelia.container.get(i18n);
is getting (or more likely creating) a different instance of i18n than the
aurelia.use.plugin('aurelia-i18n', (instance) =>
I fixed this by getting the i18n instance directly from the aurelia.use.plugin() as follows (this is typescript but same principle applies to pure js):
let i18n:I18N = null;
aurelia.use.plugin('aurelia-i18n', (instance:I18N) => {
i18n = instance;
//rest of plugin code here
}
Use the imported I18N instead:
const i18n = aurelia.container.get(I18N);
But indeed, i18n seems to stop working afterward. My solution was to update the i18n singleton instance in the first page (app.js), the first time it gets injected:
constructor(i18n) {
this.i18n = i18n;
this.initAureliaSingletons();
}
/**
* Some configurations breaks in 'main.js'
* singletons can be configure here
* #return {void}
*/
initAureliaSingletons() {
const i18n = this.i18n;
ValidationMessageProvider.prototype.getMessage = function(key) {
const translation = i18n.tr(`validation-${key}`);
return this.parser.parseMessage(translation);
};
}
I put it on my main and it works. I think that the trick was to use the variable that was initialized in the plug-in initialization:
var i18n;
aurelia.use.plugin('aurelia-i18n', (instance) => {
// register backend plugin
instance.i18next.use(Backend.with(aurelia.loader)).use(LngDetector);
i18n = instance;
(...)
aurelia.use.plugin('aurelia-validation');
var standardGetMessage = ValidationMessageProvider.prototype.getMessage;
ValidationMessageProvider.prototype.getMessage = function (key) {
if (i18n.i18next.exists(key)) {
const translation = i18n.tr(key);
return this.parser.parse(translation);
} else {
return standardGetMessage(key);
}
};
ValidationMessageProvider.prototype.getDisplayName = function (propertyName, displayName) {
if (displayName !== null && displayName !== undefined) {
return displayName;
}
return i18n.tr(propertyName);
};