Ever wanted to experiment on and customize a webpage ? The Pimp my page engine let's you do just that. The engine leverage the mighty BrowserSync to allow full HTML, CSS or JS modifications on any webpage.
- write SASS, it's automatically compiled to CSS and injected in the page (no refresh needed)
- write JS, the browser's refresh with your custom JS injected
- write some HTML and jQuery like operations to swap/modify the page content
The trick is quite simple, some URL (ex: http://www.thetimes.co.uk/) is being proxyfied with BrowserSync, it is then available through something along the lines of localhost:3000. Nothing special, but now we have the opportunity to add some middleware. This middleware is the corner stone of all "pimping" we gonna apply to the Times website pages.
Basically what it does is detect all HTML responses matching with a whitelist of URL patterns (ex:"*/magazine/*"). When one such URL request is identified for pimping, the middleware will change the response according to a set of jQuery instructions: add some HTML there, remove this style tag, append a new stylesheet...
This core behavior is then bundled with some gulp automated build tasks goodness to provide external page assets (e.g. stylesheets, js, HTML partials, images).
Finally the browser behavior is tied to gulp watch tasks to react according to real-time assets modifications: inject in the page, reload page with new assets or restart the whole thing with new instructions.
To you this mean live pimping of the Times style magazine pages in your local environment. Useful for:
- trying bug fixing production code
- quick prototyping features
- proto-theming without the need for a local environment
- just for fun of defacing a site in your own sandboxed world (no legal risks ๐)
npm install pmp-engine
given that you already installed the dependency, here is how to get started with it
const PmpEngine = require('pmp-engine');
//create engine instance
let pmpEngine = new PmpEngine();
//define your config
let config = {
bsOptions: {
proxy: {
target: 'http://www.thetimes.co.uk/',
cookies: { stripeDomain: false }
},
port:3000,
cors:true,
serveStatic: ['./dist'],
middleware: [],
rewriteRules: []
},
pimpCmds:[
{
url:'*/magazine/*',
modifs:[`
$('head').append('<link rel="stylesheet" type="text/css" href="/css/main.min.css">');
$('body').append('<script type="text/javascript" src="/js/main.min.js"></script>');
/* the 2 lines above can be replaced by using the staples helper plugin */
$('body').addClass('sample-pimping');
$('.Article-content p').html('my crazy lorem replacement');
`]
},
{
url:'*/edition/*',
modifs:[`
$('head').append('<link rel="stylesheet" type="text/css" href="/css/main.min.css">');
$('body').append('<script type="text/javascript" src="/js/main.min.js"></script>');
$('body').addClass('sample-pimping');
$('.Article-content').prepend('<img width="100%" src="http://i223.photobucket.com/albums/dd245/2ndsite/The%20Holding%20Pen/6f158ba2.jpg" />');
`]
}
],
plugins: [
'pmp-plugin-staples'
]
};
//start engine with config
pmpEngine.start(config);
BrowserSync configuration options. Beware that some of these are mandatory (ex:proxy mode or at least an empty middleware array). Check the browserSync documentation for more.
property | values |
---|---|
target | the proxied URL or host [String] |
port | the output port number [Number] |
cors | enables cross origin request headers [Boolean] |
Set of rules to be applied when the page's URL match a specific pattern. This is an array that contains pimp commands.
property | values |
---|---|
url | the exact URL or trigger pattern that will activate the following operations [String] |
modifs | Array of jQuery operations to manipulate the HTML request response [Array[String]] |
important Please notice that all commands samples are multiline strings. Multiline strings are the most practical to use (check the example below).
//mutliline case
modifs:[`
$('head').append('<link rel="stylesheet" type="text/css" href="/css/main.min.css">');
$('body').append('<script type="text/javascript" src="/js/main.min.js"></script>');
$('body').addClass('sample-pimping');
`]
//normal quotes, all inline case
modifs:["$('head').append('<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/main.min.css\">'); $('body').append('<script type=\"text/javascript\" src=\"/js/main.min.js\"></script>'); $('body').addClass('sample-pimping');"]
//normal quotes, one action per array item
modifs:[
"$('head').append('<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/main.min.css\">');",
"$('body').append('<script type=\"text/javascript\" src=\"/js/main.min.js\"></script>');",
"$('body').addClass('sample-pimping');"
]
Set of pmp-plugins to be applied as helpers (need to be npm installed before use). A plugin provide functions for easing the writing of modification rules, it also provides helper custom tags for your custom HTML templates. Just put packages names in an array under the plugins config field.
If you wanne use the engine without coding around it.
npm start
After init, the engine can be started, stopped or restarted.
//start engine with config
pmpEngine.start(config);
//restart engine with same config
pmpEngine.restart();
//restart engine and apply another config
pmpEngine.restart(updatedConfig);
//stop engine
pmpEngine.stop();
//get all logs outputs (RXjs Observable)
pmpEngine.pmpEngineLogsStream.subscribe(log => {
console.log(log);
});
//get all errors (RXjs Observable)
pmpEngine.pmpEngineErrorsStream.subscribe(err => {
console.log(err);
});
//get pmp engine status (RXjs Observable)
pmpEngine.pmpEngineStatusStream.subscribe(status => {
console.log(status);
});
//get currently applied config
console.log(pmpEngine.currentPimpConfig);
//get current status [started, stopped, pending]
console.log(pmpEngine.pmpEngineStatus);
have a look at the test to have more code samples
In the pimpCmd Object there is a set of operations that will allow you to perform some actions on the request's HTML payload. You are free to do whatever DOM transformation you see fit. Cheerio's jQuery light library is provided to ease the process. And don't worry this helper jQuery only exists in the context of the middleware, it will not pollute the resulting pimped page.
append a new external stylesheet from your local files. The href path root correspond to the dist folder in your project.
modifs:[`
$('head').append('<link rel="stylesheet" type="text/css" href="/css/main.min.css">');
`]
alternatively you could add a style tag directly
modifs:[`
$('head').append('<style>body { background-color:limegreen; color:goldenrod; }</style>');
`]
same principle to add some external javascript files for execution. Works also with inline <script>
tags.
modifs:[`
$('body').append('<script type="text/javascript" src="/js/main.min.js"></script>');
`]
modifs:[`
$('head').append('<script>alert('hello world!');</script>');
`]
just regular javascript/jQuery manipulations.
modifs:[`
//remove all paragraphs
$('body p').remove();
//deactivate links and replace links texts
$('body a').attr('href', '#').html('hello i\'m a link');
//add an image (from publicly available CDN) to the fifth paragraph in article content
$('.Article-content p:eq(5)').prepend('<img width="100%" src="http://i223.photobucket.com/albums/dd245/2ndsite/The%20Holding%20Pen/6f158ba2.jpg" />');
`]
define your own helper functions to manage HTML partials injections
modifs:[`
var injectHTMLFile = function(url){
try {
return fs.readFileSync(path.join('./dist/html', url), 'utf8');
} catch (err) {
// If the type is not missing file, then just throw the error again.
if (err.code !== 'ENOENT') throw err;
// Handle a file-not-found error
return '<p class="alert alert-warning">HTML inject file not found: ' + url + '</p>';
}
}
//inject HTML partial to the DOM from 'dist/html'
$('.Article-content').prepend(injectHTMLFile('my-article-summary-to-be-injected.html'));
`]
modifs:[`
//find and remove the external stylesheet for bootstrap minified css
$('link[href$="bootstrap.min.css"]').remove();
`]
modifs:[`
//find and remove the external script for bootstrap minified js
$('script[src$="bootstrap.min.js"]').remove();
`]
new PmpEngine({ ioEnabled:true, host: 'localhost', port: 5000 });
property | values | default | comment |
---|---|---|---|
ioEnabled | [Boolean] | false | enables remote control |
host | [url] | 'localhost' | socket.io host property |
port | [number] | 5000 | socket.io port property |
That will enables you to provide a GUI to interact with the pmp-engine. You can see a minimal socket.io client setting in socket-server.spec.js This functionnality will be leveraged in an upcoming pmp-ui package
- need some tests certainly some weird behaviors now and then ๐
- cross domains problems when dealing with https sites. This is baked in security for any browser, may be bypassed by extensions such has this one ... beware this is dangerous
โผ๏ธ - because the modifications are applied on the fly in the HTML page request, it will work only with good ol' full server rendered pages. Pimping SPAs or ajax content need some reverse engineering at best, or is plain impossible.