/jest-environment.js',
+ transformIgnorePatterns: ['/node_modules/', '/next[/\\\\]dist/'],
+ transform: {
+ '.+\\.(t|j)sx?$': [
+ // this matches our SWC options used in https://github.com/vercel/next.js/blob/canary/packages/next/taskfile-swc.js
+ '@swc/jest',
+ {
+ module: {
+ type: 'commonjs',
+ },
+ env: {
+ targets: {
+ node: '12.0.0',
+ },
+ },
+ jsc: {
+ loose: true,
+
+ parser: {
+ syntax: 'typescript',
+ dynamicImport: true,
+ tsx: true,
+ },
+ transform: {
+ react: {
+ pragma: 'React.createElement',
+ pragmaFrag: 'React.Fragment',
+ throwIfNamespace: true,
+ development: false,
+ useBuiltins: true,
+ },
+ },
+ },
+ },
+ ],
+ },
}
diff --git a/lerna.json b/lerna.json
index 452006162c3ecf..76f0b5e0762e28 100644
--- a/lerna.json
+++ b/lerna.json
@@ -17,5 +17,5 @@
"registry": "https://registry.npmjs.org/"
}
},
- "version": "11.1.1-canary.11"
+ "version": "11.1.3-canary.14"
}
diff --git a/package.json b/package.json
index bfa2825b360a62..7238399818f830 100644
--- a/package.json
+++ b/package.json
@@ -10,23 +10,19 @@
"lerna": "lerna",
"dev": "lerna run dev --stream --parallel",
"dev2": "while true; do yarn --check-files && yarn dev; done",
- "testonly": "jest --runInBand",
+ "test-types": "yarn tsc",
+ "test-unit": "yarn jest test/unit/",
+ "testonly": "yarn jest --runInBand",
"testheadless": "cross-env HEADLESS=true yarn testonly",
- "testsafari": "cross-env BROWSER_NAME=safari yarn testonly",
- "testfirefox": "cross-env BROWSER_NAME=firefox yarn testonly",
- "testie": "cross-env BROWSER_NAME=\"internet explorer\" yarn testonly",
- "testall": "yarn run testonly -- --ci --forceExit && lerna run --scope @next/codemod test",
+ "testall": "yarn test -- --ci --forceExit && lerna run --scope @next/codemod test",
"genstats": "cross-env LOCAL_STATS=true node .github/actions/next-stats-action/src/index.js",
- "pretest": "yarn run lint",
"git-reset": "git reset --hard HEAD",
"git-clean": "git clean -d -x -e node_modules -e packages -f",
- "test-take2": "yarn git-reset && yarn git-clean && yarn testall",
- "test": "yarn run testall || yarn run test-take2",
"lint-typescript": "lerna run typescript",
- "lint-eslint": "eslint . --ext js,jsx,ts,tsx --max-warnings=0",
+ "lint-eslint": "eslint . --ext js,jsx,ts,tsx --max-warnings=0 --config .eslintrc.json --no-eslintrc",
"lint-no-typescript": "run-p prettier-check lint-eslint",
- "lint": "run-p lint-typescript prettier-check lint-eslint lint-language",
- "lint-fix": "yarn prettier-fix && eslint . --ext js,jsx,ts,tsx --fix --max-warnings=0",
+ "lint": "run-p test-types lint-typescript prettier-check lint-eslint lint-language",
+ "lint-fix": "yarn prettier-fix && eslint . --ext js,jsx,ts,tsx --fix --max-warnings=0 --config .eslintrc.json --no-eslintrc",
"lint-language": "alex .",
"prettier-check": "prettier --check .",
"prettier-fix": "prettier --write .",
@@ -38,6 +34,7 @@
"lint-staged": "lint-staged",
"next-with-deps": "./scripts/next-with-deps.sh",
"next": "node --trace-deprecation --enable-source-maps packages/next/dist/bin/next",
+ "next-no-sourcemaps": "node --trace-deprecation packages/next/dist/bin/next",
"clean-trace-jaeger": "rm -rf test/integration/basic/.next && TRACE_TARGET=JAEGER node --trace-deprecation --enable-source-maps packages/next/dist/bin/next build test/integration/basic",
"debug": "node --inspect packages/next/dist/bin/next"
},
@@ -50,7 +47,8 @@
"@fullhuman/postcss-purgecss": "1.3.0",
"@mdx-js/loader": "0.18.0",
"@svgr/webpack": "5.5.0",
- "@swc/core": "1.2.74",
+ "@swc/core": "1.2.80",
+ "@swc/jest": "0.2.2",
"@testing-library/react": "11.2.5",
"@types/cheerio": "0.22.16",
"@types/fs-extra": "8.1.0",
@@ -68,8 +66,6 @@
"alex": "9.1.0",
"amphtml-validator": "1.0.33",
"async-sema": "3.0.1",
- "babel-core": "7.0.0-bridge.0",
- "babel-jest": "27.0.0-next.5",
"browserslist": "^4.14.7",
"browserstack-local": "1.4.0",
"cheerio": "0.22.0",
@@ -98,7 +94,7 @@
"image-size": "0.9.3",
"is-animated": "2.0.0",
"isomorphic-unfetch": "3.0.0",
- "jest": "27.0.0-next.8",
+ "jest": "27.0.6",
"ky": "0.19.1",
"ky-universal": "0.6.0",
"lerna": "4.0.0",
diff --git a/packages/create-next-app/create-app.ts b/packages/create-next-app/create-app.ts
index c06ea76227a54a..8d2745db86e03c 100644
--- a/packages/create-next-app/create-app.ts
+++ b/packages/create-next-app/create-app.ts
@@ -226,7 +226,7 @@ export async function createApp({
* TypeScript projects will have type definitions and other devDependencies.
*/
if (typescript) {
- devDependencies.push('typescript', '@types/react')
+ devDependencies.push('typescript', '@types/react', '@types/node')
}
/**
* Install package.json dependencies if they exist.
diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json
index deeba25ff345bc..23ff3d9616633a 100644
--- a/packages/create-next-app/package.json
+++ b/packages/create-next-app/package.json
@@ -1,6 +1,6 @@
{
"name": "create-next-app",
- "version": "11.1.1-canary.11",
+ "version": "11.1.3-canary.14",
"keywords": [
"react",
"next",
diff --git a/packages/create-next-app/templates/typescript/pages/_app.tsx b/packages/create-next-app/templates/typescript/pages/_app.tsx
index 945e8926139764..3f5c9d54858602 100644
--- a/packages/create-next-app/templates/typescript/pages/_app.tsx
+++ b/packages/create-next-app/templates/typescript/pages/_app.tsx
@@ -4,4 +4,5 @@ import type { AppProps } from 'next/app'
function MyApp({ Component, pageProps }: AppProps) {
return
}
+
export default MyApp
diff --git a/packages/create-next-app/templates/typescript/pages/index.tsx b/packages/create-next-app/templates/typescript/pages/index.tsx
index 1ef0af12355d81..72a4a59053d70f 100644
--- a/packages/create-next-app/templates/typescript/pages/index.tsx
+++ b/packages/create-next-app/templates/typescript/pages/index.tsx
@@ -19,7 +19,7 @@ const Home: NextPage = () => {
Get started by editing{' '}
- pages/index.js
+ pages/index.tsx
diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json
index 1736719f21d767..77e6ed103f2b2f 100644
--- a/packages/eslint-config-next/package.json
+++ b/packages/eslint-config-next/package.json
@@ -1,6 +1,6 @@
{
"name": "eslint-config-next",
- "version": "11.1.1-canary.11",
+ "version": "11.1.3-canary.14",
"description": "ESLint configuration used by NextJS.",
"main": "index.js",
"license": "MIT",
@@ -9,7 +9,7 @@
"directory": "packages/eslint-config-next"
},
"dependencies": {
- "@next/eslint-plugin-next": "11.1.1-canary.11",
+ "@next/eslint-plugin-next": "11.1.3-canary.14",
"@rushstack/eslint-patch": "^1.0.6",
"@typescript-eslint/parser": "^4.20.0",
"eslint-import-resolver-node": "^0.3.4",
diff --git a/packages/eslint-plugin-next/lib/index.js b/packages/eslint-plugin-next/lib/index.js
index ae474796056ceb..6098fa2570173e 100644
--- a/packages/eslint-plugin-next/lib/index.js
+++ b/packages/eslint-plugin-next/lib/index.js
@@ -16,6 +16,7 @@ module.exports = {
'no-script-in-head': require('./rules/no-script-in-head'),
'no-typos': require('./rules/no-typos'),
'no-duplicate-head': require('./rules/no-duplicate-head'),
+ 'inline-script-id': require('./rules/inline-script-id'),
'next-script-for-ga': require('./rules/next-script-for-ga'),
},
configs: {
@@ -39,6 +40,7 @@ module.exports = {
'@next/next/no-script-in-head': 2,
'@next/next/no-typos': 1,
'@next/next/no-duplicate-head': 2,
+ '@next/next/inline-script-id': 2,
},
},
'core-web-vitals': {
diff --git a/packages/eslint-plugin-next/lib/rules/inline-script-id.js b/packages/eslint-plugin-next/lib/rules/inline-script-id.js
new file mode 100644
index 00000000000000..70843a2be1fb60
--- /dev/null
+++ b/packages/eslint-plugin-next/lib/rules/inline-script-id.js
@@ -0,0 +1,48 @@
+module.exports = {
+ meta: {
+ docs: {
+ description:
+ 'next/script components with inline content must specify an `id` attribute.',
+ recommended: true,
+ },
+ },
+ create: function (context) {
+ let nextScriptImportName = null
+
+ return {
+ ImportDeclaration(node) {
+ if (node.source.value === 'next/script') {
+ nextScriptImportName = node.specifiers[0].local.name
+ }
+ },
+ JSXElement(node) {
+ if (nextScriptImportName == null) return
+
+ if (
+ node.openingElement &&
+ node.openingElement.name &&
+ node.openingElement.name.name !== nextScriptImportName
+ ) {
+ return
+ }
+
+ const attributes = node.openingElement.attributes
+
+ if (
+ node.children.length > 0 ||
+ attributes.some(
+ (attribute) => attribute.name.name === 'dangerouslySetInnerHTML'
+ )
+ ) {
+ if (!attributes.some((attribute) => attribute.name.name === 'id')) {
+ context.report({
+ node,
+ message:
+ 'next/script components with inline content must specify an `id` attribute. See: https://nextjs.org/docs/messages/inline-script-id',
+ })
+ }
+ }
+ },
+ }
+ },
+}
diff --git a/packages/eslint-plugin-next/lib/rules/next-script-for-ga.js b/packages/eslint-plugin-next/lib/rules/next-script-for-ga.js
index 09385460dcc8f1..8fe6f19189ade3 100644
--- a/packages/eslint-plugin-next/lib/rules/next-script-for-ga.js
+++ b/packages/eslint-plugin-next/lib/rules/next-script-for-ga.js
@@ -52,8 +52,8 @@ module.exports = {
// https://developers.google.com/analytics/devguides/collection/analyticsjs#the_google_analytics_tag
// https://developers.google.com/tag-manager/quickstart
if (
- attributes.has('dangerouslySetInnerHTML') &&
- attributes.value('dangerouslySetInnerHTML')[0]
+ attributes.value('dangerouslySetInnerHTML') &&
+ attributes.value('dangerouslySetInnerHTML').length > 0
) {
const htmlContent =
attributes.value('dangerouslySetInnerHTML')[0].value.quasis &&
diff --git a/packages/eslint-plugin-next/lib/rules/no-document-import-in-page.js b/packages/eslint-plugin-next/lib/rules/no-document-import-in-page.js
index 093a64190aa580..6c40f87795feee 100644
--- a/packages/eslint-plugin-next/lib/rules/no-document-import-in-page.js
+++ b/packages/eslint-plugin-next/lib/rules/no-document-import-in-page.js
@@ -15,14 +15,13 @@ module.exports = {
return
}
- const page = context.getFilename().split('pages')[1]
- if (!page) {
- return
- }
- const { name, dir } = path.parse(page)
+ const paths = context.getFilename().split('pages')
+ const page = paths[paths.length - 1]
+
if (
- name.startsWith('_document') ||
- (dir === '/_document' && name === 'index')
+ !page ||
+ page.startsWith(`${path.sep}_document`) ||
+ page.startsWith(`${path.posix.sep}_document`)
) {
return
}
diff --git a/packages/eslint-plugin-next/lib/utils/url.js b/packages/eslint-plugin-next/lib/utils/url.js
index 838b40c4d38475..6d5a448cf0032c 100644
--- a/packages/eslint-plugin-next/lib/utils/url.js
+++ b/packages/eslint-plugin-next/lib/utils/url.js
@@ -42,7 +42,10 @@ function getUrlFromPagesDirectories(urlPrefix, directories) {
(url) => `^${normalizeURL(url)}$`
)
)
- ).map((urlReg) => new RegExp(urlReg))
+ ).map((urlReg) => {
+ urlReg = urlReg.replace(/\[.*\]/g, '.*')
+ return new RegExp(urlReg)
+ })
}
// Cache for fs.readdirSync lookup.
@@ -60,7 +63,6 @@ function parseUrlForPages(urlprefix, directory) {
const res = []
fsReadDirSyncCache[directory].forEach((fname) => {
if (/(\.(j|t)sx?)$/.test(fname)) {
- fname = fname.replace(/\[.*\]/g, '.*')
if (/^index(\.(j|t)sx?)$/.test(fname)) {
res.push(`${urlprefix}${fname.replace(/^index(\.(j|t)sx?)$/, '')}`)
}
diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json
index 8141f7b3c05435..5d189848ecd40a 100644
--- a/packages/eslint-plugin-next/package.json
+++ b/packages/eslint-plugin-next/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/eslint-plugin-next",
- "version": "11.1.1-canary.11",
+ "version": "11.1.3-canary.14",
"description": "ESLint plugin for NextJS.",
"main": "lib/index.js",
"license": "MIT",
diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json
index a8a422cae274da..242eee4d98f23f 100644
--- a/packages/next-bundle-analyzer/package.json
+++ b/packages/next-bundle-analyzer/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/bundle-analyzer",
- "version": "11.1.1-canary.11",
+ "version": "11.1.3-canary.14",
"main": "index.js",
"license": "MIT",
"repository": {
diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json
index d67cb678cd6271..d9bc09d97d6472 100644
--- a/packages/next-codemod/package.json
+++ b/packages/next-codemod/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/codemod",
- "version": "11.1.1-canary.11",
+ "version": "11.1.3-canary.14",
"license": "MIT",
"dependencies": {
"chalk": "4.1.0",
diff --git a/packages/next-env/package.json b/packages/next-env/package.json
index 8903f0133c40cd..33bc50b604fb0c 100644
--- a/packages/next-env/package.json
+++ b/packages/next-env/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/env",
- "version": "11.1.1-canary.11",
+ "version": "11.1.3-canary.14",
"keywords": [
"react",
"next",
diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json
index aad2f060061c1a..d55a4419cd7475 100644
--- a/packages/next-mdx/package.json
+++ b/packages/next-mdx/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/mdx",
- "version": "11.1.1-canary.11",
+ "version": "11.1.3-canary.14",
"main": "index.js",
"license": "MIT",
"repository": {
diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json
index 08ea84f37ed2e0..ad970c0fb68a1d 100644
--- a/packages/next-plugin-storybook/package.json
+++ b/packages/next-plugin-storybook/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-storybook",
- "version": "11.1.1-canary.11",
+ "version": "11.1.3-canary.14",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-storybook"
diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json
index 2396afd741ff28..05f800b9c50515 100644
--- a/packages/next-polyfill-module/package.json
+++ b/packages/next-polyfill-module/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-module",
- "version": "11.1.1-canary.11",
+ "version": "11.1.3-canary.14",
"description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)",
"main": "dist/polyfill-module.js",
"license": "MIT",
diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json
index a4aca5ebebd0ea..bf9fdf0baded85 100644
--- a/packages/next-polyfill-nomodule/package.json
+++ b/packages/next-polyfill-nomodule/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-nomodule",
- "version": "11.1.1-canary.11",
+ "version": "11.1.3-canary.14",
"description": "A polyfill for non-dead, nomodule browsers.",
"main": "dist/polyfill-nomodule.js",
"license": "MIT",
diff --git a/packages/next/build/babel/loader/index.ts b/packages/next/build/babel/loader/index.ts
index 6fea5706fa355a..624a878dadde58 100644
--- a/packages/next/build/babel/loader/index.ts
+++ b/packages/next/build/babel/loader/index.ts
@@ -1,5 +1,4 @@
import { getOptions } from 'next/dist/compiled/loader-utils'
-import { trace } from '../../../telemetry/trace'
import { Span } from '../../../telemetry/trace'
import transform from './transform'
import { NextJsLoaderContext } from './types'
@@ -40,7 +39,7 @@ const nextBabelLoaderOuter = function nextBabelLoaderOuter(
) {
const callback = this.async()
- const loaderSpan = trace('next-babel-turbo-loader', this.currentTraceSpan?.id)
+ const loaderSpan = this.currentTraceSpan.traceChild('next-babel-turbo-loader')
loaderSpan
.traceAsyncFn(() =>
nextBabelLoader.call(this, loaderSpan, inputSource, inputSourceMap)
diff --git a/packages/next/build/babel/loader/types.d.ts b/packages/next/build/babel/loader/types.d.ts
index 200a4780f3bca2..895ea04077ff56 100644
--- a/packages/next/build/babel/loader/types.d.ts
+++ b/packages/next/build/babel/loader/types.d.ts
@@ -2,7 +2,7 @@ import { loader } from 'next/dist/compiled/webpack/webpack'
import { Span } from '../../../telemetry/trace'
export interface NextJsLoaderContext extends loader.LoaderContext {
- currentTraceSpan?: Span
+ currentTraceSpan: Span
}
export interface NextBabelLoaderOptions {
diff --git a/packages/next/build/cssnano-simple.js b/packages/next/build/cssnano-simple.js
new file mode 100644
index 00000000000000..29c9814992cbc6
--- /dev/null
+++ b/packages/next/build/cssnano-simple.js
@@ -0,0 +1 @@
+module.exports = require('cssnano-simple')(require('postcss'))
diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts
index b9705c2d0a964a..7ab24a2f4135f5 100644
--- a/packages/next/build/entries.ts
+++ b/packages/next/build/entries.ts
@@ -17,7 +17,9 @@ type PagesMapping = {
export function createPagesMapping(
pagePaths: string[],
- extensions: string[]
+ extensions: string[],
+ isWebpack5: boolean,
+ isDev: boolean
): PagesMapping {
const previousPages: PagesMapping = {}
const pages: PagesMapping = pagePaths.reduce(
@@ -45,10 +47,18 @@ export function createPagesMapping(
{}
)
- pages['/_app'] = pages['/_app'] || 'next/dist/pages/_app'
- pages['/_error'] = pages['/_error'] || 'next/dist/pages/_error'
- pages['/_document'] = pages['/_document'] || 'next/dist/pages/_document'
-
+ // we alias these in development and allow webpack to
+ // allow falling back to the correct source file so
+ // that HMR can work properly when a file is added/removed
+ if (isWebpack5 && isDev) {
+ pages['/_app'] = `${PAGES_DIR_ALIAS}/_app`
+ pages['/_error'] = `${PAGES_DIR_ALIAS}/_error`
+ pages['/_document'] = `${PAGES_DIR_ALIAS}/_document`
+ } else {
+ pages['/_app'] = pages['/_app'] || 'next/dist/pages/_app'
+ pages['/_error'] = pages['/_error'] || 'next/dist/pages/_error'
+ pages['/_document'] = pages['/_document'] || 'next/dist/pages/_document'
+ }
return pages
}
diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts
index 294bdcf1e621e6..679f34323ce84d 100644
--- a/packages/next/build/index.ts
+++ b/packages/next/build/index.ts
@@ -130,11 +130,13 @@ export default async function build(
const config: NextConfigComplete = await nextBuildSpan
.traceChild('load-next-config')
.traceAsyncFn(() => loadConfig(PHASE_PRODUCTION_BUILD, dir, conf))
+ const distDir = path.join(dir, config.distDir)
+ setGlobal('distDir', distDir)
+
const { target } = config
const buildId: string = await nextBuildSpan
.traceChild('generate-buildid')
.traceAsyncFn(() => generateBuildId(config.generateBuildId, nanoid))
- const distDir = path.join(dir, config.distDir)
const customRoutes: CustomRoutes = await nextBuildSpan
.traceChild('load-custom-routes')
@@ -176,7 +178,7 @@ export default async function build(
telemetry.record(events)
)
- const ignoreTypeScriptErrors = Boolean(config.typescript?.ignoreBuildErrors)
+ const ignoreTypeScriptErrors = Boolean(config.typescript.ignoreBuildErrors)
const typeCheckStart = process.hrtime()
const typeCheckingSpinner = createSpinner({
prefixText: `${Log.prefixes.info} ${
@@ -216,15 +218,16 @@ export default async function build(
typeCheckingSpinner.stopAndPersist()
}
- const ignoreESLint = Boolean(config.eslint?.ignoreDuringBuilds)
- const lintDirs = config.eslint?.dirs
+ const ignoreESLint = Boolean(config.eslint.ignoreDuringBuilds)
+ const eslintCacheDir = path.join(cacheDir, 'eslint/')
if (!ignoreESLint && runLint) {
await nextBuildSpan
.traceChild('verify-and-lint')
.traceAsyncFn(async () => {
await verifyAndLint(
dir,
- lintDirs,
+ eslintCacheDir,
+ config.eslint?.dirs,
config.experimental.cpus,
config.experimental.workerThreads,
telemetry
@@ -255,7 +258,9 @@ export default async function build(
const mappedPages = nextBuildSpan
.traceChild('create-pages-mapping')
- .traceFn(() => createPagesMapping(pagePaths, config.pageExtensions))
+ .traceFn(() =>
+ createPagesMapping(pagePaths, config.pageExtensions, isWebpack5, false)
+ )
const entrypoints = nextBuildSpan
.traceChild('create-entrypoints')
.traceFn(() =>
diff --git a/packages/next/build/profiler/profiler.js b/packages/next/build/profiler/profiler.js
deleted file mode 100644
index 87057ed852fa8e..00000000000000
--- a/packages/next/build/profiler/profiler.js
+++ /dev/null
@@ -1,147 +0,0 @@
-import fs from 'fs'
-import path from 'path'
-
-let maybeInspector
-try {
- maybeInspector = require('inspector')
-} catch (e) {
- console.log('Unable to CPU profile in < node 8.0')
-}
-
-class Profiler {
- constructor(inspector) {
- this.session = undefined
- this.inspector = inspector
- }
-
- hasSession() {
- return this.session !== undefined
- }
-
- startProfiling() {
- if (this.inspector === undefined) {
- return Promise.resolve()
- }
-
- try {
- this.session = new maybeInspector.Session()
- this.session.connect()
- } catch (_) {
- this.session = undefined
- return Promise.resolve()
- }
-
- return Promise.all([
- this.sendCommand('Profiler.setSamplingInterval', {
- interval: 100,
- }),
- this.sendCommand('Profiler.enable'),
- this.sendCommand('Profiler.start'),
- ])
- }
-
- sendCommand(method, params) {
- if (this.hasSession()) {
- return new Promise((resolve, reject) => {
- return this.session.post(method, params, (err, sessionParams) => {
- if (err !== null) {
- reject(err)
- } else {
- resolve(sessionParams)
- }
- })
- })
- } else {
- return Promise.resolve()
- }
- }
-
- destroy() {
- if (this.hasSession()) {
- this.session.disconnect()
- }
-
- return Promise.resolve()
- }
-
- stopProfiling() {
- return this.sendCommand('Profiler.stop')
- }
-}
-
-// eslint-disable-next-line import/no-extraneous-dependencies
-const { Tracer } = require('chrome-trace-event')
-
-/**
- * an object that wraps Tracer and Profiler with a counter
- * @typedef {Object} Trace
- * @property {Tracer} trace instance of Tracer
- * @property {number} counter Counter
- * @property {Profiler} profiler instance of Profiler
- * @property {Function} end the end function
- */
-
-/**
- * @param {string} outputPath The location where to write the log.
- * @returns {Trace} The trace object
- */
-export const createTrace = (outputPath) => {
- const trace = new Tracer({
- noStream: true,
- })
- const profiler = new Profiler(maybeInspector)
- if (/\/|\\/.test(outputPath)) {
- const dirPath = path.dirname(outputPath)
- fs.mkdirSync(dirPath, { recursive: true })
- }
- const fsStream = fs.createWriteStream(outputPath)
-
- let counter = 0
-
- trace.pipe(fsStream)
- // These are critical events that need to be inserted so that tools like
- // chrome dev tools can load the profile.
- trace.instantEvent({
- name: 'TracingStartedInPage',
- id: ++counter,
- cat: ['disabled-by-default-devtools.timeline'],
- args: {
- data: {
- sessionId: '-1',
- page: '0xfff',
- frames: [
- {
- frame: '0xfff',
- url: 'webpack',
- name: '',
- },
- ],
- },
- },
- })
-
- trace.instantEvent({
- name: 'TracingStartedInBrowser',
- id: ++counter,
- cat: ['disabled-by-default-devtools.timeline'],
- args: {
- data: {
- sessionId: '-1',
- },
- },
- })
-
- return {
- trace,
- counter,
- profiler,
- end: (callback) => {
- // Wait until the write stream finishes.
- fsStream.on('finish', () => {
- callback()
- })
- // Tear down the readable trace stream.
- trace.push(null)
- },
- }
-}
diff --git a/packages/next/build/swc/index.js b/packages/next/build/swc/index.js
index b81191bd9cdf70..3101f08da8bb5b 100644
--- a/packages/next/build/swc/index.js
+++ b/packages/next/build/swc/index.js
@@ -67,5 +67,15 @@ function toBuffer(t) {
return Buffer.from(JSON.stringify(t))
}
+export async function minify(src, opts) {
+ return bindings.minify(src, toBuffer(opts ?? {}))
+}
+
+export function minifySync(src, opts) {
+ return bindings.minifySync(src, toBuffer(opts ?? {}))
+}
+
module.exports.transform = transform
module.exports.transformSync = transformSync
+module.exports.minify = minify
+module.exports.minifySync = minifySync
diff --git a/packages/next/build/swc/src/lib.rs b/packages/next/build/swc/src/lib.rs
index f4495e4783951d..735b3010d7eba4 100644
--- a/packages/next/build/swc/src/lib.rs
+++ b/packages/next/build/swc/src/lib.rs
@@ -47,6 +47,7 @@ use swc_common::{
mod amp_attributes;
mod hook_optimizer;
+mod minify;
mod next_dynamic;
pub mod next_ssg;
mod transform;
@@ -76,6 +77,9 @@ fn init(mut exports: JsObject) -> napi::Result<()> {
exports.create_named_method("transform", transform::transform)?;
exports.create_named_method("transformSync", transform::transform_sync)?;
+ exports.create_named_method("minify", minify::minify)?;
+ exports.create_named_method("minifySync", minify::minify_sync)?;
+
Ok(())
}
diff --git a/packages/next/build/swc/src/minify.rs b/packages/next/build/swc/src/minify.rs
new file mode 100644
index 00000000000000..5eac3c8a730d71
--- /dev/null
+++ b/packages/next/build/swc/src/minify.rs
@@ -0,0 +1,83 @@
+/*
+Copyright (c) 2017 The swc Project Developers
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+*/
+use crate::{
+ complete_output, get_compiler,
+ util::{CtxtExt, MapErr},
+};
+use napi::{CallContext, JsObject, JsString, Task};
+use std::sync::Arc;
+use swc::TransformOutput;
+use swc_common::FileName;
+
+struct MinifyTask {
+ c: Arc,
+ code: String,
+ opts: swc::config::JsMinifyOptions,
+}
+
+impl Task for MinifyTask {
+ type Output = TransformOutput;
+
+ type JsValue = JsObject;
+
+ fn compute(&mut self) -> napi::Result {
+ let fm = self.c.cm.new_source_file(FileName::Anon, self.code.clone());
+
+ self.c.minify(fm, &self.opts).convert_err()
+ }
+
+ fn resolve(self, env: napi::Env, output: Self::Output) -> napi::Result {
+ complete_output(&env, output)
+ }
+}
+
+#[js_function(2)]
+pub fn minify(cx: CallContext) -> napi::Result {
+ let code = cx.get::(0)?.into_utf8()?.into_owned()?;
+ let opts = cx.get_deserialized(1)?;
+
+ let c = get_compiler(&cx);
+
+ let task = MinifyTask { c, code, opts };
+
+ cx.env.spawn(task).map(|t| t.promise_object())
+}
+
+#[js_function(2)]
+pub fn minify_sync(cx: CallContext) -> napi::Result {
+ let code = cx.get::(0)?.into_utf8()?.into_owned()?;
+ let opts = cx.get_deserialized(1)?;
+
+ let c = get_compiler(&cx);
+
+ let fm = c.cm.new_source_file(FileName::Anon, code.clone());
+
+ let output = c.minify(fm, &opts).convert_err()?;
+
+ complete_output(&cx.env, output)
+}
diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts
index 881b5169ae8f8f..822f85db4d868f 100644
--- a/packages/next/build/webpack-config.ts
+++ b/packages/next/build/webpack-config.ts
@@ -28,7 +28,6 @@ import {
} from '../shared/lib/constants'
import { execOnce } from '../shared/lib/utils'
import { NextConfigComplete } from '../server/config-shared'
-import { findPageFile } from '../server/lib/find-page-file'
import { WebpackEntrypoints } from './entries'
import * as Log from './output/log'
import { build as buildConfiguration } from './webpack/config'
@@ -45,12 +44,6 @@ import PagesManifestPlugin from './webpack/plugins/pages-manifest-plugin'
import { ProfilingPlugin } from './webpack/plugins/profiling-plugin'
import { ReactLoadablePlugin } from './webpack/plugins/react-loadable-plugin'
import { ServerlessPlugin } from './webpack/plugins/serverless-plugin'
-import WebpackConformancePlugin, {
- DuplicatePolyfillsConformanceCheck,
- GranularChunksConformanceCheck,
- MinificationConformanceCheck,
- ReactSyncScriptsConformanceCheck,
-} from './webpack/plugins/webpack-conformance-plugin'
import { WellKnownErrorsPlugin } from './webpack/plugins/wellknown-errors-plugin'
import { regexLikeCss } from './webpack/config/blocks/css'
import { CopyFilePlugin } from './webpack/plugins/copy-file-plugin'
@@ -174,20 +167,6 @@ export function attachReactRefresh(
}
}
-const WEBPACK_RESOLVE_OPTIONS = {
- // This always uses commonjs resolving, assuming API is identical
- // between ESM and CJS in a package
- // Otherwise combined ESM+CJS packages will never be external
- // as resolving mismatch would lead to opt-out from being external.
- dependencyType: 'commonjs',
- symlinks: true,
-}
-
-const WEBPACK_ESM_RESOLVE_OPTIONS = {
- dependencyType: 'esm',
- symlinks: true,
-}
-
const NODE_RESOLVE_OPTIONS = {
dependencyType: 'commonjs',
modules: ['node_modules'],
@@ -195,7 +174,7 @@ const NODE_RESOLVE_OPTIONS = {
fallback: false,
exportsFields: ['exports'],
importsFields: ['imports'],
- conditionNames: ['node', 'require', 'module'],
+ conditionNames: ['node', 'require'],
descriptionFiles: ['package.json'],
extensions: ['.js', '.json', '.node'],
enforceExtensions: false,
@@ -212,7 +191,7 @@ const NODE_RESOLVE_OPTIONS = {
const NODE_ESM_RESOLVE_OPTIONS = {
...NODE_RESOLVE_OPTIONS,
dependencyType: 'esm',
- conditionNames: ['node', 'import', 'module'],
+ conditionNames: ['node', 'import'],
fullySpecified: true,
}
@@ -241,7 +220,7 @@ export default async function getBaseWebpackConfig(
entrypoints: WebpackEntrypoints
rewrites: CustomRoutes['rewrites']
isDevFallback?: boolean
- runWebpackSpan?: Span
+ runWebpackSpan: Span
}
): Promise {
const hasRewrites =
@@ -413,28 +392,6 @@ export default async function getBaseWebpackConfig(
resolvedBaseUrl = path.resolve(dir, jsConfig.compilerOptions.baseUrl)
}
- let customAppFile: string | null = await findPageFile(
- pagesDir,
- '/_app',
- config.pageExtensions
- )
- let customAppFileExt = customAppFile ? path.extname(customAppFile) : null
- if (customAppFile) {
- customAppFile = path.resolve(path.join(pagesDir, customAppFile))
- }
-
- let customDocumentFile: string | null = await findPageFile(
- pagesDir,
- '/_document',
- config.pageExtensions
- )
- let customDocumentFileExt = customDocumentFile
- ? path.extname(customDocumentFile)
- : null
- if (customDocumentFile) {
- customDocumentFile = path.resolve(path.join(pagesDir, customDocumentFile))
- }
-
function getReactProfilingInProduction() {
if (reactProductionProfiling) {
return {
@@ -444,6 +401,9 @@ export default async function getBaseWebpackConfig(
}
}
+ // tell webpack where to look for _app and _document
+ // using aliases to allow falling back to the default
+ // version when removed or not present
const clientResolveRewrites = require.resolve(
'../shared/lib/router/utils/resolve-rewrites'
)
@@ -451,6 +411,34 @@ export default async function getBaseWebpackConfig(
'../shared/lib/router/utils/resolve-rewrites-noop'
)
+ const customAppAliases: { [key: string]: string[] } = {}
+ const customErrorAlias: { [key: string]: string[] } = {}
+ const customDocumentAliases: { [key: string]: string[] } = {}
+
+ if (dev && isWebpack5) {
+ customAppAliases[`${PAGES_DIR_ALIAS}/_app`] = [
+ ...config.pageExtensions.reduce((prev, ext) => {
+ prev.push(path.join(pagesDir, `_app.${ext}`))
+ return prev
+ }, [] as string[]),
+ 'next/dist/pages/_app.js',
+ ]
+ customAppAliases[`${PAGES_DIR_ALIAS}/_error`] = [
+ ...config.pageExtensions.reduce((prev, ext) => {
+ prev.push(path.join(pagesDir, `_error.${ext}`))
+ return prev
+ }, [] as string[]),
+ 'next/dist/pages/_error.js',
+ ]
+ customDocumentAliases[`${PAGES_DIR_ALIAS}/_document`] = [
+ ...config.pageExtensions.reduce((prev, ext) => {
+ prev.push(path.join(pagesDir, `_document.${ext}`))
+ return prev
+ }, [] as string[]),
+ 'next/dist/pages/_document.js',
+ ]
+ }
+
const resolveConfig = {
// Disable .mjs for node_modules bundling
extensions: isServer
@@ -477,25 +465,9 @@ export default async function getBaseWebpackConfig(
alias: {
next: NEXT_PROJECT_ROOT,
- // fallback to default _app when custom is removed
- ...(dev && customAppFileExt && isWebpack5
- ? {
- [`${PAGES_DIR_ALIAS}/_app${customAppFileExt}`]: [
- path.join(pagesDir, `_app${customAppFileExt}`),
- 'next/dist/pages/_app.js',
- ],
- }
- : {}),
-
- // fallback to default _document when custom is removed
- ...(dev && customDocumentFileExt && isWebpack5
- ? {
- [`${PAGES_DIR_ALIAS}/_document${customDocumentFileExt}`]: [
- path.join(pagesDir, `_document${customDocumentFileExt}`),
- 'next/dist/pages/_document.js',
- ],
- }
- : {}),
+ ...customAppAliases,
+ ...customErrorAlias,
+ ...customDocumentAliases,
[PAGES_DIR_ALIAS]: pagesDir,
[DOT_NEXT_ALIAS]: distDir,
@@ -690,30 +662,6 @@ export default async function getBaseWebpackConfig(
const crossOrigin = config.crossOrigin
- const conformanceConfig = Object.assign(
- {
- ReactSyncScriptsConformanceCheck: {
- enabled: true,
- },
- MinificationConformanceCheck: {
- enabled: true,
- },
- DuplicatePolyfillsConformanceCheck: {
- enabled: true,
- BlockedAPIToBePolyfilled: Object.assign(
- [],
- ['fetch'],
- config.conformance?.DuplicatePolyfillsConformanceCheck
- ?.BlockedAPIToBePolyfilled || []
- ),
- },
- GranularChunksConformanceCheck: {
- enabled: true,
- },
- },
- config.conformance
- )
-
const esmExternals = !!config.experimental?.esmExternals
const looseEsmExternals = config.experimental?.esmExternals === 'loose'
@@ -750,7 +698,7 @@ export default async function getBaseWebpackConfig(
}
const notExternalModules =
- /^(?:private-next-pages\/|next\/(?:dist\/pages\/|(?:app|document|link|image|constants)$)|string-hash$)/
+ /^(?:private-next-pages\/|next\/(?:dist\/pages\/|(?:app|document|link|image|constants|dynamic)$)|string-hash$)/
if (notExternalModules.test(request)) {
return
}
@@ -762,7 +710,7 @@ export default async function getBaseWebpackConfig(
const preferEsm = esmExternals && isEsmRequested
const resolve = getResolve(
- preferEsm ? WEBPACK_ESM_RESOLVE_OPTIONS : WEBPACK_RESOLVE_OPTIONS
+ preferEsm ? NODE_ESM_RESOLVE_OPTIONS : NODE_RESOLVE_OPTIONS
)
// Resolve the import with the webpack provided context, this
@@ -780,7 +728,7 @@ export default async function getBaseWebpackConfig(
// try the alternative resolving options.
if (!res && (isEsmRequested || looseEsmExternals)) {
const resolveAlternative = getResolve(
- preferEsm ? WEBPACK_RESOLVE_OPTIONS : WEBPACK_ESM_RESOLVE_OPTIONS
+ preferEsm ? NODE_RESOLVE_OPTIONS : NODE_ESM_RESOLVE_OPTIONS
)
try {
;[res, isEsm] = await resolveAlternative(context, request)
@@ -1357,34 +1305,6 @@ export default async function getBaseWebpackConfig(
isLikeServerless,
})
})(),
- config.experimental.conformance &&
- !isWebpack5 &&
- !dev &&
- new WebpackConformancePlugin({
- tests: [
- !isServer &&
- conformanceConfig.MinificationConformanceCheck.enabled &&
- new MinificationConformanceCheck(),
- conformanceConfig.ReactSyncScriptsConformanceCheck.enabled &&
- new ReactSyncScriptsConformanceCheck({
- AllowedSources:
- conformanceConfig.ReactSyncScriptsConformanceCheck
- .allowedSources || [],
- }),
- !isServer &&
- conformanceConfig.DuplicatePolyfillsConformanceCheck.enabled &&
- new DuplicatePolyfillsConformanceCheck({
- BlockedAPIToBePolyfilled:
- conformanceConfig.DuplicatePolyfillsConformanceCheck
- .BlockedAPIToBePolyfilled,
- }),
- !isServer &&
- conformanceConfig.GranularChunksConformanceCheck.enabled &&
- new GranularChunksConformanceCheck(
- splitChunksConfigs.prodGranular
- ),
- ].filter(Boolean),
- }),
new WellKnownErrorsPlugin(),
!isServer &&
new CopyFilePlugin({
@@ -1498,6 +1418,8 @@ export default async function getBaseWebpackConfig(
reactProductionProfiling,
webpack: !!config.webpack,
hasRewrites,
+ reactRoot: config.experimental.reactRoot,
+ concurrentFeatures: config.experimental.concurrentFeatures,
})
const cache: any = {
@@ -1567,7 +1489,9 @@ export default async function getBaseWebpackConfig(
webpackConfig = await buildConfiguration(webpackConfig, {
rootDirectory: dir,
- customAppFile,
+ customAppFile: new RegExp(
+ path.join(pagesDir, `_app`).replace(/\\/g, '(/|\\\\)')
+ ),
isDevelopment: dev,
isServer,
assetPrefix: config.assetPrefix || '',
diff --git a/packages/next/build/webpack/config/blocks/css/index.ts b/packages/next/build/webpack/config/blocks/css/index.ts
index 02f44cf5c70a58..bbfeaa750835cb 100644
--- a/packages/next/build/webpack/config/blocks/css/index.ts
+++ b/packages/next/build/webpack/config/blocks/css/index.ts
@@ -1,5 +1,4 @@
import curry from 'next/dist/compiled/lodash.curry'
-import path from 'path'
import { webpack, isWebpack5 } from 'next/dist/compiled/webpack/webpack'
import MiniCssExtractPlugin from '../../../plugins/mini-css-extract-plugin'
import { loader, plugin } from '../../helpers'
@@ -12,9 +11,72 @@ import {
getLocalModuleImportError,
} from './messages'
import { getPostCssPlugins } from './plugins'
+import postcss from 'postcss'
+
+// @ts-ignore backwards compat
+postcss.plugin = function postcssPlugin(name, initializer) {
+ function creator(...args: any) {
+ let transformer = initializer(...args)
+ transformer.postcssPlugin = name
+ // transformer.postcssVersion = new Processor().version
+ return transformer
+ }
+
+ let cache: any
+ Object.defineProperty(creator, 'postcss', {
+ get() {
+ if (!cache) cache = creator()
+ return cache
+ },
+ })
+
+ creator.process = function (css: any, processOpts: any, pluginOpts: any) {
+ return postcss([creator(pluginOpts)]).process(css, processOpts)
+ }
+
+ return creator
+}
+
+// @ts-ignore backwards compat
+postcss.vendor = {
+ /**
+ * Returns the vendor prefix extracted from an input string.
+ *
+ * @param {string} prop String with or without vendor prefix.
+ *
+ * @return {string} vendor prefix or empty string
+ *
+ * @example
+ * postcss.vendor.prefix('-moz-tab-size') //=> '-moz-'
+ * postcss.vendor.prefix('tab-size') //=> ''
+ */
+ prefix: function prefix(prop: any) {
+ const match = prop.match(/^(-\w+-)/)
+
+ if (match) {
+ return match[0]
+ }
+
+ return ''
+ },
+
+ /**
+ * Returns the input string stripped of its vendor prefix.
+ *
+ * @param {string} prop String with or without vendor prefix.
+ *
+ * @return {string} String name without vendor prefixes.
+ *
+ * @example
+ * postcss.vendor.unprefixed('-moz-tab-size') //=> 'tab-size'
+ */
+ unprefixed: function unprefixed(prop: any) {
+ return prop.replace(/^-\w+-/, '')
+ },
+}
// RegExps for all Style Sheet variants
-export const regexLikeCss = /\.(css|scss|sass)(\.webpack\[javascript\/auto\])?$/
+export const regexLikeCss = /\.(css|scss|sass)$/
// RegExps for Style Sheets
const regexCssGlobal = /(?'
)}. Due to the Global nature of stylesheets, and to avoid conflicts, Please move all first-party global CSS imports to ${chalk.cyan(
- file ? file : 'pages/_app.js'
+ 'pages/_app.js'
)}. Or convert the import to Component-Level CSS (CSS Modules).\nRead more: https://nextjs.org/docs/messages/css-global`
}
diff --git a/packages/next/build/webpack/config/blocks/css/overrideCssConfiguration.ts b/packages/next/build/webpack/config/blocks/css/overrideCssConfiguration.ts
index eaa726cac14a28..434487c3e442a2 100644
--- a/packages/next/build/webpack/config/blocks/css/overrideCssConfiguration.ts
+++ b/packages/next/build/webpack/config/blocks/css/overrideCssConfiguration.ts
@@ -1,5 +1,6 @@
import { webpack } from 'next/dist/compiled/webpack/webpack'
import { getPostCssPlugins } from './plugins'
+import postcss from 'postcss'
export async function __overrideCssConfiguration(
rootDirectory: string,
@@ -15,6 +16,12 @@ export async function __overrideCssConfiguration(
typeof rule.options.postcssOptions === 'object'
) {
rule.options.postcssOptions.plugins = postCssPlugins
+ } else if (
+ rule.options &&
+ typeof rule.options === 'object' &&
+ typeof rule.options.postcss !== 'undefined'
+ ) {
+ rule.options.postcss = postcss(postCssPlugins)
} else if (Array.isArray(rule.oneOf)) {
rule.oneOf.forEach(patch)
} else if (Array.isArray(rule.use)) {
diff --git a/packages/next/build/webpack/config/blocks/images/index.ts b/packages/next/build/webpack/config/blocks/images/index.ts
new file mode 100644
index 00000000000000..6ea422e5af9d8a
--- /dev/null
+++ b/packages/next/build/webpack/config/blocks/images/index.ts
@@ -0,0 +1,30 @@
+import curry from 'next/dist/compiled/lodash.curry'
+import { webpack } from 'next/dist/compiled/webpack/webpack'
+import { loader } from '../../helpers'
+import { ConfigurationContext, ConfigurationFn, pipe } from '../../utils'
+import { getCustomDocumentImageError } from './messages'
+
+export const images = curry(async function images(
+ _ctx: ConfigurationContext,
+ config: webpack.Configuration
+) {
+ const fns: ConfigurationFn[] = [
+ loader({
+ oneOf: [
+ {
+ test: /\.(png|jpg|jpeg|gif|webp|ico|bmp|svg)$/i,
+ use: {
+ loader: 'error-loader',
+ options: {
+ reason: getCustomDocumentImageError(),
+ },
+ },
+ issuer: /pages[\\/]_document\./,
+ },
+ ],
+ }),
+ ]
+
+ const fn = pipe(...fns)
+ return fn(config)
+})
diff --git a/packages/next/build/webpack/config/blocks/images/messages.ts b/packages/next/build/webpack/config/blocks/images/messages.ts
new file mode 100644
index 00000000000000..ba5d6403e0ef72
--- /dev/null
+++ b/packages/next/build/webpack/config/blocks/images/messages.ts
@@ -0,0 +1,9 @@
+import chalk from 'chalk'
+
+export function getCustomDocumentImageError() {
+ return `Images ${chalk.bold('cannot')} be imported within ${chalk.cyan(
+ 'pages/_document.js'
+ )}. Please move image imports that need to be displayed on every page into ${chalk.cyan(
+ 'pages/_app.js'
+ )}.\nRead more: https://nextjs.org/docs/messages/custom-document-image-import`
+}
diff --git a/packages/next/build/webpack/config/index.ts b/packages/next/build/webpack/config/index.ts
index 033b4c93e9b9ca..975f880acf1b13 100644
--- a/packages/next/build/webpack/config/index.ts
+++ b/packages/next/build/webpack/config/index.ts
@@ -2,6 +2,7 @@ import { webpack } from 'next/dist/compiled/webpack/webpack'
import { NextConfigComplete } from '../../../server/config-shared'
import { base } from './blocks/base'
import { css } from './blocks/css'
+import { images } from './blocks/images'
import { ConfigurationContext, pipe } from './utils'
export async function build(
@@ -18,7 +19,7 @@ export async function build(
isCraCompat,
}: {
rootDirectory: string
- customAppFile: string | null
+ customAppFile: RegExp
isDevelopment: boolean
isServer: boolean
assetPrefix: string
@@ -46,6 +47,6 @@ export async function build(
isCraCompat,
}
- const fn = pipe(base(ctx), css(ctx))
+ const fn = pipe(base(ctx), css(ctx), images(ctx))
return fn(config)
}
diff --git a/packages/next/build/webpack/config/utils.ts b/packages/next/build/webpack/config/utils.ts
index a7f856088bc426..c596727401b2bc 100644
--- a/packages/next/build/webpack/config/utils.ts
+++ b/packages/next/build/webpack/config/utils.ts
@@ -3,7 +3,7 @@ import { NextConfigComplete } from '../../../server/config-shared'
export type ConfigurationContext = {
rootDirectory: string
- customAppFile: string | null
+ customAppFile: RegExp
isDevelopment: boolean
isProduction: boolean
diff --git a/packages/next/compiled/css-loader/LICENSE b/packages/next/build/webpack/loaders/css-loader/LICENSE
similarity index 100%
rename from packages/next/compiled/css-loader/LICENSE
rename to packages/next/build/webpack/loaders/css-loader/LICENSE
diff --git a/packages/next/build/webpack/loaders/css-loader/src/CssSyntaxError.js b/packages/next/build/webpack/loaders/css-loader/src/CssSyntaxError.js
new file mode 100644
index 00000000000000..76390c9f3c8f47
--- /dev/null
+++ b/packages/next/build/webpack/loaders/css-loader/src/CssSyntaxError.js
@@ -0,0 +1,28 @@
+export default class CssSyntaxError extends Error {
+ constructor(error) {
+ super(error)
+
+ const { reason, line, column } = error
+
+ this.name = 'CssSyntaxError'
+
+ // Based on https://github.com/postcss/postcss/blob/master/lib/css-syntax-error.es6#L132
+ // We don't need `plugin` and `file` properties.
+ this.message = `${this.name}\n\n`
+
+ if (typeof line !== 'undefined') {
+ this.message += `(${line}:${column}) `
+ }
+
+ this.message += `${reason}`
+
+ const code = error.showSourceCode()
+
+ if (code) {
+ this.message += `\n\n${code}\n`
+ }
+
+ // We don't need stack https://github.com/postcss/postcss/blob/master/docs/guidelines/runner.md#31-dont-show-js-stack-for-csssyntaxerror
+ this.stack = false
+ }
+}
diff --git a/packages/next/build/webpack/loaders/css-loader/src/camelcase.js b/packages/next/build/webpack/loaders/css-loader/src/camelcase.js
new file mode 100644
index 00000000000000..d9fce419188941
--- /dev/null
+++ b/packages/next/build/webpack/loaders/css-loader/src/camelcase.js
@@ -0,0 +1,115 @@
+/*
+MIT License
+
+Copyright (c) Sindre Sorhus (https://sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+const preserveCamelCase = (string, locale) => {
+ let isLastCharLower = false
+ let isLastCharUpper = false
+ let isLastLastCharUpper = false
+
+ for (let i = 0; i < string.length; i++) {
+ const character = string[i]
+
+ if (isLastCharLower && /[\p{Lu}]/u.test(character)) {
+ string = string.slice(0, i) + '-' + string.slice(i)
+ isLastCharLower = false
+ isLastLastCharUpper = isLastCharUpper
+ isLastCharUpper = true
+ i++
+ } else if (
+ isLastCharUpper &&
+ isLastLastCharUpper &&
+ /[\p{Ll}]/u.test(character)
+ ) {
+ string = string.slice(0, i - 1) + '-' + string.slice(i - 1)
+ isLastLastCharUpper = isLastCharUpper
+ isLastCharUpper = false
+ isLastCharLower = true
+ } else {
+ isLastCharLower =
+ character.toLocaleLowerCase(locale) === character &&
+ character.toLocaleUpperCase(locale) !== character
+ isLastLastCharUpper = isLastCharUpper
+ isLastCharUpper =
+ character.toLocaleUpperCase(locale) === character &&
+ character.toLocaleLowerCase(locale) !== character
+ }
+ }
+
+ return string
+}
+
+const preserveConsecutiveUppercase = (input) => {
+ return input.replace(/^[\p{Lu}](?![\p{Lu}])/gu, (m1) => m1.toLowerCase())
+}
+
+const postProcess = (input, options) => {
+ return input
+ .replace(/[_.\- ]+([\p{Alpha}\p{N}_]|$)/gu, (_, p1) =>
+ p1.toLocaleUpperCase(options.locale)
+ )
+ .replace(/\d+([\p{Alpha}\p{N}_]|$)/gu, (m) =>
+ m.toLocaleUpperCase(options.locale)
+ )
+}
+
+const camelCase = (input, options) => {
+ if (!(typeof input === 'string' || Array.isArray(input))) {
+ throw new TypeError('Expected the input to be `string | string[]`')
+ }
+
+ options = {
+ pascalCase: false,
+ preserveConsecutiveUppercase: false,
+ ...options,
+ }
+
+ if (Array.isArray(input)) {
+ input = input
+ .map((x) => x.trim())
+ .filter((x) => x.length)
+ .join('-')
+ } else {
+ input = input.trim()
+ }
+
+ if (input.length === 0) {
+ return ''
+ }
+
+ if (input.length === 1) {
+ return options.pascalCase
+ ? input.toLocaleUpperCase(options.locale)
+ : input.toLocaleLowerCase(options.locale)
+ }
+
+ const hasUpperCase = input !== input.toLocaleLowerCase(options.locale)
+
+ if (hasUpperCase) {
+ input = preserveCamelCase(input, options.locale)
+ }
+
+ input = input.replace(/^[_.\- ]+/, '')
+
+ if (options.preserveConsecutiveUppercase) {
+ input = preserveConsecutiveUppercase(input)
+ } else {
+ input = input.toLocaleLowerCase()
+ }
+
+ if (options.pascalCase) {
+ input = input.charAt(0).toLocaleUpperCase(options.locale) + input.slice(1)
+ }
+
+ return postProcess(input, options)
+}
+
+export default camelCase
diff --git a/packages/next/build/webpack/loaders/css-loader/src/index.js b/packages/next/build/webpack/loaders/css-loader/src/index.js
new file mode 100644
index 00000000000000..29e3774c5ec403
--- /dev/null
+++ b/packages/next/build/webpack/loaders/css-loader/src/index.js
@@ -0,0 +1,203 @@
+/*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+ Author Tobias Koppers @sokra
+*/
+import { getOptions, stringifyRequest } from 'next/dist/compiled/loader-utils'
+import postcss from 'postcss'
+
+import CssSyntaxError from './CssSyntaxError'
+import Warning from '../../postcss-loader/src/Warning'
+import { icssParser, importParser, urlParser } from './plugins'
+import {
+ normalizeOptions,
+ shouldUseModulesPlugins,
+ shouldUseImportPlugin,
+ shouldUseURLPlugin,
+ shouldUseIcssPlugin,
+ getPreRequester,
+ getExportCode,
+ getFilter,
+ getImportCode,
+ getModuleCode,
+ getModulesPlugins,
+ normalizeSourceMap,
+ sort,
+} from './utils'
+
+export default async function loader(content, map, meta) {
+ const rawOptions = getOptions(this)
+
+ const plugins = []
+ const callback = this.async()
+
+ const loaderSpan = this.currentTraceSpan.traceChild('css-loader')
+
+ loaderSpan
+ .traceAsyncFn(async () => {
+ let options
+
+ try {
+ options = normalizeOptions(rawOptions, this)
+ } catch (error) {
+ throw error
+ }
+
+ const replacements = []
+ const exports = []
+
+ if (shouldUseModulesPlugins(options)) {
+ plugins.push(...getModulesPlugins(options, this))
+ }
+
+ const importPluginImports = []
+ const importPluginApi = []
+
+ if (shouldUseImportPlugin(options)) {
+ const resolver = this.getResolve({
+ conditionNames: ['style'],
+ extensions: ['.css'],
+ mainFields: ['css', 'style', 'main', '...'],
+ mainFiles: ['index', '...'],
+ restrictions: [/\.css$/i],
+ })
+
+ plugins.push(
+ importParser({
+ imports: importPluginImports,
+ api: importPluginApi,
+ context: this.context,
+ rootContext: this.rootContext,
+ filter: getFilter(options.import, this.resourcePath),
+ resolver,
+ urlHandler: (url) =>
+ stringifyRequest(
+ this,
+ getPreRequester(this)(options.importLoaders) + url
+ ),
+ })
+ )
+ }
+
+ const urlPluginImports = []
+
+ if (shouldUseURLPlugin(options)) {
+ const urlResolver = this.getResolve({
+ conditionNames: ['asset'],
+ mainFields: ['asset'],
+ mainFiles: [],
+ extensions: [],
+ })
+
+ plugins.push(
+ urlParser({
+ imports: urlPluginImports,
+ replacements,
+ context: this.context,
+ rootContext: this.rootContext,
+ filter: getFilter(options.url, this.resourcePath),
+ resolver: urlResolver,
+ urlHandler: (url) => stringifyRequest(this, url),
+ })
+ )
+ }
+
+ const icssPluginImports = []
+ const icssPluginApi = []
+
+ if (shouldUseIcssPlugin(options)) {
+ const icssResolver = this.getResolve({
+ conditionNames: ['style'],
+ extensions: [],
+ mainFields: ['css', 'style', 'main', '...'],
+ mainFiles: ['index', '...'],
+ })
+
+ plugins.push(
+ icssParser({
+ imports: icssPluginImports,
+ api: icssPluginApi,
+ replacements,
+ exports,
+ context: this.context,
+ rootContext: this.rootContext,
+ resolver: icssResolver,
+ urlHandler: (url) =>
+ stringifyRequest(
+ this,
+ getPreRequester(this)(options.importLoaders) + url
+ ),
+ })
+ )
+ }
+
+ // Reuse CSS AST (PostCSS AST e.g 'postcss-loader') to avoid reparsing
+ if (meta) {
+ const { ast } = meta
+
+ if (ast && ast.type === 'postcss') {
+ // eslint-disable-next-line no-param-reassign
+ content = ast.root
+ loaderSpan.setAttribute('astUsed', 'true')
+ }
+ }
+
+ const { resourcePath } = this
+
+ let result
+
+ try {
+ result = await postcss(plugins).process(content, {
+ from: resourcePath,
+ to: resourcePath,
+ map: options.sourceMap
+ ? {
+ prev: map ? normalizeSourceMap(map, resourcePath) : null,
+ inline: false,
+ annotation: false,
+ }
+ : false,
+ })
+ } catch (error) {
+ if (error.file) {
+ this.addDependency(error.file)
+ }
+
+ throw error.name === 'CssSyntaxError'
+ ? new CssSyntaxError(error)
+ : error
+ }
+
+ for (const warning of result.warnings()) {
+ this.emitWarning(new Warning(warning))
+ }
+
+ const imports = []
+ .concat(icssPluginImports.sort(sort))
+ .concat(importPluginImports.sort(sort))
+ .concat(urlPluginImports.sort(sort))
+ const api = []
+ .concat(importPluginApi.sort(sort))
+ .concat(icssPluginApi.sort(sort))
+
+ if (options.modules.exportOnlyLocals !== true) {
+ imports.unshift({
+ importName: '___CSS_LOADER_API_IMPORT___',
+ url: stringifyRequest(this, require.resolve('./runtime/api')),
+ })
+ }
+
+ const importCode = getImportCode(imports, options)
+ const moduleCode = getModuleCode(result, api, replacements, options, this)
+ const exportCode = getExportCode(exports, replacements, options)
+
+ return `${importCode}${moduleCode}${exportCode}`
+ })
+ .then(
+ (code) => {
+ callback(null, code)
+ },
+ (err) => {
+ callback(err)
+ }
+ )
+}
diff --git a/packages/next/build/webpack/loaders/css-loader/src/plugins/index.js b/packages/next/build/webpack/loaders/css-loader/src/plugins/index.js
new file mode 100644
index 00000000000000..799629d4e93b15
--- /dev/null
+++ b/packages/next/build/webpack/loaders/css-loader/src/plugins/index.js
@@ -0,0 +1,5 @@
+import importParser from './postcss-import-parser'
+import icssParser from './postcss-icss-parser'
+import urlParser from './postcss-url-parser'
+
+export { importParser, icssParser, urlParser }
diff --git a/packages/next/build/webpack/loaders/css-loader/src/plugins/postcss-icss-parser.js b/packages/next/build/webpack/loaders/css-loader/src/plugins/postcss-icss-parser.js
new file mode 100644
index 00000000000000..d4b7f852022fb7
--- /dev/null
+++ b/packages/next/build/webpack/loaders/css-loader/src/plugins/postcss-icss-parser.js
@@ -0,0 +1,114 @@
+import {
+ extractICSS,
+ replaceValueSymbols,
+ replaceSymbols,
+} from 'next/dist/compiled/icss-utils'
+
+import { normalizeUrl, resolveRequests, requestify } from '../utils'
+
+const plugin = (options = {}) => {
+ return {
+ postcssPlugin: 'postcss-icss-parser',
+ async OnceExit(root) {
+ const importReplacements = Object.create(null)
+ const { icssImports, icssExports } = extractICSS(root)
+ const imports = new Map()
+ const tasks = []
+
+ // eslint-disable-next-line guard-for-in
+ for (const url in icssImports) {
+ const tokens = icssImports[url]
+
+ if (Object.keys(tokens).length === 0) {
+ // eslint-disable-next-line no-continue
+ continue
+ }
+
+ let normalizedUrl = url
+ let prefix = ''
+
+ const queryParts = normalizedUrl.split('!')
+
+ if (queryParts.length > 1) {
+ normalizedUrl = queryParts.pop()
+ prefix = queryParts.join('!')
+ }
+
+ const request = requestify(
+ normalizeUrl(normalizedUrl, true),
+ options.rootContext
+ )
+ const doResolve = async () => {
+ const { resolver, context } = options
+ const resolvedUrl = await resolveRequests(resolver, context, [
+ ...new Set([normalizedUrl, request]),
+ ])
+
+ if (!resolvedUrl) {
+ return
+ }
+
+ // eslint-disable-next-line consistent-return
+ return { url: resolvedUrl, prefix, tokens }
+ }
+
+ tasks.push(doResolve())
+ }
+
+ const results = await Promise.all(tasks)
+
+ for (let index = 0; index <= results.length - 1; index++) {
+ const item = results[index]
+
+ if (!item) {
+ // eslint-disable-next-line no-continue
+ continue
+ }
+
+ const newUrl = item.prefix ? `${item.prefix}!${item.url}` : item.url
+ const importKey = newUrl
+ let importName = imports.get(importKey)
+
+ if (!importName) {
+ importName = `___CSS_LOADER_ICSS_IMPORT_${imports.size}___`
+ imports.set(importKey, importName)
+
+ options.imports.push({
+ type: 'icss_import',
+ importName,
+ url: options.urlHandler(newUrl),
+ icss: true,
+ index,
+ })
+
+ options.api.push({ importName, dedupe: true, index })
+ }
+
+ for (const [replacementIndex, token] of Object.keys(
+ item.tokens
+ ).entries()) {
+ const replacementName = `___CSS_LOADER_ICSS_IMPORT_${index}_REPLACEMENT_${replacementIndex}___`
+ const localName = item.tokens[token]
+
+ importReplacements[token] = replacementName
+
+ options.replacements.push({ replacementName, importName, localName })
+ }
+ }
+
+ if (Object.keys(importReplacements).length > 0) {
+ replaceSymbols(root, importReplacements)
+ }
+
+ for (const name of Object.keys(icssExports)) {
+ const value = replaceValueSymbols(icssExports[name], importReplacements)
+
+ options.exports.push({ name, value })
+ }
+ },
+ }
+}
+
+plugin.postcss = true
+
+export default plugin
diff --git a/packages/next/build/webpack/loaders/css-loader/src/plugins/postcss-import-parser.js b/packages/next/build/webpack/loaders/css-loader/src/plugins/postcss-import-parser.js
new file mode 100644
index 00000000000000..7962805ef1d4f5
--- /dev/null
+++ b/packages/next/build/webpack/loaders/css-loader/src/plugins/postcss-import-parser.js
@@ -0,0 +1,243 @@
+import valueParser from 'next/dist/compiled/postcss-value-parser'
+
+import {
+ normalizeUrl,
+ resolveRequests,
+ isUrlRequestable,
+ requestify,
+ WEBPACK_IGNORE_COMMENT_REGEXP,
+} from '../utils'
+
+function parseNode(atRule, key) {
+ // Convert only top-level @import
+ if (atRule.parent.type !== 'root') {
+ return
+ }
+
+ if (
+ atRule.raws &&
+ atRule.raws.afterName &&
+ atRule.raws.afterName.trim().length > 0
+ ) {
+ const lastCommentIndex = atRule.raws.afterName.lastIndexOf('/*')
+ const matched = atRule.raws.afterName
+ .slice(lastCommentIndex)
+ .match(WEBPACK_IGNORE_COMMENT_REGEXP)
+
+ if (matched && matched[2] === 'true') {
+ return
+ }
+ }
+
+ const prevNode = atRule.prev()
+
+ if (prevNode && prevNode.type === 'comment') {
+ const matched = prevNode.text.match(WEBPACK_IGNORE_COMMENT_REGEXP)
+
+ if (matched && matched[2] === 'true') {
+ return
+ }
+ }
+
+ // Nodes do not exists - `@import url('http://') :root {}`
+ if (atRule.nodes) {
+ const error = new Error(
+ "It looks like you didn't end your @import statement correctly. Child nodes are attached to it."
+ )
+
+ error.node = atRule
+
+ throw error
+ }
+
+ const { nodes: paramsNodes } = valueParser(atRule[key])
+
+ // No nodes - `@import ;`
+ // Invalid type - `@import foo-bar;`
+ if (
+ paramsNodes.length === 0 ||
+ (paramsNodes[0].type !== 'string' && paramsNodes[0].type !== 'function')
+ ) {
+ const error = new Error(`Unable to find uri in "${atRule.toString()}"`)
+
+ error.node = atRule
+
+ throw error
+ }
+
+ let isStringValue
+ let url
+
+ if (paramsNodes[0].type === 'string') {
+ isStringValue = true
+ url = paramsNodes[0].value
+ } else {
+ // Invalid function - `@import nourl(test.css);`
+ if (paramsNodes[0].value.toLowerCase() !== 'url') {
+ const error = new Error(`Unable to find uri in "${atRule.toString()}"`)
+
+ error.node = atRule
+
+ throw error
+ }
+
+ isStringValue =
+ paramsNodes[0].nodes.length !== 0 &&
+ paramsNodes[0].nodes[0].type === 'string'
+ url = isStringValue
+ ? paramsNodes[0].nodes[0].value
+ : valueParser.stringify(paramsNodes[0].nodes)
+ }
+
+ url = normalizeUrl(url, isStringValue)
+
+ const isRequestable = isUrlRequestable(url)
+ let prefix
+
+ if (isRequestable) {
+ const queryParts = url.split('!')
+
+ if (queryParts.length > 1) {
+ url = queryParts.pop()
+ prefix = queryParts.join('!')
+ }
+ }
+
+ // Empty url - `@import "";` or `@import url();`
+ if (url.trim().length === 0) {
+ const error = new Error(`Unable to find uri in "${atRule.toString()}"`)
+
+ error.node = atRule
+
+ throw error
+ }
+
+ const mediaNodes = paramsNodes.slice(1)
+ let media
+
+ if (mediaNodes.length > 0) {
+ media = valueParser.stringify(mediaNodes).trim().toLowerCase()
+ }
+
+ // eslint-disable-next-line consistent-return
+ return { atRule, prefix, url, media, isRequestable }
+}
+
+const plugin = (options = {}) => {
+ return {
+ postcssPlugin: 'postcss-import-parser',
+ prepare(result) {
+ const parsedAtRules = []
+
+ return {
+ AtRule: {
+ import(atRule) {
+ let parsedAtRule
+
+ try {
+ parsedAtRule = parseNode(atRule, 'params', result)
+ } catch (error) {
+ result.warn(error.message, { node: error.node })
+ }
+
+ if (!parsedAtRule) {
+ return
+ }
+
+ parsedAtRules.push(parsedAtRule)
+ },
+ },
+ async OnceExit() {
+ if (parsedAtRules.length === 0) {
+ return
+ }
+
+ const resolvedAtRules = await Promise.all(
+ parsedAtRules.map(async (parsedAtRule) => {
+ const { atRule, isRequestable, prefix, url, media } = parsedAtRule
+
+ if (options.filter) {
+ const needKeep = await options.filter(url, media)
+
+ if (!needKeep) {
+ return
+ }
+ }
+
+ if (isRequestable) {
+ const request = requestify(url, options.rootContext)
+
+ const { resolver, context } = options
+ const resolvedUrl = await resolveRequests(resolver, context, [
+ ...new Set([request, url]),
+ ])
+
+ if (!resolvedUrl) {
+ return
+ }
+
+ if (resolvedUrl === options.resourcePath) {
+ atRule.remove()
+
+ return
+ }
+
+ atRule.remove()
+
+ // eslint-disable-next-line consistent-return
+ return { url: resolvedUrl, media, prefix, isRequestable }
+ }
+
+ atRule.remove()
+
+ // eslint-disable-next-line consistent-return
+ return { url, media, prefix, isRequestable }
+ })
+ )
+
+ const urlToNameMap = new Map()
+
+ for (let index = 0; index <= resolvedAtRules.length - 1; index++) {
+ const resolvedAtRule = resolvedAtRules[index]
+
+ if (!resolvedAtRule) {
+ // eslint-disable-next-line no-continue
+ continue
+ }
+
+ const { url, isRequestable, media } = resolvedAtRule
+
+ if (!isRequestable) {
+ options.api.push({ url, media, index })
+
+ // eslint-disable-next-line no-continue
+ continue
+ }
+
+ const { prefix } = resolvedAtRule
+ const newUrl = prefix ? `${prefix}!${url}` : url
+ let importName = urlToNameMap.get(newUrl)
+
+ if (!importName) {
+ importName = `___CSS_LOADER_AT_RULE_IMPORT_${urlToNameMap.size}___`
+ urlToNameMap.set(newUrl, importName)
+
+ options.imports.push({
+ type: 'rule_import',
+ importName,
+ url: options.urlHandler(newUrl),
+ index,
+ })
+ }
+
+ options.api.push({ importName, media, index })
+ }
+ },
+ }
+ },
+ }
+}
+
+plugin.postcss = true
+
+export default plugin
diff --git a/packages/next/build/webpack/loaders/css-loader/src/plugins/postcss-url-parser.js b/packages/next/build/webpack/loaders/css-loader/src/plugins/postcss-url-parser.js
new file mode 100644
index 00000000000000..633fc17a81fa5b
--- /dev/null
+++ b/packages/next/build/webpack/loaders/css-loader/src/plugins/postcss-url-parser.js
@@ -0,0 +1,433 @@
+import valueParser from 'next/dist/compiled/postcss-value-parser'
+
+import {
+ resolveRequests,
+ normalizeUrl,
+ requestify,
+ isUrlRequestable,
+ isDataUrl,
+ WEBPACK_IGNORE_COMMENT_REGEXP,
+} from '../utils'
+
+const isUrlFunc = /url/i
+const isImageSetFunc = /^(?:-webkit-)?image-set$/i
+const needParseDeclaration = /(?:url|(?:-webkit-)?image-set)\(/i
+
+function getNodeFromUrlFunc(node) {
+ return node.nodes && node.nodes[0]
+}
+
+function getWebpackIgnoreCommentValue(index, nodes, inBetween) {
+ if (index === 0 && typeof inBetween !== 'undefined') {
+ return inBetween
+ }
+
+ let prevValueNode = nodes[index - 1]
+
+ if (!prevValueNode) {
+ // eslint-disable-next-line consistent-return
+ return
+ }
+
+ if (prevValueNode.type === 'space') {
+ if (!nodes[index - 2]) {
+ // eslint-disable-next-line consistent-return
+ return
+ }
+
+ prevValueNode = nodes[index - 2]
+ }
+
+ if (prevValueNode.type !== 'comment') {
+ // eslint-disable-next-line consistent-return
+ return
+ }
+
+ const matched = prevValueNode.value.match(WEBPACK_IGNORE_COMMENT_REGEXP)
+
+ return matched && matched[2] === 'true'
+}
+
+function shouldHandleURL(url, declaration, result, isSupportDataURLInNewURL) {
+ if (url.length === 0) {
+ result.warn(`Unable to find uri in '${declaration.toString()}'`, {
+ node: declaration,
+ })
+
+ return false
+ }
+
+ if (isDataUrl(url) && isSupportDataURLInNewURL) {
+ try {
+ decodeURIComponent(url)
+ } catch (ignoreError) {
+ return false
+ }
+
+ return true
+ }
+
+ if (!isUrlRequestable(url)) {
+ return false
+ }
+
+ return true
+}
+
+function parseDeclaration(declaration, key, result, isSupportDataURLInNewURL) {
+ if (!needParseDeclaration.test(declaration[key])) {
+ return
+ }
+
+ const parsed = valueParser(
+ declaration.raws && declaration.raws.value && declaration.raws.value.raw
+ ? declaration.raws.value.raw
+ : declaration[key]
+ )
+
+ let inBetween
+
+ if (declaration.raws && declaration.raws.between) {
+ const lastCommentIndex = declaration.raws.between.lastIndexOf('/*')
+
+ const matched = declaration.raws.between
+ .slice(lastCommentIndex)
+ .match(WEBPACK_IGNORE_COMMENT_REGEXP)
+
+ if (matched) {
+ inBetween = matched[2] === 'true'
+ }
+ }
+
+ let isIgnoreOnDeclaration = false
+
+ const prevNode = declaration.prev()
+
+ if (prevNode && prevNode.type === 'comment') {
+ const matched = prevNode.text.match(WEBPACK_IGNORE_COMMENT_REGEXP)
+
+ if (matched) {
+ isIgnoreOnDeclaration = matched[2] === 'true'
+ }
+ }
+
+ let needIgnore
+
+ const parsedURLs = []
+
+ parsed.walk((valueNode, index, valueNodes) => {
+ if (valueNode.type !== 'function') {
+ return
+ }
+
+ if (isUrlFunc.test(valueNode.value)) {
+ needIgnore = getWebpackIgnoreCommentValue(index, valueNodes, inBetween)
+
+ if (
+ (isIgnoreOnDeclaration && typeof needIgnore === 'undefined') ||
+ needIgnore
+ ) {
+ if (needIgnore) {
+ // eslint-disable-next-line no-undefined
+ needIgnore = undefined
+ }
+
+ return
+ }
+
+ const { nodes } = valueNode
+ const isStringValue = nodes.length !== 0 && nodes[0].type === 'string'
+ let url = isStringValue ? nodes[0].value : valueParser.stringify(nodes)
+ url = normalizeUrl(url, isStringValue)
+
+ // Do not traverse inside `url`
+ if (
+ !shouldHandleURL(url, declaration, result, isSupportDataURLInNewURL)
+ ) {
+ // eslint-disable-next-line consistent-return
+ return false
+ }
+
+ const queryParts = url.split('!')
+ let prefix
+
+ if (queryParts.length > 1) {
+ url = queryParts.pop()
+ prefix = queryParts.join('!')
+ }
+
+ parsedURLs.push({
+ declaration,
+ parsed,
+ node: getNodeFromUrlFunc(valueNode),
+ prefix,
+ url,
+ needQuotes: false,
+ })
+
+ // eslint-disable-next-line consistent-return
+ return false
+ } else if (isImageSetFunc.test(valueNode.value)) {
+ for (const [innerIndex, nNode] of valueNode.nodes.entries()) {
+ const { type, value } = nNode
+
+ if (type === 'function' && isUrlFunc.test(value)) {
+ needIgnore = getWebpackIgnoreCommentValue(innerIndex, valueNode.nodes)
+
+ if (
+ (isIgnoreOnDeclaration && typeof needIgnore === 'undefined') ||
+ needIgnore
+ ) {
+ if (needIgnore) {
+ // eslint-disable-next-line no-undefined
+ needIgnore = undefined
+ }
+
+ // eslint-disable-next-line no-continue
+ continue
+ }
+
+ const { nodes } = nNode
+ const isStringValue = nodes.length !== 0 && nodes[0].type === 'string'
+ let url = isStringValue
+ ? nodes[0].value
+ : valueParser.stringify(nodes)
+ url = normalizeUrl(url, isStringValue)
+
+ // Do not traverse inside `url`
+ if (
+ !shouldHandleURL(url, declaration, result, isSupportDataURLInNewURL)
+ ) {
+ // eslint-disable-next-line consistent-return
+ return false
+ }
+
+ const queryParts = url.split('!')
+ let prefix
+
+ if (queryParts.length > 1) {
+ url = queryParts.pop()
+ prefix = queryParts.join('!')
+ }
+
+ parsedURLs.push({
+ declaration,
+ parsed,
+ node: getNodeFromUrlFunc(nNode),
+ prefix,
+ url,
+ needQuotes: false,
+ })
+ } else if (type === 'string') {
+ needIgnore = getWebpackIgnoreCommentValue(innerIndex, valueNode.nodes)
+
+ if (
+ (isIgnoreOnDeclaration && typeof needIgnore === 'undefined') ||
+ needIgnore
+ ) {
+ if (needIgnore) {
+ // eslint-disable-next-line no-undefined
+ needIgnore = undefined
+ }
+
+ // eslint-disable-next-line no-continue
+ continue
+ }
+
+ let url = normalizeUrl(value, true)
+
+ // Do not traverse inside `url`
+ if (
+ !shouldHandleURL(url, declaration, result, isSupportDataURLInNewURL)
+ ) {
+ // eslint-disable-next-line consistent-return
+ return false
+ }
+
+ const queryParts = url.split('!')
+ let prefix
+
+ if (queryParts.length > 1) {
+ url = queryParts.pop()
+ prefix = queryParts.join('!')
+ }
+
+ parsedURLs.push({
+ declaration,
+ parsed,
+ node: nNode,
+ prefix,
+ url,
+ needQuotes: true,
+ })
+ }
+ }
+
+ // Do not traverse inside `image-set`
+ // eslint-disable-next-line consistent-return
+ return false
+ }
+ })
+
+ // eslint-disable-next-line consistent-return
+ return parsedURLs
+}
+
+const plugin = (options = {}) => {
+ return {
+ postcssPlugin: 'postcss-url-parser',
+ prepare(result) {
+ const parsedDeclarations = []
+
+ return {
+ Declaration(declaration) {
+ const { isSupportDataURLInNewURL } = options
+ const parsedURL = parseDeclaration(
+ declaration,
+ 'value',
+ result,
+ isSupportDataURLInNewURL
+ )
+
+ if (!parsedURL) {
+ return
+ }
+
+ parsedDeclarations.push(...parsedURL)
+ },
+ async OnceExit() {
+ if (parsedDeclarations.length === 0) {
+ return
+ }
+
+ const resolvedDeclarations = await Promise.all(
+ parsedDeclarations.map(async (parsedDeclaration) => {
+ const { url } = parsedDeclaration
+
+ if (options.filter) {
+ const needKeep = await options.filter(url)
+
+ if (!needKeep) {
+ // eslint-disable-next-line consistent-return
+ return
+ }
+ }
+
+ if (isDataUrl(url)) {
+ // eslint-disable-next-line consistent-return
+ return parsedDeclaration
+ }
+
+ const splittedUrl = url.split(/(\?)?#/)
+ const [pathname, query, hashOrQuery] = splittedUrl
+
+ let hash = query ? '?' : ''
+ hash += hashOrQuery ? `#${hashOrQuery}` : ''
+
+ const { needToResolveURL, rootContext } = options
+ const request = requestify(
+ pathname,
+ rootContext,
+ needToResolveURL
+ )
+
+ if (!needToResolveURL) {
+ // eslint-disable-next-line consistent-return
+ return { ...parsedDeclaration, url: request, hash }
+ }
+
+ const { resolver, context } = options
+ const resolvedUrl = await resolveRequests(resolver, context, [
+ ...new Set([request, url]),
+ ])
+
+ if (!resolvedUrl) {
+ // eslint-disable-next-line consistent-return
+ return
+ }
+
+ // eslint-disable-next-line consistent-return
+ return { ...parsedDeclaration, url: resolvedUrl, hash }
+ })
+ )
+
+ const urlToNameMap = new Map()
+ const urlToReplacementMap = new Map()
+
+ let hasUrlImportHelper = false
+
+ for (
+ let index = 0;
+ index <= resolvedDeclarations.length - 1;
+ index++
+ ) {
+ const item = resolvedDeclarations[index]
+
+ if (!item) {
+ // eslint-disable-next-line no-continue
+ continue
+ }
+
+ if (!hasUrlImportHelper) {
+ options.imports.push({
+ type: 'get_url_import',
+ importName: '___CSS_LOADER_GET_URL_IMPORT___',
+ url: options.urlHandler(
+ require.resolve('../runtime/getUrl.js')
+ ),
+ index: -1,
+ })
+
+ hasUrlImportHelper = true
+ }
+
+ const { url, prefix } = item
+ const newUrl = prefix ? `${prefix}!${url}` : url
+ let importName = urlToNameMap.get(newUrl)
+
+ if (!importName) {
+ importName = `___CSS_LOADER_URL_IMPORT_${urlToNameMap.size}___`
+ urlToNameMap.set(newUrl, importName)
+
+ options.imports.push({
+ type: 'url',
+ importName,
+ url: options.needToResolveURL
+ ? options.urlHandler(newUrl)
+ : JSON.stringify(newUrl),
+ index,
+ })
+ }
+
+ const { hash, needQuotes } = item
+ const replacementKey = JSON.stringify({ newUrl, hash, needQuotes })
+ let replacementName = urlToReplacementMap.get(replacementKey)
+
+ if (!replacementName) {
+ replacementName = `___CSS_LOADER_URL_REPLACEMENT_${urlToReplacementMap.size}___`
+ urlToReplacementMap.set(replacementKey, replacementName)
+
+ options.replacements.push({
+ replacementName,
+ importName,
+ hash,
+ needQuotes,
+ })
+ }
+
+ // eslint-disable-next-line no-param-reassign
+ item.node.type = 'word'
+ // eslint-disable-next-line no-param-reassign
+ item.node.value = replacementName
+ // eslint-disable-next-line no-param-reassign
+ item.declaration.value = item.parsed.toString()
+ }
+ },
+ }
+ },
+ }
+}
+
+plugin.postcss = true
+
+export default plugin
diff --git a/packages/next/build/webpack/loaders/css-loader/src/runtime/api.js b/packages/next/build/webpack/loaders/css-loader/src/runtime/api.js
new file mode 100644
index 00000000000000..6ca402a53a8bd6
--- /dev/null
+++ b/packages/next/build/webpack/loaders/css-loader/src/runtime/api.js
@@ -0,0 +1,95 @@
+/*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+ Author Tobias Koppers @sokra
+*/
+// css base code, injected by the css-loader
+// eslint-disable-next-line func-names
+module.exports = function (useSourceMap) {
+ var list = [] // return the list of modules as css string
+
+ list.toString = function toString() {
+ return this.map(function (item) {
+ var content = cssWithMappingToString(item, useSourceMap)
+
+ if (item[2]) {
+ return '@media '.concat(item[2], ' {').concat(content, '}')
+ }
+
+ return content
+ }).join('')
+ } // import a list of modules into the list
+ // eslint-disable-next-line func-names
+
+ list.i = function (modules, mediaQuery, dedupe) {
+ if (typeof modules === 'string') {
+ // eslint-disable-next-line no-param-reassign
+ modules = [[null, modules, '']]
+ }
+
+ var alreadyImportedModules = {}
+
+ if (dedupe) {
+ for (var i = 0; i < this.length; i++) {
+ // eslint-disable-next-line prefer-destructuring
+ var id = this[i][0]
+
+ if (id != null) {
+ alreadyImportedModules[id] = true
+ }
+ }
+ }
+
+ for (var _i = 0; _i < modules.length; _i++) {
+ var item = [].concat(modules[_i])
+
+ if (dedupe && alreadyImportedModules[item[0]]) {
+ // eslint-disable-next-line no-continue
+ continue
+ }
+
+ if (mediaQuery) {
+ if (!item[2]) {
+ item[2] = mediaQuery
+ } else {
+ item[2] = ''.concat(mediaQuery, ' and ').concat(item[2])
+ }
+ }
+
+ list.push(item)
+ }
+ }
+
+ return list
+}
+
+function cssWithMappingToString(item, useSourceMap) {
+ var content = item[1] || '' // eslint-disable-next-line prefer-destructuring
+
+ var cssMapping = item[3]
+
+ if (!cssMapping) {
+ return content
+ }
+
+ if (useSourceMap && typeof btoa === 'function') {
+ var sourceMapping = toComment(cssMapping)
+ var sourceURLs = cssMapping.sources.map(function (source) {
+ return '/*# sourceURL='
+ .concat(cssMapping.sourceRoot || '')
+ .concat(source, ' */')
+ })
+ return [content].concat(sourceURLs).concat([sourceMapping]).join('\n')
+ }
+
+ return [content].join('\n')
+} // Adapted from convert-source-map (MIT)
+
+function toComment(sourceMap) {
+ // eslint-disable-next-line no-undef
+ var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))))
+ var data =
+ 'sourceMappingURL=data:application/json;charset=utf-8;base64,'.concat(
+ base64
+ )
+ return '/*# '.concat(data, ' */')
+}
diff --git a/packages/next/build/webpack/loaders/css-loader/src/runtime/getUrl.js b/packages/next/build/webpack/loaders/css-loader/src/runtime/getUrl.js
new file mode 100644
index 00000000000000..11cfbd0f4d6e24
--- /dev/null
+++ b/packages/next/build/webpack/loaders/css-loader/src/runtime/getUrl.js
@@ -0,0 +1,29 @@
+module.exports = function (url, options) {
+ if (!options) {
+ // eslint-disable-next-line no-param-reassign
+ options = {}
+ } // eslint-disable-next-line no-underscore-dangle, no-param-reassign
+
+ url = url && url.__esModule ? url.default : url
+
+ if (typeof url !== 'string') {
+ return url
+ } // If url is already wrapped in quotes, remove them
+
+ if (/^['"].*['"]$/.test(url)) {
+ // eslint-disable-next-line no-param-reassign
+ url = url.slice(1, -1)
+ }
+
+ if (options.hash) {
+ // eslint-disable-next-line no-param-reassign
+ url += options.hash
+ } // Should url be wrapped?
+ // See https://drafts.csswg.org/css-values-3/#urls
+
+ if (/["'() \t\n]/.test(url) || options.needQuotes) {
+ return '"'.concat(url.replace(/"/g, '\\"').replace(/\n/g, '\\n'), '"')
+ }
+
+ return url
+}
diff --git a/packages/next/build/webpack/loaders/css-loader/src/utils.js b/packages/next/build/webpack/loaders/css-loader/src/utils.js
new file mode 100644
index 00000000000000..0c348014025808
--- /dev/null
+++ b/packages/next/build/webpack/loaders/css-loader/src/utils.js
@@ -0,0 +1,649 @@
+/*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+ Author Tobias Koppers @sokra
+*/
+import { fileURLToPath } from 'url'
+import path from 'path'
+
+import { urlToRequest } from 'next/dist/compiled/loader-utils'
+import modulesValues from 'next/dist/compiled/postcss-modules-values'
+import localByDefault from 'next/dist/compiled/postcss-modules-local-by-default'
+import extractImports from 'next/dist/compiled/postcss-modules-extract-imports'
+import modulesScope from 'next/dist/compiled/postcss-modules-scope'
+import camelCase from './camelcase'
+
+const whitespace = '[\\x20\\t\\r\\n\\f]'
+const unescapeRegExp = new RegExp(
+ `\\\\([\\da-f]{1,6}${whitespace}?|(${whitespace})|.)`,
+ 'ig'
+)
+const matchNativeWin32Path = /^[A-Z]:[/\\]|^\\\\/i
+
+function unescape(str) {
+ return str.replace(unescapeRegExp, (_, escaped, escapedWhitespace) => {
+ const high = `0x${escaped}` - 0x10000
+
+ /* eslint-disable line-comment-position */
+ // NaN means non-codepoint
+ // Workaround erroneous numeric interpretation of +"0x"
+ // eslint-disable-next-line no-self-compare
+ return high !== high || escapedWhitespace
+ ? escaped
+ : high < 0
+ ? // BMP codepoint
+ String.fromCharCode(high + 0x10000)
+ : // Supplemental Plane codepoint (surrogate pair)
+ // eslint-disable-next-line no-bitwise
+ String.fromCharCode((high >> 10) | 0xd800, (high & 0x3ff) | 0xdc00)
+ /* eslint-enable line-comment-position */
+ })
+}
+
+function normalizePath(file) {
+ return path.sep === '\\' ? file.replace(/\\/g, '/') : file
+}
+
+function normalizeUrl(url, isStringValue) {
+ let normalizedUrl = url
+
+ if (isStringValue && /\\(\n|\r\n|\r|\f)/.test(normalizedUrl)) {
+ normalizedUrl = normalizedUrl.replace(/\\(\n|\r\n|\r|\f)/g, '')
+ }
+
+ if (matchNativeWin32Path.test(url)) {
+ return decodeURIComponent(normalizedUrl)
+ }
+
+ return decodeURIComponent(unescape(normalizedUrl))
+}
+
+function requestify(url, rootContext) {
+ if (/^file:/i.test(url)) {
+ return fileURLToPath(url)
+ }
+
+ return url.charAt(0) === '/'
+ ? urlToRequest(url, rootContext)
+ : urlToRequest(url)
+}
+
+function getFilter(filter, resourcePath) {
+ return (...args) => {
+ if (typeof filter === 'function') {
+ return filter(...args, resourcePath)
+ }
+
+ return true
+ }
+}
+
+const moduleRegExp = /\.module\.\w+$/i
+
+function getModulesOptions(rawOptions, loaderContext) {
+ const { resourcePath } = loaderContext
+
+ if (typeof rawOptions.modules === 'undefined') {
+ const isModules = moduleRegExp.test(resourcePath)
+
+ if (!isModules) {
+ return false
+ }
+ } else if (
+ typeof rawOptions.modules === 'boolean' &&
+ rawOptions.modules === false
+ ) {
+ return false
+ }
+
+ let modulesOptions = {
+ compileType: rawOptions.icss ? 'icss' : 'module',
+ auto: true,
+ mode: 'local',
+ exportGlobals: false,
+ localIdentName: '[hash:base64]',
+ localIdentContext: loaderContext.rootContext,
+ localIdentHashPrefix: '',
+ // eslint-disable-next-line no-undefined
+ localIdentRegExp: undefined,
+ namedExport: false,
+ exportLocalsConvention: 'asIs',
+ exportOnlyLocals: false,
+ }
+
+ if (
+ typeof rawOptions.modules === 'boolean' ||
+ typeof rawOptions.modules === 'string'
+ ) {
+ modulesOptions.mode =
+ typeof rawOptions.modules === 'string' ? rawOptions.modules : 'local'
+ } else {
+ if (rawOptions.modules) {
+ if (typeof rawOptions.modules.auto === 'boolean') {
+ const isModules =
+ rawOptions.modules.auto && moduleRegExp.test(resourcePath)
+
+ if (!isModules) {
+ return false
+ }
+ } else if (rawOptions.modules.auto instanceof RegExp) {
+ const isModules = rawOptions.modules.auto.test(resourcePath)
+
+ if (!isModules) {
+ return false
+ }
+ } else if (typeof rawOptions.modules.auto === 'function') {
+ const isModule = rawOptions.modules.auto(resourcePath)
+
+ if (!isModule) {
+ return false
+ }
+ }
+
+ if (
+ rawOptions.modules.namedExport === true &&
+ typeof rawOptions.modules.exportLocalsConvention === 'undefined'
+ ) {
+ modulesOptions.exportLocalsConvention = 'camelCaseOnly'
+ }
+ }
+
+ modulesOptions = { ...modulesOptions, ...(rawOptions.modules || {}) }
+ }
+
+ if (typeof modulesOptions.mode === 'function') {
+ modulesOptions.mode = modulesOptions.mode(loaderContext.resourcePath)
+ }
+
+ if (modulesOptions.namedExport === true) {
+ if (rawOptions.esModule === false) {
+ throw new Error(
+ 'The "modules.namedExport" option requires the "esModules" option to be enabled'
+ )
+ }
+
+ if (modulesOptions.exportLocalsConvention !== 'camelCaseOnly') {
+ throw new Error(
+ 'The "modules.namedExport" option requires the "modules.exportLocalsConvention" option to be "camelCaseOnly"'
+ )
+ }
+ }
+
+ return modulesOptions
+}
+
+function normalizeOptions(rawOptions, loaderContext) {
+ if (rawOptions.icss) {
+ loaderContext.emitWarning(
+ new Error(
+ 'The "icss" option is deprecated, use "modules.compileType: "icss"" instead'
+ )
+ )
+ }
+
+ const modulesOptions = getModulesOptions(rawOptions, loaderContext)
+
+ return {
+ url: typeof rawOptions.url === 'undefined' ? true : rawOptions.url,
+ import: typeof rawOptions.import === 'undefined' ? true : rawOptions.import,
+ modules: modulesOptions,
+ // TODO remove in the next major release
+ icss: typeof rawOptions.icss === 'undefined' ? false : rawOptions.icss,
+ sourceMap:
+ typeof rawOptions.sourceMap === 'boolean'
+ ? rawOptions.sourceMap
+ : loaderContext.sourceMap,
+ importLoaders:
+ typeof rawOptions.importLoaders === 'string'
+ ? parseInt(rawOptions.importLoaders, 10)
+ : rawOptions.importLoaders,
+ esModule:
+ typeof rawOptions.esModule === 'undefined' ? true : rawOptions.esModule,
+ }
+}
+
+function shouldUseImportPlugin(options) {
+ if (options.modules.exportOnlyLocals) {
+ return false
+ }
+
+ if (typeof options.import === 'boolean') {
+ return options.import
+ }
+
+ return true
+}
+
+function shouldUseURLPlugin(options) {
+ if (options.modules.exportOnlyLocals) {
+ return false
+ }
+
+ if (typeof options.url === 'boolean') {
+ return options.url
+ }
+
+ return true
+}
+
+function shouldUseModulesPlugins(options) {
+ return options.modules.compileType === 'module'
+}
+
+function shouldUseIcssPlugin(options) {
+ return options.icss === true || Boolean(options.modules)
+}
+
+function getModulesPlugins(options, loaderContext) {
+ const {
+ mode,
+ getLocalIdent,
+ localIdentName,
+ localIdentContext,
+ localIdentHashPrefix,
+ localIdentRegExp,
+ } = options.modules
+
+ let plugins = []
+
+ try {
+ plugins = [
+ modulesValues,
+ localByDefault({ mode }),
+ extractImports(),
+ modulesScope({
+ generateScopedName(exportName) {
+ return getLocalIdent(loaderContext, localIdentName, exportName, {
+ context: localIdentContext,
+ hashPrefix: localIdentHashPrefix,
+ regExp: localIdentRegExp,
+ })
+ },
+ exportGlobals: options.modules.exportGlobals,
+ }),
+ ]
+ } catch (error) {
+ loaderContext.emitError(error)
+ }
+
+ return plugins
+}
+
+const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i
+const ABSOLUTE_SCHEME = /^[a-z0-9+\-.]+:/i
+
+function getURLType(source) {
+ if (source[0] === '/') {
+ if (source[1] === '/') {
+ return 'scheme-relative'
+ }
+
+ return 'path-absolute'
+ }
+
+ if (IS_NATIVE_WIN32_PATH.test(source)) {
+ return 'path-absolute'
+ }
+
+ return ABSOLUTE_SCHEME.test(source) ? 'absolute' : 'path-relative'
+}
+
+function normalizeSourceMap(map, resourcePath) {
+ let newMap = map
+
+ // Some loader emit source map as string
+ // Strip any JSON XSSI avoidance prefix from the string (as documented in the source maps specification), and then parse the string as JSON.
+ if (typeof newMap === 'string') {
+ newMap = JSON.parse(newMap)
+ }
+
+ delete newMap.file
+
+ const { sourceRoot } = newMap
+
+ delete newMap.sourceRoot
+
+ if (newMap.sources) {
+ // Source maps should use forward slash because it is URLs (https://github.com/mozilla/source-map/issues/91)
+ // We should normalize path because previous loaders like `sass-loader` using backslash when generate source map
+ newMap.sources = newMap.sources.map((source) => {
+ // Non-standard syntax from `postcss`
+ if (source.indexOf('<') === 0) {
+ return source
+ }
+
+ const sourceType = getURLType(source)
+
+ // Do no touch `scheme-relative` and `absolute` URLs
+ if (sourceType === 'path-relative' || sourceType === 'path-absolute') {
+ const absoluteSource =
+ sourceType === 'path-relative' && sourceRoot
+ ? path.resolve(sourceRoot, normalizePath(source))
+ : normalizePath(source)
+
+ return path.relative(path.dirname(resourcePath), absoluteSource)
+ }
+
+ return source
+ })
+ }
+
+ return newMap
+}
+
+function getPreRequester({ loaders, loaderIndex }) {
+ const cache = Object.create(null)
+
+ return (number) => {
+ if (cache[number]) {
+ return cache[number]
+ }
+
+ if (number === false) {
+ cache[number] = ''
+ } else {
+ const loadersRequest = loaders
+ .slice(
+ loaderIndex,
+ loaderIndex + 1 + (typeof number !== 'number' ? 0 : number)
+ )
+ .map((x) => x.request)
+ .join('!')
+
+ cache[number] = `-!${loadersRequest}!`
+ }
+
+ return cache[number]
+ }
+}
+
+function getImportCode(imports, options) {
+ let code = ''
+
+ for (const item of imports) {
+ const { importName, url, icss } = item
+
+ if (options.esModule) {
+ if (icss && options.modules.namedExport) {
+ code += `import ${
+ options.modules.exportOnlyLocals ? '' : `${importName}, `
+ }* as ${importName}_NAMED___ from ${url};\n`
+ } else {
+ code += `import ${importName} from ${url};\n`
+ }
+ } else {
+ code += `var ${importName} = require(${url});\n`
+ }
+ }
+
+ return code ? `// Imports\n${code}` : ''
+}
+
+function normalizeSourceMapForRuntime(map, loaderContext) {
+ const resultMap = map ? map.toJSON() : null
+
+ if (resultMap) {
+ delete resultMap.file
+
+ resultMap.sourceRoot = ''
+
+ resultMap.sources = resultMap.sources.map((source) => {
+ // Non-standard syntax from `postcss`
+ if (source.indexOf('<') === 0) {
+ return source
+ }
+
+ const sourceType = getURLType(source)
+
+ if (sourceType !== 'path-relative') {
+ return source
+ }
+
+ const resourceDirname = path.dirname(loaderContext.resourcePath)
+ const absoluteSource = path.resolve(resourceDirname, source)
+ const contextifyPath = normalizePath(
+ path.relative(loaderContext.rootContext, absoluteSource)
+ )
+
+ return `webpack://${contextifyPath}`
+ })
+ }
+
+ return JSON.stringify(resultMap)
+}
+
+function getModuleCode(result, api, replacements, options, loaderContext) {
+ if (options.modules.exportOnlyLocals === true) {
+ return ''
+ }
+
+ const sourceMapValue = options.sourceMap
+ ? `,${normalizeSourceMapForRuntime(result.map, loaderContext)}`
+ : ''
+
+ let code = JSON.stringify(result.css)
+ let beforeCode = `var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(${options.sourceMap});\n`
+
+ for (const item of api) {
+ const { url, media, dedupe } = item
+
+ beforeCode += url
+ ? `___CSS_LOADER_EXPORT___.push([module.id, ${JSON.stringify(
+ `@import url(${url});`
+ )}${media ? `, ${JSON.stringify(media)}` : ''}]);\n`
+ : `___CSS_LOADER_EXPORT___.i(${item.importName}${
+ media ? `, ${JSON.stringify(media)}` : dedupe ? ', ""' : ''
+ }${dedupe ? ', true' : ''});\n`
+ }
+
+ for (const item of replacements) {
+ const { replacementName, importName, localName } = item
+
+ if (localName) {
+ code = code.replace(new RegExp(replacementName, 'g'), () =>
+ options.modules.namedExport
+ ? `" + ${importName}_NAMED___[${JSON.stringify(
+ camelCase(localName)
+ )}] + "`
+ : `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
+ )
+ } else {
+ const { hash, needQuotes } = item
+ const getUrlOptions = []
+ .concat(hash ? [`hash: ${JSON.stringify(hash)}`] : [])
+ .concat(needQuotes ? 'needQuotes: true' : [])
+ const preparedOptions =
+ getUrlOptions.length > 0 ? `, { ${getUrlOptions.join(', ')} }` : ''
+
+ beforeCode += `var ${replacementName} = ___CSS_LOADER_GET_URL_IMPORT___(${importName}${preparedOptions});\n`
+ code = code.replace(
+ new RegExp(replacementName, 'g'),
+ () => `" + ${replacementName} + "`
+ )
+ }
+ }
+
+ return `${beforeCode}// Module\n___CSS_LOADER_EXPORT___.push([module.id, ${code}, ""${sourceMapValue}]);\n`
+}
+
+function dashesCamelCase(str) {
+ return str.replace(/-+(\w)/g, (match, firstLetter) =>
+ firstLetter.toUpperCase()
+ )
+}
+
+function getExportCode(exports, replacements, options) {
+ let code = '// Exports\n'
+ let localsCode = ''
+
+ const addExportToLocalsCode = (name, value) => {
+ if (options.modules.namedExport) {
+ localsCode += `export const ${camelCase(name)} = ${JSON.stringify(
+ value
+ )};\n`
+ } else {
+ if (localsCode) {
+ localsCode += `,\n`
+ }
+
+ localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`
+ }
+ }
+
+ for (const { name, value } of exports) {
+ switch (options.modules.exportLocalsConvention) {
+ case 'camelCase': {
+ addExportToLocalsCode(name, value)
+
+ const modifiedName = camelCase(name)
+
+ if (modifiedName !== name) {
+ addExportToLocalsCode(modifiedName, value)
+ }
+ break
+ }
+ case 'camelCaseOnly': {
+ addExportToLocalsCode(camelCase(name), value)
+ break
+ }
+ case 'dashes': {
+ addExportToLocalsCode(name, value)
+
+ const modifiedName = dashesCamelCase(name)
+
+ if (modifiedName !== name) {
+ addExportToLocalsCode(modifiedName, value)
+ }
+ break
+ }
+ case 'dashesOnly': {
+ addExportToLocalsCode(dashesCamelCase(name), value)
+ break
+ }
+ case 'asIs':
+ default:
+ addExportToLocalsCode(name, value)
+ break
+ }
+ }
+
+ for (const item of replacements) {
+ const { replacementName, localName } = item
+
+ if (localName) {
+ const { importName } = item
+
+ localsCode = localsCode.replace(new RegExp(replacementName, 'g'), () => {
+ if (options.modules.namedExport) {
+ return `" + ${importName}_NAMED___[${JSON.stringify(
+ camelCase(localName)
+ )}] + "`
+ } else if (options.modules.exportOnlyLocals) {
+ return `" + ${importName}[${JSON.stringify(localName)}] + "`
+ }
+
+ return `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
+ })
+ } else {
+ localsCode = localsCode.replace(
+ new RegExp(replacementName, 'g'),
+ () => `" + ${replacementName} + "`
+ )
+ }
+ }
+
+ if (options.modules.exportOnlyLocals) {
+ code += options.modules.namedExport
+ ? localsCode
+ : `${
+ options.esModule ? 'export default' : 'module.exports ='
+ } {\n${localsCode}\n};\n`
+
+ return code
+ }
+
+ if (localsCode) {
+ code += options.modules.namedExport
+ ? localsCode
+ : `___CSS_LOADER_EXPORT___.locals = {\n${localsCode}\n};\n`
+ }
+
+ code += `${
+ options.esModule ? 'export default' : 'module.exports ='
+ } ___CSS_LOADER_EXPORT___;\n`
+
+ return code
+}
+
+async function resolveRequests(resolve, context, possibleRequests) {
+ return resolve(context, possibleRequests[0])
+ .then((result) => {
+ return result
+ })
+ .catch((error) => {
+ const [, ...tailPossibleRequests] = possibleRequests
+
+ if (tailPossibleRequests.length === 0) {
+ throw error
+ }
+
+ return resolveRequests(resolve, context, tailPossibleRequests)
+ })
+}
+
+function isUrlRequestable(url) {
+ // Protocol-relative URLs
+ if (/^\/\//.test(url)) {
+ return false
+ }
+
+ // `file:` protocol
+ if (/^file:/i.test(url)) {
+ return true
+ }
+
+ // Absolute URLs
+ if (/^[a-z][a-z0-9+.-]*:/i.test(url) && !matchNativeWin32Path.test(url)) {
+ return false
+ }
+
+ // `#` URLs
+ if (/^#/.test(url)) {
+ return false
+ }
+
+ return true
+}
+
+function sort(a, b) {
+ return a.index - b.index
+}
+
+function isDataUrl(url) {
+ if (/^data:/i.test(url)) {
+ return true
+ }
+
+ return false
+}
+
+export {
+ isDataUrl,
+ normalizeOptions,
+ shouldUseModulesPlugins,
+ shouldUseImportPlugin,
+ shouldUseURLPlugin,
+ shouldUseIcssPlugin,
+ normalizeUrl,
+ requestify,
+ getFilter,
+ getModulesOptions,
+ getModulesPlugins,
+ normalizeSourceMap,
+ getPreRequester,
+ getImportCode,
+ getModuleCode,
+ getExportCode,
+ resolveRequests,
+ isUrlRequestable,
+ sort,
+}
diff --git a/packages/next/build/webpack/loaders/next-serverless-loader/index.ts b/packages/next/build/webpack/loaders/next-serverless-loader/index.ts
index d64ece16177d60..054def2f9feb97 100644
--- a/packages/next/build/webpack/loaders/next-serverless-loader/index.ts
+++ b/packages/next/build/webpack/loaders/next-serverless-loader/index.ts
@@ -11,7 +11,6 @@ import {
ROUTES_MANIFEST,
REACT_LOADABLE_MANIFEST,
} from '../../../../shared/lib/constants'
-import { trace } from '../../../../telemetry/trace'
export type ServerlessLoaderQuery = {
page: string
@@ -34,63 +33,61 @@ export type ServerlessLoaderQuery = {
}
const nextServerlessLoader: webpack.loader.Loader = function () {
- const loaderSpan = trace('next-serverless-loader')
- return loaderSpan.traceFn(() => {
- const {
- distDir,
- absolutePagePath,
- page,
- buildId,
- canonicalBase,
- assetPrefix,
- absoluteAppPath,
- absoluteDocumentPath,
- absoluteErrorPath,
- absolute404Path,
- generateEtags,
- poweredByHeader,
- basePath,
- runtimeConfig,
- previewProps,
- loadedEnvFiles,
- i18n,
- }: ServerlessLoaderQuery =
- typeof this.query === 'string' ? parse(this.query.substr(1)) : this.query
-
- const buildManifest = join(distDir, BUILD_MANIFEST).replace(/\\/g, '/')
- const reactLoadableManifest = join(
- distDir,
- REACT_LOADABLE_MANIFEST
- ).replace(/\\/g, '/')
- const routesManifest = join(distDir, ROUTES_MANIFEST).replace(/\\/g, '/')
-
- const escapedBuildId = escapeRegexp(buildId)
- const pageIsDynamicRoute = isDynamicRoute(page)
-
- const encodedPreviewProps = devalue(
- JSON.parse(previewProps) as __ApiPreviewProps
- )
-
- const envLoading = `
+ const {
+ distDir,
+ absolutePagePath,
+ page,
+ buildId,
+ canonicalBase,
+ assetPrefix,
+ absoluteAppPath,
+ absoluteDocumentPath,
+ absoluteErrorPath,
+ absolute404Path,
+ generateEtags,
+ poweredByHeader,
+ basePath,
+ runtimeConfig,
+ previewProps,
+ loadedEnvFiles,
+ i18n,
+ }: ServerlessLoaderQuery =
+ typeof this.query === 'string' ? parse(this.query.substr(1)) : this.query
+
+ const buildManifest = join(distDir, BUILD_MANIFEST).replace(/\\/g, '/')
+ const reactLoadableManifest = join(distDir, REACT_LOADABLE_MANIFEST).replace(
+ /\\/g,
+ '/'
+ )
+ const routesManifest = join(distDir, ROUTES_MANIFEST).replace(/\\/g, '/')
+
+ const escapedBuildId = escapeRegexp(buildId)
+ const pageIsDynamicRoute = isDynamicRoute(page)
+
+ const encodedPreviewProps = devalue(
+ JSON.parse(previewProps) as __ApiPreviewProps
+ )
+
+ const envLoading = `
const { processEnv } = require('@next/env')
processEnv(${Buffer.from(loadedEnvFiles, 'base64').toString()})
`
- const runtimeConfigImports = runtimeConfig
- ? `
+ const runtimeConfigImports = runtimeConfig
+ ? `
const { setConfig } = require('next/config')
`
- : ''
+ : ''
- const runtimeConfigSetter = runtimeConfig
- ? `
+ const runtimeConfigSetter = runtimeConfig
+ ? `
const runtimeConfig = ${runtimeConfig}
setConfig(runtimeConfig)
`
- : 'const runtimeConfig = {}'
+ : 'const runtimeConfig = {}'
- if (page.match(API_ROUTE)) {
- return `
+ if (page.match(API_ROUTE)) {
+ return `
${envLoading}
${runtimeConfigImports}
${
@@ -125,8 +122,8 @@ const nextServerlessLoader: webpack.loader.Loader = function () {
})
export default apiHandler
`
- } else {
- return `
+ } else {
+ return `
import 'next/dist/server/node-polyfill-fetch'
import routesManifest from '${routesManifest}'
import buildManifest from '${buildManifest}'
@@ -206,8 +203,7 @@ const nextServerlessLoader: webpack.loader.Loader = function () {
})
export { renderReqToHTML, render }
`
- }
- })
+ }
}
export default nextServerlessLoader
diff --git a/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts b/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts
index db761f6871d3e7..50fdcc093cbf79 100644
--- a/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts
+++ b/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts
@@ -1,7 +1,7 @@
import { IncomingMessage, ServerResponse } from 'http'
import { parse as parseUrl, format as formatUrl, UrlWithParsedQuery } from 'url'
import { DecodeError, isResSent } from '../../../../shared/lib/utils'
-import { sendPayload } from '../../../../server/send-payload'
+import { sendRenderResult } from '../../../../server/send-payload'
import { getUtils, vercelHeader, ServerlessHandlerCtx } from './utils'
import { renderToHTML } from '../../../../server/render'
@@ -11,7 +11,7 @@ import { setLazyProp, getCookieParser } from '../../../../server/api-utils'
import { getRedirectStatus } from '../../../../lib/load-custom-routes'
import getRouteNoAssetPath from '../../../../shared/lib/router/utils/get-route-from-asset-path'
import { PERMANENT_REDIRECT_STATUS } from '../../../../shared/lib/constants'
-import { resultsToString } from '../../../../server/utils'
+import RenderResult from '../../../../server/render-result'
export function getPageHandler(ctx: ServerlessHandlerCtx) {
const {
@@ -116,6 +116,7 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) {
previewProps: encodedPreviewProps,
env: process.env,
basePath,
+ supportsDynamicHTML: false, // Serverless target doesn't support streaming
..._renderOpts,
}
let _nextData = false
@@ -334,22 +335,19 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) {
defaultLocale: i18n?.defaultLocale,
})
)
- const html = result2 ? await resultsToString([result2]) : ''
- sendPayload(
+ sendRenderResult({
req,
res,
- html,
- 'html',
- {
- generateEtags,
- poweredByHeader,
- },
- {
+ result: result2 ?? RenderResult.empty,
+ type: 'html',
+ generateEtags,
+ poweredByHeader,
+ options: {
private: isPreviewMode || page === '/404',
stateful: !!getServerSideProps,
revalidate: renderOpts.revalidate,
- }
- )
+ },
+ })
return null
} else if (renderOpts.isRedirect && !_nextData) {
const redirect = {
@@ -376,21 +374,21 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) {
res.end()
return null
} else {
- sendPayload(
+ sendRenderResult({
req,
res,
- _nextData ? JSON.stringify(renderOpts.pageData) : result,
- _nextData ? 'json' : 'html',
- {
- generateEtags,
- poweredByHeader,
- },
- {
+ result: _nextData
+ ? RenderResult.fromStatic(JSON.stringify(renderOpts.pageData))
+ : result ?? RenderResult.empty,
+ type: _nextData ? 'json' : 'html',
+ generateEtags,
+ poweredByHeader,
+ options: {
private: isPreviewMode || renderOpts.is404Page,
stateful: !!getServerSideProps,
revalidate: renderOpts.revalidate,
- }
- )
+ },
+ })
return null
}
}
@@ -402,7 +400,7 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) {
}
if (renderMode) return { html: result, renderOpts }
- return result ? await resultsToString([result]) : null
+ return result ? await result.toUnchunkedString() : null
} catch (err) {
if (!parsedUrl!) {
parsedUrl = parseUrl(req.url!, true)
@@ -464,7 +462,7 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) {
err: res.statusCode === 404 ? undefined : err,
})
)
- return result2 ? await resultsToString([result2]) : null
+ return result2 ? result2.toUnchunkedString() : null
}
}
@@ -474,7 +472,11 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) {
try {
const html = await renderReqToHTML(req, res)
if (html) {
- sendPayload(req, res, html, 'html', {
+ sendRenderResult({
+ req,
+ res,
+ result: RenderResult.fromStatic(html as any),
+ type: 'html',
generateEtags,
poweredByHeader,
})
diff --git a/packages/next/build/webpack/loaders/next-style-loader/index.js b/packages/next/build/webpack/loaders/next-style-loader/index.js
index 47f56028b9c3f2..b68ca3d9c39fa4 100644
--- a/packages/next/build/webpack/loaders/next-style-loader/index.js
+++ b/packages/next/build/webpack/loaders/next-style-loader/index.js
@@ -1,79 +1,31 @@
import loaderUtils from 'next/dist/compiled/loader-utils'
import path from 'path'
-import { validate } from 'next/dist/compiled/schema-utils3'
import isEqualLocals from './runtime/isEqualLocals'
-const schema = {
- type: 'object',
- properties: {
- injectType: {
- description:
- 'Allows to setup how styles will be injected into DOM (https://github.com/webpack-contrib/style-loader#injecttype).',
- enum: [
- 'styleTag',
- 'singletonStyleTag',
- 'lazyStyleTag',
- 'lazySingletonStyleTag',
- 'linkTag',
- ],
- },
- attributes: {
- description:
- 'Adds custom attributes to tag (https://github.com/webpack-contrib/style-loader#attributes).',
- type: 'object',
- },
- insert: {
- description:
- 'Inserts `