Skip to content

Commit

Permalink
feat: track favicons runtime loader dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
jantimon committed Feb 7, 2021
1 parent 8b61457 commit 0814513
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 39 deletions.
2 changes: 1 addition & 1 deletion example/runtime/src/app.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import tags from 'favicons-webpack-plugin/runtime/tags';
import {tags} from 'favicons-webpack-plugin/runtime/tags';

console.log(tags);
19 changes: 16 additions & 3 deletions runtime/tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,22 @@
//
// This is only a placeholder file for typescript and eslint
//
// The real content for this file will be generated by runtime-loader.js
// The real content for this file will be generated by favicons-webpack-plugin/src/runtime-loader.js
//

/**
* All tags from all favicons compilations inside a flat array
*
* @type {string[]}
*
* @example
```js
[ '<link rel="icon" href="assets/favicon.png">' ]
```
*/
var tags = [];
module.exports.tags = tags;

/**
* All tags from all favicons compilations
*
Expand All @@ -16,5 +29,5 @@
{ 'assets/': [ '<link rel="icon" href="assets/favicon.png">' ] }
```
*/
var tags = {};
module.exports = tags;
var tagsPerPublicPath = {};
module.exports.tagsPerPublicPath = tagsPerPublicPath;
62 changes: 45 additions & 17 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @ts-check

const assert = require('assert');
const parse5 = require('parse5');
const path = require('path');
Expand All @@ -9,7 +8,21 @@ const url = require('url');
const { resolvePublicPath, replaceContentHash } = require('./hash');
const { webpackLogger } = require('./logger');
const runtimeLoader = require('./runtime-loader');
const attachedCompilers = new WeakSet();

/**
* @type {WeakSet<import('webpack').Compiler>}
* Static set to track if a compiler has already a FaviconWebpackPlugin instance attached
*/
const attachedWebpackCompilers = new WeakSet();

/**
* @typedef {{
tags: import('./html-tags').HtmlTagObject[],
publicPath: string,
assets: Array<{name: string, contents: import('webpack').sources.RawSource }>,
dependencies: string[],
}} FaviconCompilationResult
*/

class FaviconsWebpackPlugin {
/**
Expand All @@ -36,19 +49,36 @@ class FaviconsWebpackPlugin {
*/
apply(compiler) {
compiler.hooks.initialize.tap('FaviconsWebpackPlugin', () => {
if (!attachedWebpackCompilers.has(compiler)) {
attachedWebpackCompilers.add(compiler);
this.hookIntoCompilerOnce(compiler);
}
this.hookIntoCompiler(compiler);
});
}

/**
* This hook is only executed once per compiler no matter how many
* FaviconsWebpackPlugins will be attached
*
* @param {import('webpack').Compiler} compiler
*/
hookIntoCompilerOnce(compiler) {
// Add one loader to add support for `import { tags ] from 'favicons-webpack-plugin/runtime/tags'`
compiler.options.module.rules.push(runtimeLoader.moduleRuleConfig);
}

/**
* This hook is executed once per FaviconsWebpackPlugin instance
*
* @param {import('webpack').Compiler} compiler
*/
hookIntoCompiler(compiler) {
const webpack = compiler.webpack;
const Compilation = webpack.Compilation;
const NormalModule = webpack.NormalModule;
const oracle = new Oracle(compiler.context);
/** @type {WeakMap<any, Promise<{tags: string[], assets: Array<{name: string, contents: import('webpack').sources.RawSource}>}>>} */
/** @type {WeakMap<any, Promise<FaviconCompilationResult>>}>>} */
const faviconCompilations = new WeakMap();

{
Expand All @@ -69,12 +99,6 @@ class FaviconsWebpackPlugin {
});
}

// Add one loader to add support for `import tags from 'favicons-webpack-plugin/runtime/tags'`
if (!attachedCompilers.has(compiler)) {
attachedCompilers.add(compiler);
compiler.options.module.rules.push(runtimeLoader.moduleRuleConfig);
}

if (this.options.logo === undefined) {
const defaultLogo = path.resolve(compiler.context, 'logo.png');
try {
Expand Down Expand Up @@ -132,7 +156,7 @@ class FaviconsWebpackPlugin {
);

// Inject favicons information into runtime tags
// to allow `import tags from 'favicons-webpack-plugin/runtime/tags'`
// to allow `import { tags ] from 'favicons-webpack-plugin/runtime/tags'`
const tagsFilePath = require.resolve('../runtime/tags.js');
const normalModuleHooks = NormalModule.getCompilationHooks(compilation);
normalModuleHooks.loader.tap(
Expand Down Expand Up @@ -292,8 +316,9 @@ class FaviconsWebpackPlugin {
* @param {Buffer | string} baseManifest - the content of the file from options.manifest
* @param {import('webpack').Compilation} compilation
* @param {string} outputPath
* @returns {Promise<FaviconCompilationResult>}
*/
generateFavicons(logo, baseManifest, compilation, outputPath) {
async generateFavicons(logo, baseManifest, compilation, outputPath) {
const resolvedPublicPath = getResolvedPublicPath(
logo.hash,
compilation,
Expand All @@ -305,32 +330,35 @@ class FaviconsWebpackPlugin {
? JSON.parse(baseManifest.toString() || '{}')
: this.options.manifest || {};

// File dependencies
const dependencies = (this.options.manifest === 'string' ? [this.options.manifest] : []).concat(this.options.logo);

switch (this.getCurrentCompilationMode(compilation.compiler)) {
case 'light':
if (!this.options.mode) {
webpackLogger(compilation).info(
'generate only a single favicon for fast compilation time in development mode. This behaviour can be changed by setting the favicon mode option.'
);
}

return this.generateFaviconsLight(
const lightFaviconsCompilation = await this.generateFaviconsLight(
logo.content,
parsedBaseManifest,
compilation,
resolvedPublicPath,
outputPath
outputPath,
);
return {...lightFaviconsCompilation, dependencies};
case 'webapp':
default:
webpackLogger(compilation).log('generate favicons');

return this.generateFaviconsWebapp(
const webappFaviconsCompilation = await this.generateFaviconsWebapp(
logo.content,
parsedBaseManifest,
compilation,
resolvedPublicPath,
outputPath
outputPath,
);
return {...webappFaviconsCompilation, dependencies};
}
}

Expand Down
57 changes: 39 additions & 18 deletions src/runtime-loader.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,39 @@
/// @ts-check
const htmlTagObjectToString = require('./html-tags').htmlTagObjectToString;

/**
* Config used for the webpack config
*/
const moduleRuleConfig = Object.freeze({
test: require.resolve('../runtime/tags.js'),
use: 'favicons-webpack-plugin/src/runtime-loader'
});

/**
* @typedef {{
tags: import('./html-tags').HtmlTagObject[],
publicPath: string,
assets: Array<{name: string, contents: import('webpack').sources.RawSource }>,
dependencies: string[],
}} FaviconCompilationResult
*/

/**
* The contextMap is a bridge which receives data from the
* favicons-webpack-plugin during the NormalModule loader phase
* see ./index.js
*
* @type {
WeakMap<any, Set<Promise<{
publicPath: string;
assets: {
name: string;
contents: import('webpack').sources.RawSource;
}[];
tags: Array<import('./html-tags').HtmlTagObject>;
}>>>
WeakMap<any, Set<Promise<FaviconCompilationResult>>>
}
*/
const contextMap = new WeakMap();

/**
* Config used for the webpack config
*/
const moduleRuleConfig = Object.freeze({
test: require.resolve('../runtime/tags.js'),
use: 'favicons-webpack-plugin/src/runtime-loader'
});

/**
* the main loader is only a placeholder which will have no effect
* as the pitch function returns
*
* @this {{ async: () => ((err: Error | null, result: string) => void)}}
* @this {{ addDependency: (dependency: string) => void }}
*/
const loader = async function faviconsTagLoader() {
const faviconCompilationPromisses = contextMap.get(this);
Expand All @@ -41,6 +43,12 @@ const loader = async function faviconsTagLoader() {

const faviconCompilations = await Promise.all(faviconCompilationPromisses);

faviconCompilations.forEach(({dependencies}) => {
dependencies.forEach((dependency) => {
this.addDependency(dependency);
});
});

const tagsOfFaviconCompilations = faviconCompilations.map(
faviconCompilation => {
const { tags, publicPath } = faviconCompilation;
Expand Down Expand Up @@ -74,7 +82,20 @@ const loader = async function faviconsTagLoader() {
{}
);

return `export default ${JSON.stringify(tagsByPublicPath)}`;
// create a flat array as it will be easier to use for all users
// who use only a single FaviconWebpackPlugin instance
const allTags = tagsOfFaviconCompilations.reduce(
(result, [key, value]) => {
result.push(...value);
return result;
},
/** @type {string[]} */([])
);

return `// Generated by favicons-webpack-plugin/runtime-loader
export const tags = ${JSON.stringify(allTags)};
export const tagsPerPublicPath = ${JSON.stringify(tagsByPublicPath)};
`;
};

module.exports = Object.assign(loader, {
Expand Down

0 comments on commit 0814513

Please sign in to comment.