Files
2024-07-05 13:46:23 +02:00

217 lines
7.2 KiB
JavaScript

const fs = require('fs')
const path = require('path')
const _ = require('lodash')
const getAssetKind = require('./lib/getAssetKind')
const isHMRUpdate = require('./lib/isHMRUpdate')
const isSourceMap = require('./lib/isSourceMap')
const getDynamicImportedChildAssets = require('./lib/getDynamicImportedChildAssets')
const createQueuedWriter = require('./lib/output/createQueuedWriter')
const createOutputWriter = require('./lib/output/createOutputWriter')
function AssetsWebpackPlugin (options) {
this.options = _.merge({}, {
filename: 'webpack-assets.json',
prettyPrint: false,
update: false,
fullPath: true,
manifestFirst: true,
useCompilerPath: false,
fileTypes: ['js', 'css'],
includeAllFileTypes: true,
includeFilesWithoutChunk: false,
includeAuxiliaryAssets: false,
includeDynamicImportedAssets: false,
keepInMemory: false,
integrity: false,
removeFullPathAutoPrefix: false
}, options)
}
AssetsWebpackPlugin.prototype = {
constructor: AssetsWebpackPlugin,
apply: function (compiler) {
const self = this
self.options.path = path.resolve(
self.options.useCompilerPath
? (compiler.options.output.path || '.')
: (self.options.path || '.')
)
self.writer = createQueuedWriter(createOutputWriter(self.options))
const afterEmit = (compilation, callback) => {
const options = compiler.options
const stats = compilation.getStats().toJson({
hash: true,
publicPath: true,
assets: true,
chunks: false,
modules: false,
source: false,
errorDetails: false,
timings: false
})
let assetPath = (stats.publicPath && self.options.fullPath) ? stats.publicPath : ''
// assetsByChunkName contains a hash with the bundle names and the produced files
// e.g. { one: 'one-bundle.js', two: 'two-bundle.js' }
// in some cases (when using a plugin or source maps) it might contain an array of produced files
// e.g. {
// main:
// [ 'index-bundle-42b6e1ec4fa8c5f0303e.js',
// 'index-bundle-42b6e1ec4fa8c5f0303e.js.map' ]
// }
// starting with webpack 5, the public path is automatically determined when possible and the path is prefaced
// with `/auto/`, the `removeAutoPrefix` option can be set to turn this off
if (self.options.removeFullPathAutoPrefix) {
if (assetPath.startsWith('auto')) {
assetPath = assetPath.substring(4)
}
}
const seenAssets = {}
let chunks
if (self.options.entrypoints) {
chunks = Object.keys(stats.entrypoints)
if (self.options.includeFilesWithoutChunk) {
chunks.push('') // push "unnamed" chunk
}
} else {
chunks = Object.keys(stats.assetsByChunkName)
chunks.push('') // push "unnamed" chunk
}
const output = chunks.reduce(function (chunkMap, chunkName) {
let assets
if (self.options.entrypoints) {
assets = chunkName ? stats.entrypoints[chunkName].assets : stats.assets
} else {
assets = chunkName ? stats.assetsByChunkName[chunkName] : stats.assets
}
if (self.options.includeAuxiliaryAssets && chunkName && stats.entrypoints[chunkName].auxiliaryAssets) {
assets = [...assets, ...stats.entrypoints[chunkName].auxiliaryAssets]
}
if (self.options.includeDynamicImportedAssets && chunkName && stats.entrypoints[chunkName].children) {
const dynamicImportedChildAssets = getDynamicImportedChildAssets(options, stats.entrypoints[chunkName].children)
assets = [...assets, ...dynamicImportedChildAssets]
}
if (!Array.isArray(assets)) {
assets = [assets]
}
let added = false
const typeMap = assets.reduce(function (typeMap, obj) {
const asset = obj.name || obj
if (isHMRUpdate(options, asset) || isSourceMap(options, asset) || (!chunkName && seenAssets[asset])) {
return typeMap
}
const typeName = getAssetKind(options, asset)
if (self.options.includeAllFileTypes || self.options.fileTypes.includes(typeName)) {
const combinedPath = assetPath && assetPath.slice(-1) !== '/' ? `${assetPath}/${asset}` : assetPath + asset
const type = typeof typeMap[typeName]
const compilationAsset = compilation.assets[asset]
const integrity = compilationAsset && compilationAsset.integrity
const loadingBehavior = obj.loadingBehavior
if (type === 'undefined') {
typeMap[typeName] = combinedPath
if (self.options.integrity && integrity) {
typeMap[typeName + 'Integrity'] = integrity
}
} else {
if (type === 'string') {
typeMap[typeName] = [typeMap[typeName]]
}
if (self.options.includeDynamicImportedAssets && loadingBehavior) {
const typeNameWithLoadingBehavior = typeName + ':' + loadingBehavior
typeMap[typeNameWithLoadingBehavior] = typeMap[typeNameWithLoadingBehavior] || []
typeMap[typeNameWithLoadingBehavior].push(combinedPath)
} else {
typeMap[typeName].push(combinedPath)
}
}
added = true
seenAssets[asset] = true
}
return typeMap
}, {})
if (added) {
chunkMap[chunkName] = typeMap
}
return chunkMap
}, {})
let manifestNames = self.options.includeManifest === true ? ['manifest'] : self.options.includeManifest
if (typeof manifestNames === 'string') {
manifestNames = [manifestNames]
}
if (manifestNames) {
for (let i = 0; i < manifestNames.length; i++) {
const manifestName = manifestNames[i]
const manifestEntry = output[manifestName]
if (manifestEntry) {
let js = manifestEntry.js || manifestEntry.mjs
if (!Array.isArray(js)) {
js = [js]
}
const manifestAssetKey = js[js.length - 1].substr(assetPath.length)
const parentSource = compilation.assets[manifestAssetKey]
const entryText = parentSource.source()
if (!entryText) {
throw new Error('Could not locate manifest function in source', parentSource)
}
manifestEntry.text = entryText
}
}
}
if (self.options.metadata) {
output.metadata = self.options.metadata
}
if (!compiler.outputFileSystem.readFile) {
compiler.outputFileSystem.readFile = fs.readFile.bind(fs)
}
if (!compiler.outputFileSystem.join) {
compiler.outputFileSystem.join = path.join.bind(path)
}
self.writer(compiler.outputFileSystem, output, function (err) {
if (err) {
compilation.errors.push(err)
}
callback()
})
}
if (compiler.hooks) {
const plugin = { name: 'AssetsWebpackPlugin' }
compiler.hooks.afterEmit.tapAsync(plugin, afterEmit)
} else {
compiler.plugin('after-emit', afterEmit)
}
}
}
module.exports = AssetsWebpackPlugin