Giter Site home page Giter Site logo

twig-assets's Introduction

Twig Assets Extension

Caching and compression for Twig assets (JavaScript and CSS), inspired by Symfony Web Assets.

Latest Version on Packagist Software License Build Status Code Coverage Quality Score Total Downloads

Installation

composer require odan/twig-assets

Requirements

  • PHP 7.4, 8.1, 8.2
  • Twig 3

Configuration

$options = [
    // Public assets cache directory
    'path' => '/var/www/example.com/htdocs/public/assets/cache',
    
    // Public cache directory permissions (octal)
    // You need to prefix mode with a zero (0)
    // Use -1 to disable chmod
    'path_chmod' => 0750,
    
    // The public url base path
    'url_base_path' => 'assets/cache/',
    
    // Internal cache settings
    //
    // The main cache directory
    // Use '' (empty string) to disable the internal cache
    'cache_path' => '/var/www/example.com/htdocs/temp',
    
    // Used as the subdirectory of the cache_path directory, 
    // where cache items will be stored
    'cache_name' => 'assets-cache',
    
    // The lifetime (in seconds) for cache items
    // With a value 0 causing items to be stored indefinitely
    'cache_lifetime' => 0,
    
    // Enable JavaScript and CSS compression
    // 1 = on, 0 = off
    'minify' => 1
];

Integration

Register the Twig Extension

$loader = new \Twig\Loader\FilesystemLoader('/path/to/templates');
$twig = new \Twig\Environment($loader, array(
    'cache' => '/path/to/compilation_cache',
));

$twig->addExtension(new \Odan\Twig\TwigAssetsExtension($twig, $options));

Slim 4 Framework

Requirements

Run:

composer require slim/twig-view

Add these settings:

// Twig settings
$settings['twig'] = [
    'path' => __DIR__ . '/../templates',
    // Should be set to true in production
    'cache_enabled' => false,
    'cache_path' => __DIR__ . '/../tmp/twig-cache',
];

// Twig assets cache
$settings['assets'] = [
    // Public assets cache directory
    'path' => __DIR__ . '/../public/cache',
    // Public url base path
    'url_base_path' => 'cache/',
    // Internal cache directory for the assets
    'cache_path' => __DIR__ . '/tmp/twig-assets',
    'cache_name' => 'assets-cache',
    //  Should be set to 1 (enabled) in production
    'minify' => 1,
];

Add a DI container definition.

This examples uses PHP-DI

<?php

use Odan\Twig\TwigAssetsExtension;
use Psr\Container\ContainerInterface;
use Slim\App;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
use Twig\Loader\FilesystemLoader;

return [
    // ...

    Twig::class => function (ContainerInterface $container) {
        $settings = $container->get('settings');
        $twigSettings = $settings['twig'];

        $twig = Twig::create($twigSettings['path'], [
            'cache' => $twigSettings['cache_enabled'] ? $twigSettings['cache_path'] : false,
        ]);

        $loader = $twig->getLoader();
        if ($loader instanceof FilesystemLoader) {
            $loader->addPath($settings['public'], 'public');
        }

        $environment = $twig->getEnvironment();

        // Add Twig extensions
        $twig->addExtension(new TwigAssetsExtension($environment, (array)$settings['assets']));

        return $twig;
    },

];

Add the TwigMiddleware. In this case we pass the full class name Twig::class as the second parameter, because the container entry is defined with the same name.

use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;

// ...

$app->add(TwigMiddleware::createFromContainer($app, Twig::class));

Add a route, e.g. in confg/routes.php:

$app->get('/', \App\Action\Home\HomeAction::class)->setName('root');

Create a action class, e.g. src/Action/HomeAction.php:

<?php

namespace App\Action\Home;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Views\Twig;

/**
 * Action.
 */
final class HomeAction
{
    /**
     * @var Twig
     */
    private $twig;

    /**
     * The constructor.
     *
     * @param Twig $twig The twig engine
     */
    public function __construct(Twig $twig)
    {
        $this->twig = $twig;
    }

    public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        return $this->twig->render($response, 'home/home.twig');
    }
}

The (pseudo) content of templates/home/home.twig:

<html>
    <head>
        {{ assets({files: ['home/index.css']}) }}
    </head>
    <body>
        {{ assets({files: ['home/index.js']}) }}
    </body>
</html>

Read more: Usage

Slim 3 Framework

Requirements

In your dependencies.php or wherever you add your Service Factories:

$container[\Slim\Views\Twig::class] = function (Container $container) {
    $settings = $container->get('settings');
    $viewPath = $settings['twig']['path'];

    $twig = new \Slim\Views\Twig($viewPath, [
        'cache' => $settings['twig']['cache_enabled'] ? $settings['twig']['cache_path']: false
    ]);

    /** @var \Twig\Loader\FilesystemLoader $loader */
    $loader = $twig->getLoader();
    $loader->addPath($settings['public'], 'public');

    // Instantiate and add Slim specific extension
    $basePath = rtrim(str_ireplace('index.php', '', $container->get('request')->getUri()->getBasePath()), '/');
    $twig->addExtension(new \Slim\Views\TwigExtension($container->get('router'), $basePath));
    
    // Add the Assets extension to Twig
    $twig->addExtension(new \Odan\Twig\TwigAssetsExtension($twig->getEnvironment(), $settings['assets']));

    return $twig;
};

Usage

Custom template functions

This Twig extension exposes a custom assets() function to your Twig templates. You can use this function to generate complete URLs to any Slim application assets.

Parameters

Name Type Default Required Description
files array [] yes All assets to be delivered to the browser. Namespaced Twig Paths (@mypath/) are also supported.
inline bool false no Defines whether the browser downloads the assets inline or via URL.
minify bool true no Specifies whether JS/CSS compression is enabled or disabled.
name string file no Defines the output file name within the URL.
nonce string no The CSP (content security policy) nonce (per request)

Template

Output cached and minified CSS content

{{ assets({files: ['Login/login.css']}) }}

Output cached and minified CSS content inline:

{{ assets({files: ['Login/login.css'], inline: true}) }}

Output multiple CSS assets into a single .css file:

{{ assets({files: [
    '@public/css/default.css',
    '@public/css/print.css',
    'User/user-edit.css'
    ], name: 'layout.css'})
}}

Output cached and minified JavaScript content

{{ assets({files: ['Login/login.js']}) }}

Output multiple JavaScript assets into a single .js file:

{{ assets({files: [
    '@public/js/my-js-lib.js',
    '@public/js/notify.js',
    'Layout/app.js'
    ], name: 'layout.js'})
}}

Output page specific assets

Content of file: layout.twig

<html>
    <head>
        {% block assets %}{% endblock %}
    </head>
    <body>
        {% block content %}{% endblock %}
    </body>
</html>

Content of home.twig:

{% extends "Layout/layout.twig" %}

{% block assets %}
    {{ assets({files: ['Home/home.js'], name: 'home.js'}) }}
    {{ assets({files: ['Home/home.css'], name: 'home.css'}) }}
{% endblock %}

{% block content %}
    <div id="content" class="container"></div>
{% endblock %}

Add custom attributes to the html element

WARNING: you can override ANY attribute including i.e. href. Be careful here as it can cause unwanted results.

{{ assets({files: [
    '@public/css/default.css',
    '@public/css/print.css',
    'User/user-edit.css'
    ], attributes: {
    rel: 'preload',
    as: 'style',
    onload: 'this.onload=null;this.rel=\'stylesheet\''
    }, name: 'layout.css'})
}}

Configure a base path

You should inform the browser where to find the web assets with a base href in your layout template.

Slim Twig example:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <!-- other stuff -->
    <base href="{{ base_path() }}/"/>
    <!-- other stuff -->

Clearing the cache

Clearing the internal cache

use Odan\Twig\TwigAssetsCache;

$settings = $container->get('settings');

// Internal twig cache path e.g. tmp/twig-cache
$twigCachePath = $settings['twig']['cache_path']; 

$internalCache = new TwigAssetsCache($twigCachePath);
$internalCache->clearCache();

Clearing the public cache

use Odan\Twig\TwigAssetsCache;

$settings = $container->get('settings');

// Public assets cache directory e.g. 'public/cache' or 'public/assets'
$publicAssetsCachePath = $settings['assets']['path'];

$internalCache = new TwigAssetsCache($publicAssetsCachePath);
$internalCache->clearCache();

Testing

composer test

Similar libraries

License

The MIT License (MIT). Please see License File for more information.

twig-assets's People

Contributors

dependabot-preview[bot] avatar dependabot[bot] avatar odan avatar pararang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

twig-assets's Issues

Cannot enable cache

The Last-Modified header is always set to now, the browser is always refreshing my assets.
The configuration $settings['assets']['cache_enabled'] key seems not to have effect.

Twig 3 compatibilty

Hey Daniel,

would it be possible for you to look into making twig-assets compatible with twig 3? (The blocker is twig-extensions at version ^1.5, as far as I can see).

Thanks lots, Pavel

Compatibility with Symfony 6

Hello,

Could it be possible to update the library to make it compatible with Symfony/cache V6.0 and higher?

Best regards!

Load an images

Hi i have trouble using this plugin, i can't load an images, but everything fine on css files. this is my code

settings

<?php
   14 $settings['base_url'] = 'http://localhost:8080';
   15 $settings['root'] = dirname(__DIR__);
   16 $settings['public'] = $settings['root'] . '/public'; 
   17 $settings['templates'] = $settings['root'] . '/templates';
   18 $settings['cache'] = [ 
   19     'twig' => $settings['root'] . '/cache',
   20     'route' => $settings['root'] . '/cache/route_cache.php'
   21 ];
   22
+  23 $settings['assets'] = [
+  24     // Public assets cache directory
+  25     'path' => $settings['public'] . '/assets/local/cache',
+  26     'url_base_path' => $settings['base_url'] . '/assets/local/cache/',
+  27     //  Should be set to 1 (enabled) in production
+  28     'minify' => 1,
+  29 ];

Container

<?php
   31     Twig::class => function (ContainerInterface $container) {
   32     ¦   $config = $container->get(Configuration::class);
~  33     ¦   $twig = Twig::create(
   34     ¦   ¦   $config->getString('templates'),
   35     ¦   ¦   [
   36     ¦   ¦   ¦   'cache' => $config->getString('cache.twig')
   37     ¦   ¦   ]
   38     ¦   );
+  39
+  40     ¦   $loader = $twig->getLoader();
+  41
+  42     ¦   if ($loader instanceof FilesystemLoader) {
+  43     ¦   ¦   $loader->addPath($config->getString('public'), 'public'); 
+  44     ¦   }
+  45
+  46     ¦   $env = $twig->getEnvironment();
+  47
+  48     ¦   // Add Twig extensions
+  49     ¦   $twig->addExtension(new TwigAssetsExtension($env, $config->getArray('asset
      s')));
+  50     ¦   return $twig;
   51     },

Unable to set visibility for file

An exception has been thrown during the rendering of a template:

Unable to set visibility for file minified_javascript_core_fca972a47fddc27c98d276c1370ebabe.js. chmod(): Operation not permitted.

Generate absolute url

Is there a way to generate absolute assets URLs instead of relative ones?
Ideal would be if the base url for the assets could be specified somewhere.

Thanks for the nicely working package.

How can we disable the cache feature?

Hi!

We would like to disable the cache feature but the cache_enabled option is never used in the code. Is there any other way to disable this feature?

Thank you!

Is there any way to configure the public path?

Hello again :)

I am working on a multi module application, which have a shared assets folder. This is my folder structure for public directory

www
     assets: shared assets for all modules
     +semantic-ui.css
     +custom.css
     auth: auth module
     + index.php - auth module loader
     finances:
     + index.php - finances module loader

When the user hits /finances/{controller}/{method}/{args}, everything is redirected to /finances/index.php, which load a shared source code (using Slim Framework 3). This setup works fine since the application is completely modular, but there is a problem when using twig-assets extension.

Here my settings:

$options = 
[
    'path' => '/var/www/app/htdocs/public/assets/cache',
    'path_chmod' => -1,
    'cache_enabled' => true,
    'cache_path' => '/var/www/app/htdocs/temp',
    'cache_name' => 'assets-cache',
    'cache_lifetime' => 0,
    'minify' => 1
];

And the assets code:

{{ assets({files: [
'@public/semantic-ui.css',
'@public/custom.css',
], name: 'bundle.css'})
}}

The problem is that assets code generate a URL referencing the current folder, which in that case will be the modules folder (eg: finances), not shared assets. This is the expected behavior, not a bug, but in my case i want to customize this path.

Generated URL: cache/bundle.{hash}.css

I wanted to set the generated URL manually to /assets/cache/bundle.{hash}.css.

Is there a way to do this?

Eg: This could solve the problem:

{{ assets({files: [
    '@public/semantic-ui.css',
    '@public/custom.css',
    ], name: 'bundle.css', base_path: '/assets/'})
}}

or

$options = 
[
    'base_path' => '/assets/',
];

What do you think about this?

Thank you.

Support Symfony Cache 5

Currently the usage of this packages is limited to symfony/cache ^3.2|4.0. Any plans to make this package to play with symfony/cache 5.* as well?

Thanks!

CSP nonces for assets

Hi, I'm having problems with making twig-assets play with CSP (content security policy) headers. It seems, that doing something like

{{ assets({files: [
  '@public/assets/node_modules/react/umd/react.production.min.js',
  '@public/assets/node_modules/react-dom/umd/react-dom.production.min.js',
  '@public/assets/node_modules/react-jsonschema-form/dist/react-jsonschema-form.js',
  '@public/assets/node_modules/@babel/standalone/babel.min.js',
], name: 'rjsf.js'}) }}

will not work with, i.e. https://github.com/middlewares/csp

use Middlewares\Csp;
use ParagonIE\CSPBuilder\CSPBuilder;
// some other middleware code here...
$csp = new CSPBuilder($settings['headers']['csp']);
$nonce['script_src'] = $csp->nonce('script-src');
$app->add(new Middlewares\Csp($csp));

and fail with something like

rjsf.b931541e8784057844bd090891f00c90a5df3833.js:19 Refused to execute inline script because it violates the following Content Security Policy directive: "script-src https://10.146.149.29 http://10.146.149.29 10.146.149.29 'nonce-HKva7wv9fBswKZtpuJi35815' 'unsafe-inline' 'unsafe-eval'". Note that 'unsafe-inline' is ignored if either a hash or nonce value is present in the source list.

My guess is, that since there's no easy way to add csp nonces or hashes to the resulting rjsf.js, and the rjsf.js has inline <script>, the CSP just kills the script. If my reasoning is correct, then twig-assets would need a way how to optionally add

  1. either a nonce parameter, that could be passed to it via a twig global
{{ assets( { files: [ ...], name: 'rjsf.js', nonce: cspnonce } ) }}
  1. add a twig function which would calculate hashes of the content enveloped by the function (and with ob turned on) modify response headers as done by i.e. https://github.com/nelmio/NelmioSecurityBundle#message-digest-for-inline-script-handling , so i.e.
{% cspscript %}
<script>
    window.api_key = '{{ api_key }}';
</script>
{% endcspscript %}

If I overlooked something and there's an easy way out of this problem, would you kindly point it out for me?

Deletes public/cache folder but wont create new

Hi, i have applied the "clear cache"-mechanism to a test project.
It sure does clear the cache.

But it wont recreate the public/cache/ folder, i have to create it manually.
I've checked the permissions, they are all clear... And SELinux is not issue.

As soon as i "mkdir public/cache/", everything works and the twig-assets is starting to produce cache.

Twig error

Hi @odan , I'm trying to use this in my slim4 project but I can't get it to work.

I keep getting

Type: Twig\Error\LoaderError
Code: 0
Message: Unable to find template "cdn/assets/bootstrap.bundle.js" (looked into: C:\Users\antek\PhpstormProjects\kompcontrol/app/templates/shop).

I'm not sure as to why assets look into templates dr?

I use it like this in twig
{{ assets({files: ['cdn/assets/bootstrap.bundle.js']}) }}

But I get that error no matter what I do. I think the code is ok. I'm not sure about the base path setting as I'm not using the "Public dir"

My settings:
image

Here is my dependency code

image

Middleware
image

Not sure what am I doing wrong. Seems ok. All the paths are correct.

Thank you for any help @odan !

Subfolder URL error for assets cache

I have an application installed in a subfolder, I have to use a {{base_path()}} in all html url tags to create the correct relatives URL.
I'm facing an issue with this library because the cache folder is not mapped with base_path prefix, my current settings are:

$settings['assets'] = [
    // Public assets cache directory
    'path' => $settings['root'] . '/public/cache',
    'url_base_path' => 'cache/',
    // Cache settings
    'cache_enabled' => true,
    'cache_lifetime'=>0,
    'cache_path' => $settings['temp'],
    'cache_name' => 'assets-cache',
    //  Should be set to 1 (enabled) in production
    'minify' => 0,
];

From this page http://localhost/myapp/
the loaded cache works, looking into http://localhost/myapp/cache
but if I use a subfolder like this: http://localhost/myapp/auth/login
the rendered assets has this URL http://localhost/myapp/auth/cache and it is not found.

I tried to change thesetting 'url_base_path' to '{{base_path()}}/cache/', but if doesn't work.

An exception has been thrown during the rendering of a template ("chmod(): Operation not permitted").

It is possible to add a try catch around chmod or some configurable manner to avoid it? Because the script are working great but i am having issues on some enviroiments that i don't have the permission to do chmod (but have the permission to write). Also i usually set my files permission to 750.

A simple example is my WSL on Windows 10, i don't know why but i cannot use chmod inside my web directory (maybe because it's a symlink to a NTFS driver, probably)

Also, thank you, you saved me a lot of work because i was thinking to do a extension like this.

chmod

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.