+
{posts.map(({ node }) => (
match v.value {
- CombinatorValue::Descendant => {}
- _ => {
- combinator = Some(v.clone());
-
- new_selectors.push(sel);
- }
- },
+ ComplexSelectorChildren::Combinator(v) => {
+ combinator = Some(v.clone());
+ }
};
}
+
node.children = new_selectors;
}
}
@@ -204,8 +200,6 @@ impl Namespacer {
mut node: CompoundSelector,
) -> Result, Error> {
let mut pseudo_index = None;
-
- let empty_tokens = vec![];
let mut arg_tokens;
for (i, selector) in node.subclass_selectors.iter().enumerate() {
@@ -215,8 +209,22 @@ impl Namespacer {
.iter()
.flatten()
.flat_map(|v| match v {
- PseudoSelectorChildren::Nth(v) => nth_to_tokens(v).tokens,
- PseudoSelectorChildren::PreservedToken(v) => vec![v.clone()],
+ PseudoClassSelectorChildren::PreservedToken(v) => vec![v.clone()],
+ PseudoClassSelectorChildren::AnPlusB(an_plus_b) => match an_plus_b {
+ AnPlusB::Ident(v) => to_tokens(v).tokens,
+ AnPlusB::AnPlusBNotation(v) => to_tokens(v).tokens,
+ },
+ PseudoClassSelectorChildren::Ident(v) => to_tokens(v).tokens,
+ PseudoClassSelectorChildren::Str(v) => to_tokens(v).tokens,
+ PseudoClassSelectorChildren::Delimiter(v) => to_tokens(v).tokens,
+ PseudoClassSelectorChildren::SelectorList(v) => to_tokens(v).tokens,
+ PseudoClassSelectorChildren::CompoundSelectorList(v) => {
+ to_tokens(v).tokens
+ }
+ PseudoClassSelectorChildren::RelativeSelectorList(v) => {
+ to_tokens(v).tokens
+ }
+ PseudoClassSelectorChildren::CompoundSelector(v) => to_tokens(v).tokens,
})
.collect::>();
@@ -224,10 +232,21 @@ impl Namespacer {
}
SubclassSelector::PseudoElement(PseudoElementSelector {
name, children, ..
- }) => match children {
- Some(children) => (name, children),
- None => (name, &empty_tokens),
- },
+ }) => {
+ arg_tokens = children
+ .iter()
+ .flatten()
+ .flat_map(|v| match v {
+ PseudoElementSelectorChildren::PreservedToken(v) => vec![v.clone()],
+ PseudoElementSelectorChildren::Ident(v) => to_tokens(v).tokens,
+ PseudoElementSelectorChildren::CompoundSelector(v) => {
+ to_tokens(v).tokens
+ }
+ })
+ .collect::>();
+
+ (name, &arg_tokens)
+ }
_ => continue,
};
@@ -282,12 +301,16 @@ impl Namespacer {
trace!("Combinator: {:?}", combinator);
trace!("v[0]: {:?}", v[0]);
+ let mut result = vec![];
+
if let Some(combinator) = combinator {
match v.get(0) {
- Some(ComplexSelectorChildren::Combinator(..)) => {}
- Some(..) => {}
+ // `Descendant` combinator can't be the first because we removed it
+ // above
+ Some(ComplexSelectorChildren::Combinator(..))
+ if combinator.value == CombinatorValue::Descendant => {}
_ => {
- v.push(ComplexSelectorChildren::Combinator(combinator));
+ result.push(ComplexSelectorChildren::Combinator(combinator));
}
}
}
@@ -301,7 +324,9 @@ impl Namespacer {
}
});
- Ok(v)
+ result.extend(v);
+
+ Ok(result)
}
Err(_) => bail!("Failed to transform one off global selector"),
};
@@ -332,7 +357,15 @@ impl Namespacer {
);
}
- Ok(vec![ComplexSelectorChildren::CompoundSelector(node)])
+ let mut result = vec![];
+
+ if let Some(combinator) = combinator {
+ result.push(ComplexSelectorChildren::Combinator(combinator));
+ }
+
+ result.push(ComplexSelectorChildren::CompoundSelector(node));
+
+ Ok(result)
}
}
@@ -461,17 +494,21 @@ fn get_block_tokens(selector_tokens: &Tokens) -> Vec {
]
}
-fn nth_to_tokens(nth: &Nth) -> Tokens {
+fn to_tokens(node: &N) -> Tokens
+where
+ for<'aa> CodeGenerator<&'aa mut BasicCssWriter<'aa, &'aa mut std::string::String>>: Emit,
+{
let mut s = String::new();
{
let mut wr = BasicCssWriter::new(&mut s, BasicCssWriterConfig { indent: " " });
let mut gen = CodeGenerator::new(&mut wr, CodegenConfig { minify: true });
- gen.emit(&nth).unwrap();
+ gen.emit(node).unwrap();
}
+ let span = node.span();
let mut lexer = swc_css::parser::lexer::Lexer::new(
- StringInput::new(&s, nth.span.lo, nth.span.hi),
+ StringInput::new(&s, span.lo, span.hi),
ParserConfig {
allow_wrong_line_comments: true,
},
@@ -484,7 +521,7 @@ fn nth_to_tokens(nth: &Nth) -> Tokens {
}
Tokens {
- span: Span::new(nth.span.lo, nth.span.hi, Default::default()),
+ span: Span::new(span.lo, span.hi, Default::default()),
tokens,
}
}
diff --git a/packages/next-swc/crates/core/tests/full/example/output.js b/packages/next-swc/crates/core/tests/full/example/output.js
index e0a9c88dd80f..3311dd93d841 100644
--- a/packages/next-swc/crates/core/tests/full/example/output.js
+++ b/packages/next-swc/crates/core/tests/full/example/output.js
@@ -1,40 +1,40 @@
-function a(a, b) {
- (null == b || b > a.length) && (b = a.length);
- for(var c = 0, d = new Array(b); c < b; c++)d[c] = a[c];
+function b(c, a) {
+ (null == a || a > c.length) && (a = c.length);
+ for(var b = 0, d = new Array(a); b < a; b++)d[b] = c[b];
return d;
}
-import b from "other";
-(function(a, c) {
- return (function(a) {
- if (Array.isArray(a)) return a;
- })(a) || (function(a, c) {
- var d, e, f = null == a ? null : "undefined" != typeof Symbol && a[Symbol.iterator] || a["@@iterator"];
- if (null != f) {
- var g = [], h = !0, i = !1;
+import a from "other";
+(function(c, b) {
+ return (function(c) {
+ if (Array.isArray(c)) return c;
+ })(c) || (function(c, b) {
+ var g, h, a = null == c ? null : "undefined" != typeof Symbol && c[Symbol.iterator] || c["@@iterator"];
+ if (null != a) {
+ var d = [], e = !0, i = !1;
try {
- for(f = f.call(a); !(h = (d = f.next()).done) && (g.push(d.value), !c || g.length !== c); h = !0);
+ for(a = a.call(c); !(e = (g = a.next()).done) && (d.push(g.value), !b || d.length !== b); e = !0);
} catch (j) {
- i = !0, e = j;
+ i = !0, h = j;
} finally{
try {
- h || null == f.return || f.return();
+ e || null == a.return || a.return();
} finally{
- if (i) throw e;
+ if (i) throw h;
}
}
- return g;
+ return d;
}
- })(a, c) || (function(b, c) {
- if (b) {
- if ("string" == typeof b) return a(b, c);
- var d = Object.prototype.toString.call(b).slice(8, -1);
- if ("Object" === d && b.constructor && (d = b.constructor.name), "Map" === d || "Set" === d) return Array.from(d);
- if ("Arguments" === d || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(d)) return a(b, c);
+ })(c, b) || (function(a, d) {
+ if (a) {
+ if ("string" == typeof a) return b(a, d);
+ var c = Object.prototype.toString.call(a).slice(8, -1);
+ if ("Object" === c && a.constructor && (c = a.constructor.name), "Map" === c || "Set" === c) return Array.from(c);
+ if ("Arguments" === c || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c)) return b(a, d);
}
- })(a, c) || (function() {
+ })(c, b) || (function() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
})();
-})(b, 1)[0];
+})(a, 1)[0];
var c = function() {
"use strict";
!function(a, b) {
diff --git a/packages/next-swc/crates/napi/Cargo.toml b/packages/next-swc/crates/napi/Cargo.toml
index 493424eb23bf..ebd06062e1d9 100644
--- a/packages/next-swc/crates/napi/Cargo.toml
+++ b/packages/next-swc/crates/napi/Cargo.toml
@@ -16,12 +16,12 @@ once_cell = "1.8.0"
serde = "1"
serde_json = "1"
next-swc = { version = "0.0.0", path = "../core" }
-swc = "0.126.2"
+swc = "0.138.0"
swc_atoms = "0.2.7"
-swc_bundler = { version = "0.107.0", features = ["concurrent"] }
-swc_common = { version = "0.17.0", features = ["concurrent", "sourcemap"] }
+swc_bundler = { version = "0.114.0", features = ["concurrent"] }
+swc_common = { version = "0.17.9", features = ["concurrent", "sourcemap"] }
swc_ecma_loader = { version = "0.28.0", features = ["node", "lru"] }
-swc_ecmascript = { version = "0.114.2", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] }
+swc_ecmascript = { version = "0.123.0", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] }
swc_node_base = "0.5.1"
[build-dependencies]
diff --git a/packages/next-swc/crates/wasm/Cargo.toml b/packages/next-swc/crates/wasm/Cargo.toml
index b25e21bcbe0e..a69fa56fa20d 100644
--- a/packages/next-swc/crates/wasm/Cargo.toml
+++ b/packages/next-swc/crates/wasm/Cargo.toml
@@ -16,9 +16,9 @@ path-clean = "0.1"
serde = {version = "1", features = ["derive"]}
serde_json = "1"
next-swc = { version = "0.0.0", path = "../core" }
-swc = "0.126.2"
-swc_common = { version = "0.17.0", features = ["concurrent", "sourcemap"] }
-swc_ecmascript = { version = "0.114.2", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] }
+swc = "0.138.0"
+swc_common = { version = "0.17.9", features = ["concurrent", "sourcemap"] }
+swc_ecmascript = { version = "0.123.0", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] }
tracing = {version = "0.1.28", features = ["release_max_level_off"]}
wasm-bindgen = {version = "0.2", features = ["serde-serialize"]}
wasm-bindgen-futures = "0.4.8"
diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json
index 4daa06fb1bee..081ef4729455 100644
--- a/packages/next-swc/package.json
+++ b/packages/next-swc/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/swc",
- "version": "12.1.1-canary.4",
+ "version": "12.1.1-canary.5",
"private": true,
"scripts": {
"build-native": "napi build --platform --cargo-name next_swc_napi native",
diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts
index 2555a72795bc..fdaf07b21277 100644
--- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts
+++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts
@@ -5,32 +5,27 @@ import { parse } from '../../swc'
import { getBaseSWCOptions } from '../../swc/options'
import { getRawPageExtensions } from '../../utils'
-const getIsClientComponent =
- (pageExtensions: string[]) => (importSource: string) => {
- return new RegExp(`\\.client(\\.(${pageExtensions.join('|')}))?`).test(
- importSource
- )
- }
-
-const getIsServerComponent =
- (pageExtensions: string[]) => (importSource: string) => {
- return new RegExp(`\\.server(\\.(${pageExtensions.join('|')}))?`).test(
- importSource
- )
- }
-
-function isNextComponent(importSource: string) {
- return (
- importSource.includes('next/link') || importSource.includes('next/image')
+const imageExtensions = ['jpg', 'jpeg', 'png', 'webp', 'avif']
+
+const createClientComponentFilter = (pageExtensions: string[]) => {
+ // Special cases for Next.js APIs that are considered as client components:
+ // - .client.[ext]
+ // - next/link, next/image
+ // - .[imageExt]
+ const regex = new RegExp(
+ '(' +
+ `\\.client(\\.(${pageExtensions.join('|')}))?|` +
+ `next/link|next/image|` +
+ `\\.(${imageExtensions.join('|')})` +
+ ')$'
)
+
+ return (importSource: string) => regex.test(importSource)
}
-export function isImageImport(importSource: string) {
- // TODO: share extension with next/image
- // TODO: add other static assets, jpeg -> jpg
- return ['jpg', 'jpeg', 'png', 'webp', 'avif'].some((imageExt) =>
- importSource.endsWith('.' + imageExt)
- )
+const createServerComponentFilter = (pageExtensions: string[]) => {
+ const regex = new RegExp(`\\.server(\\.(${pageExtensions.join('|')}))?$`)
+ return (importSource: string) => regex.test(importSource)
}
async function parseImportsInfo({
@@ -57,7 +52,6 @@ async function parseImportsInfo({
})
const ast = await parse(source, { ...opts.jsc.parser, isModule: true })
const { body } = ast
- const beginPos = ast.span.start
let transformedSource = ''
let lastIndex = 0
let defaultExportName
@@ -74,16 +68,18 @@ async function parseImportsInfo({
const importDeclarations = source.substring(
lastIndex,
- node.source.span.start - beginPos
+ node.source.span.start
)
- if (
- !(
- isClientComponent(importSource) ||
- isNextComponent(importSource) ||
- isImageImport(importSource)
- )
- ) {
+ if (isClientComponent(importSource)) {
+ // A client component. It should be loaded as module reference.
+ transformedSource += importDeclarations
+ transformedSource += JSON.stringify(`${importSource}?__sc_client__`)
+ imports.push(`require(${JSON.stringify(importSource)})`)
+ } else {
+ // This is a special case to avoid the Duplicate React error.
+ // Since we already include React in the SSR runtime,
+ // here we can't create a new module with the ?__rsc_server__ query.
if (
['react/jsx-runtime', 'react/jsx-dev-runtime'].includes(
importSource
@@ -96,11 +92,6 @@ async function parseImportsInfo({
// component.
transformedSource += importDeclarations
transformedSource += JSON.stringify(`${importSource}?__sc_server__`)
- } else {
- // A client component. It should be loaded as module reference.
- transformedSource += importDeclarations
- transformedSource += JSON.stringify(`${importSource}?__sc_client__`)
- imports.push(`require(${JSON.stringify(importSource)})`)
}
} else {
// For the client compilation, we skip all modules imports but
@@ -108,12 +99,7 @@ async function parseImportsInfo({
// have to be imported from either server or client components.
if (
!(
- isClientComponent(importSource) ||
- isServerComponent(importSource) ||
- // Special cases for Next.js APIs that are considered as client
- // components:
- isNextComponent(importSource) ||
- isImageImport(importSource)
+ isClientComponent(importSource) || isServerComponent(importSource)
)
) {
continue
@@ -122,7 +108,7 @@ async function parseImportsInfo({
imports.push(`require(${JSON.stringify(importSource)})`)
}
- lastIndex = node.source.span.end - beginPos
+ lastIndex = node.source.span.end
break
}
case 'ExportDefaultDeclaration': {
@@ -170,8 +156,8 @@ export default async function transformSource(
}
const rawRawPageExtensions = getRawPageExtensions(pageExtensions)
- const isServerComponent = getIsServerComponent(rawRawPageExtensions)
- const isClientComponent = getIsClientComponent(rawRawPageExtensions)
+ const isServerComponent = createServerComponentFilter(rawRawPageExtensions)
+ const isClientComponent = createClientComponentFilter(rawRawPageExtensions)
if (!isClientCompilation) {
// We only apply the loader to server components, or shared components that
diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx
index 03f5438f9361..6fad0b58d355 100644
--- a/packages/next/client/image.tsx
+++ b/packages/next/client/image.tsx
@@ -8,6 +8,7 @@ import {
} from '../shared/lib/image-config'
import { useIntersection } from './use-intersection'
import { ImageConfigContext } from '../shared/lib/image-config-context'
+import { warnOnce } from '../shared/lib/utils'
const configEnv = process.env.__NEXT_IMAGE_OPTS as any as ImageConfigComplete
const loadedImageURLs = new Set()
@@ -23,17 +24,6 @@ if (typeof window === 'undefined') {
;(global as any).__NEXT_IMAGE_IMPORTED = true
}
-let warnOnce = (_: string) => {}
-if (process.env.NODE_ENV !== 'production') {
- const warnings = new Set()
- warnOnce = (msg: string) => {
- if (!warnings.has(msg)) {
- console.warn(msg)
- }
- warnings.add(msg)
- }
-}
-
const VALID_LOADING_VALUES = ['lazy', 'eager', undefined] as const
type LoadingValue = typeof VALID_LOADING_VALUES[number]
type ImageConfig = ImageConfigComplete & { allSizes: number[] }
diff --git a/packages/next/package.json b/packages/next/package.json
index f3f8ebd40354..dc59420eb07c 100644
--- a/packages/next/package.json
+++ b/packages/next/package.json
@@ -1,6 +1,6 @@
{
"name": "next",
- "version": "12.1.1-canary.4",
+ "version": "12.1.1-canary.5",
"description": "The React Framework",
"main": "./dist/server/next.js",
"license": "MIT",
@@ -69,7 +69,7 @@
]
},
"dependencies": {
- "@next/env": "12.1.1-canary.4",
+ "@next/env": "12.1.1-canary.5",
"caniuse-lite": "^1.0.30001283",
"postcss": "8.4.5",
"styled-jsx": "5.0.0",
@@ -117,11 +117,11 @@
"@hapi/accept": "5.0.2",
"@napi-rs/cli": "2.4.4",
"@napi-rs/triples": "1.1.0",
- "@next/polyfill-module": "12.1.1-canary.4",
- "@next/polyfill-nomodule": "12.1.1-canary.4",
- "@next/react-dev-overlay": "12.1.1-canary.4",
- "@next/react-refresh-utils": "12.1.1-canary.4",
- "@next/swc": "12.1.1-canary.4",
+ "@next/polyfill-module": "12.1.1-canary.5",
+ "@next/polyfill-nomodule": "12.1.1-canary.5",
+ "@next/react-dev-overlay": "12.1.1-canary.5",
+ "@next/react-refresh-utils": "12.1.1-canary.5",
+ "@next/swc": "12.1.1-canary.5",
"@peculiar/webcrypto": "1.1.7",
"@taskr/clear": "1.1.0",
"@taskr/esnext": "1.1.0",
diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts
index 9a6e81bf2590..6a4d6d2dc175 100644
--- a/packages/next/server/base-server.ts
+++ b/packages/next/server/base-server.ts
@@ -560,9 +560,6 @@ export default abstract class Server {
if (url.locale?.path.detectedLocale) {
req.url = formatUrl(url)
addRequestMeta(req, '__nextStrippedLocale', true)
- if (url.pathname === '/api' || url.pathname.startsWith('/api/')) {
- return this.render404(req, res, parsedUrl)
- }
}
if (!this.minimalMode || !parsedUrl.query.__nextLocale) {
diff --git a/packages/next/server/image-optimizer.ts b/packages/next/server/image-optimizer.ts
index 747d07ce5d4e..976bd8c29fd6 100644
--- a/packages/next/server/image-optimizer.ts
+++ b/packages/next/server/image-optimizer.ts
@@ -546,10 +546,18 @@ export async function imageOptimizer(
throw new ImageError(500, 'Unable to optimize buffer')
}
} catch (error) {
- return {
- buffer: upstreamBuffer,
- contentType: upstreamType!,
- maxAge,
+ if (upstreamBuffer && upstreamType) {
+ // If we fail to optimize, fallback to the original image
+ return {
+ buffer: upstreamBuffer,
+ contentType: upstreamType,
+ maxAge: nextConfig.images.minimumCacheTTL,
+ }
+ } else {
+ throw new ImageError(
+ 500,
+ 'Unable to optimize image and unable to fallback to upstream image'
+ )
}
}
}
diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx
index 014b6c104a12..2dd64eb1b402 100644
--- a/packages/next/server/render.tsx
+++ b/packages/next/server/render.tsx
@@ -304,17 +304,15 @@ const rscCache = new Map()
function createRSCHook() {
return (
- writable: WritableStream,
+ writable: WritableStream,
id: string,
- req: ReadableStream,
+ req: ReadableStream,
bootstrap: boolean
) => {
let entry = rscCache.get(id)
if (!entry) {
const [renderStream, forwardStream] = readableStreamTee(req)
- entry = createFromReadableStream(
- pipeThrough(renderStream, createTextEncoderStream())
- )
+ entry = createFromReadableStream(renderStream)
rscCache.set(id, entry)
let bootstrapped = false
@@ -325,10 +323,11 @@ function createRSCHook() {
if (bootstrap && !bootstrapped) {
bootstrapped = true
writer.write(
- ``
+ encodeText(
+ ``
+ )
)
}
if (done) {
@@ -336,11 +335,11 @@ function createRSCHook() {
writer.close()
} else {
writer.write(
- ``
+ encodeText(
+ ``
+ )
)
process()
}
@@ -365,7 +364,7 @@ function createServerComponentRenderer(
runtime,
}: {
cachePrefix: string
- transformStream: TransformStream
+ transformStream: TransformStream
serverComponentManifest: NonNullable
runtime: 'nodejs' | 'edge'
}
@@ -381,12 +380,9 @@ function createServerComponentRenderer(
const writable = transformStream.writable
const ServerComponentWrapper = (props: any) => {
const id = (React as any).useId()
- const reqStream: ReadableStream = pipeThrough(
- renderToReadableStream(
- renderFlight(App, OriginalComponent, props),
- serverComponentManifest
- ),
- createTextDecoderStream()
+ const reqStream: ReadableStream = renderToReadableStream(
+ renderFlight(App, OriginalComponent, props),
+ serverComponentManifest
)
const response = useRSCResponse(
@@ -482,8 +478,8 @@ export async function renderToHTML(
let Component: React.ComponentType<{}> | ((props: any) => JSX.Element) =
renderOpts.Component
let serverComponentsInlinedTransformStream: TransformStream<
- string,
- string
+ Uint8Array,
+ Uint8Array
> | null = null
if (isServerComponent) {
@@ -1181,21 +1177,16 @@ export async function renderToHTML(
if (isResSent(res) && !isSSG) return null
if (renderServerComponentData) {
- const stream: ReadableStream = pipeThrough(
- renderToReadableStream(
- renderFlight(App, OriginalComponent, {
- ...props.pageProps,
- ...serverComponentProps,
- }),
- serverComponentManifest
- ),
- createTextDecoderStream()
+ const stream: ReadableStream = renderToReadableStream(
+ renderFlight(App, OriginalComponent, {
+ ...props.pageProps,
+ ...serverComponentProps,
+ }),
+ serverComponentManifest
)
+
return new RenderResult(
- pipeThrough(
- pipeThrough(stream, createBufferedTransformStream()),
- createTextEncoderStream()
- )
+ pipeThrough(stream, createBufferedTransformStream())
)
}
@@ -1360,7 +1351,8 @@ export async function renderToHTML(
generateStaticHTML: true,
})
- return await streamToString(flushEffectStream)
+ const flushed = await streamToString(flushEffectStream)
+ return flushed
}
return await renderToStream({
@@ -1607,9 +1599,7 @@ export async function renderToHTML(
return new RenderResult(html)
}
- return new RenderResult(
- pipeThrough(chainStreams(streams), createTextEncoderStream())
- )
+ return new RenderResult(chainStreams(streams))
}
function errorToJSON(err: Error) {
@@ -1707,27 +1697,10 @@ function createTransformStream ({
}
}
-function createTextDecoderStream(): TransformStream {
- const decoder = new TextDecoder()
- return createTransformStream({
- transform(chunk, controller) {
- controller.enqueue(
- typeof chunk === 'string' ? chunk : decoder.decode(chunk)
- )
- },
- })
-}
-
-function createTextEncoderStream(): TransformStream {
- const encoder = new TextEncoder()
- return createTransformStream({
- transform(chunk, controller) {
- controller.enqueue(encoder.encode(chunk))
- },
- })
-}
-
-function createBufferedTransformStream(): TransformStream {
+function createBufferedTransformStream(): TransformStream<
+ Uint8Array,
+ Uint8Array
+> {
let bufferedString = ''
let pendingFlush: Promise | null = null
@@ -1735,7 +1708,7 @@ function createBufferedTransformStream(): TransformStream {
if (!pendingFlush) {
pendingFlush = new Promise((resolve) => {
setTimeout(() => {
- controller.enqueue(bufferedString)
+ controller.enqueue(encodeText(bufferedString))
bufferedString = ''
pendingFlush = null
resolve()
@@ -1747,7 +1720,7 @@ function createBufferedTransformStream(): TransformStream {
return createTransformStream({
transform(chunk, controller) {
- bufferedString += chunk
+ bufferedString += decodeText(chunk)
flushBuffer(controller)
},
@@ -1761,11 +1734,11 @@ function createBufferedTransformStream(): TransformStream {
function createFlushEffectStream(
handleFlushEffect: () => Promise
-): TransformStream {
+): TransformStream {
return createTransformStream({
async transform(chunk, controller) {
const extraChunk = await handleFlushEffect()
- controller.enqueue(extraChunk + chunk)
+ controller.enqueue(encodeText(extraChunk + decodeText(chunk)))
},
})
}
@@ -1781,10 +1754,10 @@ function renderToStream({
ReactDOMServer: typeof import('react-dom/server')
element: React.ReactElement
suffix?: string
- dataStream?: ReadableStream
+ dataStream?: ReadableStream
generateStaticHTML: boolean
flushEffectHandler?: () => Promise
-}): Promise> {
+}): Promise> {
return new Promise((resolve, reject) => {
let resolved = false
@@ -1799,7 +1772,7 @@ function renderToStream({
// defer to a microtask to ensure `stream` is set.
resolve(
Promise.resolve().then(() => {
- const transforms: Array> = [
+ const transforms: Array> = [
createBufferedTransformStream(),
flushEffectHandler
? createFlushEffectStream(flushEffectHandler)
@@ -1820,45 +1793,57 @@ function renderToStream({
}
}
- const renderStream = pipeThrough(
- (ReactDOMServer as any).renderToReadableStream(element, {
- onError(err: Error) {
- if (!resolved) {
- resolved = true
- reject(err)
- }
- },
- onCompleteShell() {
- if (!generateStaticHTML) {
- doResolve()
- }
- },
- onCompleteAll() {
+ const renderStream: ReadableStream = (
+ ReactDOMServer as any
+ ).renderToReadableStream(element, {
+ onError(err: Error) {
+ if (!resolved) {
+ resolved = true
+ reject(err)
+ }
+ },
+ onCompleteShell() {
+ if (!generateStaticHTML) {
doResolve()
- },
- }),
- createTextDecoderStream()
- )
+ }
+ },
+ onCompleteAll() {
+ doResolve()
+ },
+ })
})
}
-function createSuffixStream(suffix: string): TransformStream {
+function encodeText(input: string) {
+ return new TextEncoder().encode(input)
+}
+
+function decodeText(input?: Uint8Array) {
+ return new TextDecoder().decode(input)
+}
+
+function createSuffixStream(
+ suffix: string
+): TransformStream {
return createTransformStream({
flush(controller) {
if (suffix) {
- controller.enqueue(suffix)
+ controller.enqueue(encodeText(suffix))
}
},
})
}
-function createPrefixStream(prefix: string): TransformStream {
+function createPrefixStream(
+ prefix: string
+): TransformStream {
let prefixFlushed = false
return createTransformStream({
transform(chunk, controller) {
if (!prefixFlushed && prefix) {
prefixFlushed = true
- controller.enqueue(chunk + prefix)
+ controller.enqueue(chunk)
+ controller.enqueue(encodeText(prefix))
} else {
controller.enqueue(chunk)
}
@@ -1866,15 +1851,15 @@ function createPrefixStream(prefix: string): TransformStream {
flush(controller) {
if (!prefixFlushed && prefix) {
prefixFlushed = true
- controller.enqueue(prefix)
+ controller.enqueue(encodeText(prefix))
}
},
})
}
function createInlineDataStream(
- dataStream: ReadableStream
-): TransformStream {
+ dataStream: ReadableStream
+): TransformStream {
let dataStreamFinished: Promise | null = null
return createTransformStream({
transform(chunk, controller) {
@@ -1966,19 +1951,21 @@ function chainStreams(streams: ReadableStream[]): ReadableStream {
return readable
}
-function streamFromArray(strings: string[]): ReadableStream {
+function streamFromArray(strings: string[]): ReadableStream {
// Note: we use a TransformStream here instead of instantiating a ReadableStream
// because the built-in ReadableStream polyfill runs strings through TextEncoder.
const { readable, writable } = new TransformStream()
const writer = writable.getWriter()
- strings.forEach((str) => writer.write(str))
+ strings.forEach((str) => writer.write(encodeText(str)))
writer.close()
return readable
}
-async function streamToString(stream: ReadableStream): Promise {
+async function streamToString(
+ stream: ReadableStream
+): Promise {
const reader = stream.getReader()
let bufferedString = ''
@@ -1989,6 +1976,6 @@ async function streamToString(stream: ReadableStream): Promise {
return bufferedString
}
- bufferedString += value
+ bufferedString += decodeText(value)
}
}
diff --git a/packages/next/server/router.ts b/packages/next/server/router.ts
index 1c618e7616a3..b101f838544e 100644
--- a/packages/next/server/router.ts
+++ b/packages/next/server/router.ts
@@ -317,8 +317,20 @@ export default class Router {
currentPathnameNoBasePath,
this.locales
)
+
const activeBasePath = keepBasePath ? this.basePath : ''
+ // don't match API routes when they are locale prefixed
+ // e.g. /api/hello shouldn't match /en/api/hello as a page
+ // rewrites/redirects can match though
+ if (
+ !isCustomRoute &&
+ localePathResult.detectedLocale &&
+ localePathResult.pathname.match(/^\/api(?:\/|$)/)
+ ) {
+ continue
+ }
+
if (keepLocale) {
if (
!testRoute.internal &&
diff --git a/packages/next/shared/lib/head.tsx b/packages/next/shared/lib/head.tsx
index 81d339e21a9f..d4bbcc64bed2 100644
--- a/packages/next/shared/lib/head.tsx
+++ b/packages/next/shared/lib/head.tsx
@@ -3,6 +3,7 @@ import Effect from './side-effect'
import { AmpStateContext } from './amp-context'
import { HeadManagerContext } from './head-manager-context'
import { isInAmpMode } from './amp'
+import { warnOnce } from './utils'
type WithInAmpMode = {
inAmpMode?: boolean
@@ -161,17 +162,20 @@ function reduceComponents(
return React.cloneElement(c, newProps)
}
}
- if (process.env.NODE_ENV === 'development') {
+ if (
+ process.env.NODE_ENV === 'development' &&
+ process.env.__NEXT_CONCURRENT_FEATURES
+ ) {
// omit JSON-LD structured data snippets from the warning
if (c.type === 'script' && c.props['type'] !== 'application/ld+json') {
const srcMessage = c.props['src']
? `
+
+
+ Streaming Head
+
+)
diff --git a/test/integration/react-streaming-and-server-components/test/index.test.js b/test/integration/react-streaming-and-server-components/test/index.test.js
index d93f233a1aab..527a17c87517 100644
--- a/test/integration/react-streaming-and-server-components/test/index.test.js
+++ b/test/integration/react-streaming-and-server-components/test/index.test.js
@@ -151,7 +151,7 @@ describe('Edge runtime - prod', () => {
})
basic(context, { env: 'prod' })
- streaming(context)
+ streaming(context, { env: 'prod' })
rsc(context, { runtime: 'edge', env: 'prod' })
})
@@ -184,14 +184,14 @@ describe('Edge runtime - dev', () => {
})
basic(context, { env: 'dev' })
- streaming(context)
+ streaming(context, { env: 'dev' })
rsc(context, { runtime: 'edge', env: 'dev' })
})
const nodejsRuntimeBasicSuite = {
runTests: (context, env) => {
basic(context, { env })
- streaming(context)
+ streaming(context, { env })
rsc(context, { runtime: 'nodejs' })
if (env === 'prod') {
diff --git a/test/integration/react-streaming-and-server-components/test/streaming.js b/test/integration/react-streaming-and-server-components/test/streaming.js
index 31b6e6259149..edfeccebdcd8 100644
--- a/test/integration/react-streaming-and-server-components/test/streaming.js
+++ b/test/integration/react-streaming-and-server-components/test/streaming.js
@@ -1,6 +1,6 @@
/* eslint-env jest */
import webdriver from 'next-webdriver'
-import { fetchViaHTTP } from 'next-test-utils'
+import { fetchViaHTTP, waitFor } from 'next-test-utils'
async function resolveStreamResponse(response, onData) {
let result = ''
@@ -16,7 +16,7 @@ async function resolveStreamResponse(response, onData) {
return result
}
-export default function (context) {
+export default function (context, { env }) {
it('should support streaming for fizz response', async () => {
await fetchViaHTTP(context.appPort, '/streaming', null, {}).then(
async (response) => {
@@ -99,4 +99,82 @@ export default function (context) {
expect(result).toMatch(/<\/body><\/html>/)
})
})
+
+ if (env === 'dev') {
+ it('should warn when stylesheets or scripts are in head', async () => {
+ let browser
+ try {
+ browser = await webdriver(context.appPort, '/head')
+
+ await browser.waitForElementByCss('h1')
+ await waitFor(1000)
+ const browserLogs = await browser.log('browser')
+ let foundStyles = false
+ let foundScripts = false
+ const logs = []
+ browserLogs.forEach(({ message }) => {
+ if (message.includes('Do not add stylesheets using next/head')) {
+ foundStyles = true
+ logs.push(message)
+ }
+ if (message.includes('Do not add