diff --git a/.github/labeler.json b/.github/labeler.json index 1499d280fb296..4da78e9152291 100644 --- a/.github/labeler.json +++ b/.github/labeler.json @@ -7,7 +7,10 @@ "packages/next/**", "packages/react-dev-overlay/**", "packages/react-refresh-utils/**", - "packages/next-codemod/**" + "packages/next-codemod/**", + "packages/eslint-plugin-next/**", + "packages/eslint-config-next/**", + "packages/next-env/**" ], "created-by: Chrome Aurora": [ { "type": "user", "pattern": "spanicker" }, diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 3a00a6f0f0ee9..8a216544cd23e 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -291,7 +291,7 @@ jobs: runs-on: ubuntu-latest needs: [build, build-native] env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }} steps: # https://github.com/actions/virtual-environments/issues/1187 - name: tune linux network diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index b093a8d1598b9..b7d6cbba79008 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -262,7 +262,7 @@ Other properties on the `` component will be passed to the underlying ## Styling -`next/image` wraps the `img` element with other `div` elements to maintain the aspect ratio of the image and prevent [Cumulative Layout Shift](https://vercel.com/blog/core-web-vitals#cumulative-layout-shift). +`next/image` wraps the `img` element with a single `div` element to maintain the aspect ratio of the image and prevent [Cumulative Layout Shift](https://vercel.com/blog/core-web-vitals#cumulative-layout-shift). To add styles to the underlying `img` element, pass the `className` prop to the `` component. Then, use Next.js' [built-in CSS support](/docs/basic-features/built-in-css-support.md) to add rules to that class. diff --git a/docs/basic-features/data-fetching.md b/docs/basic-features/data-fetching.md index b63575f7f9eae..2eb8dd943e82e 100644 --- a/docs/basic-features/data-fetching.md +++ b/docs/basic-features/data-fetching.md @@ -672,7 +672,7 @@ The `context` parameter is an object containing the following keys: `getServerSideProps` should return an object with: -- `props` - An **optional** object with the props that will be received by the page component. It should be a [serializable object](https://en.wikipedia.org/wiki/Serialization) +- `props` - An **optional** object with the props that will be received by the page component. It should be a [serializable object](https://en.wikipedia.org/wiki/Serialization) or a Promise that resolves to a serializable object. - `notFound` - An **optional** boolean value to allow the page to return a 404 status and page. Below is an example of how it works: ```js diff --git a/docs/basic-features/environment-variables.md b/docs/basic-features/environment-variables.md index 7e2ff8e70c3bf..93f65cfdc3a39 100644 --- a/docs/basic-features/environment-variables.md +++ b/docs/basic-features/environment-variables.md @@ -78,7 +78,7 @@ export async function getStaticProps() { ## Exposing Environment Variables to the Browser -By default all environment variables loaded through `.env.local` are only available in the Node.js environment, meaning they won't be exposed to the browser. +By default environment variables are only available in the Node.js environment, meaning they won't be exposed to the browser. In order to expose a variable to the browser you have to prefix the variable with `NEXT_PUBLIC_`. For example: diff --git a/docs/basic-features/image-optimization.md b/docs/basic-features/image-optimization.md index 7c8e28074cd7d..39c09b84f93ec 100644 --- a/docs/basic-features/image-optimization.md +++ b/docs/basic-features/image-optimization.md @@ -52,9 +52,9 @@ export default Home ## Image Imports -You can `import` images that live in your project. (Note that `require` is not supported—only `import`.) +You can statically `import` images that live in your project. Dynamic `await import()` or `require()` are _not_ supported. -With direct `import`s, `width`, `height`, and `blurDataURL` will be automatically provided to the image component. Alt text is still needed separately. +With static `import`s, you only need to provide the `src` prop. The `width`, `height`, and `blurDataURL` props will automatically be populated. Alt text is still needed separately. ```js import Image from 'next/image' @@ -70,8 +70,7 @@ function Home() { // width={500} automatically provided // height={500} automatically provided // blurDataURL="data:..." automatically provided - // Optionally allows to add a blurred version of the image while loading - // placeholder="blur" + // placeholder="blur" // Optional blur-up while loading />

Welcome to my homepage!

@@ -79,7 +78,7 @@ function Home() { } ``` -For dynamic or remote images, you'll have to provide [`width`](/docs/api-reference/next/image#width), [`height`](/docs/api-reference/next/image#height) and [`blurDataURL`](/docs/api-reference/next/image#blurdataurl) manually. +For remote images, you'll need to provide the [`width`](/docs/api-reference/next/image.md#width), [`height`](/docs/api-reference/next/image.md#height) and [`blurDataURL`](/docs/api-reference/next/image.md#blurdataurl) props manually. ## Properties diff --git a/errors/manifest.json b/errors/manifest.json index f1580c3b658fd..693fb8f055149 100644 --- a/errors/manifest.json +++ b/errors/manifest.json @@ -269,6 +269,10 @@ "title": "next-image-unconfigured-host", "path": "/errors/next-image-unconfigured-host.md" }, + { + "title": "next-script-for-ga", + "path": "/errors/next-script-for-ga.md" + }, { "title": "next-start-serverless", "path": "/errors/next-start-serverless.md" diff --git a/examples/cms-sanity/lib/config.js b/examples/cms-sanity/lib/config.js index 018ad4f2aeeb3..b4b0101e5494c 100644 --- a/examples/cms-sanity/lib/config.js +++ b/examples/cms-sanity/lib/config.js @@ -6,4 +6,6 @@ export const sanityConfig = { // useCdn == true gives fast, cheap responses using a globally distributed cache. // Set this to false if your application require the freshest possible // data always (potentially slightly slower and a bit more expensive). + apiVersion: '2021-03-25', + // see https://www.sanity.io/docs/api-versioning for how versioning works } diff --git a/lerna.json b/lerna.json index 9ba3016673591..79a6a9e879c91 100644 --- a/lerna.json +++ b/lerna.json @@ -17,5 +17,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "11.1.1-canary.17" + "version": "11.1.3-canary.8" } diff --git a/package.json b/package.json index abbb81aef8449..7238399818f83 100644 --- a/package.json +++ b/package.json @@ -34,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" }, diff --git a/packages/create-next-app/create-app.ts b/packages/create-next-app/create-app.ts index c06ea76227a54..8d2745db86e03 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 5a0e55fbcb894..f88c420e5f57d 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.17", + "version": "11.1.3-canary.8", "keywords": [ "react", "next", diff --git a/packages/create-next-app/templates/typescript/pages/index.tsx b/packages/create-next-app/templates/typescript/pages/index.tsx index 1ef0af12355d8..72a4a59053d70 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 e89c028289755..9e742ff08a4e9 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.17", + "version": "11.1.3-canary.8", "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.17", + "@next/eslint-plugin-next": "11.1.3-canary.8", "@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/rules/next-script-for-ga.js b/packages/eslint-plugin-next/lib/rules/next-script-for-ga.js index 09385460dcc8f..8fe6f19189ade 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 55eea712a4680..6c40f87795fee 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 @@ -1,3 +1,5 @@ +const path = require('path') + module.exports = { meta: { docs: { @@ -16,7 +18,11 @@ module.exports = { const paths = context.getFilename().split('pages') const page = paths[paths.length - 1] - if (!page || page.startsWith('/_document')) { + if ( + !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 838b40c4d3847..6d5a448cf0032 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 51466e4e7dffb..0aebdde57f3e3 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.17", + "version": "11.1.3-canary.8", "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 a889c2f92bc11..9ec932a9f102b 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.17", + "version": "11.1.3-canary.8", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 8e04ad891e343..a1cc18d295540 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.17", + "version": "11.1.3-canary.8", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 4fd41e02f03da..9eb6feb90425c 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.17", + "version": "11.1.3-canary.8", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 04df673cd2a84..2b169213bfc5b 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.17", + "version": "11.1.3-canary.8", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 06484a4ae4b5c..1d7fdbc10393e 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.17", + "version": "11.1.3-canary.8", "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 0c47139a28490..7cff767937f06 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.17", + "version": "11.1.3-canary.8", "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 a9ed9d1f44977..8f5eb654ff8aa 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.17", + "version": "11.1.3-canary.8", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next/build/cssnano-simple.js b/packages/next/build/cssnano-simple.js new file mode 100644 index 0000000000000..29c9814992cbc --- /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/webpack-config.ts b/packages/next/build/webpack-config.ts index 766d0776c33f9..b98cfcc20297f 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -173,20 +173,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'], @@ -194,7 +180,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, @@ -211,7 +197,7 @@ const NODE_RESOLVE_OPTIONS = { const NODE_ESM_RESOLVE_OPTIONS = { ...NODE_RESOLVE_OPTIONS, dependencyType: 'esm', - conditionNames: ['node', 'import', 'module'], + conditionNames: ['node', 'import'], fullySpecified: true, } @@ -754,7 +740,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 @@ -772,7 +758,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) @@ -1561,7 +1547,9 @@ export default async function getBaseWebpackConfig( webpackConfig = await buildConfiguration(webpackConfig, { rootDirectory: dir, - customAppFile: new RegExp(path.join(pagesDir, `_app`)), + 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 56dae4c11841d..0b89fd05e743b 100644 --- a/packages/next/build/webpack/config/blocks/css/index.ts +++ b/packages/next/build/webpack/config/blocks/css/index.ts @@ -11,6 +11,69 @@ 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\])?$/ diff --git a/packages/next/build/webpack/config/blocks/css/loaders/global.ts b/packages/next/build/webpack/config/blocks/css/loaders/global.ts index 984345f5b86f5..c6692d7830ab2 100644 --- a/packages/next/build/webpack/config/blocks/css/loaders/global.ts +++ b/packages/next/build/webpack/config/blocks/css/loaders/global.ts @@ -3,10 +3,11 @@ import { webpack } from 'next/dist/compiled/webpack/webpack' import { ConfigurationContext } from '../../../utils' import { getClientStyleLoader } from './client' import { cssFileResolve } from './file-resolve' +import postcss from 'postcss' export function getGlobalCssLoader( ctx: ConfigurationContext, - postCssPlugins: readonly AcceptedPlugin[], + postCssPlugins: AcceptedPlugin[], preProcessors: readonly webpack.RuleSetUseItem[] = [] ): webpack.RuleSetUseItem[] { const loaders: webpack.RuleSetUseItem[] = [] @@ -24,7 +25,7 @@ export function getGlobalCssLoader( // Resolve CSS `@import`s and `url()`s loaders.push({ - loader: require.resolve('next/dist/compiled/css-loader'), + loader: require.resolve('../../../../loaders/css-loader/src'), options: { importLoaders: 1 + preProcessors.length, // Next.js controls CSS Modules eligibility: @@ -37,9 +38,9 @@ export function getGlobalCssLoader( // Compile CSS loaders.push({ - loader: require.resolve('next/dist/compiled/postcss-loader'), + loader: require.resolve('../../../../loaders/postcss-loader/src'), options: { - postcssOptions: { plugins: postCssPlugins, config: false }, + postcss: postcss(postCssPlugins), }, }) diff --git a/packages/next/build/webpack/config/blocks/css/loaders/modules.ts b/packages/next/build/webpack/config/blocks/css/loaders/modules.ts index e9b3034408545..eca2e5547d18e 100644 --- a/packages/next/build/webpack/config/blocks/css/loaders/modules.ts +++ b/packages/next/build/webpack/config/blocks/css/loaders/modules.ts @@ -4,10 +4,11 @@ import { ConfigurationContext } from '../../../utils' import { getClientStyleLoader } from './client' import { cssFileResolve } from './file-resolve' import { getCssModuleLocalIdent } from './getCssModuleLocalIdent' +import postcss from 'postcss' export function getCssModuleLoader( ctx: ConfigurationContext, - postCssPlugins: readonly AcceptedPlugin[], + postCssPlugins: AcceptedPlugin[], preProcessors: readonly webpack.RuleSetUseItem[] = [] ): webpack.RuleSetUseItem[] { const loaders: webpack.RuleSetUseItem[] = [] @@ -25,7 +26,7 @@ export function getCssModuleLoader( // Resolve CSS `@import`s and `url()`s loaders.push({ - loader: require.resolve('next/dist/compiled/css-loader'), + loader: require.resolve('../../../../loaders/css-loader/src'), options: { importLoaders: 1 + preProcessors.length, // Use CJS mode for backwards compatibility: @@ -53,9 +54,9 @@ export function getCssModuleLoader( // Compile CSS loaders.push({ - loader: require.resolve('next/dist/compiled/postcss-loader'), + loader: require.resolve('../../../../loaders/postcss-loader/src'), options: { - postcssOptions: { plugins: postCssPlugins, config: false }, + postcss: postcss(postCssPlugins), }, }) diff --git a/packages/next/build/webpack/config/blocks/css/overrideCssConfiguration.ts b/packages/next/build/webpack/config/blocks/css/overrideCssConfiguration.ts index eaa726cac14a2..434487c3e442a 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/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 0000000000000..76390c9f3c8f4 --- /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 0000000000000..d9fce41918894 --- /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 0000000000000..2e3a168f5db13 --- /dev/null +++ b/packages/next/build/webpack/loaders/css-loader/src/index.js @@ -0,0 +1,193 @@ +/* + 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() + + let options + + try { + options = normalizeOptions(rawOptions, this) + } catch (error) { + callback(error) + + return + } + + 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 + } + } + + 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) + } + + callback( + error.name === 'CssSyntaxError' ? new CssSyntaxError(error) : error + ) + + return + } + + 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) + + callback(null, `${importCode}${moduleCode}${exportCode}`) +} 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 0000000000000..799629d4e93b1 --- /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 0000000000000..d4b7f852022fb --- /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 0000000000000..7962805ef1d4f --- /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 0000000000000..633fc17a81fa5 --- /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 0000000000000..6ca402a53a8bd --- /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 0000000000000..11cfbd0f4d6e2 --- /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 0000000000000..0c34801402580 --- /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/page-handler.ts b/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts index 28c2db77b797a..daffeeb9f0f08 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 { @@ -335,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 = { @@ -377,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 } } @@ -403,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) @@ -465,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 } } @@ -475,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 47f56028b9c3f..b68ca3d9c39fa 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 `