sootsnoot
sootsnoot

Reputation: 2226

Cache busting static assets in Zend Framework 1 application

I'm feeling pretty lost and stupid in the brave new world of "modern" web applications with node.js, module bundlers, task runners, and such.

I have a working Zend Framework (ZF 1) PHP application (which also embeds WordPress multi-site allowing users to create their own blog sites). It is hosted on an Apache server with mod_php. It uses html tables a fair bit for forms and data displays, but thankfully not for entire page layouts, though the css is based on a fixed-width page of 1000px.

The application began development under the notion that javascript should be used only for "progressive enhancement", though eventually we succumbed to requiring that javascript be enabled in order to get correct behavior. We support signup and login using OAuth2 authentication through several providers (Facebook, Google, LinkedIn, Twitter and others), but only via the server flow not the javascript SDKs. We use jQuery and limited amounts of Zend_Dojo javascript libraries plus a handful of homegrown javascript functions (in addition to whatever WordPress uses).

We fairly recently added an Nginx reverse proxy in front of the original Apache webserver. It hosts our ssl certificate and serves static file assets.

Now we're looking to move toward a more responsive design to better accommodate mobile and tablet users, and musing about progressive web apps. So major changes to css and increased use of javascript are in the cards. Although the Nginx serving of static assets gives us eTags, Google Page Speed Insights tells us that we have blocking downloads of javascript and css resources, and that we don't take advantage of browser caching.

It appears from various articles I've seen that the Webpack bundling tool can provide major help in addressing all of these performance bottlenecks. But for the life of me I don't see how it fits in to this ecosystem. My mental model of how our site works is that an http query is analyzed by PHP code, dispatched to a PHP action routine that accesses session data and our MySQL database, and then outputs html via phtml templates (ZF1 view scripts) that contain embedded PHP tags. The phtml templates may contain <script>, <style>, and <img> tags, either directly or by being injected into the html by other PHP functions that manage the overall page layout and the content of its <header> section.

But when I look at Webpack, it seems like it's expecting some sort of top-level javascript file from which it can build a dependency tree of other javascript and css files via import or require directives, or something. And it somehow supports cache-busting by hashing the contents of static asset files, creating new files from them with the hash value embedded in the file name, and editing the references to those files to include the hash value. But for this application, all of the references to javascript, css, and image files appear either in .phtml files (usually within embedded <?php> tags) or in pure .php files. Yet webpack doesn't appear to process php files at all - so I don't see how it can either find the references to javascript, css, and image files, or edit them to include a hash! And the articles I've seen about using webpack in PHP projects don't seem to mention this issue at all. There's an html loader, but not one for PHP. Is there some sort of standard practice about using javascript in an independent modular fashion within PHP sites instead of using <script> tags that I just don't know about?

And finally, different web pages have different requirements for javascript and styles, while webpack seems to want a single javascript main entry point from which all dependencies can be found. Does using webpack in this ecosystem imply making a separate webpack project for each page? I've read lots of articles about webpack, but they all seem to be dealing with web apps that are not structured at all like mine!

I did read this answer here on Stackoverflow which I expected would enlighten me. It came fairly close - it explained that I do need to create a top-level index.js file that requires all of the other javascript files. But since different pages use different javascript, I deduced that I would need to create a different index.js file for each page (and thus treat each page as a different project). Can that be true? Many articles talk about "single page apps", so perhaps that's just the assumption in these kinds of descriptions. Or maybe I need to understand "Code Splitting". Maybe if I keep reading that answer over and over again I'll eventually get the gist. It talks about CSS and style-loader and css-loader, but it's not clear to me how the <style> tags present in my .phtml files get processed by them (not to mention styles enqueued in WordPress code). I've attempted the SurviveJs and Official Webpack documentation, but again, they seem to be talking about a different universe than the one I live in. I'm thinking that a Rosetta stone exists somewhere that would map this new world back to traditional PHP apps! Any pointers?

Upvotes: 3

Views: 793

Answers (1)

Jarek.D
Jarek.D

Reputation: 1294

It's an old question, but I attempt to give some pointers, as I've just went through similar hurdle: trying to integrate Webpack with legacy ZF1 app to do:

  1. Asset bundling
  2. Cache busting via appending version hash to filename
  3. Catering for different bundles for different app routes

My approach:

First I checked in newer versions of Zend_View provide some solutions for versioning front end assets. I found this:

https://docs.zendframework.com/zend-view/helpers/asset/

and really liked the idea of encapsulating versioning concerns in separate config file. Obviously to be able to use this format I either have to use this zend_view helper in legacy app, or simply extend legacy zend_view class and add ->asset method that simply reads in the resource map of this format:

'view_helper_config' => [
    'asset' => [
        'resource_map' => [
            'css/style.css' => 'css/style-3a97ff4ee3.css',
            'js/vendor.js' => 'js/vendor-a507086eba.js',
        ],
    ],
],

The additional advantage of sticking to this format is that once you'll upgrade your app to newer version of Zend Framework or Zend Expressive, you don't need to change anything, just start using Asset helper of modern Zend_View.

Once we have a map like that we need to make webpack write it. So HtmlWebpackPlugin is not restricted to html files. We can write our own template and have a full control over how templates being written with webpack variables (such as asset name and hash). The big advantage here is that webpack doesn't need to overwrite typically numerous view templates that can turn into a mess and has its own problems (eg. what if we include scripts in controllers via headScript calls?) - it only writes the map. This bit solves the issue #2 - cache busting. Issues #1 and #3 - asset bundling and creating different bundles can now be solved native webpack way - by creating multiple bundles and then writing config file using our custom template:

const path = require('path');

 module.exports = {
     mode: 'development',
     entry: {
         'js/vendor.js': './frontend/src/js/vendor.js',
         'css/style.css': './frontend/src/css/style.js',
         // and so on...
     },
     output: {
         filename: '[name]-[hash].js',
         path: path.resolve(__dirname, 'public/js'),
     },
     plugins: [
         new HtmlWebpackPlugin({  // Also generate a test.html
             filename: 'view-helper-config.php',
             template: 'view-helper-config.tpl'
         })
     ]
 };

And the view-helper-config.tpl would be:

'view_helper_config' => [
    'asset' => [
        'resource_map' => [
             <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
                 <%chunk%> => <%= htmlWebpackPlugin.files.chunks[chunk].entry %>
            <% } %>
        ],
    ],
],

Upvotes: 2

Related Questions