b18489461b
This PR makes sure that the same polyfills are added in app dir for browsers that match `nomodule`. Main difference from the polyfills in pages is that the script tags here cannot have `defer` as all other scripts will be async by default, and polyfills must be executed before all of them. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [ ] 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] Integration 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` ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
64 lines
2.2 KiB
TypeScript
64 lines
2.2 KiB
TypeScript
import { webpack, sources } from 'next/dist/compiled/webpack/webpack'
|
|
import crypto from 'crypto'
|
|
import { SUBRESOURCE_INTEGRITY_MANIFEST } from '../../../shared/lib/constants'
|
|
|
|
const PLUGIN_NAME = 'SubresourceIntegrityPlugin'
|
|
|
|
export type SubresourceIntegrityAlgorithm = 'sha256' | 'sha384' | 'sha512'
|
|
|
|
export class SubresourceIntegrityPlugin {
|
|
constructor(private readonly algorithm: SubresourceIntegrityAlgorithm) {}
|
|
|
|
public apply(compiler: webpack.Compiler) {
|
|
compiler.hooks.make.tap(PLUGIN_NAME, (compilation) => {
|
|
compilation.hooks.afterOptimizeAssets.tap(
|
|
{
|
|
name: PLUGIN_NAME,
|
|
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
|
|
},
|
|
(assets) => {
|
|
// Collect all the assets.
|
|
let files = new Set<string>()
|
|
for (const asset of compilation.getAssets()) {
|
|
files.add(asset.name)
|
|
}
|
|
|
|
// For each file, deduped, calculate the file hash.
|
|
const hashes: Record<string, string> = {}
|
|
for (const file of files.values()) {
|
|
// Get the buffer for the asset.
|
|
const asset = assets[file]
|
|
if (!asset) {
|
|
throw new Error(`could not get asset: ${file}`)
|
|
}
|
|
|
|
// Get the buffer for the asset.
|
|
const buffer = asset.buffer()
|
|
|
|
// Create the hash for the content.
|
|
const hash = crypto
|
|
.createHash(this.algorithm)
|
|
.update(buffer)
|
|
.digest()
|
|
.toString('base64')
|
|
|
|
hashes[file] = `${this.algorithm}-${hash}`
|
|
}
|
|
|
|
const json = JSON.stringify(hashes, null, 2)
|
|
const file = 'server/' + SUBRESOURCE_INTEGRITY_MANIFEST
|
|
assets[file + '.js'] = new sources.RawSource(
|
|
'self.__SUBRESOURCE_INTEGRITY_MANIFEST=' + json
|
|
// Work around webpack 4 type of RawSource being used
|
|
// TODO: use webpack 5 type by default
|
|
) as unknown as webpack.sources.RawSource
|
|
assets[file + '.json'] = new sources.RawSource(
|
|
json
|
|
// Work around webpack 4 type of RawSource being used
|
|
// TODO: use webpack 5 type by default
|
|
) as unknown as webpack.sources.RawSource
|
|
}
|
|
)
|
|
})
|
|
}
|
|
}
|