Skip to content

Commit

Permalink
feat: use the processAssets webpack api to add assets
Browse files Browse the repository at this point in the history
  • Loading branch information
jantimon committed Oct 30, 2020
1 parent 31739ec commit d15d92f
Show file tree
Hide file tree
Showing 7 changed files with 829 additions and 990 deletions.
2 changes: 1 addition & 1 deletion .node-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
10.18.0
10.13.0
1,628 changes: 703 additions & 925 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@
"diffable-html": "4.0.0",
"eslint": "^6.8.0",
"fs-extra": "^8.1.0",
"html-webpack-plugin": "4.3.0",
"html-webpack-plugin": "^5.0.0-alpha.7",
"image-size": "0.8.3",
"nyc": "^15.0.0",
"prettier": "1.19.1",
"standard-version": "8.0.2",
"typescript": "3.7.4",
"webpack": "^5.2.0",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1",
"webpack-cli": "4.1.0",
"webpack-dev-server": "3.11.0",
"webpack-merge": "^4.2.2"
},
"dependencies": {
Expand All @@ -86,6 +86,6 @@
}
},
"engines": {
"node": ">=8.9.4"
"node": ">=10.13.0"
}
}
6 changes: 0 additions & 6 deletions src/compiler.d.ts

This file was deleted.

106 changes: 70 additions & 36 deletions src/compiler.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const path = require('path');
const findCacheDir = require('find-cache-dir');
const entryPlugin = require('webpack/lib/EntryPlugin');
const Compilation = require('webpack').Compilation;

module.exports.run = (faviconOptions, context, compilation) => {
const {
Expand All @@ -12,7 +13,7 @@ module.exports.run = (faviconOptions, context, compilation) => {
outputPath
} = faviconOptions;
// The entry file is just an empty helper
const filename = '[fullhash]';
const filename = 'favicon-[fullhash]';
const publicPath = getPublicPath(
publicPathOption,
compilation.outputOptions.publicPath
Expand All @@ -21,13 +22,16 @@ module.exports.run = (faviconOptions, context, compilation) => {
// Create an additional child compiler which takes the template
// and turns it into an Node.JS html factory.
// This allows us to use loaders during the compilation
const compiler = compilation.createChildCompiler('favicons-webpack-plugin', {
filename,
publicPath,
libraryTarget: 'var',
iife: false
});
compiler.context = context;
const childCompiler = compilation.createChildCompiler(
'favicons-webpack-plugin',
{
filename,
publicPath,
libraryTarget: 'var',
iife: false
}
);
childCompiler.context = context;

const cacheDirectory =
cache &&
Expand All @@ -53,41 +57,71 @@ module.exports.run = (faviconOptions, context, compilation) => {
`!${cacheLoader}!${faviconsLoader}!${logo}`,
path.basename(logo)
);
logoCompilationEntry.apply(compiler);
logoCompilationEntry.apply(childCompiler);

// Compile and return a promise
return new Promise((resolve, reject) => {
compiler.runAsChild((err, [chunk] = [], { hash, errors = [] } = {}) => {
/** @type {Promise<{ tags: Array<string>, assets: Array<{name: string, contents: string}> }>} */
const compiledFavicons = new Promise((resolve, reject) => {
/** @type {Array<import('webpack').sources.CachedSource>} extracted webpack assets */
const extractedAssets = [];
childCompiler.hooks.thisCompilation.tap(
'FaviconsWebpackPlugin',
compilation => {
compilation.hooks.processAssets.tap(
{
name: 'FaviconsWebpackPlugin',
stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
},
assets => {
Object.keys(assets)
.filter(temporaryTemplateName =>
temporaryTemplateName.startsWith('favicon-')
)
.forEach(temporaryTemplateName => {
if (assets[temporaryTemplateName]) {
extractedAssets.push(assets[temporaryTemplateName]);
compilation.deleteAsset(temporaryTemplateName);
}
});
if (extractedAssets.length > 1) {
reject('Unexpected multiplication of favicon generations');

return;
}
const extractedAsset = extractedAssets[0];
if (extractedAsset) {
/**
* @type {{ tags: Array<string>, assets: Array<{name: string, contents: string}> }}
* The extracted result of the favicon-webpack-plugin loader
*/
const result = eval(extractedAsset.source().toString()); // eslint-disable-line
if (result && result.assets) {
resolve(result);
}
}
}
);
}
);
childCompiler.runAsChild((err, result, { errors = [] } = {}) => {
if (err || errors.length) {
return reject(err || errors[0].error);
}

// Replace [hash] placeholders in filename
const result = extractAssetFromCompilation(
compilation,
compilation.getAssetPath(filename, { hash, chunk })
);

for (const { name, contents } of result.assets) {
const binaryContents = Buffer.from(contents, 'base64');
compilation.assets[name] = {
source: () => binaryContents,
size: () => binaryContents.length
};
}

return resolve(result.tags);
// If no error occured and this promise was not resolved inside the `processAssets` hook
// reject the promise although it's unclear why it failed:
reject('Could not extract assets');
});
});
};

function extractAssetFromCompilation(compilation, assetPath) {
const content = compilation.assets[assetPath].source();
compilation.deleteAsset(assetPath);

/* eslint-disable no-eval */
return eval(content);
}
return compiledFavicons.then(faviconCompilationResult => {
return {
assets: faviconCompilationResult.assets.map(({ name, contents }) => ({
name,
contents: Buffer.from(contents, 'base64')
})),
tags: faviconCompilationResult.tags
};
});
};

/**
* faviconsPublicPath always wins over compilerPublicPath
Expand Down
59 changes: 43 additions & 16 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ const path = require('path');
const child = require('./compiler');
const crypto = require('crypto');
const Oracle = require('./oracle');
const Compilation = require('webpack').Compilation;
const RawSource = require('webpack').sources.RawSource;

/** @type {WeakMap<any, Promise<{tags: string[], assets: Array<{name: string, contents: Buffer | string}>}>>} */
const faviconCompilations = new WeakMap();

class FaviconsWebpackPlugin {
Expand Down Expand Up @@ -102,9 +105,9 @@ class FaviconsWebpackPlugin {
}

faviconCompilation
.then(tags => {
.then(faviconCompilation => {
htmlPluginData.assetTags.meta.push(
...tags
...faviconCompilation.tags
.map(tag => parse5.parseFragment(tag).childNodes[0])
.map(({ tagName, attrs }) => ({
tagName,
Expand All @@ -131,6 +134,25 @@ class FaviconsWebpackPlugin {
}
);

compiler.hooks.thisCompilation.tap('FaviconsWebpackPlugin', compilation => {
compilation.hooks.processAssets.tapPromise(
{
name: 'FaviconsWebpackPlugin',
stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
},
async () => {
const faviconCompilation = faviconCompilations.get(compilation);
if (!faviconCompilation) {
return;
}
const faviconAssets = (await faviconCompilation).assets;
faviconAssets.forEach(({ name, contents }) => {
compilation.emitAsset(name, new RawSource(contents, false));
});
}
);
});

// Make sure that the build waits for the favicon generation to complete
compiler.hooks.afterCompile.tapPromise(
'FaviconsWebpackPlugin',
Expand All @@ -157,6 +179,8 @@ class FaviconsWebpackPlugin {
* The light mode will only add a favicon
* this is very fast but also very limited
* it is the default mode for development
*
* @returns {Promise<{tags: string[], assets: Array<{name: string, contents: Buffer | string}>}>}
*/
generateFaviconsLight(compilation) {
return new Promise((resolve, reject) => {
Expand All @@ -180,31 +204,34 @@ class FaviconsWebpackPlugin {
.createHash('sha256')
.update(content.toString('utf8'))
.digest('hex');
const outputPath = compilation.getAssetPath(
this.options.prefix,
{
hash,
chunk: {
hash
}
const outputPath = compilation.getAssetPath(this.options.prefix, {
hash,
chunk: {
hash
}
);
});
const logoOutputPath = `${outputPath +
(outputPath.substr(-1) === '/' ? '' : '/')}favicon${faviconExt}`;
compilation.assets[logoOutputPath] = {
source: () => content,
size: () => content.length
};
resolve([`<link rel="icon" href="${publicPath}${logoOutputPath}">`]);
resolve({
assets: [
{
name: logoOutputPath,
contents: content
}
],
tags: [`<link rel="icon" href="${publicPath}${logoOutputPath}">`]
});
}
);
});
}

/**
* The webapp mode will add a variety of icons
* The webapp mode will add a variety of icons
* this is not as fast as the light mode but
* supports all common browsers and devices
*
* @returns {Promise<{tags: string[], assets: Array<{name: string, contents: Buffer | string}>}>}
*/
generateFaviconsWebapp(compilation) {
// Generate favicons using the npm favicons library
Expand Down
10 changes: 8 additions & 2 deletions test/failure.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ test('should fail gracefully if the image stream is empty', async t => {
plugins: [new FaviconsWebpackPlugin({ logo: empty })]
});
} catch (err) {
t.is(err.message, 'Invalid image buffer');
const errorMessage = err.message
.split('\n')
.find(errorLine => errorLine.startsWith('Error:'));
t.is(errorMessage, 'Error: Invalid image buffer');
}
});

Expand All @@ -50,7 +53,10 @@ test('should fail gracefully if logo is not a valid image file', async t => {
plugins: [new FaviconsWebpackPlugin({ logo: invalid })]
});
} catch (err) {
t.is(err.message, 'Invalid image buffer');
const errorMessage = err.message
.split('\n')
.find(errorLine => errorLine.startsWith('Error:'));
t.is(errorMessage, 'Error: Invalid image buffer');
}
});

Expand Down

0 comments on commit d15d92f

Please sign in to comment.