Skip to content
This repository has been archived by the owner on May 17, 2019. It is now read-only.

Commit

Permalink
Ensure scripts are flagged as parser-inserted
Browse files Browse the repository at this point in the history
  • Loading branch information
rtsao committed Nov 8, 2018
1 parent 75416e7 commit a71bee7
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 77 deletions.
2 changes: 1 addition & 1 deletion build/get-webpack-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ function getWebpackConfig(opts /*: WebpackConfigOpts */) {
new InstrumentedImportDependencyTemplatePlugin(
runtime !== 'client'
? // Server
state.clientChunkMetadata
state.mergedClientChunkMetadata
: /**
* Client
* Don't wait for the client manifest on the client.
Expand Down
86 changes: 49 additions & 37 deletions plugins/ssr-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ const SSRBodyTemplate = createPlugin({

const legacyUrls = [];
const modernUrls = [];

for (let chunkId of allCriticalChunkIds) {
const url = chunks.get(chunkId);
if (url.includes('client-legacy')) {
Expand All @@ -92,18 +93,61 @@ const SSRBodyTemplate = createPlugin({
}
}

const criticalChunkScripts = getLoaderScript(ctx, {
legacyUrls,
modernUrls,
});
let preloadHints = [];
let criticalChunkScripts = [];

const browser = ctx.useragent.browser;

/*
Edge must get transpiled classes due to:
- https://github.com/Microsoft/ChakraCore/issues/5030
- https://github.com/Microsoft/ChakraCore/issues/4663
- https://github.com/babel/babel/issues/8019
Rather than transpile classes in the modern bundles, Edge should be forced on the slow path
*/
if (browser.name !== 'Edge') {
for (let url of modernUrls) {
const crossoriginAttr = url.startsWith(__webpack_public_path__)
? ''
: ' crossorigin="anonymous"';
preloadHints.push(
`<link rel="modulepreload" href="${url}"${crossoriginAttr}/>`
);
criticalChunkScripts.push(
`<script type="module" defer src="${url}" nonce="${
ctx.nonce
}"${crossoriginAttr}></script>`
);
}
}

const isSafari10_1 =
(browser.name === 'Safari' || browser.name === 'Mobile Safari') &&
browser.version.startsWith('10.1');

// Safari 10.1 supports modules but not `nomodule` attribute.
if (!isSafari10_1) {
for (let url of legacyUrls) {
const crossoriginAttr = url.startsWith(__webpack_public_path__)
? ''
: ' crossorigin="anonymous"';
criticalChunkScripts.push(
`<script nomodule defer src="${url}" nonce="${
ctx.nonce
}"${crossoriginAttr}></script>`
);
}
}

return [
'<!doctype html>',
`<html${safeAttrs}>`,
`<head>`,
`<meta charset="utf-8" />`,
`<title>${safeTitle}</title>`,
`${coreGlobals}${criticalChunkScripts}${safeHead}`,
`${preloadHints.join('')}${coreGlobals}${criticalChunkScripts.join(
''
)}${safeHead}`,
`</head>`,
`<body${safeBodyAttrs}>${ctx.rendered}${safeBody}</body>`,
'</html>',
Expand All @@ -113,35 +157,3 @@ const SSRBodyTemplate = createPlugin({
});

export {SSRBodyTemplate};

/**
Safari 10.1 supports modules but not `nomodule` attribute.
Edge must get transpiled classes due to:
https://github.com/Microsoft/ChakraCore/issues/5030
https://github.com/Microsoft/ChakraCore/issues/4663
https://github.com/babel/babel/issues/8019
Edge UA check is based on
https://github.com/faisalman/ua-parser-js/blob/7aca357879ba18ec2e57d36403d391c860a1be2e/src/ua-parser.js#L264
*/
function getLoaderScript(ctx, {legacyUrls, modernUrls}) {
return `
<script nomodule nonce="${ctx.nonce}">window.__NOMODULE__ = true;</script>
<script nonce="${
ctx.nonce
}">(window.__NOMODULE__ || /(edge|edgios|edga)/i.test(window.navigator.userAgent) ? ${JSON.stringify(
legacyUrls
)} : ${JSON.stringify(modernUrls)}).forEach(function(src) {
var script = document.createElement('script');
script.src = src;
script.async = false;
script.setAttribute("nonce", ${JSON.stringify(ctx.nonce)});
script.defer = true;
if (script.src.indexOf(window.location.origin + '/') !== 0) {
script.crossorigin = "anonymous";
}
document.head.appendChild(script);
});</script>
`;
}
83 changes: 44 additions & 39 deletions test/cli/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,76 +262,76 @@ test('`fusion build` app with dynamic imports integration', async t => {
content.includes('loaded-dynamic-import'),
'app content contains loaded-dynamic-import'
);
const NON_CHUNK_SCRIPT_COUNT = 4;
const SYNC_CHUNK_COUNT = 3; // runtime + main + vendor
const ROUTE_INDEPENDENT_ASYNC_CHUNK_COUNT = 1;

const BASE_COUNT =
NON_CHUNK_SCRIPT_COUNT +
SYNC_CHUNK_COUNT +
ROUTE_INDEPENDENT_ASYNC_CHUNK_COUNT;
const BASE_COUNT = SYNC_CHUNK_COUNT + ROUTE_INDEPENDENT_ASYNC_CHUNK_COUNT;

t.equal(await page.$$eval('script', els => els.length), BASE_COUNT);
t.equal(
await page.$$eval('link[rel="modulepreload"]', els => els.length),
BASE_COUNT
);
t.equal(
await page.$$eval(
'script[src]:not([type="module"]):not([type="application/json"])',
els => els.length
),
BASE_COUNT
);
t.equal(
await page.$$eval('script[src][type="module"]', els => els.length),
BASE_COUNT
);

// Async can causes race conditions as scripts may be executed before DOM is fully parsed.
t.ok(
await page.$$eval('script:not([type="application/json"])', els =>
await page.$$eval('script[src]:not([type="application/json"])', els =>
els.every(el => el.async === false)
),
'all scripts not be async'
);

await page.click('#split-route-link');
t.equal(
await page.$$eval('script', els => els.length),
await page.$$eval(
'script[src]:not([type="module"]):not([type="application/json"])',
els => els.length
),
BASE_COUNT + 1,
'one extra script after loading new route'
);

t.ok(
await page.$$eval('script', els =>
await page.$$eval('script[src]:not([type="application/json"])', els =>
els.every(el => el.crossOrigin === null)
),
'all scripts do not have crossorigin attribute'
);

t.ok(
await page.$$eval('script:not([type="application/json"])', els =>
await page.$$eval('script[src]:not([type="application/json"])', els =>
// eslint-disable-next-line
els.every(el => el.getAttribute('nonce') === window.__NONCE__)
),
'all scripts have nonce attribute'
);

await page.setRequestInterception(true);

let jsRequests = [];

page.on('request', req => {
if (req.url().endsWith('.js')) {
jsRequests.push(req);
// Prevent chunk exectution by blocking requests
} else {
req.continue();
}
});

await page.goto(`http://localhost:${port}/`, {waitUntil: 'domcontentloaded'});
await page.goto(`http://localhost:${port}/split-route`);

t.equal(
jsRequests.length,
SYNC_CHUNK_COUNT + ROUTE_INDEPENDENT_ASYNC_CHUNK_COUNT
await page.$$eval('link[rel="modulepreload"]', els => els.length),
BASE_COUNT + 1
);

jsRequests = [];

await page.goto(`http://localhost:${port}/split-route`, {
waitUntil: 'domcontentloaded',
});

t.equal(
jsRequests.length,
SYNC_CHUNK_COUNT + ROUTE_INDEPENDENT_ASYNC_CHUNK_COUNT + 1
await page.$$eval(
'script[src]:not([type="module"]):not([type="application/json"])',
els => els.length
),
BASE_COUNT + 1
);
t.equal(
await page.$$eval('script[src][type="module"]', els => els.length),
BASE_COUNT + 1
);

await browser.close();
Expand Down Expand Up @@ -376,6 +376,7 @@ test('`fusion build` app with split translations integration', async t => {
page.click('#split1-link'),
page.waitForSelector('#split1-translation'),
]);

const content2 = await page.content();
t.ok(
content2.includes('__SPLIT1_TRANSLATED__'),
Expand Down Expand Up @@ -703,7 +704,11 @@ test('`fusion build` with dynamic imports', async t => {
testContent.dynamicContent.includes('loaded dynamic import'),
'dynamic import is executed'
);
t.deepEqual(testContent.chunkIds, [[1], [0]], 'Chunk IDs are populated');
t.deepEqual(
testContent.chunkIds,
[[10001, 1], [10000, 0]],
'Chunk IDs are populated'
);

t.ok(
await exists(
Expand All @@ -726,9 +731,9 @@ test('`fusion build` with dynamic imports and group chunks', async t => {
const resA = await request(`http://localhost:${port}/test-a`);
const resB = await request(`http://localhost:${port}/test-b`);
const res = await request(`http://localhost:${port}/test`);
t.deepLooseEqual(JSON.parse(res), [3]);
t.deepLooseEqual(JSON.parse(resA), [1, 3]);
t.deepLooseEqual(JSON.parse(resB), [2, 3]);
t.deepLooseEqual(JSON.parse(res), [10003, 3]);
t.deepLooseEqual(JSON.parse(resA), [10001, 10003, 1, 3]);
t.deepLooseEqual(JSON.parse(resB), [10002, 10003, 2, 3]);
proc.kill();
t.end();
});
Expand Down

0 comments on commit a71bee7

Please sign in to comment.