Skip to content

Commit

Permalink
Add asset module tests and documentation (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
sirreal authored Sep 7, 2021
1 parent 65e68d3 commit ada695f
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 32 deletions.
48 changes: 32 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,43 @@ yarn add webpack-web-app-manifest-plugin --dev

Name all of your icon files `manifest/icon_[square dimension].(png|jpeg|jpg)`. If you have a different naming scheme for your files, check out the section [Using it the hard way](#using-it-the-hard-way) below.

In your webpack config file:
When using [webpack asset modules](https://webpack.js.org/guides/asset-modules/) you will have a configuration section like this to produce the icon assets:

```js
import AppManifestPlugin from 'webpack-web-app-manifest-plugin';
{
module: {
rules: [
// Manifest icons
{
test: /\.png$/,
type: 'asset/resource',
generator: {
filename: 'manifest/[name]-[contenthash:8][ext][query]',
},
},
],
},
}
```

...
In your webpack config file:

plugins: [
new AppManifestPlugin({
content: {
name: 'Tumblr',
short_name: 'Tumblr',
background_color: '#36465d',
},
destination: '/manifest',
}),
],
```js
import AppManifestPlugin from 'webpack-web-app-manifest-plugin';

...
{
//
plugins: [
new AppManifestPlugin({
content: {
name: 'Tumblr',
short_name: 'Tumblr',
background_color: '#36465d',
},
destination: '/manifest',
}),
],
}
```

To access the app manifest file path from your asset manifest:
Expand All @@ -47,8 +65,6 @@ To access the app manifest file path from your asset manifest:
const manifest = // however you usually access your asset manifest in code
const appManifestPath = manifest['app-manifest'].json;

...

<link rel="manifest" href={appManifestPath} />
```

Expand Down
18 changes: 4 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "webpack-web-app-manifest-plugin",
"version": "4.0.0",
"version": "4.0.1",
"description": "webpack-web-app-manifest-plugin is a webpack plugin that generates a PWA manifest and integrates with the assets JSON generated by assets-webpack-plugin.",
"main": "dist/index.js",
"scripts": {
Expand All @@ -17,13 +17,7 @@
"engines": {
"node": ">=10.13.0"
},
"keywords": [
"pwa",
"webpack",
"web",
"app",
"manifest"
],
"keywords": ["pwa", "webpack", "web", "app", "manifest"],
"author": "Paul Rehkugler",
"license": "MIT",
"dependencies": {
Expand All @@ -49,12 +43,8 @@
"webpack": "^5.0.0"
},
"jest": {
"testMatch": [
"**/*.tests.js"
],
"collectCoverageFrom": [
"src/**/*.ts"
],
"testMatch": ["**/*.tests.js"],
"collectCoverageFrom": ["src/**/*.ts"],
"testURL": "http://localhost/",
"coverageDirectory": "./reports/coverage",
"coverageThreshold": {
Expand Down
196 changes: 194 additions & 2 deletions src/__tests__/plugin.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ afterAll(() => {
cleanTestOutput();
});

describe('webpack-web-app-manifest-plugin', () => {
describe('Using file-loader', () => {
async function runCompilation(plugin, publicPath = '/') {
return new Promise((resolve, reject) => {
webpack(
Expand All @@ -40,7 +40,7 @@ describe('webpack-web-app-manifest-plugin', () => {
test: /\.png$/,
loader: 'file-loader',
options: {
name: '[path][name]-[hash:8].[ext]',
name: '[path][name]-[fullhash:8].[ext]',
context: path.join(__dirname, 'assets'),
},
},
Expand Down Expand Up @@ -208,3 +208,195 @@ describe('webpack-web-app-manifest-plugin', () => {
]);
});
});

describe('Using asset modules', () => {
async function runCompilation(plugin, publicPath = '/') {
return new Promise((resolve, reject) => {
webpack(
{
entry: {
main: [
path.join(__dirname, 'assets', 'manifest', 'icon_192.png'),
path.join(__dirname, 'assets', 'manifest', 'icon_512.png'),
path.join(__dirname, 'assets', 'manifest', 'definitely_not_a_manifest_icon.png'),
path.join(__dirname, 'assets', 'manifest', 'this_is_a_manifest_icon.png'),
],
},
output: {
publicPath,
path: distPath,
clean: true,
},
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource',
generator: {
filename: 'manifest/[name]-[contenthash:8][ext][query]',
},
},
],
},

plugins: [plugin],
},
async (err, stats) => {
if (err) {
reject(err);
}
if (stats.hasErrors()) {
reject(stats);
}

const manifestFile = glob(path.join(distPath, plugin.destination, `manifest-*.json`))[0];
const contents = JSON.parse(await fs.readFile(manifestFile, 'utf-8'));
resolve([contents, stats]);
},
);
});
}

it('emits the app manifest as an asset via webpack', async () => {
const plugin = new WebAppManifestPlugin({
content: {},
destination: '/manifest',
});

const [manifest] = await runCompilation(plugin);
expect(manifest.icons).toHaveLength(2);
});

it('allows valid manifest keys to pass through to the final manifest', async () => {
const plugin = new WebAppManifestPlugin({
content: {
name: 'Tumblr',
short_name: 'Tumblr',
background_color: '#36465d',
theme_color: '#36465d',
display: 'standalone',
},
destination: '/manifest',
});

const [manifest] = await runCompilation(plugin);

expect(manifest.name).toEqual('Tumblr');
expect(manifest.short_name).toEqual('Tumblr');
expect(manifest.background_color).toEqual('#36465d');
expect(manifest.theme_color).toEqual('#36465d');
expect(manifest.display).toEqual('standalone');
});

it('adds chunks to the compilation for use in the assets manifest', async () => {
const plugin = new WebAppManifestPlugin({
content: {},
destination: '/manifest',
});

const [_manifest, stats] = await runCompilation(plugin);

expect(stats.toJson().assetsByChunkName['app-manifest']).toBeTruthy();
});

it('correctly adds icons using the default icon functions', async () => {
const assets = [192, 512].map((dimension) => ({
dimension,
name: `/manifest/icon_${dimension}`,
type: 'image/png',
}));

const plugin = new WebAppManifestPlugin({
content: {},
destination: '/manifest',
});

const [manifest] = await runCompilation(plugin);
const icons = manifest.icons;
expect(icons).toHaveLength(2);

assets.forEach(({ dimension, name, type }) => {
const iconsWhoseSrcMatchesName = icons.filter((icon) => icon.src.startsWith(name));
expect(iconsWhoseSrcMatchesName).toHaveLength(1);

const icon = iconsWhoseSrcMatchesName[0];
expect(icon.type).toEqual(type);
expect(icon.sizes).toEqual(`${dimension}x${dimension}`);
});
});

it('correctly adds icons with an absolute public path', async () => {
const assets = [192, 512].map((dimension) => ({
dimension,
name: `manifest/icon_${dimension}`,
type: 'image/png',
}));

const publicPath = '/assets/';
const plugin = new WebAppManifestPlugin({
content: {},
destination: '/manifest',
});

const [manifest] = await runCompilation(plugin, '/assets/');

const icons = manifest.icons;

assets.forEach(({ dimension, name, type }) => {
const iconsWhoseSrcMatchesName = icons.filter((icon) =>
icon.src.startsWith(`/assets/${name}`),
);
expect(iconsWhoseSrcMatchesName).toHaveLength(1);

const icon = iconsWhoseSrcMatchesName[0];
expect(icon.type).toEqual(type);
expect(icon.sizes).toEqual(`${dimension}x${dimension}`);
});
});

it('avoids adding other images using the default icon functions', async () => {
const plugin = new WebAppManifestPlugin({
content: {},
destination: '/manifest',
});

const [manifest] = await runCompilation(plugin);

expect(
manifest.icons.filter((icon) => icon.src.includes('definitely_not_a_manifest_icon')),
).toEqual([]);
});

it('correctly adds icons using custom functions', async () => {
const plugin = new WebAppManifestPlugin({
content: {},
destination: '/manifest',
isAssetManifestIcon: (fileName) => /this_is_a_manifest_icon.*\.png$/.test(fileName),
getIconSize: () => ({ width: 120, height: 90 }),
getIconType: () => 'image/png',
});

const [manifest] = await runCompilation(plugin);
const icons = manifest.icons;
expect(icons).toHaveLength(1);

const manifestIcon = icons[0];
expect(manifestIcon.sizes).toEqual('120x90');
expect(manifestIcon.type).toEqual('image/png');
});

it('correctly normalizes compilation output paths', async () => {
const plugin = new WebAppManifestPlugin({
content: {},
destination: 'web-app-manifest/', // destination has a trailing slash instead of leading for this test
});

const [manifest, stats] = await runCompilation(plugin);

expect(manifest).toBeTruthy();

expect(stats.toJson().assetsByChunkName['app-manifest']).toEqual([
expect.stringMatching(/web-app-manifest\/manifest-[0-9a-f]{8}\.json/),
]);
});
});

0 comments on commit ada695f

Please sign in to comment.