I'm trying to write a webpack loader, and one of the requirements is that I need to inject code ONCE into each bundle.
Is there a way in the loader to detect if the module I'm processing is an entry point? If not, is there an easy way to inject code once per bundle?
You can inject code like you inject code normally. Your loader should return the transformed source with a require to the code you need to inject once. Webpack's documentation covers this referring to this common code as a runtime for the loader. http://webpack.github.io/docs/how-to-write-a-loader.html#extract-common-code
var loaderUtils = require('loader-utils');
module.exports = function(content) {
return "require(" +
loaderUtils.stringifyRequest(this, "!" + require.resolve("./runtime")) +
");\n\n" +
content;
};
Is this helpful?
module.exports = function (content) {
if (this.options.entry == this.resourcePath)
content = 'var newCode = 10;\r\n\r\n' + content;
return content;
};
I am using this to inject code on the entry file only.
You can't use loaders on entry points (as well as you can't depends on entry points).
So if you want to inject some code through the loader you have to require on it in each entry point.
Related
Is there a way to assign the file contents directly to a variable/method without webpack generating an additional variable?
Currently webpack does something like this:
index.js
import scss from './styles.scss';
console.log(scss);
output.js (actual)
const styles = '...';
console.log(styles);
I want webpack to return the content of the styles.scss file directly to the console.log() method in the output without adding styles variable.
output.js (expected)
console.log('...');
I'm currently using my custom loader for reading .scss files:
css-loader.js
const sass = require('sass');
module.exports = function(source) {
const result = sass.compile(this.resourcePath);
return 'export default `\n' + result.css + '\n`';
};
I know there is a way to do this using the require() function but it generates a lot of extra code coming from webpack. Is there any other better way to do it?
So it's really a scoping issue. So there's no real requirement to not wanting webpacks default behaviour.
Webpack has a few ways how to do that. See this other answer for how to define a global variable, which is Webpacks way of doing what you want to do.
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' });
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.
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/
I'm building an angular directive which will be used in a few different locations.
I can't always guarantee the file structure of the app the directive is used in, but I can force the user to put the directive.js and directive.html (not the real file names) in the same folder.
When the page evaluates the directive.js, it considers the templateUrl to be relative to itself. Is it possible to set the templateUrl to be relative to the directive.js file?
Or is it recommended to just include the template in the directive itself.
I'm thinking I may want to load different templates based on different circumstances, so would prefer to be able to use a relative path rather than updating the directive.js
The currently executing script file will always be the last one in the scripts array, so you can easily find its path:
// directive.js
var scripts = document.getElementsByTagName("script")
var currentScriptPath = scripts[scripts.length-1].src;
angular.module('app', [])
.directive('test', function () {
return {
templateUrl: currentScriptPath.replace('directive.js', 'directive.html')
};
});
If you're not sure what is the script name (for example if you're packing multiple scripts into one), use this:
return {
templateUrl: currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1)
+ 'directive.html'
};
Note: In cases where a closure is used, your code should be outside to ensure that the currentScript is evaluated at the correct time, such as:
// directive.js
(function(currentScriptPath){
angular.module('app', [])
.directive('test', function () {
return {
templateUrl: currentScriptPath.replace('directive.js', 'directive.html')
};
});
})(
(function () {
var scripts = document.getElementsByTagName("script");
var currentScriptPath = scripts[scripts.length - 1].src;
return currentScriptPath;
})()
);
As you said you wanted to provide different templates at different times to the directives, why not allow the template itself to be passed to the directive as an attribute?
<div my-directive my-template="template"></div>
Then use something like $compile(template)(scope) inside the directive.
In addition to the answer from Alon Gubkin I'd suggest to define a constant using an Immediately-Invoked Function Expression to store the path of the script and inject it into the directive:
angular.module('app', [])
.constant('SCRIPT_URL', (function () {
var scripts = document.getElementsByTagName("script");
var scriptPath = scripts[scripts.length - 1].src;
return scriptPath.substring(0, scriptPath.lastIndexOf('/') + 1)
})())
.directive('test', function(SCRIPT_URL) {
return {
restrict : 'A',
templateUrl : SCRIPT_URL + 'directive.html'
}
});
This code is in a file called routes.js
The following did not work for me:
var scripts = document.getElementsByTagName("script")
var currentScriptPath = scripts[scripts.length-1].src;
var baseUrl = currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1);
the following did:
var bu2 = document.querySelector("script[src$='routes.js']");
currentScriptPath = bu2.src;
baseUrl = currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1);
My test is based on the following blog about using require to lazy load angular:
http://ify.io/lazy-loading-in-angularjs/
require.js begets a requireConfig bootstrap
requireConfig begets an angular app.js
angular app.js begets my routes.js
I had the same code being served up by a revel web framework and asp.net mvc.
In revel
document.getElementsByTagName("script")
produced a path to my require bootstrap js file and NOT my routes.js.
in ASP.NET MVC it produced a path to Visual Studio's injected Browser Link script element that is put there during debugging sessions.
this is my working routes.js code:
define([], function()
{
var scripts = document.getElementsByTagName("script");
var currentScriptPath = scripts[scripts.length-1].src;
console.log("currentScriptPath:"+currentScriptPath);
var baseUrl = currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1);
console.log("baseUrl:"+baseUrl);
var bu2 = document.querySelector("script[src$='routes.js']");
currentScriptPath = bu2.src;
console.log("bu2:"+bu2);
console.log("src:"+bu2.src);
baseUrl = currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1);
console.log("baseUrl:"+baseUrl);
return {
defaultRoutePath: '/',
routes: {
'/': {
templateUrl: baseUrl + 'views/home.html',
dependencies: [
'controllers/HomeViewController',
'directives/app-style'
]
},
'/about/:person': {
templateUrl: baseUrl + 'views/about.html',
dependencies: [
'controllers/AboutViewController',
'directives/app-color'
]
},
'/contact': {
templateUrl: baseUrl + 'views/contact.html',
dependencies: [
'controllers/ContactViewController',
'directives/app-color',
'directives/app-style'
]
}
}
};
});
This is my console output when running from Revel.
currentScriptPath:http://localhost:9000/public/ngApps/1/requireBootstrap.js routes.js:8
baseUrl:http://localhost:9000/public/ngApps/1/ routes.js:10
bu2:[object HTMLScriptElement] routes.js:13
src:http://localhost:9000/public/ngApps/1/routes.js routes.js:14
baseUrl:http://localhost:9000/public/ngApps/1/
Another nice thing I have done is to take advantage of the require config and put some custom configurations in it.
i.e. add
customConfig: { baseRouteUrl: '/AngularLazyBaseLine/Home/Content' }
you can then get it by using the following code from inside of routes.js
var requireConfig = requirejs.s.contexts._.config;
console.log('requireConfig.customConfig.baseRouteUrl:' + requireConfig.customConfig.baseRouteUrl);
sometimes you need to define a baseurl upfront, sometimes you need to dynamically generate it. Your choice for your situation.
Some might suggest it slightly "hacky", but I think until there is only 1 way to do it, anything is going to be hacky.
I've had a lot of luck with also doing this:
angular.module('ui.bootstrap', [])
.provider('$appUrl', function(){
this.route = function(url){
var stack = new Error('dummy').stack.match(new RegExp(/(http(s)*\:\/\/)[^\:]+/igm));
var app_path = stack[1];
app_path = app_path.slice(0, app_path.lastIndexOf('App/') + 'App/'.length);
return app_path + url;
}
this.$get = function(){
return this.route;
}
});
Then when using the code in an application after including the module in the app.
In an app config function:
.config(['$routeProvider', '$appUrlProvider', function ($routeProvider, $appUrlProvider) {
$routeProvider
.when('/path:folder_path*', {
controller: 'BrowseFolderCntrl',
templateUrl: $appUrlProvider.route('views/browse-folder.html')
});
}]);
And in an app controller (if required):
var MyCntrl = function ($scope, $appUrl) {
$scope.templateUrl = $appUrl('views/my-angular-view.html');
};
It creats a new javascript error and pulls out the stack trace. It then parses out all urls (excluding the calling line/char number).
You can then just pull out the first in the array which will be the current file where the code is running.
This is also helpful if you want to centralise the code and then pull out the second ([1]) in the array, to get the calling file location
As several users have pointed out, relevant paths are not helpful when building the static files, and I would highly recommend doing so.
There is a nifty feature in Angular called $templateCache, which more or less caches template files, and next time that angular requires one, instead of making an actual request it provides the cached version. This is a typical way to use it:
module = angular.module('myModule');
module.run(['$templateCache', function($templateCache) {
$templateCache.put('as/specified/in/templateurl/file.html',
'<div>blabla</div>');
}]);
})();
So in this way you both tackle the problem of relative urls and you gain in performance.
Of course we love the idea of having separate template html files (in contrast to react), so the above by its own is no good. Here comes the build system, which can read all template html files and construct a js such as the above.
There are several html2js modules for grunt, gulp, webpack, and this is the main idea behind them. I personally use gulp a lot, so I particularly fancy gulp-ng-html2js because it does exactly this very easily.