Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/vite-unhashed-public-assets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apostrophecms/vite": minor
---

Assets from a module's `public/` folder that are referenced from CSS, such as web fonts loaded with `url('/modules/...')`, are now emitted at their original path instead of a content-hashed copy under `assets/`. As a result, `apos.asset.url('/modules/...')` in a template now matches the URL requested by the built CSS, so `<link rel="preload">` tags for web fonts work as intended rather than downloading each font twice. Cache busting is still provided by the release directory. Note that Vite still inlines assets smaller than 4 KB into the CSS, so such small fonts should not be preloaded. Also fixes the `assetsDir` build option, which was misspelled and silently ignored.
20 changes: 18 additions & 2 deletions packages/vite/lib/vite-base-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,30 @@ module.exports = ({
manifest: manifestRelPath,
sourcemap: sourceMaps,
emptyOutDir: false,
assetDir: 'assets',
assetsDir: 'assets',
rollupOptions: {
output: {
entryFileNames: '[name]-build.js'
entryFileNames: '[name]-build.js',
assetFileNames
}
}
}
};

return config;
};

// Assets that come from a module's `public/` folder (e.g. fonts referenced
// from CSS via `url('/modules/...')`) are emitted at their original path,
// without a content hash. This way `apos.asset.url('/modules/...')` in a
// template matches the URL the built CSS requests, e.g. for preloading.
// Cache busting is already provided by the release directory, just like
// for the unhashed `[name]-build.js` entry files.
function assetFileNames({ names = [], originalFileNames = [] }) {
const original = originalFileNames[0]?.replaceAll('\\', '/');
const isCss = names.some((name) => name.endsWith('.css'));
if (!isCss && original?.startsWith('modules/')) {
return original;
}
return 'assets/[name]-[hash][extname]';
}
Binary file not shown.
9 changes: 9 additions & 0 deletions packages/vite/test/modules/article-page/ui/src/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@font-face {
font-family: "Page Sans";
src: url("/modules/article-page/fonts/sans.woff2") format("woff2");
}

@font-face {
font-family: "Widget Sans";
src: url("/modules/article-widget/fonts/sans.woff2") format("woff2");
}
Binary file not shown.
24 changes: 24 additions & 0 deletions packages/vite/test/vite.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,30 @@ describe('@apostrophecms/vite', function () {
assert.ok(fs.existsSync(path.join(bundleDir, 'public-src-module-bundle.js')));
assert.ok(fs.existsSync(path.join(bundleDir, 'tools-module-bundle.js')));
});

it('should emit public module assets at the URL apos.asset.url() returns', async function () {
const bundleDir = apos.asset.getBundleRootDir();
const css = await fs.readFile(path.join(bundleDir, 'article-bundle.css'), 'utf8');
const fonts = [
// Same file name in two modules must not collide
'/modules/article-page/fonts/sans.woff2',
'/modules/article-widget/fonts/sans.woff2'
];
for (const font of fonts) {
assert.ok(css.includes(`url(${apos.asset.url(font)})`), `CSS does not reference ${font}`);
const built = await fs.readFile(path.join(bundleDir, font));
const source = await fs.readFile(path.join(
__dirname,
'modules',
font.replace(/^\/modules\/([^/]+)\//, '$1/public/')
));
assert.ok(built.equals(source), `unexpected content for ${font}`);
}
const hashed = fs.existsSync(path.join(bundleDir, 'assets'))
? (await fs.readdir(path.join(bundleDir, 'assets'))).filter(f => f.endsWith('.woff2'))
: [];
assert.deepEqual(hashed, [], 'no hashed copies of fonts expected');
});
});
});

Expand Down
Loading