diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 7b574f6626478..a05cc889f778f 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -551,15 +551,18 @@ export default async function build(dir: string, conf = null): Promise { if (result.isAmpOnly) { // ensure all AMP only bundles got removed try { - await fsUnlink( - path.join( - distDir, - 'static', - buildId, - 'pages', - actualPage + '.js' - ) + const clientBundle = path.join( + distDir, + 'static', + buildId, + 'pages', + actualPage + '.js' ) + await fsUnlink(clientBundle) + + if (config.experimental.modern) { + await fsUnlink(clientBundle.replace(/\.js$/, '.module.js')) + } } catch (err) { if (err.code !== 'ENOENT') { throw err diff --git a/test/integration/amphtml/next.config.js b/test/integration/amphtml/next.config.js index 65173cdd89f98..9645154cca885 100644 --- a/test/integration/amphtml/next.config.js +++ b/test/integration/amphtml/next.config.js @@ -6,4 +6,5 @@ module.exports = { amp: { canonicalBase: 'http://localhost:1234', }, + // edit here } diff --git a/test/integration/amphtml/test/index.test.js b/test/integration/amphtml/test/index.test.js index 1a95fedf456c5..b6b98222560ee 100644 --- a/test/integration/amphtml/test/index.test.js +++ b/test/integration/amphtml/test/index.test.js @@ -4,7 +4,13 @@ import { join } from 'path' import cheerio from 'cheerio' import webdriver from 'next-webdriver' import { validateAMP } from 'amp-test-utils' -import { accessSync, readFileSync, writeFileSync } from 'fs' +import { + accessSync, + readFileSync, + writeFileSync, + writeFile, + readFile, +} from 'fs-extra' import { waitFor, nextServer, @@ -28,220 +34,318 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 5 const context = {} describe('AMP Usage', () => { - beforeAll(async () => { - await nextBuild(appDir) - app = nextServer({ - dir: join(__dirname, '../'), - dev: false, - quiet: true, - }) + describe('production mode', () => { + beforeAll(async () => { + await nextBuild(appDir) + app = nextServer({ + dir: join(__dirname, '../'), + dev: false, + quiet: true, + }) - server = await startApp(app) - context.appPort = appPort = server.address().port - }) - afterAll(() => stopApp(server)) + server = await startApp(app) + context.appPort = appPort = server.address().port + }) + afterAll(() => stopApp(server)) - describe('With basic usage', () => { - it('should render the page', async () => { - const html = await renderViaHTTP(appPort, '/') - expect(html).toMatch(/Hello World/) + describe('With basic usage', () => { + it('should render the page', async () => { + const html = await renderViaHTTP(appPort, '/') + expect(html).toMatch(/Hello World/) + }) }) - }) - describe('With basic AMP usage', () => { - it('should render the page as valid AMP', async () => { - const html = await renderViaHTTP(appPort, '/?amp=1') - await validateAMP(html) - expect(html).toMatch(/Hello World/) + describe('With basic AMP usage', () => { + it('should render the page as valid AMP', async () => { + const html = await renderViaHTTP(appPort, '/?amp=1') + await validateAMP(html) + expect(html).toMatch(/Hello World/) - const $ = cheerio.load(html) - expect($('.abc').length === 1) - }) + const $ = cheerio.load(html) + expect($('.abc').length === 1) + }) - it('should render the page without leaving render target', async () => { - const html = await renderViaHTTP(appPort, '/special-chars') - await validateAMP(html) - expect(html).not.toContain('__NEXT_AMP_RENDER_TARGET__') - }) + it('should render the page without leaving render target', async () => { + const html = await renderViaHTTP(appPort, '/special-chars') + await validateAMP(html) + expect(html).not.toContain('__NEXT_AMP_RENDER_TARGET__') + }) - it('should not output client pages for AMP only', async () => { - const buildId = readFileSync(join(appDir, '.next/BUILD_ID'), 'utf8') - const ampOnly = ['only-amp', 'root-hmr', 'another-amp'] - for (const pg of ampOnly) { - expect(() => - accessSync(join(appDir, '.next/static', buildId, 'pages', pg + '.js')) - ).toThrow() - expect(() => - accessSync( - join(appDir, '.next/server/static', buildId, 'pages', pg + '.html') - ) - ).not.toThrow() - expect(() => - accessSync( - join(appDir, '.next/server/static', buildId, 'pages', pg + '.js') - ) - ).toThrow() - } - }) + it('should not output client pages for AMP only', async () => { + const buildId = readFileSync(join(appDir, '.next/BUILD_ID'), 'utf8') + const ampOnly = ['only-amp', 'root-hmr', 'another-amp'] + for (const pg of ampOnly) { + expect(() => + accessSync( + join(appDir, '.next/static', buildId, 'pages', pg + '.js') + ) + ).toThrow() + expect(() => + accessSync( + join( + appDir, + '.next/server/static', + buildId, + 'pages', + pg + '.html' + ) + ) + ).not.toThrow() + expect(() => + accessSync( + join(appDir, '.next/server/static', buildId, 'pages', pg + '.js') + ) + ).toThrow() + } + }) - it('should add link preload for amp script', async () => { - const html = await renderViaHTTP(appPort, '/?amp=1') - await validateAMP(html) - const $ = cheerio.load(html) - expect( - $( - $('link[rel=preload]') - .toArray() - .find(i => $(i).attr('href') === 'https://cdn.ampproject.org/v0.js') - ).attr('href') - ).toBe('https://cdn.ampproject.org/v0.js') - }) + it('should add link preload for amp script', async () => { + const html = await renderViaHTTP(appPort, '/?amp=1') + await validateAMP(html) + const $ = cheerio.load(html) + expect( + $( + $('link[rel=preload]') + .toArray() + .find( + i => $(i).attr('href') === 'https://cdn.ampproject.org/v0.js' + ) + ).attr('href') + ).toBe('https://cdn.ampproject.org/v0.js') + }) - it('should drop custom scripts', async () => { - const html = await renderViaHTTP(appPort, '/custom-scripts') - expect(html).not.toMatch(/src='\/im-not-allowed\.js'/) - expect(html).not.toMatch(/console\.log("I'm not either :p")'/) - }) + it('should drop custom scripts', async () => { + const html = await renderViaHTTP(appPort, '/custom-scripts') + expect(html).not.toMatch(/src='\/im-not-allowed\.js'/) + expect(html).not.toMatch(/console\.log("I'm not either :p")'/) + }) - it('should not drop custom amp scripts', async () => { - const html = await renderViaHTTP(appPort, '/amp-script?amp=1') - await validateAMP(html) - }) + it('should not drop custom amp scripts', async () => { + const html = await renderViaHTTP(appPort, '/amp-script?amp=1') + await validateAMP(html) + }) - it('should optimize clean', async () => { - const html = await renderViaHTTP(appPort, '/only-amp') - await validateAMP(html) - }) + it('should optimize clean', async () => { + const html = await renderViaHTTP(appPort, '/only-amp') + await validateAMP(html) + }) - it('should auto import extensions', async () => { - const html = await renderViaHTTP(appPort, '/auto-import') - await validateAMP(html) + it('should auto import extensions', async () => { + const html = await renderViaHTTP(appPort, '/auto-import') + await validateAMP(html) + }) }) - }) - describe('With AMP context', () => { - it('should render the normal page that uses the AMP hook', async () => { - const html = await renderViaHTTP(appPort, '/use-amp-hook') - expect(html).toMatch(/Hello others/) - expect(html).toMatch(/no AMP for you\.\.\./) - }) + describe('With AMP context', () => { + it('should render the normal page that uses the AMP hook', async () => { + const html = await renderViaHTTP(appPort, '/use-amp-hook') + expect(html).toMatch(/Hello others/) + expect(html).toMatch(/no AMP for you\.\.\./) + }) - it('should render the AMP page that uses the AMP hook', async () => { - const html = await renderViaHTTP(appPort, '/use-amp-hook?amp=1') - await validateAMP(html) - expect(html).toMatch(/Hello AMP/) - expect(html).toMatch(/AMP Power!!!/) - }) + it('should render the AMP page that uses the AMP hook', async () => { + const html = await renderViaHTTP(appPort, '/use-amp-hook?amp=1') + await validateAMP(html) + expect(html).toMatch(/Hello AMP/) + expect(html).toMatch(/AMP Power!!!/) + }) - it('should render nested normal page with AMP hook', async () => { - const html = await renderViaHTTP(appPort, '/nested') - expect(html).toMatch(/Hello others/) - }) + it('should render nested normal page with AMP hook', async () => { + const html = await renderViaHTTP(appPort, '/nested') + expect(html).toMatch(/Hello others/) + }) - it('should render nested AMP page with AMP hook', async () => { - const html = await renderViaHTTP(appPort, '/nested?amp=1') - await validateAMP(html) - expect(html).toMatch(/Hello AMP/) + it('should render nested AMP page with AMP hook', async () => { + const html = await renderViaHTTP(appPort, '/nested?amp=1') + await validateAMP(html) + expect(html).toMatch(/Hello AMP/) + }) }) - }) - describe('canonical amphtml', () => { - it('should render link rel amphtml', async () => { - const html = await renderViaHTTP(appPort, '/use-amp-hook') - const $ = cheerio.load(html) - expect( - $('link[rel=amphtml]') - .first() - .attr('href') - ).toBe('http://localhost:1234/use-amp-hook.amp') - }) + describe('canonical amphtml', () => { + it('should render link rel amphtml', async () => { + const html = await renderViaHTTP(appPort, '/use-amp-hook') + const $ = cheerio.load(html) + expect( + $('link[rel=amphtml]') + .first() + .attr('href') + ).toBe('http://localhost:1234/use-amp-hook.amp') + }) - it('should render amphtml from provided rel link', async () => { - const html = await renderViaHTTP(appPort, '/use-amp-hook.amp') - await validateAMP(html) - }) + it('should render amphtml from provided rel link', async () => { + const html = await renderViaHTTP(appPort, '/use-amp-hook.amp') + await validateAMP(html) + }) - it('should render link rel amphtml with existing query', async () => { - const html = await renderViaHTTP(appPort, '/use-amp-hook?hello=1') - expect(html).not.toMatch(/&amp=1/) - }) + it('should render link rel amphtml with existing query', async () => { + const html = await renderViaHTTP(appPort, '/use-amp-hook?hello=1') + expect(html).not.toMatch(/&amp=1/) + }) - it('should render the AMP page that uses the AMP hook', async () => { - const html = await renderViaHTTP(appPort, '/use-amp-hook?amp=1') - const $ = cheerio.load(html) - await validateAMP(html) - expect( - $('link[rel=canonical]') - .first() - .attr('href') - ).toBe('http://localhost:1234/use-amp-hook') - }) + it('should render the AMP page that uses the AMP hook', async () => { + const html = await renderViaHTTP(appPort, '/use-amp-hook?amp=1') + const $ = cheerio.load(html) + await validateAMP(html) + expect( + $('link[rel=canonical]') + .first() + .attr('href') + ).toBe('http://localhost:1234/use-amp-hook') + }) - it('should render a canonical regardless of amp-only status (explicit)', async () => { - const html = await renderViaHTTP(appPort, '/only-amp') - const $ = cheerio.load(html) - await validateAMP(html) - expect( - $('link[rel=canonical]') - .first() - .attr('href') - ).toBe('http://localhost:1234/only-amp') - }) + it('should render a canonical regardless of amp-only status (explicit)', async () => { + const html = await renderViaHTTP(appPort, '/only-amp') + const $ = cheerio.load(html) + await validateAMP(html) + expect( + $('link[rel=canonical]') + .first() + .attr('href') + ).toBe('http://localhost:1234/only-amp') + }) - it('should not render amphtml link tag with no AMP page', async () => { - const html = await renderViaHTTP(appPort, '/normal') - const $ = cheerio.load(html) - expect( - $('link[rel=amphtml]') - .first() - .attr('href') - ).not.toBeTruthy() - }) + it('should not render amphtml link tag with no AMP page', async () => { + const html = await renderViaHTTP(appPort, '/normal') + const $ = cheerio.load(html) + expect( + $('link[rel=amphtml]') + .first() + .attr('href') + ).not.toBeTruthy() + }) - it('should remove conflicting amp tags', async () => { - const html = await renderViaHTTP(appPort, '/conflicting-tag?amp=1') - const $ = cheerio.load(html) - await validateAMP(html) - expect($('meta[name=viewport]').attr('content')).not.toBe('something :p') - }) + it('should remove conflicting amp tags', async () => { + const html = await renderViaHTTP(appPort, '/conflicting-tag?amp=1') + const $ = cheerio.load(html) + await validateAMP(html) + expect($('meta[name=viewport]').attr('content')).not.toBe( + 'something :p' + ) + }) - it('should allow manually setting canonical', async () => { - const html = await renderViaHTTP(appPort, '/manual-rels?amp=1') - const $ = cheerio.load(html) - await validateAMP(html) - expect($('link[rel=canonical]').attr('href')).toBe('/my-custom-canonical') - }) + it('should allow manually setting canonical', async () => { + const html = await renderViaHTTP(appPort, '/manual-rels?amp=1') + const $ = cheerio.load(html) + await validateAMP(html) + expect($('link[rel=canonical]').attr('href')).toBe( + '/my-custom-canonical' + ) + }) - it('should allow manually setting amphtml rel', async () => { - const html = await renderViaHTTP(appPort, '/manual-rels') - const $ = cheerio.load(html) - expect($('link[rel=amphtml]').attr('href')).toBe('/my-custom-amphtml') - expect($('link[rel=amphtml]')).toHaveLength(1) + it('should allow manually setting amphtml rel', async () => { + const html = await renderViaHTTP(appPort, '/manual-rels') + const $ = cheerio.load(html) + expect($('link[rel=amphtml]').attr('href')).toBe('/my-custom-amphtml') + expect($('link[rel=amphtml]')).toHaveLength(1) + }) }) - }) - describe('combined styles', () => { - it('should combine style tags', async () => { - const html = await renderViaHTTP(appPort, '/styled?amp=1') - const $ = cheerio.load(html) - expect( - $('style[amp-custom]') + describe('combined styles', () => { + it('should combine style tags', async () => { + const html = await renderViaHTTP(appPort, '/styled?amp=1') + const $ = cheerio.load(html) + expect( + $('style[amp-custom]') + .first() + .text() + ).toMatch( + /div.jsx-\d+{color:red}span.jsx-\d+{color:#00f}body{background-color:green}/ + ) + }) + + it('should remove sourceMaps from styles', async () => { + const html = await renderViaHTTP(appPort, '/styled?amp=1') + const $ = cheerio.load(html) + const styles = $('style[amp-custom]') .first() .text() - ).toMatch( - /div.jsx-\d+{color:red}span.jsx-\d+{color:#00f}body{background-color:green}/ + + expect(styles).not.toMatch(/\/\*@ sourceURL=.*?\*\//) + expect(styles).not.toMatch(/\/\*# sourceMappingURL=.*\*\//) + }) + }) + }) + + describe('production mode modern', () => { + let origNextConfig = '' + const nextConfigPath = join(appDir, 'next.config.js') + + beforeAll(async () => { + origNextConfig = await readFile(nextConfigPath, 'utf8') + + await writeFile( + nextConfigPath, + origNextConfig.replace( + '// edit here', + ` + experimental: { + modern: true + } + ` + ) ) + await nextBuild(appDir) + app = nextServer({ + dir: join(__dirname, '../'), + dev: false, + quiet: true, + }) + + server = await startApp(app) + context.appPort = appPort = server.address().port + }) + afterAll(async () => { + await stopApp(server) + await writeFile(nextConfigPath, origNextConfig) }) - it('should remove sourceMaps from styles', async () => { - const html = await renderViaHTTP(appPort, '/styled?amp=1') - const $ = cheerio.load(html) - const styles = $('style[amp-custom]') - .first() - .text() + it('should not output client pages for AMP only', async () => { + const buildId = readFileSync(join(appDir, '.next/BUILD_ID'), 'utf8') + const ampOnly = ['only-amp', 'root-hmr', 'another-amp'] + for (const pg of ampOnly) { + expect(() => + accessSync(join(appDir, '.next/static', buildId, 'pages', pg + '.js')) + ).toThrow() + expect(() => + accessSync( + join(appDir, '.next/server/static', buildId, 'pages', pg + '.html') + ) + ).not.toThrow() + expect(() => + accessSync( + join(appDir, '.next/server/static', buildId, 'pages', pg + '.js') + ) + ).toThrow() + } + }) - expect(styles).not.toMatch(/\/\*@ sourceURL=.*?\*\//) - expect(styles).not.toMatch(/\/\*# sourceMappingURL=.*\*\//) + it('should not output modern client pages for AMP only', async () => { + const buildId = readFileSync(join(appDir, '.next/BUILD_ID'), 'utf8') + const ampOnly = ['only-amp', 'root-hmr', 'another-amp'] + for (const pg of ampOnly) { + expect(() => + accessSync( + join(appDir, '.next/static', buildId, 'pages', pg + '.module.js') + ) + ).toThrow() + expect(() => + accessSync( + join(appDir, '.next/server/static', buildId, 'pages', pg + '.html') + ) + ).not.toThrow() + expect(() => + accessSync( + join( + appDir, + '.next/server/static', + buildId, + 'pages', + pg + '.module.js' + ) + ) + ).toThrow() + } }) })