Skip to content

Commit

Permalink
use cache busting for KP bundles (#64414)
Browse files Browse the repository at this point in the history
* convert into TS

* load plugin scripts in html body

* use buildNum as a unique Id for cache busting

* add tests for caching

* fix tests

* remove the last TODO. url should be inlined with assetss server

* this logic handled by publicPathMap on the client

* cache kbn-shared-deps as well

* attempt to fix karma tests

* always run file through replace stream

* place buildHash at begining of path, include all static files

* update bundles_route tests to inject buildNum everywhere

* fix karma config to point to right prefix

* use isDist naming throughout

* explain magic number with variables

* restore replacePublicPath option from #64226

* replace one more instance of replacePublicPath

* use promisify instead of bluebird + non-null assertions

* remove one more magic number

Co-authored-by: spalger <spalger@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
3 people authored Apr 29, 2020
1 parent 5ee6dcd commit 5082ff3
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 61 deletions.
15 changes: 9 additions & 6 deletions src/legacy/ui/ui_render/ui_render_mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,12 @@ export function uiRenderMixin(kbnServer, server, config) {
? await uiSettings.get('theme:darkMode')
: false;

const buildHash = server.newPlatform.env.packageInfo.buildNum;
const basePath = config.get('server.basePath');
const regularBundlePath = `${basePath}/bundles`;
const dllBundlePath = `${basePath}/built_assets/dlls`;

const regularBundlePath = `${basePath}/${buildHash}/bundles`;
const dllBundlePath = `${basePath}/${buildHash}/built_assets/dlls`;

const dllStyleChunks = DllCompiler.getRawDllConfig().chunks.map(
chunk => `${dllBundlePath}/vendors${chunk}.style.dll.css`
);
Expand All @@ -108,15 +111,15 @@ export function uiRenderMixin(kbnServer, server, config) {

const styleSheetPaths = [
...(isCore ? [] : dllStyleChunks),
`${basePath}/bundles/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`,
`${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`,
...(darkMode
? [
`${basePath}/bundles/kbn-ui-shared-deps/${UiSharedDeps.darkCssDistFilename}`,
`${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.darkCssDistFilename}`,
`${basePath}/node_modules/@kbn/ui-framework/dist/kui_dark.css`,
`${regularBundlePath}/dark_theme.style.css`,
]
: [
`${basePath}/bundles/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`,
`${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`,
`${basePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`,
`${regularBundlePath}/light_theme.style.css`,
]),
Expand All @@ -131,7 +134,7 @@ export function uiRenderMixin(kbnServer, server, config) {
)
.map(path =>
path.localPath.endsWith('.scss')
? `${basePath}/built_assets/css/${path.publicPath}`
? `${basePath}/${buildHash}/built_assets/css/${path.publicPath}`
: `${basePath}/${path.publicPath}`
)
.reverse(),
Expand Down
119 changes: 101 additions & 18 deletions src/optimize/bundles_route/__tests__/bundles_route.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { PUBLIC_PATH_PLACEHOLDER } from '../../public_path_placeholder';

const chance = new Chance();
const outputFixture = resolve(__dirname, './fixtures/output');
const pluginNoPlaceholderFixture = resolve(__dirname, './fixtures/plugin/no_placeholder');

const randomWordsCache = new Set();
const uniqueRandomWord = () => {
Expand All @@ -58,6 +59,9 @@ describe('optimizer/bundle route', () => {
dllBundlesPath = outputFixture,
basePublicPath = '',
builtCssPath = outputFixture,
npUiPluginPublicDirs = [],
buildHash = '1234',
isDist = false,
} = options;

const server = new Hapi.Server();
Expand All @@ -69,6 +73,9 @@ describe('optimizer/bundle route', () => {
dllBundlesPath,
basePublicPath,
builtCssPath,
npUiPluginPublicDirs,
buildHash,
isDist,
})
);

Expand Down Expand Up @@ -158,7 +165,7 @@ describe('optimizer/bundle route', () => {
it('responds with exact file data', async () => {
const server = createServer();
const response = await server.inject({
url: '/bundles/image.png',
url: '/1234/bundles/image.png',
});

expect(response.statusCode).to.be(200);
Expand All @@ -173,7 +180,7 @@ describe('optimizer/bundle route', () => {
it('responds with no content-length and exact file data', async () => {
const server = createServer();
const response = await server.inject({
url: '/bundles/no_placeholder.js',
url: '/1234/bundles/no_placeholder.js',
});

expect(response.statusCode).to.be(200);
Expand All @@ -187,12 +194,12 @@ describe('optimizer/bundle route', () => {
});

describe('js file with placeholder', () => {
it('responds with no content-length and modified file data', async () => {
it('responds with no content-length and modifiedfile data ', async () => {
const basePublicPath = `/${uniqueRandomWord()}`;
const server = createServer({ basePublicPath });

const response = await server.inject({
url: '/bundles/with_placeholder.js',
url: '/1234/bundles/with_placeholder.js',
});

expect(response.statusCode).to.be(200);
Expand All @@ -204,7 +211,7 @@ describe('optimizer/bundle route', () => {
);
expect(response.result.indexOf(source)).to.be(-1);
expect(response.result).to.be(
replaceAll(source, PUBLIC_PATH_PLACEHOLDER, `${basePublicPath}/bundles/`)
replaceAll(source, PUBLIC_PATH_PLACEHOLDER, `${basePublicPath}/1234/bundles/`)
);
});
});
Expand All @@ -213,7 +220,7 @@ describe('optimizer/bundle route', () => {
it('responds with no content-length and exact file data', async () => {
const server = createServer();
const response = await server.inject({
url: '/bundles/no_placeholder.css',
url: '/1234/bundles/no_placeholder.css',
});

expect(response.statusCode).to.be(200);
Expand All @@ -231,7 +238,7 @@ describe('optimizer/bundle route', () => {
const server = createServer({ basePublicPath });

const response = await server.inject({
url: '/bundles/with_placeholder.css',
url: '/1234/bundles/with_placeholder.css',
});

expect(response.statusCode).to.be(200);
Expand All @@ -240,7 +247,7 @@ describe('optimizer/bundle route', () => {
expect(response.headers).to.have.property('content-type', 'text/css; charset=utf-8');
expect(response.result.indexOf(source)).to.be(-1);
expect(response.result).to.be(
replaceAll(source, PUBLIC_PATH_PLACEHOLDER, `${basePublicPath}/bundles/`)
replaceAll(source, PUBLIC_PATH_PLACEHOLDER, `${basePublicPath}/1234/bundles/`)
);
});
});
Expand All @@ -250,7 +257,7 @@ describe('optimizer/bundle route', () => {
const server = createServer();

const response = await server.inject({
url: '/bundles/../outside_output.js',
url: '/1234/bundles/../outside_output.js',
});

expect(response.statusCode).to.be(404);
Expand All @@ -267,7 +274,7 @@ describe('optimizer/bundle route', () => {
const server = createServer();

const response = await server.inject({
url: '/bundles/non_existent.js',
url: '/1234/bundles/non_existent.js',
});

expect(response.statusCode).to.be(404);
Expand All @@ -286,7 +293,7 @@ describe('optimizer/bundle route', () => {
});

const response = await server.inject({
url: '/bundles/with_placeholder.js',
url: '/1234/bundles/with_placeholder.js',
});

expect(response.statusCode).to.be(404);
Expand All @@ -306,31 +313,31 @@ describe('optimizer/bundle route', () => {

sinon.assert.notCalled(createHash);
const resp1 = await server.inject({
url: '/bundles/no_placeholder.js',
url: '/1234/bundles/no_placeholder.js',
});

sinon.assert.calledOnce(createHash);
createHash.resetHistory();
expect(resp1.statusCode).to.be(200);

const resp2 = await server.inject({
url: '/bundles/no_placeholder.js',
url: '/1234/bundles/no_placeholder.js',
});

sinon.assert.notCalled(createHash);
expect(resp2.statusCode).to.be(200);
});

it('is unique per basePublicPath although content is the same', async () => {
it('is unique per basePublicPath although content is the same (by default)', async () => {
const basePublicPath1 = `/${uniqueRandomWord()}`;
const basePublicPath2 = `/${uniqueRandomWord()}`;

const [resp1, resp2] = await Promise.all([
createServer({ basePublicPath: basePublicPath1 }).inject({
url: '/bundles/no_placeholder.js',
url: '/1234/bundles/no_placeholder.js',
}),
createServer({ basePublicPath: basePublicPath2 }).inject({
url: '/bundles/no_placeholder.js',
url: '/1234/bundles/no_placeholder.js',
}),
]);

Expand All @@ -349,13 +356,13 @@ describe('optimizer/bundle route', () => {
it('responds with 304 when etag and last modified are sent back', async () => {
const server = createServer();
const resp = await server.inject({
url: '/bundles/with_placeholder.js',
url: '/1234/bundles/with_placeholder.js',
});

expect(resp.statusCode).to.be(200);

const resp2 = await server.inject({
url: '/bundles/with_placeholder.js',
url: '/1234/bundles/with_placeholder.js',
headers: {
'if-modified-since': resp.headers['last-modified'],
'if-none-match': resp.headers.etag,
Expand All @@ -366,4 +373,80 @@ describe('optimizer/bundle route', () => {
expect(resp2.result).to.have.length(0);
});
});

describe('kibana platform assets', () => {
describe('caching', () => {
describe('for non-distributable mode', () => {
it('uses "etag" header to invalidate cache', async () => {
const basePublicPath = `/${uniqueRandomWord()}`;

const npUiPluginPublicDirs = [
{
id: 'no_placeholder',
path: pluginNoPlaceholderFixture,
},
];
const responce = await createServer({ basePublicPath, npUiPluginPublicDirs }).inject({
url: '/1234/bundles/plugin/no_placeholder/no_placeholder.plugin.js',
});

expect(responce.statusCode).to.be(200);

expect(responce.headers.etag).to.be.a('string');
expect(responce.headers['cache-control']).to.be('must-revalidate');
});

it('creates the same "etag" header for the same content with the same basePath', async () => {
const npUiPluginPublicDirs = [
{
id: 'no_placeholder',
path: pluginNoPlaceholderFixture,
},
];
const [resp1, resp2] = await Promise.all([
createServer({ basePublicPath: '', npUiPluginPublicDirs }).inject({
url: '/1234/bundles/plugin/no_placeholder/no_placeholder.plugin.js',
}),
createServer({ basePublicPath: '', npUiPluginPublicDirs }).inject({
url: '/1234/bundles/plugin/no_placeholder/no_placeholder.plugin.js',
}),
]);

expect(resp1.statusCode).to.be(200);
expect(resp2.statusCode).to.be(200);

expect(resp1.rawPayload).to.eql(resp2.rawPayload);

expect(resp1.headers.etag).to.be.a('string');
expect(resp2.headers.etag).to.be.a('string');
expect(resp1.headers.etag).to.eql(resp2.headers.etag);
});
});

describe('for distributable mode', () => {
it('commands to cache assets for each release for a year', async () => {
const basePublicPath = `/${uniqueRandomWord()}`;

const npUiPluginPublicDirs = [
{
id: 'no_placeholder',
path: pluginNoPlaceholderFixture,
},
];
const responce = await createServer({
basePublicPath,
npUiPluginPublicDirs,
isDist: true,
}).inject({
url: '/1234/bundles/plugin/no_placeholder/no_placeholder.plugin.js',
});

expect(responce.statusCode).to.be(200);

expect(responce.headers.etag).to.be(undefined);
expect(responce.headers['cache-control']).to.be('max-age=31536000');
});
});
});
});
});
Loading

0 comments on commit 5082ff3

Please sign in to comment.