Skip to content

Commit

Permalink
feat: Add light mode during development
Browse files Browse the repository at this point in the history
  • Loading branch information
jantimon committed Aug 14, 2019
1 parent 6d66d8b commit 47986be
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 17 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ const FaviconsWebpackPlugin = require('favicons-webpack-plugin')
plugins: [
new FaviconsWebpackPlugin({
logo: '/path/to/logo.png', // svg works too!
mode: 'webapp', // optional can be 'webapp' or 'light' - 'webapp' by default
devMode: 'webapp', // optional can be 'webapp' or 'light' - 'light' by default
favicons: {
appName: 'my-app',
appDescription: 'My awesome App',
Expand All @@ -171,6 +173,16 @@ plugins: [
]
```

### Compilation Modes

Modes allow you to choose a very fast simplified favicon compilation or a production ready favicon compilation

By default this mode is controlled by webpack
If the webpack mode is set to `development` the favicons mode will use `light`.
If the webpack mode is set to `production` the favicons mode will use `webapp`.

This behaviour can be adjusted by setting the favicon `mode` and `devMode` options.

#### Handling Multiple HTML Files

```javascript
Expand Down
10 changes: 10 additions & 0 deletions example/custom/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ module.exports = (env, args) => {
new FaviconsWebpackPlugin({
// Your source logo (required)
logo: './src/favicon.png',
// Favicon generation mode which is used if webpack is in `production` mode
// Can be set to:
// `webapp` (Slow but production ready favicon geneation)
// `light` (Limited but fast favicon geneation during development)
mode: 'webapp',
// Favicon generation mode which is used if webpack is in `development` mode
// Can be set to:
// `webapp` (Slow but production ready favicon geneation)
// `light` (Limited but fast favicon geneation during development)
devMode: 'light',
// Path to store cached data or false/null to disable caching altogether
// Note: disabling caching may increase build times considerably
cache: '.wwp-cache',
Expand Down
108 changes: 91 additions & 17 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
const assert = require('assert');
const child = require('./compiler');
const Oracle = require('./oracle');
const { tap, tapHtml } = require('./compat');
const { tap, tapHtml, getAssetPath } = require('./compat');
const path = require('path');

const faviconCompilations = new WeakMap();

module.exports = class FaviconsWebpackPlugin {
constructor(args) {
Expand Down Expand Up @@ -44,21 +47,92 @@ module.exports = class FaviconsWebpackPlugin {
|| htmlPlugin.options.favicons !== false && htmlPlugin.options.inject && inject;
}

tap(compiler, 'make', 'FaviconsWebpackPlugin', (compilation, callback) =>
// Generate favicons
child.run(this.options, compiler.context, compilation)
.then(tags => {
// Hook into the html-webpack-plugin processing and add the html
tapHtml(compilation, 'FaviconsWebpackPlugin', (htmlPluginData, callback) => {
if (this.options.inject(htmlPluginData.plugin)) {
const idx = (htmlPluginData.html + '</head>').search(/<\/head>/i);
htmlPluginData.html = [htmlPluginData.html.slice(0, idx), ...tags, htmlPluginData.html.slice(idx)].join('');
}
return callback(null, htmlPluginData);
});
return callback();
})
.catch(callback)
);
// Hook into the webpack compilation
// to start the favicon generation
tap(compiler, 'make', 'FaviconsWebpackPlugin', (compilation, callback) => {
let faviconCompilation;
switch (this.getCurrentCompilationMode(compiler) ) {
case 'light':
faviconCompilation = this.generateFaviconsLight(compiler, compilation);
break;
case 'webapp':
default:
faviconCompilation = this.generateFaviconsWebapp(compiler, compilation);
}

// Hook into the html-webpack-plugin processing and add the html
tapHtml(compilation, 'FaviconsWebpackPlugin', (htmlPluginData, htmlWebpackPluginCallback) => {
faviconCompilation.then((tags) => {
if (this.options.inject(htmlPluginData.plugin)) {
const idx = (htmlPluginData.html + '</head>').search(/<\/head>/i);
htmlPluginData.html = [htmlPluginData.html.slice(0, idx), ...tags, htmlPluginData.html.slice(idx)].join('');
}
htmlWebpackPluginCallback(null, htmlPluginData);
}).catch(htmlWebpackPluginCallback);
});

// Save the promise and execute the callback immediately to not block
// the webpack build see the `afterCompile` FaviconsWebpackPlugin hook
// implementation where the promise is picked up again
faviconCompilations.set(compilation, faviconCompilation);
callback();
});

// Make sure that the build waits for the favicon generation to complete
tap(compiler, 'afterCompile', 'FaviconsWebpackPlugin', (compilation, callback) => {
const faviconCompilation = faviconCompilations.get(compilation) || Promise.resolve();
faviconCompilations.delete(compilation);
faviconCompilation.then(() => callback(), callback);
});

}

/**
* The light mode will only add a favicon
* this is very fast but also very limited
* it is the default mode for development
*/
generateFaviconsLight(compiler, compilation) {
return new Promise((resolve, reject) => {
const logoFileName = path.resolve(compilation.compiler.context, this.options.logo);
const webpackPublicPath = compilation.outputOptions.publicPath || '/';
const outputPath = webpackPublicPath + getAssetPath(compilation, this.options.prefix, {hash: compilation.hash, chunk: {}});
const faviconExt = path.extname(this.options.logo);
const logoOutputPath = outputPath + 'favicon' + faviconExt;
compiler.inputFileSystem.readFile(logoFileName, (err, content) => {
if (err) {
return reject(err);
}
compilation.assets[logoOutputPath] = {
source: () => content,
size: () => content.length
}
resolve([
`<link rel="icon" href="${logoOutputPath}">`
]);
});
});
}

/**
* 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
*/
generateFaviconsWebapp(compiler, compilation) {
// Generate favicons using the npm favicons library
return child.run(this.options, compiler.context, compilation)
}

/**
* Returns wether the plugin should generate a light version or a full webapp
*/
getCurrentCompilationMode(compiler) {
// From https://github.com/webpack/webpack/blob/3366421f1784c449f415cda5930a8e445086f688/lib/WebpackOptionsDefaulter.js#L12-L14
const isProductionLikeMode = compiler.options.mode === 'production' || !compiler.options.mode;
// Read the current `mode` and `devMode` option
const faviconDefaultMode = isProductionLikeMode ? 'webapp': 'light';
const faviconMode = isProductionLikeMode ? (this.options.mode || faviconDefaultMode) : (this.options.devMode || this.options.mode || faviconDefaultMode);
return faviconMode;
}
}
Binary file added test/fixtures/expected/light/assets/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions test/fixtures/expected/light/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!doctype html><html><head><link rel="icon" href="/assets/favicon.png"></head><body></body></html>
1 change: 1 addition & 0 deletions test/fixtures/expected/light/main.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions test/light.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const test = require('ava');
const path = require('path');
const fs = require('fs-extra');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const FaviconsWebpackPlugin = require('../');

const { logo, mkdir, generate, compare, expected } = require('./util');

test.beforeEach(async t => t.context.root = await mkdir());

test('should work if manual set to light mode', async t => {
const dist = path.join(t.context.root, 'dist');
await generate({
context: t.context.root,
output: {
path: dist,
},
plugins: [
new HtmlWebpackPlugin(),
new FaviconsWebpackPlugin({ logo, mode: 'light' }),
],
});

t.deepEqual(await compare(dist, path.resolve(expected, 'light')), []);
});

test('should automatically pick up the dev mode from webpack', async t => {
const dist = path.join(t.context.root, 'dist');
await generate({
mode: 'development',
context: t.context.root,
output: {
path: dist,
},
plugins: [
new HtmlWebpackPlugin(),
new FaviconsWebpackPlugin({ logo }),
],
});

t.deepEqual(await compare(dist, path.resolve(expected, 'light')), []);
})

test.afterEach(t => fs.remove(t.context.root));

0 comments on commit 47986be

Please sign in to comment.