Linking assets in express js depending on environment - javascript

Is there a way of linking assets in my html files (to be precise: nunjucks files in my case) depending on the environment? I want to include several partial *.css or *.js files in the dev env in order to debug it easier and one concatanated *.min.css and *.min.js file in prod env (kind of like with assetic in Symfony).

You would probably need to do this via a build process. Good task runners include Grunt and Gulp.
You could use something like grunt-usemin. This would allow you to put direct script/link tags in your html, then run a grunt command to combine them.

It would vary based on your template system but the basics are like this:
In controller - make the env available to your template:
res.render("template", {
env: process.env.NODE_ENV || 'development'
});
Template:
{% if env === 'development' %}
<script>....</script>
<script>....</script>
{% else %}
<script src="prod.min.js"></script>
{% endif %}
In addition use the answer by #vernak2539 to build your minify your prod.min.js file using gulp or grunt and the minify/uglify/concat plugins or do it 'manually' using things like CodeKit.

Related

Passing Symfony Translation to Symfony Webpack Encore

With Symfony, I use translation, Twig and Webpack encore components.
I can translate in frontend Twig with :
'my_key'|trans
I use command yarn encore dev for generate my app.js, but PHP translation component it's not accessible in Javascript.
I have a lot of things to translate in javascript.
Unfortunately since JS is not handled by PHP and by extension also not by Symfony, you will not have access to Symfony's Translation component inside your js files.
A workaround that could work when you don't have too many translations you need to pass is create a JS data object inside your twig template as part of your Symfony application and then access it from your js files. So roughly like this:
# inside your twig template, e.g. index.html.twig
{% block javascripts %}
<script type="text/javascript">
const TRANSLATION_MAP = {
'my_key': "{{ 'some_key '|trans }}",
'my_other_key': "{{ 'other_key '|trans }}"
};
</script>
{{ parent() }} # This loads all your js files which can then access the translation map defined above
{% endblock %}
The downside to this solution is, that you have to decide which keys to put in your translation map without really knowing whether they are used, so this might become a bit inefficient and hard to follow. Also you have to be careful that your translated content is valid json. You can apply (custom) escaping/filtering to ensure that, but still makes it a bit fragile.
All in all, this might not be the best solution but can be a decent workaround for smaller projects until you find it becomes more of a nuisance and you have to find something more sophisticated.
You have to use BazingaJsTranslationBundle which allows you to access translations you have exposed through javascript:
Translator.trans('key', {}, 'DOMAIN_NAME');
Translator.transChoice('key', 1, {}, 'DOMAIN_NAME');
You could transform translations yaml to json with webpack or a task manager (gulp | grunt).
Put built json translation files to assets.
Require them inside js script.
Emit locale value to frontend to choose proper translation json object inside js script.
Note: in webpack case you should run 2 steps consequently:
first step will build translations to assets to be required in js scripts.
second step will compile js scripts afterwards.
Here is a part of webpack encore config which transforms translations *.yaml files to json with 'js-yaml' and puts them into assets directory:
.addPlugin(new CopyWebpackPlugin(
{
patterns: [
{
from: './translations/*.yaml',
to: './[name].json',
transform(content) {
return Buffer.from(
JSON.stringify(
yaml.safeLoad(content.toString('utf8'), {schema: yaml.JSON_SCHEMA})
),
'utf8'
)
}
}
],
}));

How to concat and bundle JS files using webpack to be loaded in a script tag

I'm struggling to find a "quick" way to transition some legacy javascript files from assetic to webpack. I (think I) need to find a way to bundle a set of javascript files by simply concatenating them and not having webpack wrap it in a jsonp.
Our platform is built in Symfony, using twig templates and we've got most of our legacy javascript files loaded using assetic (it simply concatenates, and minifies), we've got newer javascript/typescript files bundled with webpack.
Naturally I want to remove all assetic dependencies and use webpack to bundle the old javascript. However, due to the way that webpack produces the bundles, it gets wrapped in the jsonp and any functions/variables defined in those javascript files are not available in global scope.
In an assetic world, our twig files are littered with these:
{%- javascripts
'%kernel.root_dir%/../vendor/twbs/bootstrap/js/bootstrap-transition.js'
'#MyBundle/Resources/js/legacy-1.js'
'#MyBundle/Resources/js/legacy-2.js'
filter="?uglifyjs2"
output="js/compiled/page-one.js" %}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}
Which concatenates and minifies them all. I'd like to replace this with a similar webpack version.
I've identified some approaches, but I'm not sure any of them are the best.
Use script-loader to get the scripts to load in global scope - Since script-loader uses eval which is disabled using CSP, this is out of the question
Rewrite each javascript file, exposing the members on the window. - This would probably be the easiest option but I really don't like the idea, and it goes against best practices.
Rewrite each javascript file, exporting the members, then use the expose-loader to load the files, then go through all the twig files and update the references to the members with the global namespace. This would take a lot of time, and I'd prefer not to do this if at all possible
I figured that I could simply configure a queryParameter and the file-loader to load each one, but I'd need a script tag per resource, and would lose out on the bundling nature. (I use a Symfony webpack bundle to generate an entries list for webpack, and provide twig functions for loading entries at runtime from the cdn)
<script src='{{ webpack_asset('%kernel.root_dir%/../vendor/twbs/bootstrap/js/bootstrap-transition.js?asFile') }}"></script>
<script src='{{ webpack_asset('#MyBundle/Resources/js/legacy-1.js?asFile') }}"></script>
<script src='{{ webpack_asset('#MyBundle/Resources/js/legacy-2.js?asFile') }}"></script>
So I'm looking for a way to concatenate the files together. Creating a separate entry file, and loading that as a file wouldn't be good enough as the requires/imports wouldn't be resolved.
I understand the webpack-concat-plugin would require me to define all of the various files to concat up front in the webpack config, which isn't really possible.
I haven't come across a loader that would replace the import '#MyBundle/Resources/js/legacy-2.js' with the content of that js file - but maybe I'm missing something.
I wonder if after all of this effort trying to find an easy solution is greater than the effort to rewrite the legacy javascript to use modules...
I found the following answers but they don't really help me:
Webpack - How to load non module scripts into global scope | window - This assumes I can define the files to concatenate up front. I have almost 100 different includes using assetic, almost all of them use different files.
Webpack - How to load non module scripts into global scope | window - eval is disabled by our CSP
Expose javascript globals bundled via webpack - As I mentioned, I don't really want to create 100 'entry' files just for this, and the webpack-raw-bundler also requires everything to be defined up front in the webpack config file.

Get js variable transpiled by Symfony4 Webpack Encore in twig / FosJsRouting bundle

I use fos_js_routing bundle on symfony4 . I need to get the Routing object reachable in my twig view. I defined Routing in assets/js/app.js, a transpiled js file with Webpack Encore.
Because my Routing object is correctly built in this file,
I want to access it in a Twig view.
// assets/js/app.js
const routes = require('../../web/js/fos_js_routes.json');
import Routing from '../../vendor/friendsofsymfony/jsrouting-bundle/Resources/public/js/router.min.js';
Routing.setRoutingData(routes);
I got this line in my webpack config:
Encore
.setOutputPath('public/build/')
.setPublicPath('/build')
.cleanupOutputBeforeBuild()
.enableSourceMaps(!Encore.isProduction())
.addEntry('js/app', './assets/js/app.js')
I get my twig view, where the transpiled app.js is reachable and succesfully transpiled by webpack Encore. But the variable Routing is not reachable ( I got client error : Routing is not defined),
// in my twig view, inside a script block
var redirectionUrl = Routing.generate('my_route', {arg: arg}); // Routing is not defined
probably because is defined as let in the transpilation process, and I want it as a var, to be reachable in each twig view where I include transpiled app.js ( public/build/js/app.js ). My Twig view is including the transpiled file in this line, and succesfully imported in my twig sources
<script src="{{ asset('build/js/app.js') }}"></script>
How can I get Routing in my twig view using Webpack encore ?
AUTO ANSWER :
As far as i understood ( please comment if I'm wrong ), because Webpack is transpiling ES6 syntax to ES5 syntax and because in this process all useless and unrevelant data is erased in your production js transpiled files
( when you call the command: node_modules/.bin/encore production ). So as far as i know, you cannot pass unused data between ES6 js file transpiled to an ES5 js file with Encore. Maybe entering in advanced configuration of Webpack but i didnt spend the time for that ( and this is not usually what you want to deal with transpilers ). My using of fosjsrouting bundle in my project wasn't essential, so I just removed it. But today i will explain how to properly load fosjsrouting Routing object from your twig templates ( instead of trying to access it from your Encore transpiled js files ).
1- Read documentation of FosJsRouting Bundle here
in order to :
--- A/ Install the bundle
--- B/ Build your routes
I don't remember the commands, but everything is well explained in the documentation link provided.
At the end of your bundle installation, you must get :
1 - router.min.js file in your public/ folder => ./public/bundles/fosjsrouting/js/router.min.js
2 - fos_js_routes.json in your public/ folder =>
./public/js/fos_js_routes.json
Now I show you how to generate a Route on a basic twig template view.
{% extends "base.html.twig" %}
{% block javascripts %}
<script src="{{ asset('bundles/fosjsrouting/js/router.min.js')}}">
</script>
<script>
var r = Routing; // loaded from router.min.js
// because ES5 doesnt support require syntax,
// we use jquery getJSON function in order to set
// routing data to our Routing object
$.getJSON("{{ asset('js/fos_js_routes.json')}}", function(routes) {
console.log(routes);
r.setRoutingData(routes);
console.log(r.generate('my_route'));
});
</script>
{% endblock %}

Conditionally include JS script in html

I am making a simple website hosted on Github Pages and use CDN to include several script files:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.js"></script>
During development on a local machine it is sometimes useful to include full versions of scripts to simplify debug, but on target server I'd prefer to use minified scripts, i.e. include angular.min.js instead of angular.js. Is there a way to have "conditional include" in html, or do Github Pages have some mechanism to substitute parts of last submitted files?
The Jekyll documentation says you can use the Jekyll environment to conditionally include code when building the site. For your case:
...
{% if jekyll.environment == "production" %}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
{% else %}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.js"></script>
{% endif %}
...
Another option suggests using another config file specifically for development.
_config.yml sets environment: prod.
_config_dev.yml sets environment: dev.
Running jekyll build --config _config.yml,_config_dev.yml in development, the second config file overrides the first and sets the environment to dev. GitHub Pages runs jekyll build - which only uses _config.yml - and would have environment set to prod. You could use a similar syntax to the above to conditionally include the file.
...
{% if site.environment == "prod" %}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
{% else %}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.js"></script>
{% endif %}
...

Include javascript dojo files via Twig in Symfony 2

I try to achieve a simple goal: add developped dojo classes in a symfony2 html page with Twig.
I had read a lot of documentation on assetic, and i found two types of method to include assets:
The first one with the asset() twig function. And the second one with the "javascripts" tag.
The assets function is used to include files which are stored in the web/ folder whereas javascripts tag load files which are stored in the resource folder of the current bundle.
My dojo classes files are stored in the resource folder of a bundle, so i tried to load them with a javascripts tag like this:
{% javascripts '#MyBundle/Resources/public/js/MyClass.js' output= 'js/myBundle/MyClass.js' %}
<script type='text/javascript' src='{{ asset_url }}'></script>
{% endjavascripts %}
And it work, my file is successfully included, unfortunately the name is not "MyClass.js" but "MyCmass_MyClass_1.js". I have executed the assetic:dump --env=prod command but my file name still "MyCmass_MyClass_1.js"
What can I do to correct this ?
(I tried to delete the cache, relaunch my server in prod/dev, launch the assetic command with the dev env, and no change).
Visit your site in production mode: yoursite.com/app.php instead of yoursite.com/app_dev.php, or point your webserver to correct file in web folder: web/app.php.

Categories

Resources