view release on metacpan or search on metacpan
devdata/https_mojolicious.io_blog_2018_12_09_add-a-theme-system-to-your-mojolicious-app_ view on Meta::CPAN
<img alt="Four lines of paint drawn on a roller, in green, red, orange and blue" src="/blog/2018/12/09/add-a-theme-system-to-your-mojolicious-app/banner.jpg">
</div>
<div class="post-content">
<section id="section-1">
<p>You wrote an awesome Mojolicious app, and people use it.
Marvellous!
But users may want to modify the theme of your app: change the logo, use another CSS framework, such sort of things.</p>
<p>Modifying the theme of a Mojolicious app is quite easy: add, modify or delete things in <code>public</code> and <code>templates</code>.
But all those direct modifications may not survive on update of the app: they will simply be erased by the files of the new version.</p>
<p>Let's see how we can provide a way to have a theme system in a Mojolicious application, that allows users to have a custom theme without pain and without risk of losing it on updates.</p>
</section>
<section id="section-2">
<h2>A fresh application</h2>
<p>When you create a new Mojolicious app with <code>mojo generate MyApplication</code>, these are the default directories that will serve files and their default contents to be served:</p>
<pre><code>$ tree public templates
public
âÂÂâÂÂâ index.html
templates
âÂÂâÂÂâ example
âÂÂààâÂÂâÂÂâ welcome.html.ep
âÂÂâÂÂâ layouts
âÂÂâÂÂâ default.html.ep
2 directories, 3 files
</code></pre>
<p><code>public</code> is where static files are stored, and <code>templates</code> is where templates are stored.</p>
<p>Those paths are registered in your Mojolicious application in <code>$app->static->paths</code> and <code>$app->renderer->paths</code>.
Luckily, those two objects are array references, so we can add or remove directories to them.</p>
<p>When serving a static file, our application search for the file in the first directory of the <code>$app->static->paths</code> array, and if it does not found it, search in the next directory, and so on.
It goes the same for template rendering.</p>
<h2>Let's change paths</h2>
<p>We could keep the <code>public</code> and <code>templates</code> default directories at the root of the application directory but I like to regroup all the themes-related stuff in a directory called <code>themes</code> and call my default themeâÂ...
<p>Create the new directories and move the default theme directories in it:</p>
<pre><code>$ mkdir -p themes/default
$ mv public templates themes/default
</code></pre>
<p>Then, we need to change the paths in our application.
Add this in <code>lib/MyApplication.pm</code>:</p>
<pre><code># Replace the default paths
$self->renderer->paths([$self->home->rel_file('themes/default/templates')]);
$self->static->paths([$self->home->rel_file('themes/default/public')]);
</code></pre>
<h2>Add a way to use another theme</h2>
<p>As said before, Mojolicious search for static files or templates in the first directory of the registered paths, and goes to next if it can't find the files or templates.</p>
<p>Thus, we need to add our new theme paths before the default ones.</p>
<p>Let's say that we created a <code>christmas</code> theme which files are in <code>themes/christmas/public</code> and which templates are in <code>themes/christmas/templates</code>.</p>
<p>Our snippet to add to the code becomes:</p>
<pre><code># Replace the default paths
$self->renderer->paths([$self->home->rel_file('themes/default/templates')]);
$self->static->paths([$self->home->rel_file('themes/default/public')]);
# Put the new theme first
unshift @{$self->renderer->paths}, $self->home->rel_file('themes/christmas/templates');
unshift @{$self->static->paths}, $self->home->rel_file('themes/christmas/public');
</code></pre>
<p>By doing that way, we can overload the default files.</p>
<p>You don't have to modify each file of the default theme to have a new theme: just copy the files you want to overload in your new theme directory and it will be used instead of the default one.</p>
<p>Let's say that you have a <code>background.png</code> file in your default theme:</p>
<pre><code>$ cd themes/default
$ tree public templates
public
âÂÂâÂÂâ background.png
âÂÂâÂÂâ index.html
templates
âÂÂâÂÂâ example
âÂÂààâÂÂâÂÂâ welcome.html.ep
âÂÂâÂÂâ layouts
âÂÂâÂÂâ default.html.ep
2 directories, 4 files
</code></pre>
<p>In order to overload it, you just have to have this:</p>
<pre><code>$ cd themes/christmas
$ tree public templates
public
âÂÂâÂÂâ background.png
templates
0 directories, 1 files
</code></pre>
<h2>Using Mojolicious::Plugin::Config plugin</h2>
<p><a href="https://mojolicious.org/perldoc/Mojolicious/Plugin/Config">Mojolicious::Plugin::Config</a> comes with Mojolicious itself and is a great way to let users configure your application.
Why not using it to let them choose the theme they want?
devdata/https_mojolicious.io_blog_2018_12_09_add-a-theme-system-to-your-mojolicious-app_ view on Meta::CPAN
});
</code></pre>
<p>Note that I added a default value to the configuration of the plugin.
It makes sure that we will have a correct value for the chosen theme even if the user didn't choose one.</p>
<p>Now, we just have to use that configuration setting in our code:</p>
<pre><code># Replace the default paths
$self->renderer->paths([$self->home->rel_file('themes/default/templates')]);
$self->static->paths([$self->home->rel_file('themes/default/public')]);
# Do we use a different theme?
if ($config->{theme} ne 'default') {
# Put the new theme first
my $theme = $self->home->rel_file('themes/'.$config->{theme});
unshift @{$self->renderer->paths}, $theme.'/templates' if -d $theme.'/templates';
unshift @{$self->static->paths}, $theme.'/public' if -d $theme.'/public';
}
</code></pre>
<p>Note the <code>if -d $theme.'/templates'</code>: it prevents problems if the use made a typo in the name of the theme and allow to avoid creating both <code>templates</code> and <code>public</code> in the theme directory if you only need o...
<h2>Conclusion</h2>
<p>You are now providing a theme system in your application.
Users will now be able to change the style of it without fearing losing their changes on updates (though they will need to check the changes they made in case the default theme changed a lot).</p>
<p>You may even provides different themes yourself, like I did for my <a href="https://framagit.org/fiat-tux/hat-softwares/lstu">URL-shortening app, Lstu</a>àðÂÂÂ</p>
</section>
<small><p><a href="https://unsplash.com/photos/46juD4zY1XA">Photo</a> by <a href="https://unsplash.com/@davidpisnoy">David Pisnoy</a>, <a href="https://unsplash.com/license">Unsplash license</a> (quite similar to public domain)</p>
</small>
<p class="tags">
<span>Tagged in </span>:
<a href="/blog/tag/advent/">advent</a>,
<a href="/blog/tag/theme/">theme</a>
</p>
<div class="bio cf">
devdata/https_mojolicious.io_blog_2018_12_15_practical-web-content-munging_ view on Meta::CPAN
<pre><code>use HTML::WikiConverter;
my $wc = new HTML::WikiConverter(dialect => 'Markdown');
my $md = $wc->html2wiki( $para );
</code></pre>
<p>Done!</p>
<h2>Generating the Metadata</h2>
<p>As we saw earlier, Hugo posts have metadata that precede the Markdown content, and contains information like author information, date of publication, description, etc. Some are optional, but some are mandatory (and I need dates so I can show the m...
<p>I'm going to gloss over how the <code>@entries</code> data structure was built, but I will mention that it's an array of hashes containing the three pieces of data we found above. I'll also link to a GitHub repo with the real world cod...
<pre><code>use Mojo::File;
use String::Truncate qw(elide);
for my $e (@entries) {
my $desc = elide($e->{'text'}, 100, {at_space => 1});
my $md = <<"EOF";
---
devdata/https_mojolicious.io_blog_2018_12_16_browser-diet_ view on Meta::CPAN
<p>"But the JavaScript needs to load <strong>FIRST</strong>!", squeaked the nut-botherer.</p>
<p>Sigh - this really is one demanding Sciurus vulgaris.</p>
<p>Well ... it <em>is</em> Christmas, but you'll need to install the
<a href="https://metacpan.org/pod/Mojolicious::Plugin::StaticCache">StaticCache plugin</a>
written for you only last year by
<a href="https://fiat-tux.fr/">Luc Didry</a>.
It sets the <code>Control-Cache</code> header for all static files served by Mojolicious.
With the <strong>nut.js</strong> and <strong>nut.css</strong> files in the <code>public</code> directory
(properly <a href="https://www.minifier.org/">minified</a> of course),
they should only be downloaded once and use the cached version until it expires.
The default <strong>max-age</strong> is 30 days and
if you want you can even cache during development with <code>even_in_dev => 1</code>.</p>
<p><img class="pull-right" src="speedtest_before_StaticCache.png"></p>
<p>The magpies in the forest had cluttered the calendar with 3 JavaScript libraries,
3 CSS files and 4 logos. Sure, the biggest and shiniest was only 66 kB
and the whole collection was a paltry 164 kB, but bandwidth is precious in the wilderness.
devdata/https_mojolicious.io_blog_2018_12_22_use-carton-for-your-mojolicious-app-deployment_ view on Meta::CPAN
You can do so using the core <a href="https://metacpan.org/pod/lib">lib</a> module, the handy <a href="https://metacpan.org/pod/lib::relative">lib::relative</a> from CPAN, <a href="https://perldoc.pl/perlrun#PERL5LIB">PERL5LIB</a> environment variabl...
<h2>Conclusion</h2>
<p>Carton and cpanfile are a great way to ease Mojolicious apps deployment.
Not only it avoids to list all the dependencies needed by your application in the README or the INSTALL file, but it speeds up deployments and make them more safer, since it sure lowers the risks of bugs due to bad versions of dependencies.</p>
<p><small id="footnote-1">1: or INSTALL, or wherever you put your installation documentation <a href="#back-to-1">â©ï¸Â</a><small></small></small></p>
</section>
<small><p><a href="https://unsplash.com/photos/mTkXSSScrzw">Photo</a> by <a href="https://unsplash.com/@fempreneurstyledstock">Leone Venter</a>, <a href="https://unsplash.com/license">Unsplash license</a> (quite similar to public domain...
</small>
<p class="tags">
<span>Tagged in </span>:
<a href="/blog/tag/advent/">advent</a>,
<a href="/blog/tag/deployment/">deployment</a>,
<a href="/blog/tag/carton/">carton</a>
</p>
devdata/https_mojolicious.io_blog_2018_12_23_mojolicious-and-angular_ view on Meta::CPAN
[write] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/script/mojo_angular_app
[chmod] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/script/mojo_angular_app 744
[mkdir] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/lib
[write] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/lib/MojoAngularApp.pm
[exist] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app
[write] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/mojo_angular_app.conf
[mkdir] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/lib/MojoAngularApp/Controller
[write] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/lib/MojoAngularApp/Controller/Example.pm
[mkdir] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/t
[write] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/t/basic.t
[mkdir] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/public
[write] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/public/index.html
[mkdir] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/templates/layouts
[write] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/templates/layouts/default.html.ep
[mkdir] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/templates/example
[write] /Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/templates/example/welcome.html.ep
Sachin@01:07 PM[~/workspace/project/mojo_angular]$
</code></pre>
<p>Now that the Mojolicious full-app is created, start <a href="https://mojolicious.org/perldoc/Mojo/Server/Hypnotoad">hypnotoad</a>, a production web server.</p>
<pre><code>Sachin@01:07 PM[~/workspace/project/mojo_angular]$ cd mojo_angular_app/
devdata/https_mojolicious.io_blog_2018_12_23_mojolicious-and-angular_ view on Meta::CPAN
Sachin@02:06 PM[~/workspace/project/mojo_angular/NgDemo]$
</code></pre>
<p>Note that <code>dist</code> directory is created with <code>NgDemo</code> folder which contains the compiled angular app files:</p>
<pre><code>Sachin@02:10 PM[~/workspace/project/mojo_angular/NgDemo]$ ls
README.md angular.json dist e2e node_modules package-lock.json package.json src tsconfig.json tslint.json
Sachin@02:10 PM[~/workspace/project/mojo_angular/NgDemo]$
</code></pre>
<p>Next, we'll copy everything within the folder dist/ to a folder on the <code>public</code> directory in Mojolicious app.
The Mojo full app consists of <code>public</code> directory which is a static file directory (served automatically).
Copy Angular app compiled into <code>dist</code> to <code>public</code> directory of mojo app so that mojo will serve automatically.</p>
<pre><code>Sachin@02:13 PM[~/workspace/project/mojo_angular/NgDemo]$ cp dist/NgDemo ~/workspace/project/mojo_angular/mojo_angular_app/public/
</code></pre>
<p>Of course if this were a proper project, you could generate it to build in the public directory, or even build at start time using something like <a href="https://metacpan.org/pod/Mojolicious::Plugin::AssetPack">Mojolicious::Plugin::AssetPack</a>....
<p>Finally, let's run the Mojo (hypnotoad) server to see if the Angular page is served as it was from the Angular server.</p>
<pre><code>Sachin@02:48 PM[~/workspace/project/mojo_angular/mojo_angular_app]$ hypnotoad -f script/mojo_angular_app
[Sat Dec 15 14:49:03 2018] [info] Listening at "http://*:8080"
Server available at http://127.0.0.1:8080
[Sat Dec 15 14:49:03 2018] [info] Manager 40633 started
[Sat Dec 15 14:49:03 2018] [info] Creating process id file "/Users/Sachin/workspace/project/mojo_angular/mojo_angular_app/script/hypnotoad.pid"
[Sat Dec 15 14:49:03 2018] [info] Worker 40634 started
[Sat Dec 15 14:49:03 2018] [info] Worker 40635 started
devdata/https_mojolicious.io_blog_2018_12_23_mojolicious-and-angular_ view on Meta::CPAN
th, td {
padding: 5px;
text-align: left;
}
Sachin@12:34 AM[~/workspace/project/mojo_angular/NgDemo/src/app]$
</code></pre>
<h4>Build the Angular app</h4>
<p>Run <code>ng serve</code> command(as earlier) to see if angular app is looking good.
Then run <code>ng build --base-href=./</code> and copy <code>dist</code> folder content to mojolicious app's <code>public</code> directory as shown earlier.</p>
<h2>Try it out!</h2>
<p>Finally run <code>hypnotoad</code> as shown earlier
Visit <code>localhost:8080/NgDemo</code> in browser to witness wedding of Mojolicious and Angular:</p>
<p><img alt="final mojolicious serving angular SPA" src="final_mojo_angular_app.png"></p>
<p>That's all you need for simple single page app with angular and mojo. Take it further and see what is possible!</p>