Skip to content

Commit

Permalink
Support filebased metadata icon and apple touch icons (#45612)
Browse files Browse the repository at this point in the history
This PR add supports for a shortcut for adding `icons.icon` or
`icons.apple` metadata for page or layout with static files.

Closes NEXT-263
Closes NEXT-260

If you specific icon.png or apple-touch-icon.png, they will be
automatically picked by and added into metadata as `icon` and
`apple-touch-icon` fields, and replace the `icons` field specified in
page/layout level metadata exports.

File matching rule:
```
icon -> /^icon\d?\.(ico|jpg|png|svg)$/
apple-touch-icon -> /^apple-touch-icon\d?\.(ico|jpg|png|svg)$/
```

## Feature

- [x] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [x]
[e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have a helpful link attached, see
[`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md)

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
huozhi and kodiakhq[bot] committed Feb 7, 2023
1 parent 588a9c1 commit 25dfdbb
Show file tree
Hide file tree
Showing 19 changed files with 600 additions and 238 deletions.
6 changes: 6 additions & 0 deletions packages/next/src/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { AppBuildManifestPlugin } from './webpack/plugins/app-build-manifest-plu
import { SubresourceIntegrityPlugin } from './webpack/plugins/subresource-integrity-plugin'
import { FontLoaderManifestPlugin } from './webpack/plugins/font-loader-manifest-plugin'
import { getSupportedBrowsers } from './utils'
import { METADATA_IMAGE_RESOURCE_QUERY } from './webpack/loaders/app-dir/metadata'

const EXTERNAL_PACKAGES = require('../lib/server-external-packages.json')

Expand Down Expand Up @@ -1636,6 +1637,7 @@ export default async function getBaseWebpackConfig(
'next-swc-loader',
'next-client-pages-loader',
'next-image-loader',
'next-metadata-image-loader',
'next-serverless-loader',
'next-style-loader',
'next-flight-loader',
Expand Down Expand Up @@ -1875,6 +1877,8 @@ export default async function getBaseWebpackConfig(
loader: 'next-image-loader',
issuer: { not: regexLikeCss },
dependency: { not: ['url'] },
resourceQuery: (queryString: string) =>
queryString !== METADATA_IMAGE_RESOURCE_QUERY,
options: {
isServer: isNodeServer || isEdgeServer,
isDev: dev,
Expand Down Expand Up @@ -2515,6 +2519,8 @@ export default async function getBaseWebpackConfig(
// webpack config such as `@svgr/webpack` plugin or
// the `babel-plugin-inline-react-svg` plugin.
nextImageRule.test = /\.(png|jpg|jpeg|gif|webp|avif|ico|bmp)$/i
nextImageRule.resourceQuery = (queryString: string) =>
queryString !== METADATA_IMAGE_RESOURCE_QUERY
}
}

Expand Down
128 changes: 128 additions & 0 deletions packages/next/src/build/webpack/loaders/app-dir/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import path from 'path'
import { stringify } from 'querystring'

const METADATA_TYPE = 'metadata'

export const METADATA_IMAGE_RESOURCE_QUERY = '?__next_metadata'

const staticAssetIconsImage = {
icon: {
filename: 'icon',
extensions: ['ico', 'jpg', 'png', 'svg'],
},
apple: {
filename: 'apple-touch-icon',
extensions: ['jpg', 'png', 'svg'],
},
}

// Produce all compositions with filename (icon, apple-touch-icon, etc.) with extensions (png, jpg, etc.)
async function enumMetadataFiles(
dir: string,
filename: string,
extensions: string[],
{
resolvePath,
addDependency,
addMissingDependency,
}: {
resolvePath: (pathname: string) => Promise<string>
addDependency: (dep: string) => any
addMissingDependency: (dep: string) => any
}
) {
const collectedFiles: string[] = []
// Possible filename without extension could: icon, icon0, ..., icon9
const possibleFileNames = [filename].concat(
Array(10)
.fill(0)
.map((_, index) => filename + index)
)
for (const name of possibleFileNames) {
for (const ext of extensions) {
const pathname = path.join(dir, `${name}.${ext}`)
try {
const resolved = await resolvePath(pathname)
addDependency(resolved)

collectedFiles.push(resolved)
} catch (err: any) {
if (!err.message.includes("Can't resolve")) {
throw err
}
addMissingDependency(pathname)
}
}
}

return collectedFiles
}

export async function discoverStaticMetadataFiles(
resolvedDir: string,
{
isDev,
resolvePath,
addDependency,
addMissingDependency,
}: {
isDev: boolean
addDependency: (dep: string) => any
addMissingDependency: (dep: string) => any
resolvePath: (pathname: string) => Promise<string>
}
) {
let hasStaticMetadataFiles = false
const iconsMetadata: {
icon: string[]
apple: string[]
} = {
icon: [],
apple: [],
}

const opts = {
resolvePath,
addDependency,
addMissingDependency,
}

async function collectIconModuleIfExists(type: 'icon' | 'apple') {
const resolvedMetadataFiles = await enumMetadataFiles(
resolvedDir,
staticAssetIconsImage[type].filename,
staticAssetIconsImage[type].extensions,
opts
)
resolvedMetadataFiles
.sort((a, b) => a.localeCompare(b))
.forEach((filepath) => {
const iconModule = `() => import(/* webpackMode: "eager" */ ${JSON.stringify(
`next-metadata-image-loader?${stringify({ isDev })}!` +
filepath +
METADATA_IMAGE_RESOURCE_QUERY
)})`

hasStaticMetadataFiles = true
iconsMetadata[type].push(iconModule)
})
}

await Promise.all([
collectIconModuleIfExists('icon'),
collectIconModuleIfExists('apple'),
])

return hasStaticMetadataFiles ? iconsMetadata : null
}

export function buildMetadata(
metadata: Awaited<ReturnType<typeof discoverStaticMetadataFiles>>
) {
return metadata
? `${METADATA_TYPE}: {
icon: [${metadata.icon.join(',')}],
apple: [${metadata.apple.join(',')}]
}`
: ''
}
11 changes: 11 additions & 0 deletions packages/next/src/build/webpack/loaders/app-dir/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// TODO-APP: check if this can be narrowed.
export type ComponentModule = () => any
export type ModuleReference = [
componentModule: ComponentModule,
filePath: string
]

export type CollectedMetadata = {
icon: ComponentModule[]
apple: ComponentModule[]
}
Loading

0 comments on commit 25dfdbb

Please sign in to comment.