sveltejs/template

Hash in file name (replace index.html srcs in production, also using css)

frederikhors opened this issue ยท 18 comments

Any hint to build with hash in file name?

You could try this plugin, which I have not tested. You'd also need some way to know what the filename is when generating the <script> tag in index.html.

@Conduitry that plugin is still no longer needed (https://twitter.com/rich_harris/status/1079991930623283200).

This is a serious problem.

Every time I need to delete my browser cache! This can't go in production as it is.

I tried with rollup-plugin-bundle-html (https://github.com/haifeng2013/rollup-plugin-bundle-html) but as you can see the author is not so much active on the project.

Something is moving on here: vladshcherbin/rollup-plugin-copy#21 but there are still problems with css, especially if I'm using .scss files.

Any hint?

What is it you're trying to do? I don't think it's clear yet.

Seemed that you were after filename hashing (which has been solved) but not sure what the production-blocking problem is or why your browser cache is coming into question.

Ok. Using this svelte base template I would like to use hash in file name when in production and not when in development.

For this to work I need to change index.html's src="".

Using also an .scss import like this:

import './style.scss'

in my main.js needs the same index.html treatment.

Is the problem clear now?

EDIT:

I'm using rollup-plugin-postcss but still I have problem for css hash file name and index.html replace.

It seems in rollup world nobody has been interested in it before.

I'm also facing this problem.
This is critical for cache invalidation in a production env.
@frederikhors Did you finally find a way to add a hash to the css file filename and change the index.html accordingly.
I was able to do it for js chunks, but not css...

So, you guys want to have something like <a has of say 20 random chars>.main.css. That's what the build process should create as a file. In addition, obviously, you want that exact filename put in your index.html or template.html or whatever your app entry point is yes?

well, if you just set emitCSS: false for example, you don't have that concern afaict because your CSS is inlined anyway. Otherwise if you build with emitCSS: true then you might be correct and you'll always have that constant_name.css. But even then, a proper chain of cache-control caches starting with a browser cache, the CDN and so on should handle that for you without the need for hashing the filename. CDNs do has the file you know, then compare the new arrival with what they have on store, then look up cache-control in order to decide what to do with the new/old file. Not sure if all CDNs handle that but many do...

@frederikhors @VincentDauliac

One solution is to hash the CSS/JS files after the static assets are built using PostHTML.

Example repo

After hashing, the index.html would look something like this:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Svelte Rollup Template</title>
-   <link rel="stylesheet" href="bundle.css">
+   <link rel="stylesheet" href="bundle.b19ea05c629cee24e7b1.css">
  </head>
  <body>
-   <script src="bundle.js"></script>
+   <script src="bundle.3df36d777ae1e0db2646.js"></script>
  </body>
</html>
Niek commented

Another option is to use @rollup/plugin-html. Here's a sample rollup config: https://github.com/Niek/obs-web/blob/master/rollup.config.js

Here's my current rollup.config.js, with file hashing halfway figured out. I haven't found a way to hash the styles files in a way that the file would show up in the html() section to be added dynamically. Maybe someone can help with that one.

I wish some of this stuff were part of the template, so I wouldn't have to figure it out for myself. Like babel, emitCss:true so there's a single style bundle, hashing and html generation, which as far as I know is the safest way to handle cache busting across deployments. But everytime I search these things, official answer seems to be "sapper does it right, use that".

Hope you find this useful.

import babel from "@rollup/plugin-babel";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import livereload from "rollup-plugin-livereload";
import { terser } from "rollup-plugin-terser";
import visualizer from "rollup-plugin-visualizer";
import html from "@rollup/plugin-html";
import copy from "rollup-plugin-copy";
import scss from "rollup-plugin-scss";
import autoprefixer from "autoprefixer";
import cssnano from "cssnano";
import postcss from "postcss";
import autoPreprocess from "svelte-preprocess";
import svg from "rollup-plugin-svg";
import indexTemplate from "./indexTemplate";

const production = !process.env.ROLLUP_WATCH;

export default {
    input: "src/main.js",
    output: {
        sourcemap: true,
        format: "iife",
        name: "app",
        dir: "build/",
        entryFileNames: "bundle.[hash].js"
    },
    plugins: [
        copy({
            targets: [{ src: "static/*", dest: "build/" }]
        }),
        scss({
            output: "build/bundle.css",
            processor: css =>
                postcss([
                    autoprefixer,
                    cssnano({
                        preset: [
                            "default",
                            {
                                discardComments: {
                                    removeAll: true
                                }
                            }
                        ]
                    })
                ])
                    .process(css, { from: undefined })
                    .then(result => result.css)
        }),
        svg({ base64: false }),
        svelte({
            dev: !production,
            emitCss: true,
            onwarn: (warning, handler) => {
                // if (warning.code === 'a11y-autofocus') return;
                handler(warning);
            },
            preprocess: autoPreprocess()
        }),
        resolve(),
        commonjs(),
        production &&
            babel({
                exclude: ["node_modules/@babel/**", "node_modules/core-js/**"],
                extensions: [".js", ".mjs", ".html", ".svelte"]
            }),
        html({
            title: "My App Title",
            meta: [{ charset: "utf-8" }, { name: "viewport", content: "width=device-width, initial-scale=1" }],
            publicPath: "/",
            template: opts => {
                return indexTemplate({
                    ...opts,
                    extraLinks: [{ fileName: "bundle.css" }]
                });
            }
        }),
        !production && livereload("build/index.html"),
        production && terser(),
        production &&
            visualizer({
                filename: "stats.html",
                open: true,
                template: "sunburst"
            }),
    ]
};

I used a bit of anomaly. But it works well.

// rollup.config.js
import fs from 'fs';
...
function insertHashToIndex() {
	return {
		writeBundle() {
			const stats = fs.statSync('public/build/bundle.js');

			let indexHtml = fs.readFileSync(`public/index.html`, 'utf8');
			indexHtml = indexHtml
				.replace(
					/\/global\.css(\?_=[0-9\.]+)?/,
					'/global.css?_=' + stats.ctimeMs
				)
				.replace(
					/\/build\/bundle\.css(\?_=[0-9\.]+)?/,
					'/build/bundle.css?_=' + stats.ctimeMs
				)
				.replace(
					/\/build\/bundle\.js(\?_=[0-9\.]+)?/,
					'/build/bundle.js?_=' + stats.ctimeMs
				);

			// You can create a new file if you don't want to make a mutable modification.
			fs.writeFileSync(`public/index.html`, indexHtml);
		},
	};
}

export default {
	...
	plugins: [
		...
		production && insertHashToIndex(),
	]
};

This method changes index.html every time it builds as follows.

...
	<link rel='stylesheet' href='/global.css?_=1607996277779.0574'>
	<link rel='stylesheet' href='/build/bundle.css?_=1607996277779.0574'>
	<script defer src='/build/bundle.js?_=1607996277779.0574'></script>
...

And what if the css is the same? Why the invalidation based on time?

And what if the css is the same? Why the invalidation based on time?

That's right. My method was based on the time of bundle.js. Therefore, the disadvantage is that unnecessary cache-busting occurs as you say. The advantage is that cache-bursting works without complex settings and dependencies.

@metonym

Thanks for your template!

Note that rollup-plugin-svelte 7.0.0 has a breaking change so you might want to:

import css from 'rollup-plugin-css-only'

and add the following to the plugins array:

css({ output: 'bundle.[hash].css' }),

doesn't seem like rollup-plugin-css-only supports hashing

thgh/rollup-plugin-css-only#25 (comment)

brgrz commented

Coming from Angular/Webpack this is such a common/basic requirement it should be available with Svelte/Rollup out of the box. I only just noticed this after several months working on a new project where I didn't find a single thing missing from Svelte but this certainly is lacking.

Would it be possible for the team to bring in @metonym 's solution to this template?

I'm working on a project that won't get high traffic but absolutely requires the latest version of every asset, so while it's not as efficient as a content-based hash, it's quick and effective with no dependencies. thanks @zidell

Coming from Angular/Webpack this is such a common/basic requirement it should be available with Svelte/Rollup out of the box. I only just noticed this after several months working on a new project where I didn't find a single thing missing from Svelte but this certainly is lacking.

Would it be possible for the team to bring in @metonym 's solution to this template?

I agree this should be made available in the default config. While the maintainers may argue that SvelteKit / Sapper does this out of the box, I find that for a statically-hosted website (S3, Cloudflare pages, etc), using pure svelte + a basic router is incredibly effective (and a truly wonderful experience), rather than have to manually "export" the site (e.g. __sapper__/export ). And in those cases, having an efficient way to invalidate production content is necessary!

For anyone still stuck at this, I recommend you to checkout vite as a replacement for the rollup based tooling (I think it still uses rollup internally in some capacity). I've recently migrated a medium-ish plain svelte project to it and it was a breeze, and it generates some nice hashed bundles both for js and css with almost no configuration.

I know this project uses rollup, but I came here off a search looking for file hashing in snowpack. For those using snowpack with svelte, it looks like there are a few options until snowpack itself supports this via esbuild:

Plugins:

until snowpack itself supports this via esbuild:
TylorS/snowpack-plugin-hash#14
|-> evanw/esbuild#1001
|---> FredKSchott/snowpack#3402

I used the first snowpack plugin above, which has the most downloads. Though the last one has zero dependencies, soooo, up to you...

npm i -D snowpack-plugin-hash

My snowpack config looks like:

  plugins: [
    '@snowpack/plugin-svelte',
    [
      '@snowpack/plugin-sass',
      {
        native: true,
      },
    ],
    [
      'snowpack-plugin-hash',
      // Entirely optional object. Showing default values
      {
        // Configured length of your hashes
        hashLength: 12,
        // Configure log level of plugin, 'error' | 'info' | 'debug'
        logLevel: 'info',
      }
    ],
  ],