diff --git a/e2e-tests/production-runtime/cypress/integration/assets.js b/e2e-tests/production-runtime/cypress/integration/assets.js
new file mode 100644
index 0000000000000..a25e13d253fd7
--- /dev/null
+++ b/e2e-tests/production-runtime/cypress/integration/assets.js
@@ -0,0 +1,36 @@
+describe(`webpack assets`, () => {
+ beforeEach(() => {
+ cy.intercept("/gatsby-astronaut.png").as("static-folder-image")
+ // Should load two files: normal and italic
+ cy.intercept("/static/merriweather-latin-300**.woff2").as("font")
+ cy.intercept("/static/gatsby-astronaut-**.png").as("img-import")
+ cy.visit(`/assets/`).waitForRouteChange()
+ })
+
+ // Service worker is handling requests so this one is cached by previous runs
+ if (!Cypress.env(`TEST_PLUGIN_OFFLINE`)) {
+ it(`should only create one font file (no duplicates with different hashes)`, () => {
+ // Check that there is no duplicate files (should have italic as second request, not another normal font)
+ cy.wait("@font").should(req => {
+ expect(req.response.url).to.match(/merriweather-latin-300-/i)
+ })
+ cy.wait("@font").should(req => {
+ expect(req.response.url).to.match(/merriweather-latin-300italic-/i)
+ })
+ })
+ it(`should load image import`, () => {
+ cy.wait("@img-import").should(req => {
+ expect(req.response.statusCode).to.be.gte(200).and.lt(400)
+ })
+ })
+ it(`should load file import`, () => {
+ cy.getTestElement('assets-pdf-import').should('have.attr', 'href').and('match', /\/static\/pdf-example-.*\.pdf/i)
+ })
+ }
+
+ it(`should load static folder asset`, () => {
+ cy.wait("@static-folder-image").should(req => {
+ expect(req.response.statusCode).to.be.gte(200).and.lt(400)
+ })
+ })
+})
diff --git a/e2e-tests/production-runtime/package.json b/e2e-tests/production-runtime/package.json
index 0f24c4e07d504..c29748947caf0 100644
--- a/e2e-tests/production-runtime/package.json
+++ b/e2e-tests/production-runtime/package.json
@@ -6,18 +6,18 @@
"dependencies": {
"babel-plugin-search-and-replace": "^1.1.0",
"cypress": "^6.5.0",
- "gatsby": "^3.0.0-next.6",
- "gatsby-cypress": "^1.3.0",
- "gatsby-plugin-image": "^1.0.0-next.5",
- "gatsby-plugin-less": "^5.1.0-next.2",
- "gatsby-plugin-manifest": "^3.0.0-next.0",
- "gatsby-plugin-offline": "^4.0.0-next.1",
- "gatsby-plugin-react-helmet": "^4.0.0-next.0",
- "gatsby-plugin-sass": "^4.1.0-next.2",
- "gatsby-plugin-sharp": "^3.0.0-next.5",
- "gatsby-plugin-stylus": "^3.1.0-next.2",
+ "gatsby": "^4.1.6",
+ "gatsby-cypress": "^2.1.0",
+ "gatsby-plugin-image": "^2.1.3",
+ "gatsby-plugin-less": "^6.1.0",
+ "gatsby-plugin-manifest": "^4.1.4",
+ "gatsby-plugin-offline": "^5.1.4",
+ "gatsby-plugin-react-helmet": "^5.1.0",
+ "gatsby-plugin-sass": "^5.1.1",
+ "gatsby-plugin-sharp": "^4.1.4",
+ "gatsby-plugin-stylus": "^4.1.0",
"gatsby-seo": "^0.1.0",
- "gatsby-source-filesystem": "^3.3.0",
+ "gatsby-source-filesystem": "^4.1.3",
"glob": "^7.1.3",
"react": "^16.9.0",
"react-dom": "^16.9.0",
@@ -32,10 +32,11 @@
"scripts": {
"build": "cross-env CYPRESS_SUPPORT=y gatsby build",
"build:offline": "cross-env TEST_PLUGIN_OFFLINE=y CYPRESS_SUPPORT=y gatsby build",
- "develop": "gatsby develop",
+ "develop": "cross-env CYPRESS_SUPPORT=y gatsby develop",
"format": "prettier --write '**/*.js' --ignore-path .gitignore",
"serve": "gatsby serve",
"start": "npm run develop",
+ "clean": "gatsby clean",
"test": "npm run build && npm run start-server-and-test && npm run test-env-vars",
"test:offline": "npm run build:offline && yarn start-server-and-test:offline && npm run test-env-vars",
"test-env-vars": " node __tests__/env-vars.js",
@@ -51,7 +52,7 @@
"devDependencies": {
"cross-env": "^5.2.0",
"fs-extra": "^7.0.1",
- "gatsby-core-utils": "^2.12.0",
+ "gatsby-core-utils": "^3.1.3",
"is-ci": "^2.0.0",
"prettier": "2.0.4",
"start-server-and-test": "^1.7.1"
@@ -59,8 +60,5 @@
"repository": {
"type": "git",
"url": "https://github.com/gatsbyjs/gatsby-starter-default"
- },
- "resolutions": {
- "graphql-config": "3.3.0"
}
}
diff --git a/e2e-tests/production-runtime/src/files/pdf-example.pdf b/e2e-tests/production-runtime/src/files/pdf-example.pdf
new file mode 100644
index 0000000000000..33114e14c25e5
Binary files /dev/null and b/e2e-tests/production-runtime/src/files/pdf-example.pdf differ
diff --git a/e2e-tests/production-runtime/src/images/gatsby-astronaut.png b/e2e-tests/production-runtime/src/images/gatsby-astronaut.png
new file mode 100644
index 0000000000000..da58ece0a8c5b
Binary files /dev/null and b/e2e-tests/production-runtime/src/images/gatsby-astronaut.png differ
diff --git a/e2e-tests/production-runtime/src/index.css b/e2e-tests/production-runtime/src/index.css
index 4e165ba8657fd..bc6b26fb45c76 100644
--- a/e2e-tests/production-runtime/src/index.css
+++ b/e2e-tests/production-runtime/src/index.css
@@ -15,6 +15,19 @@
font-size: 18px;
}
+.merriweather-300 {
+ font-family: "Merriweather";
+ font-weight: 300;
+ font-size: 18px;
+}
+
+.merriweather-300-italic {
+ font-family: "Merriweather";
+ font-weight: 300;
+ font-size: 18px;
+ font-style: italic;
+}
+
.dog-background-flip {
background: url("//localhost:9000/dog-thumbnail-flip.jpg");
width: 640px;
diff --git a/e2e-tests/production-runtime/src/pages/assets.js b/e2e-tests/production-runtime/src/pages/assets.js
new file mode 100644
index 0000000000000..67f85ee166738
--- /dev/null
+++ b/e2e-tests/production-runtime/src/pages/assets.js
@@ -0,0 +1,16 @@
+import * as React from "react"
+import Layout from "../components/layout"
+import astronaut from "../images/gatsby-astronaut.png"
+import pdf from "../files/pdf-example.pdf"
+
+const Assets = () => (
+
+ Font
+ Font Italic
+
+
+ Download PDF
+
+)
+
+export default Assets
diff --git a/e2e-tests/production-runtime/static/gatsby-astronaut.png b/e2e-tests/production-runtime/static/gatsby-astronaut.png
new file mode 100644
index 0000000000000..da58ece0a8c5b
Binary files /dev/null and b/e2e-tests/production-runtime/static/gatsby-astronaut.png differ
diff --git a/integration-tests/artifacts/gatsby-browser.js b/integration-tests/artifacts/gatsby-browser.js
index af169d50fce67..1a0a374d24012 100644
--- a/integration-tests/artifacts/gatsby-browser.js
+++ b/integration-tests/artifacts/gatsby-browser.js
@@ -2,6 +2,9 @@ const React = require(`react`)
const { useMoreInfoQuery } = require("./src/hooks/use-more-info-query")
const Github = require(`./src/components/github`).default
+// global css import (make sure warm rebuild doesn't invalidate every file when css is imported)
+require("./imported.css")
+
exports.wrapRootElement = ({ element }) => {
return (
<>
diff --git a/integration-tests/artifacts/imported.css b/integration-tests/artifacts/imported.css
new file mode 100644
index 0000000000000..50c8ae6828d6e
--- /dev/null
+++ b/integration-tests/artifacts/imported.css
@@ -0,0 +1,3 @@
+.foo {
+ background: blue;
+}
diff --git a/packages/gatsby-plugin-less/src/gatsby-node.js b/packages/gatsby-plugin-less/src/gatsby-node.js
index 0845a60649f21..767e367fcf651 100644
--- a/packages/gatsby-plugin-less/src/gatsby-node.js
+++ b/packages/gatsby-plugin-less/src/gatsby-node.js
@@ -34,6 +34,7 @@ exports.onCreateWebpackConfig = (
}
const lessRuleModules = {
test: /\.module\.less$/,
+ // TODO(v5): Remove obsolete modules option from miniCssExtract
use: [
!isSSR &&
loaders.miniCssExtract({
diff --git a/packages/gatsby-plugin-netlify-cms/package.json b/packages/gatsby-plugin-netlify-cms/package.json
index 9d7750f6a4890..1c848bcd09d85 100644
--- a/packages/gatsby-plugin-netlify-cms/package.json
+++ b/packages/gatsby-plugin-netlify-cms/package.json
@@ -14,7 +14,7 @@
"html-webpack-skip-assets-plugin": "^1.0.3",
"html-webpack-tags-plugin": "^3.0.2",
"lodash": "^4.17.21",
- "mini-css-extract-plugin": "1.6.2",
+ "mini-css-extract-plugin": "^2.4.4",
"netlify-identity-widget": "^1.9.2"
},
"devDependencies": {
diff --git a/packages/gatsby-plugin-postcss/src/gatsby-node.js b/packages/gatsby-plugin-postcss/src/gatsby-node.js
index 98ef0bfbb7a58..366a6c541482a 100644
--- a/packages/gatsby-plugin-postcss/src/gatsby-node.js
+++ b/packages/gatsby-plugin-postcss/src/gatsby-node.js
@@ -50,6 +50,7 @@ exports.onCreateWebpackConfig = (
}
const postcssRuleModules = {
test: MODULE_CSS_PATTERN,
+ // TODO(v5): Remove obsolete modules option from miniCssExtract
use: [
!isSSR &&
loaders.miniCssExtract({
diff --git a/packages/gatsby-plugin-sass/src/gatsby-node.js b/packages/gatsby-plugin-sass/src/gatsby-node.js
index dc8bd2f05be7b..0512a17869a53 100644
--- a/packages/gatsby-plugin-sass/src/gatsby-node.js
+++ b/packages/gatsby-plugin-sass/src/gatsby-node.js
@@ -43,6 +43,7 @@ exports.onCreateWebpackConfig = (
const sassRuleModules = {
test: sassRuleModulesTest || /\.module\.s(a|c)ss$/,
+ // TODO(v5): Remove obsolete modules option from miniCssExtract
use: [
!isSSR &&
loaders.miniCssExtract({
diff --git a/packages/gatsby-plugin-stylus/src/gatsby-node.js b/packages/gatsby-plugin-stylus/src/gatsby-node.js
index 2a8e9425c17f0..9036c39d50886 100644
--- a/packages/gatsby-plugin-stylus/src/gatsby-node.js
+++ b/packages/gatsby-plugin-stylus/src/gatsby-node.js
@@ -54,6 +54,7 @@ exports.onCreateWebpackConfig = (
const stylusRuleModules = {
test: /\.module\.styl$/,
+ // TODO(v5): Remove obsolete modules option from miniCssExtract
use: [
!isSSR &&
loaders.miniCssExtract({
diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json
index 71a80d737199e..b79b326d021d6 100644
--- a/packages/gatsby/package.json
+++ b/packages/gatsby/package.json
@@ -48,7 +48,7 @@
"cookie": "^0.4.1",
"core-js": "^3.17.2",
"cors": "^2.8.5",
- "css-loader": "^5.2.7",
+ "css-loader": "^6.5.1",
"css-minimizer-webpack-plugin": "^2.0.0",
"css.escape": "^1.5.1",
"date-fns": "^2.25.0",
@@ -108,7 +108,7 @@
"memoizee": "^0.4.15",
"micromatch": "^4.0.4",
"mime": "^2.5.2",
- "mini-css-extract-plugin": "1.6.2",
+ "mini-css-extract-plugin": "^2.4.4",
"mitt": "^1.2.0",
"moment": "^2.29.1",
"multer": "^1.4.3",
diff --git a/packages/gatsby/src/utils/webpack-utils.ts b/packages/gatsby/src/utils/webpack-utils.ts
index 92a75778a7b3c..6d1449e4c4add 100644
--- a/packages/gatsby/src/utils/webpack-utils.ts
+++ b/packages/gatsby/src/utils/webpack-utils.ts
@@ -1,5 +1,5 @@
import * as path from "path"
-import { RuleSetRule, WebpackPluginInstance } from "webpack"
+import { RuleSetRule, WebpackPluginInstance, Configuration } from "webpack"
import { GraphQLSchema } from "graphql"
import { Plugin as PostCSSPlugin } from "postcss"
import autoprefixer from "autoprefixer"
@@ -65,12 +65,16 @@ type CSSModulesOptions =
exportOnlyLocals?: boolean
}
-type MiniCSSExtractLoaderModuleOptions =
- | undefined
- | boolean
- | {
- namedExport?: boolean
- }
+interface IMiniCSSExtractLoaderModuleOptions {
+ filename?: Required["output"]["filename"] | undefined
+ chunkFilename?: Required["output"]["chunkFilename"] | undefined
+ experimentalUseImportModule?: boolean | undefined
+ ignoreOrder?: boolean | undefined
+ insert?: string | ((linkTag: any) => void) | undefined
+ attributes?: Record | undefined
+ linkType?: string | false | "text/css" | undefined
+ runtime?: boolean | undefined
+}
/**
* Utils that produce webpack `loader` objects
*/
@@ -234,27 +238,13 @@ export const createWebpackUtils = (
}
},
- miniCssExtract: (
- options: {
- modules?: MiniCSSExtractLoaderModuleOptions
- } = {}
- ) => {
- let moduleOptions: MiniCSSExtractLoaderModuleOptions = undefined
-
+ miniCssExtract: (options: IMiniCSSExtractLoaderModuleOptions = {}) => {
+ // @ts-ignore - legacy modules
const { modules, ...restOptions } = options
- if (typeof modules === `boolean` && options.modules) {
- moduleOptions = {
- namedExport: true,
- }
- } else {
- moduleOptions = modules
- }
-
return {
loader: MiniCssExtractPlugin.loader,
options: {
- modules: moduleOptions,
...restOptions,
},
}
@@ -283,13 +273,15 @@ export const createWebpackUtils = (
loader: require.resolve(`css-loader`),
options: {
// Absolute urls (https or //) are not send to this function. Only resolvable paths absolute or relative ones.
- url: function (url: string): boolean {
- // When an url starts with /
- if (url.startsWith(`/`)) {
- return false
- }
-
- return true
+ url: {
+ filter: function (url: string): boolean {
+ // When an url starts with /
+ if (url.startsWith(`/`)) {
+ return false
+ }
+
+ return true
+ },
},
sourceMap: !PRODUCTION,
modules: modulesOptions,
@@ -350,6 +342,7 @@ export const createWebpackUtils = (
}
},
+ // TODO(v5): Consider removing this (as not used anymore internally)
url: (options = {}) => {
return {
loader: require.resolve(`url-loader`),
@@ -545,8 +538,11 @@ export const createWebpackUtils = (
*/
rules.fonts = (): RuleSetRule => {
return {
- use: [loaders.url()],
test: /\.(eot|otf|ttf|woff(2)?)(\?.*)?$/,
+ type: `asset/resource`,
+ generator: {
+ filename: `${assetRelativeRoot}[name]-[hash][ext]`,
+ },
}
}
@@ -556,8 +552,11 @@ export const createWebpackUtils = (
*/
rules.images = (): RuleSetRule => {
return {
- use: [loaders.url()],
test: /\.(ico|svg|jpg|jpeg|png|gif|webp|avif)(\?.*)?$/,
+ type: `asset/resource`,
+ generator: {
+ filename: `${assetRelativeRoot}[name]-[hash][ext]`,
+ },
}
}
@@ -567,8 +566,11 @@ export const createWebpackUtils = (
*/
rules.media = (): RuleSetRule => {
return {
- use: [loaders.url()],
test: /\.(mp4|webm|ogv|wav|mp3|m4a|aac|oga|flac)$/,
+ type: `asset/resource`,
+ generator: {
+ filename: `${assetRelativeRoot}[name]-[hash][ext]`,
+ },
}
}
diff --git a/yarn.lock b/yarn.lock
index 7e96d690bda91..3e7c0337fe880 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8260,26 +8260,24 @@ css-list-helpers@^2.0.0:
resolved "https://registry.yarnpkg.com/css-list-helpers/-/css-list-helpers-2.0.0.tgz#7cb3d6f9ec9e5087ae49d834cead282806e8818f"
integrity sha512-9Bj8tZ0jWbAM3u/U6m/boAzAwLPwtjzFvwivr2piSvyVa3K3rChJzQy4RIHkNkKiZCHrEMWDJWtTR8UyVhdDnQ==
-css-loader@^5.2.7:
- version "5.2.7"
- resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae"
- integrity sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==
+css-loader@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.2.0.tgz#9663d9443841de957a3cb9bcea2eda65b3377071"
+ integrity sha512-/rvHfYRjIpymZblf49w8jYcRo2y9gj6rV8UroHGmBxKrIyGLokpycyKzp9OkitvqT29ZSpzJ0Ic7SpnJX3sC8g==
dependencies:
icss-utils "^5.1.0"
- loader-utils "^2.0.0"
postcss "^8.2.15"
postcss-modules-extract-imports "^3.0.0"
postcss-modules-local-by-default "^4.0.0"
postcss-modules-scope "^3.0.0"
postcss-modules-values "^4.0.0"
postcss-value-parser "^4.1.0"
- schema-utils "^3.0.0"
semver "^7.3.5"
-css-loader@^6.2.0:
- version "6.2.0"
- resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.2.0.tgz#9663d9443841de957a3cb9bcea2eda65b3377071"
- integrity sha512-/rvHfYRjIpymZblf49w8jYcRo2y9gj6rV8UroHGmBxKrIyGLokpycyKzp9OkitvqT29ZSpzJ0Ic7SpnJX3sC8g==
+css-loader@^6.5.1:
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.5.1.tgz#0c43d4fbe0d97f699c91e9818cb585759091d1b1"
+ integrity sha512-gEy2w9AnJNnD9Kuo4XAP9VflW/ujKoS9c/syO+uWMlm5igc7LysKzPXaDoR2vroROkSwsTS2tGr1yGGEbZOYZQ==
dependencies:
icss-utils "^5.1.0"
postcss "^8.2.15"
@@ -17123,14 +17121,12 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
-mini-css-extract-plugin@1.6.2:
- version "1.6.2"
- resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz#83172b4fd812f8fc4a09d6f6d16f924f53990ca8"
- integrity sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q==
+mini-css-extract-plugin@^2.4.4:
+ version "2.4.4"
+ resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.4.4.tgz#c7e5d2d931dcf100ae50ae949ba757c506b54b0f"
+ integrity sha512-UJ+aNuFQaQaECu7AamlWOBLj2cJ6XSGU4zNiqXeZ7lZLe5VD0DoSPWFbWArXueo+6FZVbgHzpX9lUIaBIDLuYg==
dependencies:
- loader-utils "^2.0.0"
- schema-utils "^3.0.0"
- webpack-sources "^1.1.0"
+ schema-utils "^3.1.0"
mini-svg-data-uri@^1.4.3:
version "1.4.3"
@@ -22984,11 +22980,6 @@ source-list-map@^1.1.1:
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.2.tgz#9889019d1024cce55cdc069498337ef6186a11a1"
integrity sha1-mIkBnRAkzOVc3AaUmDN+9hhqEaE=
-source-list-map@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
- integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
-
source-map-js@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"
@@ -25837,14 +25828,6 @@ webpack-sources@^0.2.0:
source-list-map "^1.1.1"
source-map "~0.5.3"
-webpack-sources@^1.1.0:
- version "1.4.3"
- resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
- integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
- dependencies:
- source-list-map "^2.0.0"
- source-map "~0.6.1"
-
webpack-sources@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.0.tgz#b16973bcf844ebcdb3afde32eda1c04d0b90f89d"