├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json └── spec ├── fixtures ├── app.css ├── entry.js └── exclude.css ├── index.spec.js └── support └── jasmine.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules/ 3 | /dist/ 4 | npm-debug.log 5 | /.idea/ 6 | *.iml 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "6" 5 | - "8" 6 | script: 7 | - npm test 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 James Ye 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Exclude Assets extension for the HTML Webpack Plugin 2 | ======================================== 3 | [![npm version](https://badge.fury.io/js/html-webpack-exclude-assets-plugin.svg)](https://badge.fury.io/js/html-webpack-exclude-assets-plugin) [![Build Status](https://travis-ci.org/jamesjieye/html-webpack-exclude-assets-plugin.svg?branch=master)](https://travis-ci.org/jamesjieye/html-webpack-exclude-assets-plugin) [![js-semistandard-style](https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg?style=flat-square)](https://github.com/Flet/semistandard) 4 | 5 | Enhances [html-webpack-plugin](https://github.com/ampedandwired/html-webpack-plugin) 6 | functionality by adding the `{excludeAssets: RegExp | [RegExp]}` option to allow you to exclude assets. 7 | 8 | When adding an entry with third party css files, for example, `style: ['bootstrap/dist/css/bootstrap.css']`, to webpack, the injected scripts include style.js or style.[chunkhash].js. The `excludeChunks` option of `html-webpack-plugin` will exclude both style.css and style.js. With this plugin, you can keep style.css in and style.js out by setting `excludeAssets: /style.*.js/`. 9 | 10 | You can also exclude CSS assets, for example, a theme CSS style, by setting `excludeAssets: /theme.*.css/`. 11 | 12 | Installation 13 | ------------ 14 | You must be running webpack on Node v4.0.0 or higher. 15 | 16 | Note: `style-loader`'s dependency `loader-utils` only supports Node v4.0.0 since v1.0. See `style-loader`'s [#174 pull request](https://github.com/webpack-contrib/style-loader/pull/174) for details. 17 | 18 | Install the plugin with npm: 19 | ```shell 20 | $ npm install --save-dev html-webpack-exclude-assets-plugin 21 | ``` 22 | 23 | 24 | Basic Usage 25 | ----------- 26 | Require the plugin in your webpack config: 27 | 28 | ```javascript 29 | var HtmlWebpackExcludeAssetsPlugin = require('html-webpack-exclude-assets-plugin'); 30 | ``` 31 | 32 | Add the plugin to your webpack config as follows: 33 | 34 | ```javascript 35 | plugins: [ 36 | new HtmlWebpackPlugin(), 37 | new HtmlWebpackExcludeAssetsPlugin() 38 | ] 39 | ``` 40 | 41 | The above configuration will actually do nothing due to the configuration defaults. 42 | 43 | When you set `excludeAssets` to an array of regular expressions or a single regular expression, the matched assets will be skipped when the chunks are injected into the HTML template. 44 | 45 | ```javascript 46 | plugins: [ 47 | new HtmlWebpackPlugin({ 48 | excludeAssets: [/style.*.js/] // exclude style.js or style.[chunkhash].js 49 | }), 50 | new HtmlWebpackExcludeAssetsPlugin() 51 | ] 52 | ``` 53 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var assert = require('assert'); 3 | 4 | function HtmlWebpackExcludeAssetsPlugin (options) { 5 | assert.equal(options, undefined, 'The HtmlWebpackExcludeAssetsPlugin does not accept any options'); 6 | this.PluginName = 'HtmlWebpackExcludeAssetsPlugin'; 7 | } 8 | 9 | HtmlWebpackExcludeAssetsPlugin.prototype.apply = function (compiler) { 10 | if ('hooks' in compiler) { 11 | // v4 approach: 12 | compiler.hooks.compilation.tap(this.PluginName, this.applyCompilation.bind(this)); 13 | } else { 14 | // legacy approach: 15 | // Hook into the html-webpack-plugin processing 16 | compiler.plugin('compilation', this.applyCompilation.bind(this)); 17 | } 18 | }; 19 | 20 | HtmlWebpackExcludeAssetsPlugin.prototype.applyCompilation = function applyCompilation (compilation) { 21 | var self = this; 22 | if ('hooks' in compilation) { 23 | // If our target hook is not present, throw a descriptive error 24 | if (!compilation.hooks.htmlWebpackPluginAlterAssetTags) { 25 | throw new Error('The expected HtmlWebpackPlugin hook was not found! Ensure HtmlWebpackPlugin is installed and' + 26 | ' was initialized before this plugin.'); 27 | } 28 | compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(this.PluginName, registerCb); 29 | } else { 30 | compilation.plugin('html-webpack-plugin-alter-asset-tags', registerCb); 31 | } 32 | function registerCb (htmlPluginData, callback) { 33 | var excludeAssets = htmlPluginData.plugin.options.excludeAssets; 34 | // Skip if the plugin configuration didn't set `excludeAssets` 35 | if (!excludeAssets) { 36 | if (callback) { 37 | return callback(null, htmlPluginData); 38 | } else { 39 | return Promise.resolve(htmlPluginData); 40 | } 41 | } 42 | 43 | if (excludeAssets.constructor !== Array) { 44 | excludeAssets = [excludeAssets]; 45 | } 46 | 47 | // Skip invalid RegExp patterns 48 | var excludePatterns = excludeAssets.filter(function (excludePattern) { 49 | return excludePattern.constructor === RegExp; 50 | }); 51 | 52 | var result = self.processAssets(excludePatterns, htmlPluginData); 53 | if (callback) { 54 | callback(null, result); 55 | } else { 56 | return Promise.resolve(result); 57 | } 58 | } 59 | }; 60 | 61 | HtmlWebpackExcludeAssetsPlugin.prototype.isExcluded = function (excludePatterns, assetPath) { 62 | return excludePatterns.filter(function (excludePattern) { 63 | return excludePattern.test(assetPath); 64 | }).length > 0; 65 | }; 66 | 67 | HtmlWebpackExcludeAssetsPlugin.prototype.processAssets = function (excludePatterns, pluginData) { 68 | var self = this; 69 | var body = []; 70 | var head = []; 71 | 72 | pluginData.head.forEach(function (tag) { 73 | if (!tag.attributes || !self.isExcluded(excludePatterns, tag.attributes.src || tag.attributes.href)) { 74 | head.push(tag); 75 | } 76 | }); 77 | 78 | pluginData.body.forEach(function (tag) { 79 | if (!tag.attributes || !self.isExcluded(excludePatterns, tag.attributes.src || tag.attributes.href)) { 80 | body.push(tag); 81 | } 82 | }); 83 | 84 | return { head: head, body: body, plugin: pluginData.plugin, chunks: pluginData.chunks, outputName: pluginData.outputName }; 85 | }; 86 | 87 | module.exports = HtmlWebpackExcludeAssetsPlugin; 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html-webpack-exclude-assets-plugin", 3 | "version": "0.0.7", 4 | "description": "Add the ability to exclude assets based on RegExp patterns", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js" 8 | ], 9 | "scripts": { 10 | "prepublish": "npm run test", 11 | "pretest": "semistandard", 12 | "test": "jasmine", 13 | "debug": "node-debug jasmine" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/jamesjieye/html-webpack-exclude-assets-plugin.git" 18 | }, 19 | "keywords": [ 20 | "webpack", 21 | "plugin", 22 | "html-webpack-plugin", 23 | "exclude", 24 | "assets" 25 | ], 26 | "engines": { 27 | "node": ">=4.0.0" 28 | }, 29 | "author": "James Ye (https://github.com/jamesjieye)", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/jamesjieye/html-webpack-exclude-assets-plugin/issues" 33 | }, 34 | "homepage": "https://github.com/jamesjieye/html-webpack-exclude-assets-plugin", 35 | "devDependencies": { 36 | "cheerio": "^0.22.0", 37 | "css-loader": "^0.25.0", 38 | "extract-text-webpack-plugin": "^1.0.1", 39 | "html-webpack-plugin": "^2.16.0", 40 | "jasmine": "^2.4.1", 41 | "rimraf": "^2.5.4", 42 | "semistandard": "^7.0.5", 43 | "style-loader": "^0.13.1", 44 | "webpack": "^1.13.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /spec/fixtures/app.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-size: 24px; 3 | } -------------------------------------------------------------------------------- /spec/fixtures/entry.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesjieye/html-webpack-exclude-assets-plugin/78eecaca6f3561c5986685a9c4829f6b263faf2c/spec/fixtures/entry.js -------------------------------------------------------------------------------- /spec/fixtures/exclude.css: -------------------------------------------------------------------------------- 1 | .exclude { 2 | font-size: 24px; 3 | } 4 | -------------------------------------------------------------------------------- /spec/index.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var cheerio = require('cheerio'); 5 | var webpack = require('webpack'); 6 | var rm_rf = require('rimraf'); 7 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 8 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 9 | var HtmlWebpackExcludeAssetsPlugin = require('../'); 10 | 11 | var OUTPUT_DIR = path.join(__dirname, '../dist'); 12 | 13 | describe('HtmlWebpackExcludeAssetsPlugin', function () { 14 | beforeEach(function (done) { 15 | rm_rf(OUTPUT_DIR, done); 16 | }); 17 | 18 | it('should not exclude assets by default', function (done) { 19 | webpack({ 20 | entry: { 21 | app: path.join(__dirname, 'fixtures', 'entry.js'), 22 | style: [path.join(__dirname, 'fixtures', 'app.css')] 23 | }, 24 | output: { 25 | path: OUTPUT_DIR, 26 | filename: '[name].js' 27 | }, 28 | module: { 29 | loaders: [{ test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader') }] 30 | }, 31 | plugins: [ 32 | new ExtractTextPlugin('[name].css'), 33 | new HtmlWebpackPlugin(), 34 | new HtmlWebpackExcludeAssetsPlugin() 35 | ] 36 | }, function (err) { 37 | expect(err).toBeFalsy(); 38 | var htmlFile = path.resolve(__dirname, '../dist/index.html'); 39 | fs.readFile(htmlFile, 'utf8', function (er, data) { 40 | expect(er).toBeFalsy(); 41 | var $ = cheerio.load(data); 42 | expect($('script[src="style.js"]').toString()).toBe(''); 43 | expect($('link[href="style.css"]').toString()).toBe(''); 44 | done(); 45 | }); 46 | }); 47 | }); 48 | 49 | it('should not get compilation warnings when skipping assets', function (done) { 50 | webpack({ 51 | entry: { 52 | app: path.join(__dirname, 'fixtures', 'entry.js'), 53 | style: [path.join(__dirname, 'fixtures', 'app.css')] 54 | }, 55 | output: { 56 | path: OUTPUT_DIR, 57 | filename: '[name].js' 58 | }, 59 | module: { 60 | loaders: [{ test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader') }] 61 | }, 62 | plugins: [ 63 | new ExtractTextPlugin('[name].css'), 64 | new HtmlWebpackPlugin(), 65 | new HtmlWebpackExcludeAssetsPlugin() 66 | ] 67 | }, function (err, stats) { 68 | expect(err).toBeFalsy(); 69 | expect(stats.compilation.warnings.length).toBe(0); 70 | done(); 71 | }); 72 | }); 73 | 74 | it('should exclude assets when regex matches asset file names', function (done) { 75 | webpack({ 76 | entry: { 77 | app: path.join(__dirname, 'fixtures', 'entry.js'), 78 | styleInclude: path.join(__dirname, 'fixtures', 'app.css'), 79 | styleExclude: path.join(__dirname, 'fixtures', 'exclude.css') 80 | }, 81 | output: { 82 | path: OUTPUT_DIR, 83 | filename: '[name].js' 84 | }, 85 | module: { 86 | loaders: [{ test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader') }] 87 | }, 88 | plugins: [ 89 | new ExtractTextPlugin('[name].css'), 90 | new HtmlWebpackPlugin({ 91 | excludeAssets: [/styleInclude.*.js/, /styleExclude.*.css/] 92 | }), 93 | new HtmlWebpackExcludeAssetsPlugin() 94 | ] 95 | }, function (err) { 96 | expect(err).toBeFalsy(); 97 | var htmlFile = path.resolve(__dirname, '../dist/index.html'); 98 | fs.readFile(htmlFile, 'utf8', function (er, data) { 99 | expect(er).toBeFalsy(); 100 | var $ = cheerio.load(data); 101 | expect($('script[src="styleInclude.js"]').toString()).toBe(''); 102 | expect($('link[href="styleInclude.css"]').toString()).toBe(''); 103 | expect($('link[href="styleExclude.css"]').toString()).toBe(''); 104 | done(); 105 | }); 106 | }); 107 | }); 108 | 109 | it('should exclude assets when only use one regex instead of an array', function (done) { 110 | webpack({ 111 | entry: { 112 | app: path.join(__dirname, 'fixtures', 'entry.js'), 113 | style: [path.join(__dirname, 'fixtures', 'app.css')] 114 | }, 115 | output: { 116 | path: OUTPUT_DIR, 117 | filename: '[name].js' 118 | }, 119 | module: { 120 | loaders: [{ test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader') }] 121 | }, 122 | plugins: [ 123 | new ExtractTextPlugin('[name].css'), 124 | new HtmlWebpackPlugin({ 125 | excludeAssets: /style.*.js/ 126 | }), 127 | new HtmlWebpackExcludeAssetsPlugin() 128 | ] 129 | }, function (err) { 130 | expect(err).toBeFalsy(); 131 | var htmlFile = path.resolve(__dirname, '../dist/index.html'); 132 | fs.readFile(htmlFile, 'utf8', function (er, data) { 133 | expect(er).toBeFalsy(); 134 | var $ = cheerio.load(data); 135 | expect($('script[src="style.js"]').toString()).toBe(''); 136 | expect($('link[href="style.css"]').toString()).toBe(''); 137 | done(); 138 | }); 139 | }); 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "stopSpecOnExpectationFailure": false, 7 | "random": true 8 | } 9 | --------------------------------------------------------------------------------