Workaround to dynamic includes in Pug/Jade - javascript

I understand that Pug does not support dynamic includes or extends in templates. Ie
extend path/to/template
works but not
extend #{dynamic_path_to_template}
Is there a workaround (however convoluted) that will allow the same goal of modifying the template used by a view at runtime
Context: My use case is that I am developing an npm module and the template being used to extend other views is located inside the module. After the module is published and installed, the path will be defined (ie. node_modules/my_module/path/to/template) but during the development phase, I need to just be able to "npm link" to the module and have the templates work. I also would prefer not to hard code the links so I can publish the same code as tested.

I had this issue aswell and found this question while searching for a solution. My solution is similar to Nikolay Schambergs Answer, but i thought i should share it.
I've created a function that renders templates by giving it a path and passed it to the options object. Maybe it helps in your case aswell
const includeFunc = (pathToPug, options = {}) => {
return pug.renderFile(pathToPug, options); //render the pug file
}
const html = pug.renderFile('template.pug', {include: includeFunc});
and then use it as followed in your template:
body
h1 Hello World
|!{include(dynamicPugFilePathFromVariable)}

There is no way to do this for now, but you can work out your application architecture without dynamic extends.
Possible solution #1
Make a layout.jade that conditionally include multiple layouts:
layout.jade:
if conditionalVariable
include firstLayout.jade
else
include otherLayout
In your view, extend layout.jade, and define conditionalVariable in the controller (true/false):
view.jade:
extends layout
block content
p here goes my content!
Possible solution #2
Pass configurations to the layout
- var lang = req.getLocale();
doctype html
block modifyLayout
split the project into multiple entrances, each entrance extends the layout and passes its different configs, and includes different things in different blocks
extends ../layout
block modifyLayout
- var lang = "en" //force language to be en in this page.
block body
include my-page-body
Possible solution #3
use something like terraform which uses pug as its rendering engine, but it enables you to use dynamic partials like this
!= partial(dynamicFileFromVariable)

It works!
First, set res.locals middleware.
middlewares/setResLocals.js
const pug = require('pug')
const path = require('path')
module.exports = function(req, res, next) {
res.locals.include = (pathToPug, options = {}) => { // used for imitate includ pug function
return pug.renderFile(pathToPug, options); //render the pug file
}
res.locals.__PATH__ = path.join(__dirname, '../')
next()
}
server/index.js
app.use(require('../middlewares/setResLocals'))
file.pug
|!{include(`${__PATH__}/${something}`)}

In order to do dynamic include, you will have to use Unescaped String Interpolation, inserting pug contents that are pre-compiled before your main .pug file inside your route. In other words it works as follows:
1) Some .pug files are pre-compiled into HTML
2) The HTML gets fed into another .pug file compilation process
Here's an example how to do it
Inside your router file (routes.js or whatever)
var pug = require('pug')
var html = []
var files = ['file1','file2'] // file names in your views folders
let dir = path.resolve(path.dirname(require.main.filename) + `/app/server/views/`)
//dir is the folder with your templates
app.get('/some-route', (req,res) => {
for (let n = 0; n < files.length; n++) {
let file = path.resolve(dir + '/' + files[n] + `.pug`)
fs.access(file, fs.constants.F_OK, (err) => {
if (!err) {
html.push(pug.renderFile(file, data))
if (n === files.length - 1) {
res.render('dashboard', {html})
}
}
else {
res.status(500).json({code:500,status:"error", error:"system-error"})
}
})
}
})
Inside your desired .pug file:
for item in html
.
!{item}
The example above is specific to my own use case, but it should be easy enough to adapt it.

I know, this is a bit late for answering. But I found a possibility suitable for my purpose by this bit of information from the pug docs:
If the path is absolute (e.g., include /root.pug), it is resolved by
prepending options.basedir. Otherwise, paths are resolved relative to
the current file being compiled.
So, I provide most of my pug modules by relative paths and the stuff I want to exchange dynamically is organised in pug files of the same name but in different folders (think theme) and include them by absolute paths . Then I change the basedir option to dynamically choose a set of pug files (like choosing the theme).
May this help others, too.

Related

How to create vanilla javascript widgets as web components and import into a page dynamically?

I am working on a web application which can host mini-apps (or modules) developed in vanilla Js, HTML, CSS.
The host application dynamically loads (using fetch API) the mini-apps (or modules) into its pages and then I want these mini-apps to independently request for their data or do whatever they want to. I want these mini-apps isolated from the host scripts and styling but the host should be able to execute functions of these mini-apps (or modules).
Example: The dashboard of Microsoft Azure portal. It has widgets which can be selected, customised and placed by the user, and after loading of the host dashboard these widgets independently fetch for their data. Also, the period and auto-refresh time can be controlled by the host application.
Priorities:
• Modules should be able to execute their own JS scripts.
• If possible then everything should be in vanilla js (or Stenciljs / Vue.js)
Current File Structure:
main.html
js (dir)
style (dir)
modules (dir)
• module-one
| module.html
| module.css
| module.js
• module-n
...
I have tried creating custom HTML element and then appending HTML and CSS of module to shadowDom. But I still don't know how to get its JS working. If I insert module.js dynamically to main.html then somehow I need to change roots of all module.js appended in host application from
document to shadowRoot
Example:
//module.js
const sayHelloBtn = document.getElementById('sayHello');
sayHelloBtn.addEventListener('click', () => {console.log('Hello')});
//module.js after appending to host (main.html)
const mod = document.querySelector('module-one').shadowRoot;
const sayHelloBtn = mod.getElementById('sayHello');
sayHelloBtn.addEventListener('click', () => {console.log('Hello')});
Please let me know if further elaboration or clarification on question is required. Thank You :)
Problem Update:
I get a json of all the modules from an API and dynamically create custom elements with shadow root . I fetch the module files using the fetch API and then append them to the custom elements. I am able to successfully append the .html and .css to respective custom elements but cannot run JS inside shadow DOM. If I dynamically import the JS globally to the main.html then some how I need the JS to access the elements in its shadowRoot and also the functions should not conflict with other module's js functions with same name.
I have tried creating classes in each module.js which holds its respective methods, variable and a init() which does all the module's initialisation.
//module.js
class ModuleAbc {
constructor(host = document) { // shadowRoot is passed when instantiating from the host application
this.docRoot = host;
console.log('docRoot set: ', this.docRoot);
}
init() {
console.log('initialising module at host: ', this.docRoot);
const docRoot = this.docRoot;
const btn = docRoot.getElementById('cm'); //this element is inside shadowRoot
btn.addEventListener('click', () => {
console.log('Hello from mod 1!');
});
}
}
Now I do not know how to call init() of all the classes because their names are different for every module.
//HOST JS (main.js)
let mod_name = 'ModuleAbc';
const mod = new mod_name(module_root); //THIS DOESN'T WORK
mod.init();
Problem is resolved using Estus Flask's suggested solution

Webpack loader for file, subfiles and add them to tracking list

I have the map+tilemap project created in a 3rd-party app. The whole project is a set of files, the main file (XML) representing the 2D game level map and some other files (subfiles) representing graphics and tilemaps.
I am trying to create a Webpack Loader that will compile and convert the whole map/tilemap project into JSON object, that is comfortable to use in javascript. And I still can't get how to:
How can I access subfiles (taken from relative paths from the main file), what is the way to access the current directory (where the main file is placed), and the name of the main file?
How can I explain to Webpack to track changes in all subfiles, so it will run the loader again automatically to compile the whole map/tilemap project (partial re-packing).
I spent 2 days to find any working solutions, it is possible at all?
Thanks a lot!
For the first question, webpack loader is expose the file info by this, you can do like this:
module.exports = function (source) {
const loaderContext = this;
const { sourceMap, rootContext, resourcePath } = loaderContext;
const sourceRoot = path.dirname(path.relative(context, resourcePath));
...
}
For the second question, i think that maybe you can use the module.hot to track the subfiles change, like this:
if (module.hot) {
module.hot.accept('filepath', ()=>{
...
})
}

How to template dynamically generated react code?

I'm currently working on a framework for a web app that a number of developers will be working on. The web app will be in a dashboard style, with individual modules or "views" written in React.
One of the requirements of the framework is that it must ship with a tool that allows developers to automatically generate a new module or "view", so that it creates the files and folders needed and they can get straight to work on the code logic.
An extremely simple flow would be as follows:
Developer enters the name of their new module as an argument to a npm script
A script runs which creates [moduleName.js] and [moduleName.less], links them together, places them in a directory, and writes the generic react code.
I'm now up to the point where I am generating the common react code. Here is what I wrote:
function writeBoilerplate(moduleName) {
var jsFileStream = fs.createWriteStream("./src/m-" + moduleName + "/" + moduleName + ".js");
jsFileStream.once('open', (fd) => {
jsFileStream.write("import React from \"react\"\;\n");
jsFileStream.write("import Style from \"\.\/" + moduleName + "\.less\"\;");
jsFileStream.write("\n");
jsFileStream.write("\n");
jsFileStream.write("export default class " + moduleName + " extends React\.Component \{\n");
jsFileStream.write("\n");
jsFileStream.write(" constructor\(\) \{\n");
jsFileStream.write(" super\(\)\;\n");
jsFileStream.write(" \}\n");
jsFileStream.write("\n");
jsFileStream.write(" componentDidMount\(\) \{");
jsFileStream.write(" \}");
jsFileStream.write("\}");
jsFileStream.end();
});
You can immediately see the problem here, and I stopped before going too far. If I continue on this path, the code will become unreadable and unmanageable.
I want to refactor this to use javascript templates. However, I have never used templating before and I am unsure of how to create a template and use it, or if there are any tools to help.
How can I refactor this code to use a template?
You need to use a template library for that. You can try lodash template one, for example: https://lodash.com/docs#template.
Put your boilerplate in a template file, read it and use something like:
var compiled = _.template(templateFileContent);
compiled({ 'moduleName': 'mymodule' });

Webpack plugin to attach static JSON object to output

I am trying to write a webpack plugin that will go into a directory containing html files, open each, remove new lines, and generate an object to attach as a static property to my output file (which is a var).
The object would look something like:
{
htmlFile1: '<p>some html one!</p>',
htmlFile2: '<span>some html two!</span>
}
Then I would like it to be attached to my output like:
App.templates = { .... }
The creation of the object is done. I'm just struggling with webpack and figuring out how to attach it to the main object. Should I just write it to disk and require it? Or is there a way to add it to the bundle via the plugin?
I'm using Rivets.js as a template engine and I have not been able to find anything out there that does something like this already.
Also worth noting, all I'm using is webpack and npm. No Grunt/Gulp/etc
Thanks so much!
Mike
You could use webpack's require.context to import all html files in a directory as text, process the text, and export the results as a single object:
const requireTemplate = require.context('./templates', false, /\.html$/);
module.exports = requireTemplate.keys().reduce((templateMap, templatePath) => {
const templateName = templatePath.match(/\/(\w*?)\.html/)[1]; // get filename without path and extention
templateMap[templateName] = require(templatePath);
return templateMap;
}, {});

How to require javascript model files within the index.js file?

I have 2 model files containing a constructor in each, and an index.js file, that I wish to use to insert elements into a HTML file, using innerHTML. I want to use one of the variables from the js model file, however when I try to require the files in the index.js file, the innerHTML file suddenly stops working. Please note, the code in the current `window.onload' function is inserting h1 elements as a test, I will be replacing this with a return value from the constructor, but at the moment, when I require the files, even the h1 insert stops working. Code snippets that I think are relevant can be seen below:
index.js file:
var ToDo = require('../src/toDo.js');
var ToDoList = require('../src/toDoList.js');
window.onload = function() {
// create a couple of elements in an otherwise empty HTML page
var heading = document.createElement("h1");
var heading_text = document.createTextNode("Big Head!");
heading.appendChild(heading_text);
document.body.appendChild(heading);
}
Model file 1:
function ToDo(task) {
this.task = task;
this.complete = false;
}
module.exports = ToDo;
function ToDoList() {
this.array = [];
}
ToDoList.prototype.add = function(task) {
this.array.push(task);
};
ToDoList.prototype.popTask = function() {
var poppedTask = this.array.pop();
var concat = "<ul><li>";
var concat2 = "</li></ul>";
return (concat + poppedTask.task + concat2);
};
module.exports = ToDoList;
require is CommonJs feature, and it's not supported by browsers without this library. So you would need to use it in your project if you want modules with require syntax.
Update
To use require feature, you would need some module loader. There are two very popular that you could check out - Webpack or Browserify. They both support CommonJS require that you are looking for. As for me, I like webpack most, cause it is very powerful out of the box.
To read about their comparison :
https://medium.com/#housecor/browserify-vs-webpack-b3d7ca08a0a9#.lb3sscovr
RequireJS is another module loader, but he works not with CommonJs-style modules (require). He implements AMD-style modules.
You can check this article to understand how AMD is different from CommonJS:
https://auth0.com/blog/2016/03/15/javascript-module-systems-showdown/

Categories