提交 80210bc3 编写于 作者: J JJ Kasper 提交者: Tim Neutkens

Update removing AMP pages to not require during build (#7081)

* Update to use babel plugin with webpack plugin to
remove AMP client bundles since they are not used

* Remove acorn dependency since it isn't used
上级 bcf6918b
import {PluginObj} from '@babel/core'
import {NodePath} from '@babel/traverse'
import * as BabelTypes from '@babel/types'
let idx = 0
export default function ({ types: t }: {types: typeof BabelTypes}): PluginObj {
return {
visitor: {
Program: {
enter(path, state) {
path.traverse({
ImportDeclaration (path: NodePath<BabelTypes.ImportDeclaration>, state) {
const source = path.node.source.value
if (source !== 'next/amp') return
state.hasAmp = true
},
}, state)
}
},
CallExpression (path: NodePath<BabelTypes.CallExpression>, state) {
if (!state.hasAmp || state.insertedDrop) return
// @ts-ignore
if (path.node.callee.name !== 'withAmp') return
if (path.node.arguments.length > 1) {
if (!t.isObjectExpression(path.node.arguments[1])) return
// @ts-ignore
const options: BabelTypes.ObjectExpression = path.node.arguments[1]
// make sure it isn't a hybrid page e.g. hybrid: true
if (options.properties.some((prop: any): boolean => {
if (!t.isObjectProperty(prop)) return false
if (prop.key.name !== 'hybrid' || !t.isBooleanLiteral(prop.value)) {
return false
}
// found hybrid: true
return Boolean(prop.value.value)
})) {
return
}
}
// use random number and an increment to make sure HMR still updates
idx++
state.insertedDrop = true
path.replaceWith(t.expressionStatement(t.stringLiteral(`throw new Error('__NEXT_DROP_CLIENT_FILE__' + ${Math.random() + idx})`)));
}
}
}
}
......@@ -2,7 +2,6 @@ import chalk from 'chalk'
import { PHASE_PRODUCTION_BUILD, BLOCKED_PAGES } from 'next-server/constants'
import loadConfig from 'next-server/next-config'
import nanoid from 'next/dist/compiled/nanoid/index.js'
import Sema from 'async-sema'
import path from 'path'
import fs from 'fs'
......@@ -22,9 +21,6 @@ import {
} from './utils'
import getBaseWebpackConfig from './webpack-config'
import { writeBuildId } from './write-build-id'
import { promisify } from 'util'
const unlink = promisify(fs.unlink)
export default async function build(dir: string, conf = null): Promise<void> {
if (!(await isWriteable(dir))) {
......@@ -196,37 +192,6 @@ export default async function build(dir: string, conf = null): Promise<void> {
result = formatWebpackMessages(result)
const pages = Object.keys(mappedPages)
const sema = new Sema(20, { capacity: pages.length })
await Promise.all(pages.map(async page => {
await sema.acquire()
page = page === '/' ? '/index' : page
if (BLOCKED_PAGES.includes(page)) {
return sema.release()
}
const serverPage = path.join(distDir, config.target === 'serverless' ? 'serverless/pages' : `server/static/${buildId}/pages`, page + '.js')
const clientPage = path.join(distDir, 'static', buildId, 'pages', page + '.js')
try {
require('next/config').setConfig({
publicRuntimeConfig: config.publicRuntimeConfig,
serverRuntimeConfig: config.serverRuntimeConfig
})
let mod = require(serverPage)
mod = mod.default || mod
if (mod && mod.__nextAmpOnly) {
await unlink(clientPage)
}
} catch (err) {
if (err.code !== 'ENOENT' && err.code !== 'MODULE_NOT_FOUND') {
throw err
}
}
sema.release()
}))
if (isFlyingShuttle) {
console.log()
}
......
......@@ -15,6 +15,7 @@ import { AllModulesIdentifiedPlugin } from './webpack/plugins/all-modules-identi
import { SharedRuntimePlugin } from './webpack/plugins/shared-runtime-plugin'
import { HashedChunkIdsPlugin } from './webpack/plugins/hashed-chunk-ids-plugin'
import { ChunkGraphPlugin } from './webpack/plugins/chunk-graph-plugin'
import { DropClientPage } from './webpack/plugins/next-drop-client-page-plugin'
import { WebpackEntrypoints } from './entries'
type ExcludesFalse = <T>(x: T | false) => x is T
......@@ -250,7 +251,7 @@ export default function getBaseWebpackConfig (dir: string, {dev = false, debug =
return /node_modules/.test(path)
},
use: defaultLoaders.babel
}
},
].filter(Boolean)
},
plugins: [
......@@ -281,6 +282,7 @@ export default function getBaseWebpackConfig (dir: string, {dev = false, debug =
filename: REACT_LOADABLE_MANIFEST
}),
!isServer && selectivePageBuilding && new ChunkGraphPlugin(buildId, path.resolve(dir), { filename: CHUNK_GRAPH_MANIFEST, selectivePageBuildingCacheIdentifier }),
!isServer && new DropClientPage(),
...(dev ? (() => {
// Even though require.cache is server only we have to clear assets from both compilations
// This is because the client compilation generates the build manifest that's used on the server side
......
......@@ -52,6 +52,12 @@ module.exports = babelLoader.custom(babel => {
options.presets = [...options.presets, presetItem]
}
if (!isServer && source.indexOf('next/amp')) {
const dropClientPlugin = babel.createConfigItem([require('../../babel/plugins/next-drop-client-page'), {}], { type: 'plugin' })
options.plugins = options.plugins || []
options.plugins.push(dropClientPlugin)
}
if (isServer && source.indexOf('next/data') !== -1) {
const nextDataPlugin = babel.createConfigItem([require('../../babel/plugins/next-data'), { key: basename(filename) + '-' + hash(filename) }], { type: 'plugin' })
options.plugins = options.plugins || []
......
import { Compiler, Plugin } from 'webpack'
// Prevents outputting client pages when they are not needed
export class DropClientPage implements Plugin {
apply(compiler: Compiler) {
compiler.hooks.emit.tap('DropClientPage', compilation => {
Object.keys(compilation.assets).forEach(assetKey => {
const asset = compilation.assets[assetKey]
if (asset && asset._value && asset._value.includes('__NEXT_DROP_CLIENT_FILE__')) {
delete compilation.assets[assetKey]
}
})
})
}
}
......@@ -2,7 +2,6 @@ import DynamicEntryPlugin from 'webpack/lib/DynamicEntryPlugin'
import { EventEmitter } from 'events'
import { join, posix } from 'path'
import { parse } from 'url'
import fs from 'fs'
import { pageNotFoundError } from 'next-server/dist/server/require'
import { normalizePagePath } from 'next-server/dist/server/normalize-page-path'
import { ROUTE_NAME_REGEX, IS_BUNDLED_PAGE_REGEX } from 'next-server/constants'
......@@ -254,24 +253,6 @@ export default function onDemandEntryHandler (devMiddleware, multiCompiler, {
function handleCallback (err) {
if (err) return reject(err)
const { name } = entries[normalizedPage]
let serverPage = join(dir, distDir, 'server', name)
const clientPage = join(dir, distDir, name)
try {
require('next/config').setConfig({
serverRuntimeConfig,
publicRuntimeConfig
})
let mod = require(serverPage)
mod = mod.default || mod
if (mod && mod.__nextAmpOnly) {
fs.unlinkSync(clientPage)
}
} catch (err) {
if (err.code !== 'ENOENT' && err.code !== 'MODULE_NOT_FOUND') {
return reject(err)
}
}
resolve()
}
})
......
......@@ -4,7 +4,7 @@ import { join } from 'path'
import cheerio from 'cheerio'
import webdriver from 'next-webdriver'
import { validateAMP } from 'amp-test-utils'
import { readFileSync, writeFileSync } from 'fs'
import { accessSync, readFileSync, writeFileSync } from 'fs'
import {
waitFor,
nextServer,
......@@ -58,6 +58,19 @@ describe('AMP Usage', () => {
expect($('.abc').length === 1)
})
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']
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 + '.js'))
).not.toThrow()
}
})
it('should add link preload for amp script', async () => {
const html = await renderViaHTTP(appPort, '/?amp=1')
await validateAMP(html)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册