Skip to content

Commit

Permalink
Merge branch 'canary' into fix-placeholder-blur-with-priority
Browse files Browse the repository at this point in the history
  • Loading branch information
styfle committed Mar 2, 2022
2 parents 383d5a3 + a518036 commit 1d56c8b
Show file tree
Hide file tree
Showing 20 changed files with 218 additions and 111 deletions.
61 changes: 59 additions & 2 deletions packages/next/build/entries.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fs from 'fs'
import chalk from 'next/dist/compiled/chalk'
import { posix, join } from 'path'
import { stringify } from 'querystring'
Expand All @@ -12,6 +13,7 @@ import { ClientPagesLoaderOptions } from './webpack/loaders/next-client-pages-lo
import { ServerlessLoaderQuery } from './webpack/loaders/next-serverless-loader'
import { LoadedEnvFiles } from '@next/env'
import { NextConfigComplete } from '../server/config-shared'
import { parse } from '../build/swc'
import { isCustomErrorPage, isFlightPage, isReservedPage } from './utils'
import { ssrEntries } from './webpack/plugins/middleware-plugin'
import type { webpack5 } from 'next/dist/compiled/webpack/webpack'
Expand Down Expand Up @@ -101,6 +103,60 @@ type Entrypoints = {
edgeServer: webpack5.EntryObject
}

export async function getPageRuntime(pageFilePath: string) {
let pageRuntime: string | undefined = undefined
const pageContent = await fs.promises.readFile(pageFilePath, {
encoding: 'utf8',
})
// branch prunes for entry page without runtime option
if (pageContent.includes('runtime')) {
const { body } = await parse(pageContent, {
filename: pageFilePath,
isModule: true,
})
body.some((node: any) => {
const { type, declaration } = node
const valueNode = declaration?.declarations?.[0]
if (type === 'ExportDeclaration' && valueNode?.id?.value === 'config') {
const props = valueNode.init.properties
const runtimeKeyValue = props.find(
(prop: any) => prop.key.value === 'runtime'
)
const runtime = runtimeKeyValue?.value?.value
pageRuntime =
runtime === 'edge' || runtime === 'nodejs' ? runtime : pageRuntime
return true
}
return false
})
}

return pageRuntime
}

export async function createPagesRuntimeMapping(
pagesDir: string,
pages: PagesMapping
) {
const pagesRuntime: Record<string, string> = {}

const promises = Object.keys(pages).map(async (page) => {
const absolutePagePath = pages[page]
const isReserved = isReservedPage(page)
if (!isReserved) {
const pageFilePath = join(
pagesDir,
absolutePagePath.replace(PAGES_DIR_ALIAS, '')
)
const runtime = await getPageRuntime(pageFilePath)
if (runtime) {
pagesRuntime[page] = runtime
}
}
})
return await Promise.all(promises)
}

export function createEntrypoints(
pages: PagesMapping,
target: 'server' | 'serverless' | 'experimental-serverless-trace',
Expand All @@ -117,8 +173,6 @@ export function createEntrypoints(
Object.keys(config.publicRuntimeConfig).length > 0 ||
Object.keys(config.serverRuntimeConfig).length > 0

const edgeRuntime = config.experimental.runtime === 'edge'

const defaultServerlessOptions = {
absoluteAppPath: pages['/_app'],
absoluteDocumentPath: pages['/_document'],
Expand Down Expand Up @@ -146,6 +200,9 @@ export function createEntrypoints(
reactRoot: config.experimental.reactRoot ? 'true' : '',
}

const globalRuntime = config.experimental.runtime
const edgeRuntime = globalRuntime === 'edge'

Object.keys(pages).forEach((page) => {
const absolutePagePath = pages[page]
const bundleFile = normalizePagePath(page)
Expand Down
7 changes: 7 additions & 0 deletions packages/next/build/swc/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function isWasm(): Promise<boolean>
export function transform(src: string, options?: any): Promise<any>
export function transformSync(src: string, options?: any): any
export function minify(src: string, options: any): Promise<string>
export function minifySync(src: string, options: any): string
export function bundle(options: any): Promise<any>
export function parse(src: string, options: any): any
4 changes: 3 additions & 1 deletion packages/next/build/swc/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { platform, arch } from 'os'
import { platformArchTriples } from 'next/dist/compiled/@napi-rs/triples'
import * as Log from '../output/log'
import { getParserOptions } from './options'

const ArchName = arch()
const PlatformName = platform()
Expand Down Expand Up @@ -229,5 +230,6 @@ export async function bundle(options) {

export async function parse(src, options) {
let bindings = loadBindingsSync()
return bindings.parse(src, options).then((astStr) => JSON.parse(astStr))
let parserOptions = getParserOptions(options)
return bindings.parse(src, parserOptions).then((astStr) => JSON.parse(astStr))
}
7 changes: 7 additions & 0 deletions packages/next/build/swc/options.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function getParserOptions(options: {
filename: string
jsConfig?: any
[key: string]: any
}): any
export function getJestSWCOptions(...args: any[]): any
export function getLoaderSWCOptions(...args: any[]): any
29 changes: 19 additions & 10 deletions packages/next/build/swc/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,23 @@ const regeneratorRuntimePath = require.resolve(
'next/dist/compiled/regenerator-runtime'
)

export function getBaseSWCOptions({
export function getParserOptions({ filename, jsConfig, ...rest }) {
const isTSFile = filename.endsWith('.ts')
const isTypeScript = isTSFile || filename.endsWith('.tsx')
const enableDecorators = Boolean(
jsConfig?.compilerOptions?.experimentalDecorators
)
return {
...rest,
syntax: isTypeScript ? 'typescript' : 'ecmascript',
dynamicImport: true,
decorators: enableDecorators,
// Exclude regular TypeScript files from React transformation to prevent e.g. generic parameters and angle-bracket type assertion from being interpreted as JSX tags.
[isTypeScript ? 'tsx' : 'jsx']: isTSFile ? false : true,
}
}

function getBaseSWCOptions({
filename,
jest,
development,
Expand All @@ -15,8 +31,7 @@ export function getBaseSWCOptions({
resolvedBaseUrl,
jsConfig,
}) {
const isTSFile = filename.endsWith('.ts')
const isTypeScript = isTSFile || filename.endsWith('.tsx')
const parserConfig = getParserOptions({ filename, jsConfig })
const paths = jsConfig?.compilerOptions?.paths
const enableDecorators = Boolean(
jsConfig?.compilerOptions?.experimentalDecorators
Expand All @@ -32,13 +47,7 @@ export function getBaseSWCOptions({
paths,
}
: {}),
parser: {
syntax: isTypeScript ? 'typescript' : 'ecmascript',
dynamicImport: true,
decorators: enableDecorators,
// Exclude regular TypeScript files from React transformation to prevent e.g. generic parameters and angle-bracket type assertion from being interpreted as JSX tags.
[isTypeScript ? 'tsx' : 'jsx']: isTSFile ? false : true,
},
parser: parserConfig,

transform: {
// Enables https://github.com/swc-project/swc/blob/0359deb4841be743d73db4536d4a22ac797d7f65/crates/swc_ecma_ext_transforms/src/jest.rs
Expand Down
2 changes: 1 addition & 1 deletion packages/next/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -881,7 +881,7 @@ export async function isPageStatic(
throw new Error('INVALID_DEFAULT_EXPORT')
}

const hasFlightData = !!(Comp as any).__next_rsc__
const hasFlightData = !!(mod as any).__next_rsc__
const hasGetInitialProps = !!(Comp as any).getInitialProps
const hasStaticProps = !!mod.getStaticProps
const hasStaticPaths = !!mod.getStaticPaths
Expand Down
11 changes: 1 addition & 10 deletions packages/next/build/webpack/loaders/next-flight-client-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

// TODO: add ts support for next-swc api
// @ts-ignore
import { parse } from '../../swc'
// @ts-ignore
import { getBaseSWCOptions } from '../../swc/options'

function addExportNames(names: string[], node: any) {
switch (node.type) {
Expand Down Expand Up @@ -48,13 +44,8 @@ async function parseExportNamesInto(
transformedSource: string,
names: Array<string>
): Promise<void> {
const opts = getBaseSWCOptions({
filename: resourcePath,
globalWindow: true,
})

const { body } = await parse(transformedSource, {
...opts.jsc.parser,
filename: resourcePath,
isModule: true,
})
for (let i = 0; i < body.length; i++) {
Expand Down
83 changes: 25 additions & 58 deletions packages/next/build/webpack/loaders/next-flight-server-loader.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
// TODO: add ts support for next-swc api
// @ts-ignore
import { parse } from '../../swc'
// @ts-ignore
import { getBaseSWCOptions } from '../../swc/options'
import { getRawPageExtensions } from '../../utils'

const imageExtensions = ['jpg', 'jpeg', 'png', 'webp', 'avif']
Expand Down Expand Up @@ -31,30 +27,26 @@ const createServerComponentFilter = (pageExtensions: string[]) => {
async function parseImportsInfo({
resourcePath,
source,
imports,
isClientCompilation,
isServerComponent,
isClientComponent,
}: {
resourcePath: string
source: string
imports: Array<string>
isClientCompilation: boolean
isServerComponent: (name: string) => boolean
isClientComponent: (name: string) => boolean
}): Promise<{
source: string
defaultExportName: string
imports: string
}> {
const opts = getBaseSWCOptions({
filename: resourcePath,
globalWindow: isClientCompilation,
})
const ast = await parse(source, { ...opts.jsc.parser, isModule: true })
const ast = await parse(source, { filename: resourcePath, isModule: true })
const { body } = ast

let transformedSource = ''
let lastIndex = 0
let defaultExportName
let imports = ''

for (let i = 0; i < body.length; i++) {
const node = body[i]
switch (node.type) {
Expand All @@ -75,7 +67,7 @@ async function parseImportsInfo({
// A client component. It should be loaded as module reference.
transformedSource += importDeclarations
transformedSource += JSON.stringify(`${importSource}?__sc_client__`)
imports.push(`require(${JSON.stringify(importSource)})`)
imports += `require(${JSON.stringify(importSource)})\n`
} else {
// This is a special case to avoid the Duplicate React error.
// Since we already include React in the SSR runtime,
Expand Down Expand Up @@ -105,27 +97,12 @@ async function parseImportsInfo({
continue
}

imports.push(`require(${JSON.stringify(importSource)})`)
imports += `require(${JSON.stringify(importSource)})\n`
}

lastIndex = node.source.span.end
break
}
case 'ExportDefaultDeclaration': {
const def = node.decl
if (def.type === 'Identifier') {
defaultExportName = def.name
} else if (def.type === 'FunctionExpression') {
defaultExportName = def.identifier.value
}
break
}
case 'ExportDefaultExpression':
const exp = node.expression
if (exp.type === 'Identifier') {
defaultExportName = exp.value
}
break
default:
break
}
Expand All @@ -135,7 +112,7 @@ async function parseImportsInfo({
transformedSource += source.substring(lastIndex)
}

return { source: transformedSource, defaultExportName }
return { source: transformedSource, imports }
}

export default async function transformSource(
Expand Down Expand Up @@ -170,44 +147,34 @@ export default async function transformSource(
}
}

const imports: string[] = []
const { source: transformedSource, defaultExportName } =
await parseImportsInfo({
resourcePath,
source,
imports,
isClientCompilation,
isServerComponent,
isClientComponent,
})
const { source: transformedSource, imports } = await parseImportsInfo({
resourcePath,
source,
isClientCompilation,
isServerComponent,
isClientComponent,
})

/**
* For .server.js files, we handle this loader differently.
*
* Server compilation output:
* export default function ServerComponent() { ... }
* export const __rsc_noop__ = () => { ... }
* ServerComponent.__next_rsc__ = 1
* ServerComponent.__webpack_require__ = __webpack_require__
* (The content of the Server Component module will be kept.)
* export const __next_rsc__ = { __webpack_require__, _: () => { ... } }
*
* Client compilation output:
* The function body of Server Component will be removed
* (The content of the Server Component module will be removed.)
* export const __next_rsc__ = { __webpack_require__, _: () => { ... } }
*/

const noop = `export const __rsc_noop__=()=>{${imports.join(';')}}`
let rscExports = `export const __next_rsc__={
__webpack_require__,
_: () => {${imports}}
}`

let defaultExportNoop = ''
if (isClientCompilation) {
defaultExportNoop = `export default function ${
defaultExportName || 'ServerComponent'
}(){}\n${defaultExportName || 'ServerComponent'}.__next_rsc__=1;`
} else {
if (defaultExportName) {
// It's required to have the default export for pages. For other components, it's fine to leave it as is.
defaultExportNoop = `${defaultExportName}.__next_rsc__=1;${defaultExportName}.__webpack_require__=__webpack_require__;`
}
rscExports += '\nexport default function RSC () {}'
}

const transformed = transformedSource + '\n' + noop + '\n' + defaultExportNoop
return transformed
return transformedSource + '\n' + rscExports
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ export default async function middlewareSSRLoader(this: any) {
import { getRender } from 'next/dist/build/webpack/loaders/next-middleware-ssr-loader/render'
import App from ${stringifiedAppPath}
import Document from ${stringifiedDocumentPath}
const appMod = require(${stringifiedAppPath})
const pageMod = require(${stringifiedPagePath})
const errorMod = require(${stringifiedErrorPath})
const error500Mod = ${stringified500Path} ? require(${stringified500Path}) : null
Expand All @@ -48,10 +48,10 @@ export default async function middlewareSSRLoader(this: any) {
const render = getRender({
dev: ${dev},
page: ${JSON.stringify(page)},
appMod,
pageMod,
errorMod,
error500Mod,
App,
Document,
buildManifest,
reactLoadableManifest,
Expand Down

0 comments on commit 1d56c8b

Please sign in to comment.