diff --git a/README.md b/README.md index c4b421c..f2b3fcc 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,21 @@ ![SvgChunkWebpackPlugin](https://img.shields.io/badge/svg--chunk--webpack--plugin-v1.0.0-29008a.svg?style=for-the-badge) ![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/yoriiis/svg-chunk-webpack-plugin/Build/master?style=for-the-badge) [![Coverage Status](https://img.shields.io/coveralls/github/yoriiis/svg-chunk-webpack-plugin?style=for-the-badge)](https://coveralls.io/github/yoriiis/svg-chunk-webpack-plugin?branch=master) ![Node.js](https://img.shields.io/node/v/svg-chunk-webpack-plugin?style=for-the-badge) -> Generate SVG sprites according to entrypoint dependencies. Each page only imports its own svgs, wrapped in sprite and optimized by svgo. +> Generate SVG sprites according to entrypoint dependencies. Each page only imports its own svgs, wrapped as a sprite and optimized by svgo. -The SvgChunkWebpackPlugin creates optimized sprites of SVG, according to the Webpack entrypoints. Each sprites contains only SVG dependencies listed on its entrypoints to improved code splitting, even on SVG files. +The SvgChunkWebpackPlugin creates optimized SVG sprites, according to Webpack's entrypoints. Each sprite contains only the SVG dependencies listed on its entrypoints to improved code splitting, even on SVG files. The plugin includes the popular [svgo](https://github.com/svg/svgo) package with the optimized settings, to generates clean and optimized SVG sprites. -Code splitting is the key to deliver files without unused content for the pages. It already exists for CSS, Javascript and now for SVG with this plugin. +Code splitting is the key to deliver files without any content that is unused by the pages. It already exists for CSS, Javascript and now for SVG files with this plugin. ## When to use this plugin -On multiple page application, each pages must includes only necessary dependencies. In other words, includes only the SVG files imported by the entrypoint and all its dependencies. +On multiple page application, each pages must includes only its necessary dependencies. In other words, it must include only the SVG files imported by its entrypoint and all its dependencies. -With reusable components, SVGs are often duplicated on all the project. Now, you can create a global SVG library and every Javascript files can import SVG from this library. Entrypoint dependencies are automatically updated, thanks to the Webpack compilation. +With reusable components, SVGs are often duplicated on all the project. Now, you can create a global SVG library and every Javascript files can easily import any SVG from this library. Entrypoint dependencies are automatically updated, thanks to the Webpack compilation. -When you work with SVGs exported by design softwares, like Sketch or Illustrator. Indeed, source code of these SVG files are commonly not optimized and contains comments, CSS classes and create conflicts betwen them. The plugin automatically clean all SVGs before to create the sprite. +When you work with SVGs exported by design softwares, like Sketch or Illustrator, their source code is never optimized and often contains comments, CSS classes which can create conflicts between them. The plugin automatically cleans all SVGs before creating the sprite. ## Zero config @@ -24,7 +24,7 @@ The plugin works without configuration with already the optimized settings. For ## Installation -The plugin is available as the `svg-chunk-webpack-plugin` package name on [npm](https://www.npmjs.com/package/svg-chunk-webpack-plugin) and [Github](https://github.com/yoriiis/svg-chunk-webpack-plugin). +The plugin is available as a package with the name of `svg-chunk-webpack-plugin` on [npm](https://www.npmjs.com/package/svg-chunk-webpack-plugin) and [Github](https://github.com/yoriiis/svg-chunk-webpack-plugin). ```bash npm install svg-chunk-webpack-plugin --save-dev @@ -40,11 +40,11 @@ SvgChunkWebpackPlugin was built for Node.js `>=8.11.2` and Webpack `>=4.x`. ## Example -The project includes a minimalist example in the `./example` directory. Run the `npm run build:example` command to execute the Webpack example and see SvgChunkWebpackPlugin implementation in action. +The project includes a minimalist example in the `./example` directory. Run the `npm run build:example` command to execute the Webpack example and see SvgChunkWebpackPlugin's implementation in action. ## Basic usage -The plugin will generates one SVG sprite for each entrypoints. Sprites are built in the output path directory with all the other assets. Each sprite filename is composed by the entrypoint name (`sprite-home.svg` in the example below). +The plugin will generate one SVG sprite for each entrypoints. Sprites are built in the output path directory with all the other assets. Each sprite filename is composed with its entrypoint name (in the example below, that would be `home.svg`). First, let's add the loader and the plugin to the Webpack configuration. @@ -80,9 +80,9 @@ module.exports = { }; ``` -> 💡 Prefer inline SVG for more flexibility and better performance. Fewer HTTP requests, CSS properties to change the style, no flickering during the page load. +> 💡 For more flexibility and better performance, inline SVG files are better. Fewer HTTP requests, CSS properties to change the style, no flickering during the page load. -Then, include the sprite in the according pages (_we use Twig in the following example_). +Then, include the sprite in the wanted pages (_we use Twig in the following example_). **home.html.twig** @@ -148,7 +148,7 @@ new SvgChunkWebpackPlugin({ SVG sprites are built using the svgstore package. Tells the plugin whether to personalize the default settings for [svgstore](https://github.com/svgstore/svgstore). -> Sprites contains already minimal inline styles to hide the sprite on the page to keep full support with all SVG features. To avoid LinearGradient conflicts, avoid the `display: none` property which break SVG defs. +> Sprites already contain minimal inline styles to hide the sprite on the page to keep full support with all SVG features. To avoid LinearGradient conflicts, avoid the `display: none` property which break SVG defs. ```javascript new SvgChunkWebpackPlugin({ @@ -192,7 +192,7 @@ new SvgChunkWebpackPlugin({ }); ``` -![Sprites preview](./example/sprites-preview.jpg) +![Sprites preview](./example/sprites-preview.png) ### Caching @@ -200,7 +200,7 @@ With [Webpack caching](https://webpack.js.org/guides/caching), several placehold > 💡 The `[contenthash]` placeholder is the best option because it depends on the sprite content. > -> Cache placeholders are expensive in build performance, use it only with production mode. +> Cache placeholders are expensive in build performance, use it only in production mode. > > With SVG inlined in the page, this option is not useful. @@ -212,7 +212,7 @@ new SvgChunkWebpackPlugin({ }); ``` -The `[chunkhash]` placeholder will add a unique hash based on the content of the chunk. When the chunk's content changes, `[chunkhash]` will change as well. +The `[chunkhash]` placeholder will add a unique hash based on the content of the entrypoint. When the entrypoint's content changes, `[chunkhash]` will change as well. ```javascript new SvgChunkWebpackPlugin({ diff --git a/example/dist/app-a.js b/example/dist/home.js similarity index 100% rename from example/dist/app-a.js rename to example/dist/home.js diff --git a/example/dist/app-a.svg b/example/dist/home.svg similarity index 100% rename from example/dist/app-a.svg rename to example/dist/home.svg diff --git a/example/dist/manifest.json b/example/dist/manifest.json index d02ab96..e751d43 100644 --- a/example/dist/manifest.json +++ b/example/dist/manifest.json @@ -1,6 +1,6 @@ { - "app-a.js": "/dist/app-a.js", - "app-b.js": "/dist/app-b.js", - "app-a.svg": "/dist/app-a.svg", - "app-b.svg": "/dist/app-b.svg" + "home.js": "/dist/home.js", + "news.js": "/dist/news.js", + "home.svg": "/dist/home.svg", + "news.svg": "/dist/news.svg" } \ No newline at end of file diff --git a/example/dist/app-b.js b/example/dist/news.js similarity index 100% rename from example/dist/app-b.js rename to example/dist/news.js diff --git a/example/dist/app-b.svg b/example/dist/news.svg similarity index 100% rename from example/dist/app-b.svg rename to example/dist/news.svg diff --git a/example/sprites-preview.jpg b/example/sprites-preview.jpg deleted file mode 100644 index 092a783..0000000 Binary files a/example/sprites-preview.jpg and /dev/null differ diff --git a/example/sprites-preview.png b/example/sprites-preview.png new file mode 100644 index 0000000..1dff6ff Binary files /dev/null and b/example/sprites-preview.png differ diff --git a/example/src/js/app-c.js b/example/src/js/component.js similarity index 100% rename from example/src/js/app-c.js rename to example/src/js/component.js diff --git a/example/src/js/app-a.js b/example/src/js/home.js similarity index 73% rename from example/src/js/app-a.js rename to example/src/js/home.js index fcd6f1d..6af3d94 100644 --- a/example/src/js/app-a.js +++ b/example/src/js/home.js @@ -1,4 +1,4 @@ import '../svgs/gradient.svg'; import '../svgs/video.svg'; -import './app-c'; +import './component'; diff --git a/example/src/js/app-b.js b/example/src/js/news.js similarity index 100% rename from example/src/js/app-b.js rename to example/src/js/news.js diff --git a/example/webpack.config.js b/example/webpack.config.js index e775906..98ef9a7 100644 --- a/example/webpack.config.js +++ b/example/webpack.config.js @@ -10,8 +10,8 @@ module.exports = (env, argv) => { return { watch: !isProduction, entry: { - 'app-a': `${path.resolve(__dirname, './src/js/app-a.js')}`, - 'app-b': `${path.resolve(__dirname, './src/js/app-b.js')}` + home: `${path.resolve(__dirname, './src/js/home.js')}`, + news: `${path.resolve(__dirname, './src/js/news.js')}` }, watchOptions: { ignored: /node_modules/ diff --git a/package.json b/package.json index b5eb3b3..d2cd4b9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "svg-chunk-webpack-plugin", "version": "1.0.0", - "description": "Generate SVG sprites according to entrypoint dependencies. Each page only imports its own svgs, wrapped in sprite and optimized by svgo", + "description": "Generate SVG sprites according to entrypoint dependencies. Each page only imports its own svgs, wrapped as a sprite and optimized by svgo", "keywords": [ "svg", "svg-sprite", diff --git a/src/__tests__/index.test.js b/src/__tests__/index.test.js index ca485e7..81e2636 100644 --- a/src/__tests__/index.test.js +++ b/src/__tests__/index.test.js @@ -58,7 +58,7 @@ const svgsFixture = { }; const spritesFixture = { - 'app-a': + home: '' }; @@ -73,8 +73,8 @@ const svgsSprite = [ { name: 'smiley-love', content: svgsFixture['smiley-love'] } ]; const spritesList = { - name: 'app-a', - content: spritesFixture['app-a'], + name: 'home', + content: spritesFixture.home, svgs: ['gradient', 'video', 'smiley-love'] }; @@ -86,13 +86,13 @@ const options = { const getInstance = () => new SvgChunkWebpackPlugin(options); const entrypointsMap = new Map(); -entrypointsMap.set('app-a', { +entrypointsMap.set('home', { chunks: [ { hash: 'beb18939e5093045258b8d24a34dd844', getModules: () => [ { - buildInfo: { fileDependencies: ['example/src/js/app-a.js'] } + buildInfo: { fileDependencies: ['example/src/js/home.js'] } }, { buildInfo: { fileDependencies: ['example/src/svgs/gradient.svg'] } @@ -101,7 +101,7 @@ entrypointsMap.set('app-a', { buildInfo: { fileDependencies: ['example/src/svgs/video.svg'] } }, { - buildInfo: { fileDependencies: ['example/src/js/app-c.js'] } + buildInfo: { fileDependencies: ['example/src/js/component.js'] } }, { buildInfo: { @@ -112,7 +112,7 @@ entrypointsMap.set('app-a', { } ] }); -entrypointsMap.set('app-b', { +entrypointsMap.set('news', { chunks: [ { hash: 'beb18939e5093045258b8d24a34dd843', @@ -129,7 +129,7 @@ entrypointsMap.set('app-b', { }); beforeEach(() => { - entryNames = ['app-a', 'app-b']; + entryNames = ['home', 'news']; compilationWebpack = { assets: {}, hash: '4cc05208d925b7b31259', @@ -242,8 +242,8 @@ describe('SvgChunkWebpackPlugin hookCallback', () => { expect(svgChunkWebpackPlugin.compilation).toEqual(compilationWebpack); expect(svgChunkWebpackPlugin.getEntryNames).toHaveBeenCalled(); expect(svgChunkWebpackPlugin.processEntry).toHaveBeenCalledTimes(2); - expect(svgChunkWebpackPlugin.processEntry).toHaveBeenCalledWith('app-a'); - expect(svgChunkWebpackPlugin.processEntry).toHaveBeenCalledWith('app-b'); + expect(svgChunkWebpackPlugin.processEntry).toHaveBeenCalledWith('home'); + expect(svgChunkWebpackPlugin.processEntry).toHaveBeenCalledWith('news'); expect(svgChunkWebpackPlugin.createSpritesManifest).toHaveBeenCalled(); expect(svgChunkWebpackPlugin.createSpritesPreview).toHaveBeenCalled(); }); @@ -267,7 +267,7 @@ describe('SvgChunkWebpackPlugin getEntryNames', () => { it('Should call the getEntryNames function', () => { svgChunkWebpackPlugin.compilation = compilationWebpack; - expect(svgChunkWebpackPlugin.getEntryNames()).toEqual(['app-a', 'app-b']); + expect(svgChunkWebpackPlugin.getEntryNames()).toEqual(['home', 'news']); }); }); @@ -275,7 +275,7 @@ describe('SvgChunkWebpackPlugin processEntry', () => { it('Should call the processEntry function', async () => { mockGetSvgsByEntrypoint(svgChunkWebpackPlugin, svgsFilepath); mockOptimizeSvg(svgChunkWebpackPlugin, svgsFixture); - mockGenerateSprite(svgChunkWebpackPlugin, spritesFixture['app-a']); + mockGenerateSprite(svgChunkWebpackPlugin, spritesFixture.home); svgChunkWebpackPlugin.createSpriteAsset = jest.fn(); // Mock core node module to avoid absolute path conflict in the test environment @@ -285,9 +285,9 @@ describe('SvgChunkWebpackPlugin processEntry', () => { .mockImplementation((context, filepath) => `${context}${filepath}`); svgChunkWebpackPlugin.compilation = compilationWebpack; - await svgChunkWebpackPlugin.processEntry('app-a'); + await svgChunkWebpackPlugin.processEntry('home'); - const entry = 'app-a'; + const entry = 'home'; expect(svgChunkWebpackPlugin.getSvgsByEntrypoint).toHaveBeenCalledWith(entry); expect(svgChunkWebpackPlugin.optimizeSvg).toHaveBeenCalledTimes(3); expect(svgChunkWebpackPlugin.optimizeSvg).toHaveBeenCalledWith( @@ -302,17 +302,17 @@ describe('SvgChunkWebpackPlugin processEntry', () => { expect(svgChunkWebpackPlugin.generateSprite).toHaveBeenCalledWith(svgsSprite); expect(svgChunkWebpackPlugin.createSpriteAsset).toHaveBeenCalledWith({ entryName: entry, - sprite: spritesFixture['app-a'] + sprite: spritesFixture.home }); expect(svgChunkWebpackPlugin.spritesManifest).toEqual({ - 'app-a': svgsFilepath.map( + home: svgsFilepath.map( filepath => `${svgChunkWebpackPlugin.compilation.options.context}${filepath}` ) }); expect(svgChunkWebpackPlugin.spritesList).toEqual([ { name: entry, - content: spritesFixture['app-a'], + content: spritesFixture.home, svgs: ['gradient', 'video', 'smiley-love'] } ]); @@ -343,7 +343,7 @@ describe('SvgChunkWebpackPlugin getSvgsByEntrypoint', () => { it('Should call the getSvgsByEntrypoint function', () => { svgChunkWebpackPlugin.compilation = compilationWebpack; - const result = svgChunkWebpackPlugin.getSvgsByEntrypoint('app-a'); + const result = svgChunkWebpackPlugin.getSvgsByEntrypoint('home'); expect(result).toEqual(svgsFilepath); }); @@ -353,29 +353,27 @@ describe('SvgChunkWebpackPlugin generateSprite', () => { it('Should call the generateSprite function', () => { const result = svgChunkWebpackPlugin.generateSprite(svgsSprite); - expect(result).toBe(spritesFixture['app-a']); + expect(result).toBe(spritesFixture.home); }); }); describe('SvgChunkWebpackPlugin createSpriteAsset', () => { it('Should call the createSpriteAsset function', () => { - svgChunkWebpackPlugin.getFileName = jest.fn().mockImplementation(() => 'sprite/app-a.svg'); + svgChunkWebpackPlugin.getFileName = jest.fn().mockImplementation(() => 'home.svg'); svgChunkWebpackPlugin.compilation = compilationWebpack; - const output = spritesFixture['app-a']; + const output = spritesFixture.home; - svgChunkWebpackPlugin.createSpriteAsset({ entryName: 'app-a', sprite: output }); + svgChunkWebpackPlugin.createSpriteAsset({ entryName: 'home', sprite: output }); expect(svgChunkWebpackPlugin.compilation.assets).toEqual({ - 'sprite/app-a.svg': { + 'home.svg': { source: expect.any(Function), size: expect.any(Function) } }); - expect(svgChunkWebpackPlugin.compilation.assets['sprite/app-a.svg'].source()).toBe(output); - expect(svgChunkWebpackPlugin.compilation.assets['sprite/app-a.svg'].size()).toBe( - output.length - ); + expect(svgChunkWebpackPlugin.compilation.assets['home.svg'].source()).toBe(output); + expect(svgChunkWebpackPlugin.compilation.assets['home.svg'].size()).toBe(output.length); }); }); @@ -386,14 +384,14 @@ describe('SvgChunkWebpackPlugin getFileName', () => { mockGetContentHash(svgChunkWebpackPlugin); const result = svgChunkWebpackPlugin.getFileName({ - entryName: 'app-a', - output: spritesFixture['app-a'] + entryName: 'home', + output: spritesFixture.home }); expect(svgChunkWebpackPlugin.getBuildHash).not.toHaveBeenCalled(); expect(svgChunkWebpackPlugin.getChunkHash).not.toHaveBeenCalled(); expect(svgChunkWebpackPlugin.getContentHash).not.toHaveBeenCalled(); - expect(result).toBe('app-a.svg'); + expect(result).toBe('home.svg'); }); it('Should call the getFileName function with custom name', () => { @@ -401,13 +399,13 @@ describe('SvgChunkWebpackPlugin getFileName', () => { mockGetChunkHash(svgChunkWebpackPlugin); mockGetContentHash(svgChunkWebpackPlugin); - svgChunkWebpackPlugin.options.filename = 'custom-name.svg'; + svgChunkWebpackPlugin.options.filename = 'sprite/custom-name.svg'; const result = svgChunkWebpackPlugin.getFileName({ - entryName: 'app-a', - output: spritesFixture['app-a'] + entryName: 'home', + output: spritesFixture.home }); - expect(result).toBe('custom-name.svg'); + expect(result).toBe('sprite/custom-name.svg'); }); it('Should call the getFileName function with [hash]', () => { @@ -417,11 +415,11 @@ describe('SvgChunkWebpackPlugin getFileName', () => { svgChunkWebpackPlugin.options.filename = '[name].[hash].svg'; const result = svgChunkWebpackPlugin.getFileName({ - entryName: 'app-a', - output: spritesFixture['app-a'] + entryName: 'home', + output: spritesFixture.home }); - expect(result).toBe('app-a.4cc05208d925b7b31259.svg'); + expect(result).toBe('home.4cc05208d925b7b31259.svg'); }); it('Should call the getFileName function with [chunkhash]', () => { @@ -431,11 +429,11 @@ describe('SvgChunkWebpackPlugin getFileName', () => { svgChunkWebpackPlugin.options.filename = '[name].[chunkhash].svg'; const result = svgChunkWebpackPlugin.getFileName({ - entryName: 'app-a', - output: spritesFixture['app-a'] + entryName: 'home', + output: spritesFixture.home }); - expect(result).toBe('app-a.beb18939e5093045258b8d24a34dd844.svg'); + expect(result).toBe('home.beb18939e5093045258b8d24a34dd844.svg'); }); it('Should call the getFileName function with [contenthash]', () => { @@ -445,11 +443,11 @@ describe('SvgChunkWebpackPlugin getFileName', () => { svgChunkWebpackPlugin.options.filename = '[name].[contenthash].svg'; const result = svgChunkWebpackPlugin.getFileName({ - entryName: 'app-a', - output: spritesFixture['app-a'] + entryName: 'home', + output: spritesFixture.home }); - expect(result).toBe('app-a.a5934d97b38c748213317d7e5ffd31b6.svg'); + expect(result).toBe('home.a5934d97b38c748213317d7e5ffd31b6.svg'); }); }); @@ -465,15 +463,15 @@ describe('SvgChunkWebpackPlugin getBuildHash', () => { describe('SvgChunkWebpackPlugin getChunkHash', () => { it('Should call the getChunkHash function', () => { svgChunkWebpackPlugin.compilation = compilationWebpack; - const result = svgChunkWebpackPlugin.getChunkHash('app-a'); + const result = svgChunkWebpackPlugin.getChunkHash('home'); expect(result).toBe('beb18939e5093045258b8d24a34dd844'); }); it('Should call the getChunkHash function without chunks[0]', () => { svgChunkWebpackPlugin.compilation = compilationWebpack; - svgChunkWebpackPlugin.compilation.entrypoints.get('app-a').chunks = []; - const result = svgChunkWebpackPlugin.getChunkHash('app-a'); + svgChunkWebpackPlugin.compilation.entrypoints.get('home').chunks = []; + const result = svgChunkWebpackPlugin.getChunkHash('home'); expect(result).toBe(''); }); @@ -482,10 +480,10 @@ describe('SvgChunkWebpackPlugin getChunkHash', () => { describe('SvgChunkWebpackPlugin getContentHash', () => { it('Should call the getContentHash function', () => { svgChunkWebpackPlugin.compilation = compilationWebpack; - const result = svgChunkWebpackPlugin.getContentHash(spritesFixture['app-a']); + const result = svgChunkWebpackPlugin.getContentHash(spritesFixture.home); expect(util.createHash).toHaveBeenCalledWith('md4'); - expect(util.update).toHaveBeenCalledWith(spritesFixture['app-a']); + expect(util.update).toHaveBeenCalledWith(spritesFixture.home); expect(util.digest).toHaveBeenCalledWith('hex'); expect(result).toBe('a5934d97b38c748213317d7e5ffd31b6'); }); @@ -496,7 +494,7 @@ describe('SvgChunkWebpackPlugin createSpritesManifest', () => { svgChunkWebpackPlugin.compilation = compilationWebpack; svgChunkWebpackPlugin.spritesManifest = { - 'app-a': [ + home: [ 'example/src/svgs/gradient.svg', 'example/src/svgs/video.svg', 'example/src/svgs/smiley-love.svg' diff --git a/src/__tests__/preview.test.js b/src/__tests__/preview.test.js index 5761b4c..1759bec 100644 --- a/src/__tests__/preview.test.js +++ b/src/__tests__/preview.test.js @@ -6,7 +6,7 @@ let previewSprite; beforeEach(() => { previewSprite = preview([ { - name: 'app-a', + name: 'home', content: '', svgs: ['gradient', 'video', 'smiley-love']