UNPKG

fusion-cli

Version:
294 lines (244 loc) • 8.13 kB
// @flow /* eslint-env node */ const t = require('assert'); const path = require('path'); const fs = require('fs'); const {promisify} = require('util'); const readdir = promisify(fs.readdir); const puppeteer = require('puppeteer'); const getPort = require('get-port'); const httpProxy = require('http-proxy'); const {cmd, start} = require('../utils.js'); const dir = path.resolve(__dirname, './fixture'); async function getDistFiles(dir) { const clientFiles = await readdir( path.resolve(dir, '.fusion/dist/production/client') ); const clientMainFile = clientFiles.filter(f => /client-main-(.*?).js$/.test(f) )[0]; const clientVendorFile = clientFiles.filter(f => /client-vendor-(.*?).js$/.test(f) )[0]; const splitClientChunks = clientFiles.filter(f => /[0-9]+-(.*?).js$/.test(f)); return { clientFiles, clientMainFile, clientVendorFile, splitClientChunks, }; } test('`fusion build` app with dynamic imports chunk hashing', async () => { await cmd(`build --dir=${dir} --production`); const splitChunkId = 1; const distFiles = await getDistFiles(dir); const dynamicFileBundlePath = path.resolve( dir, '.fusion/dist/production/client', distFiles.splitClientChunks[splitChunkId] ); // Ensure that we have a dynamic chunk with content const dynamicFileBundleContent = fs .readFileSync(dynamicFileBundlePath) .toString(); t.ok( dynamicFileBundleContent.includes('loaded-dynamic-import'), 'dynamic content exists in bundle' ); // Update dynamic file content, and rebuild. const dynamicFileContent = fs .readFileSync(path.resolve(dir, 'src/dynamic.js')) .toString(); const newContent = dynamicFileContent.replace( 'loaded-dynamic-import', 'loaded-dynamic-import-updated' ); fs.writeFileSync(path.resolve(dir, 'src/dynamic.js'), newContent); await cmd(`build --dir=${dir} --production`); // Ensure that vendor and main chunks do not change. const rebuiltDistFiles = await getDistFiles(dir); t.equal( distFiles.clientVendorFile, rebuiltDistFiles.clientVendorFile, 'vendor file hash should not change' ); t.equal( distFiles.clientMainFile, rebuiltDistFiles.clientMainFile, 'main file hash should not change' ); t.notEqual( distFiles.splitClientChunks[splitChunkId], rebuiltDistFiles.splitClientChunks[splitChunkId], 'split client file hash should change' ); // Clean up changed files fs.writeFileSync(path.resolve(dir, 'src/dynamic.js'), dynamicFileContent); }, 100000); test('`fusion build` app with dynamic imports integration', async () => { var env = Object.create(process.env); env.NODE_ENV = 'production'; await cmd(`build --dir=${dir} --production`, {env}); const proxyPort = await getPort(); // Run puppeteer test to ensure that page loads with dynamic content. const {proc, port} = await start(`--dir=${dir}`, {env}); let proxy = httpProxy .createProxyServer({target: `http://localhost:${port}`}) .listen(proxyPort); const cookies = []; proxy.on('proxyReq', (proxyReq, req, res, options) => { if (req.url.endsWith('.js')) { cookies.push(req.headers.cookie); } }); const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'], }); const page = await browser.newPage(); await page.setCookie({ name: 'foo', value: 'bar', url: `http://localhost:${proxyPort}/`, }); await page.goto(`http://localhost:${proxyPort}/`, {waitUntil: 'load'}); // eslint-disable-next-line t.ok(await page.evaluate(() => window.__MAIN_EXECUTED__)); const content = await page.content(); t.ok( content.includes('loaded-dynamic-import'), 'app content contains loaded-dynamic-import' ); const SYNC_CHUNK_COUNT = 3; // runtime + main + vendor const ROUTE_INDEPENDENT_ASYNC_CHUNK_COUNT = 1; const BASE_COUNT = SYNC_CHUNK_COUNT + ROUTE_INDEPENDENT_ASYNC_CHUNK_COUNT; t.equal( await page.$$eval('link[rel="preload"]', els => els.length), BASE_COUNT ); t.ok( await page.$$eval('link[rel="preload"]', els => // eslint-disable-next-line els.every(el => el.getAttribute('nonce') === window.__NONCE__) ), 'all preload hints have correct nonce attribute' ); t.equal( await page.$$eval( 'script[src]:not([type="application/json"])', 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[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[src]:not([type="application/json"])', els => els.length ), BASE_COUNT + 1, 'one extra script after loading new route' ); t.ok( await page.$$eval('script[src]:not([type="application/json"])', els => els.every(el => el.crossOrigin === null) ), 'non-module scripts do not have crossorigin attribute' ); t.ok( 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' ); t.equal(cookies.length, BASE_COUNT + 1); t.ok( cookies.every(cookie => cookie === 'foo=bar'), 'cookies sent w/ every request to chunk' ); await page.goto(`http://localhost:${port}/split-route`); t.equal( await page.$$eval('link[rel="preload"]', els => els.length), BASE_COUNT + 1 ); t.equal( await page.$$eval( 'script[src]:not([type="application/json"])', els => els.length ), BASE_COUNT + 1 ); browser.close(); proc.kill(); proxy.close(); }, 100000); test('`fusion build` app with Safari user agent and same-origin', async () => { var env = Object.create(process.env); env.NODE_ENV = 'production'; await cmd(`build --dir=${dir} --production`, {env}); // Run puppeteer test to ensure that page loads with dynamic content. const {proc, port} = await start(`--dir=${dir}`, {env}); const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'], }); const page = await browser.newPage(); await page.setUserAgent( 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.1 Safari/605.1.15' ); await page.goto(`http://localhost:${port}/`, {waitUntil: 'load'}); // eslint-disable-next-line t.ok(await page.evaluate(() => window.__MAIN_EXECUTED__)); const content = await page.content(); t.ok( content.includes('loaded-dynamic-import'), 'app content contains loaded-dynamic-import' ); const SYNC_CHUNK_COUNT = 3; // runtime + main + vendor const ROUTE_INDEPENDENT_ASYNC_CHUNK_COUNT = 1; const BASE_COUNT = SYNC_CHUNK_COUNT + ROUTE_INDEPENDENT_ASYNC_CHUNK_COUNT; t.equal( await page.$$eval( 'script[src]:not([type="application/json"])', 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[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[src]:not([type="application/json"])', els => els.length ), BASE_COUNT + 1, 'one extra script after loading new route' ); t.ok( await page.$$eval('script[src]:not([type="application/json"])', els => els.every(el => el.crossOrigin === null) ), 'non-module scripts do not have crossorigin attribute' ); t.ok( 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' ); browser.close(); proc.kill(); }, 100000);