How to call Nunjucks configuration file into my main app.js - express

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}`);
});

Related

How can I configure Vite's dev server to give 404 errors?

Using Vite's dev server, if I try to access a non-existent URL (e.g. localhost:3000/nonexistent/index.html), I would expect to receive a 404 error. Instead I receive a 200 status code, along with the contents of localhost:3000/index.html.
How can I configure Vite so that it returns a 404 in this situation?
(This question: Serve a 404 page with app created with Vue-CLI, is very similar but relates to the Webpack-based Vue-CLI rather than Vite.)
Vite 3
Vite 3.x introduced appType, which can be used to enable/disable the history fallback. Setting it to 'mpa' disables the history fallback while keeping the index.html transform and the 404 handler enabled. The naming is somewhat misleading, as it implies the mode is only for MPAs, but on the contrary, you can use this mode for SPAs:
import { defineConfig } from 'vite'
export default defineConfig({
appType: 'mpa', // disable history fallback
})
Note the history fallback normally rewrites / to /index.html, so you'd have to insert your own middleware to do that if you want to keep that behavior:
import { defineConfig } from 'vite'
const rewriteSlashToIndexHtml = () => {
return {
name: 'rewrite-slash-to-index-html',
apply: 'serve',
enforce: 'post',
configureServer(server) {
// rewrite / as index.html
server.middlewares.use('/', (req, _, next) => {
if (req.url === '/') {
req.url = '/index.html'
}
next()
})
},
}
}
export default defineConfig({
appType: 'mpa', // disable history fallback
plugins: [
rewriteSlashToIndexHtml(),
],
})
Vite 2
Vite 2.x does not support disabling the history API fallback out of the box.
As a workaround, you can add a Vite plugin that removes Vite's history API fallback middleware (based on #ChrisCalo's answer):
// vite.config.js
import { defineConfig } from 'vite'
const removeViteSpaFallbackMiddleware = (middlewares) => {
const { stack } = middlewares
const index = stack.findIndex(({ handle }) => handle.name === 'viteSpaFallbackMiddleware')
if (index > -1) {
stack.splice(index, 1)
} else {
throw Error('viteSpaFallbackMiddleware() not found in server middleware')
}
}
const removeHistoryFallback = () => {
return {
name: 'remove-history-fallback',
apply: 'serve',
enforce: 'post',
configureServer(server) {
// rewrite / as index.html
server.middlewares.use('/', (req, _, next) => {
if (req.url === '/') {
req.url = '/index.html'
}
next()
})
return () => removeViteSpaFallbackMiddleware(server.middlewares)
},
}
}
export default defineConfig({
plugins: [
removeHistoryFallback(),
],
})
One disadvantage of this plugin is it relies on Vite's own internal naming of the history fallback middleware, which makes this workaround brittle.
You could modify fallback middleware to change the default behaves, or anything else you want. Here is an example. https://github.com/legend-chen/vite-404-redirect-plugin
Here's an approach that doesn't try to check what's on disk (which yielded incorrect behavior for me).
Instead, this approach:
removes Vite's SPA fallback middleware
it uses Vite's built-in HTML transformation and returns /dir/index.html (if it exists) for /dir or /dir/ requests
404s for everything else
// express not necessary, but its API does simplify things
const express = require("express");
const { join } = require("path");
const { readFile } = require("fs/promises");
// ADJUST THIS FOR YOUR PROJECT
const PROJECT_ROOT = join(__dirname, "..");
function removeHistoryFallback() {
return {
name: "remove-history-fallback",
configureServer(server) {
// returned function runs AFTER Vite's middleware is built
return function () {
removeViteSpaFallbackMiddleware(server.middlewares);
server.middlewares.use(transformHtmlMiddleware(server));
server.middlewares.use(notFoundMiddleware());
};
},
};
}
function removeViteSpaFallbackMiddleware(middlewares) {
const { stack } = middlewares;
const index = stack.findIndex(function (layer) {
const { handle: fn } = layer;
return fn.name === "viteSpaFallbackMiddleware";
});
if (index > -1) {
stack.splice(index, 1);
} else {
throw Error("viteSpaFallbackMiddleware() not found in server middleware");
}
}
function transformHtmlMiddleware(server) {
const middleware = express();
middleware.use(async (req, res, next) => {
try {
const rawHtml = await getIndexHtml(req.path);
const transformedHtml = await server.transformIndexHtml(
req.url, rawHtml, req.originalUrl
);
res.set(server.config.server.headers);
res.send(transformedHtml);
} catch (error) {
return next(error);
}
});
// named function for easier debugging
return function customViteHtmlTransformMiddleware(req, res, next) {
middleware(req, res, next);
};
}
async function getIndexHtml(path) {
const indexPath = join(PROJECT_ROOT, path, "index.html");
return readFile(indexPath, "utf-8");
}
function notFoundMiddleware() {
const middleware = express();
middleware.use((req, res) => {
const { method, path } = req;
res.status(404);
res.type("html");
res.send(`<pre>Cannot ${method} ${path}</pre>`);
});
return function customNotFoundMiddleware(req, res, next) {
middleware(req, res, next);
};
}
module.exports = {
removeHistoryFallback,
};
What's funny is that Vite seems to take the stance that:
it's a dev and build tool only, it's not to be used in production
built files are meant to be served statically, therefore, it doesn't come with a production server
However, for static file servers:
some configurations of static file servers will return index files when a directory is requested
they generally don't fallback to serving index.html when a file is not found and instead return a 404 in those situations
Therefore, it doesn't make much sense that Vite's dev server has this fallback behavior when it's targeting production environments that don't have it. It would be nice if there were a "correct" way to just turn off the history fallback while keeping the rest of the serving behavior (HTML transformation, etc).

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,
});

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);

ExpressJS with NuxtJS middleware passing post data to page

Can someone help me understand how to pass data from post request to the nuxt page that is loaded. I dont know how to send the data to the page that will be loaded.
I want to be able to process the POST request, then send that data for usage on the following page. I am open to suggestions but I can't find proper documentation, tutorials or examples to accomplish this task.
I don't want to use axios here (with JSON type response), because I would prefer to send POST data and load new page. Therefor if page is reloaded, POST data must be submitted again.
const express = require('express')
const bodyParser = require('body-parser')
const { Nuxt, Builder } = require('nuxt')
const app = express()
const host = process.env.HOST || '127.0.0.1'
const port = process.env.PORT || 3000
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
app.set('port', port)
// Import and Set Nuxt.js options
let config = require('../nuxt.config.js')
config.dev = !(process.env.NODE_ENV === 'production')
async function start() {
// Init Nuxt.js
const nuxt = new Nuxt(config)
// Build only in dev mode
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
}
// Routes added
app.post('/events/booking', function (req, res, next) {
console.log('REQUEST:', req.body)
res.set('eventId', req.body.eventId)
res.set('moreData', ['some', 'more', 'data'])
next()
})
// Give nuxt middleware to express
app.use(nuxt.render)
// Listen the server
app.listen(port, host)
console.log('Server listening on http://' + host + ':' + port) // eslint-disable-line no-console
}
start()
I believe the source of your issue is the disconnect between Nuxt's implementation of Express, the deprecation/version-conflicts of bodyParser middleware and/or the Node event system.
I would personally take a step back by removing the custom express routing, handle the body parsing yourself in the middleware and take advantage of the Vuex store.
store/index.js
export const state = () => ({
postBody: null,
postError: null
})
export const mutations = {
postBody: (state, postBody) => {
state.postBody = postBody;
},
postError: (state, postError) => {
state.postError = postError;
},
}
export const getters = {
postBody: state => state.postBody,
postError: state => state.postError,
}
middleware/index.js
export default ({req, store}) => {
if (process.server && req && req.method === 'POST') {
return new Promise((resolve, reject) => {
req.on('data', data => resolve(store.commit('postBody', JSON.parse(data))));
req.on('error', data => reject(store.commit('postError', JSON.parse(data))));
})
}
}
pages/index.vue
<template>
<div>
<h1>Test page</h1>
<div v-if="postBody">
<h2>post body</h2>
<p>{{postBody}}</p>
</div>
<div v-if="postError">
<h2>post error</h2>
<p>{{postError}}</p>
</div>
<div v-if="!postError && !postBody">
Please post JSON data to this URL to see a response
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
middleware: 'post-data',
computed: mapGetters({
postBody: 'postBody',
postError: 'postError'
})
}
</script>
Below is a live and working example project of the above. POST JSON data using a client app (Postman, web form, etc) to see the posted data rendered on the page.
Live Code: https://glitch.com/edit/#!/terrific-velociraptor
Live Example: https://terrific-velociraptor.glitch.me/

Native support for ES6 in PhantomJS

is there a way to make PhantomJS natively support ES6, I have a bunch of ES6 code which is converted to ES5 via Babel, what I need to accomplish is accurate measurement of code coverage which is done for ES6 code rather than ES5. It's a requirement from client, so I can't just tell him to stop requesting such thing...
Afaik NodeJS already has native support for ES6, is there a way to do that with PhantomJS?
I've ended up using raw NodeJS (without PhantomJs) + Express + JSDom (https://github.com/tmpvar/jsdom), the POC looks like this:
"use strict"
const $module = require('module');
const path = require('path');
const babel = require("babel-core");
const Jasmine = require('jasmine');
const reporters = require('jasmine-reporters');
const express = require('express');
const jsdom = require("jsdom");
const app = express();
const vm = require('vm');
const fs = require("fs");
app.get('/', function (req, res) {
res.sendFile('index.html', { root: __dirname });
});
app.use('/bower_components', express.static('bower_components'));
const load = function (filename) {
return fs.readFileSync(`./bower_components/${filename}`, "utf-8");
};
const packages = [
fs.readFileSync('./bower_components/jquery/dist/jquery.js', "utf-8"),
fs.readFileSync('./bower_components/angular/angular.js', "utf-8"),
fs.readFileSync('./bower_components/angular-mocks/angular-mocks.js', "utf-8")
];
const sut = {
'./js/code.js': fs.readFileSync('./js/code.js', "utf-8")
};
const tests = {
'./tests/test.js': fs.readFileSync('./tests/test.js', "utf-8")
};
function navigate(FakeFileSystem, root, cwd, filename) {
// Normalize path according to root
let relative = path.relative(root, path.resolve(root, cwd, filename));
let parts = relative.split(path.sep);
let iterator = FakeFileSystem;
for (let part of parts) {
iterator = iterator[part] || (iterator[part] = { });
}
return iterator;
}
const server = app.listen(3333, function () {
const host = server.address().address;
const port = server.address().port;
const url = `http://${host === '::' ? 'localhost' : host}:${port}`;
console.log(`Server launched at ${ url }`);
console.log(`Running tests...`)
jsdom.env({
url: url,
src: packages,
done: function (err, window) {
let jasmine = new Jasmine();
let FakeFileSystem = {};
let descriptors = [];
jasmine.configureDefaultReporter({ showColors: true });
let env = jasmine.env;
for (let propertyName in env) {
if (env.hasOwnProperty(propertyName)) {
window[propertyName] = env[propertyName];
}
}
let context = vm.createContext(window);
let collections = [sut, tests];
for (let collection of collections) {
for (let filename in collection) {
let descriptor = navigate(FakeFileSystem, __dirname, '.', filename);
let source = collection[filename];
let transpiled = babel.transform(source, { "plugins": ["transform-es2015-modules-commonjs"] });
let code = $module.wrap(transpiled.code);
let _exports = {};
let _module = { exports: _exports };
descriptor.code = vm.runInContext(code, context);
descriptor.module = _module;
descriptor.exports = _exports;
descriptor.filename = filename;
descriptors.push(descriptor);
}
}
for (let descriptor of descriptors) {
let cwd = path.dirname(path.relative(__dirname, descriptor.filename));
descriptor.code.call(
undefined,
descriptor.exports,
// Closure is used to capture cwd
(function (cwd) {
return function (filename) { // Fake require function
return navigate(FakeFileSystem, __dirname, cwd, filename).exports;
}
})(cwd),
descriptor.module,
descriptor.filename
);
}
jasmine.execute();
server.close();
}
});
});
The beauty of this approach is that there is no need in transpiling code with babel, it allows frontend packages such as Angular to get loaded from bower, while all the config stuff comes from npm...
EDIT
I've stumbled upon the fact that NodeJS doesn't support all ES6 features yet, and such feature as ES6 modules is a real pain, they aren't supported anywhere, so I've ended up with doing partial transpilation of code with babel, with the expectation that as NodeJS will start providing richer and richer support for ES6 I will eventually turn-off babel features step by step and switch to native support when it will become available...