├── .editorconfig ├── .gitignore ├── README.md ├── package.json ├── type.d.ts └── wrapper-webpack-plugin.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [package.json] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A webpack plugin that wraps output files (chunks) with custom text or code. 2 | 3 | ## Installation 4 | 5 | Install locally using npm: 6 | `npm i -D wrapper-webpack-plugin` 7 | 8 | #### Webpack compatibility 9 | 10 | Version 2 of this plugin only works with webpack >=4.\ 11 | For webpack <4 use [version 1](https://github.com/levp/wrapper-webpack-plugin/tree/v1) of the plugin with `npm i -D wrapper-webpack-plugin@1` 12 | 13 | ## Usage 14 | 15 | The `WrapperPlugin` class has a single parameter, an object with a `header` and/or `footer` properties. Header text will 16 | be *prepended* to the output file, footer text will be *appended*. These can be either a string or a function. A string 17 | will simply be a appended/prepended to the file output. A function is expected to return a string, and will receive the 18 | name of the output file as an argument. 19 | 20 | An optional `test` property (a string or a `RegExp` object) can control which output files are affected; otherwise all output files will be wrapped. 21 | 22 | *New in 2.1:* 23 | The optional `afterOptimizations` property can be used to avoid having the added text affected by the optimization stage, e.g. if you don't want it to be minified. 24 | 25 | ## API 26 | 27 | ``` 28 | function WrapperPlugin({ 29 | test: string | RegExp, 30 | header: string | function, 31 | footer: string | function, 32 | afterOptimizations: bool // default: false 33 | }) 34 | ``` 35 | 36 | ## Example configuration #1 37 | 38 | Wraps bundle files with '.js' extension in a self invoking function and enables strict mode: 39 | 40 | ```javascript 41 | const WrapperPlugin = require('wrapper-webpack-plugin'); 42 | 43 | module.exports = { 44 | // other webpack config here 45 | 46 | plugins: [ 47 | // strict mode for the whole bundle 48 | new WrapperPlugin({ 49 | test: /\.js$/, // only wrap output of bundle files with '.js' extension 50 | header: '(function () { "use strict";\n', 51 | footer: '\n})();' 52 | }) 53 | ] 54 | }; 55 | ``` 56 | 57 | ## Example configuration #2 58 | 59 | Prepends bundles with a doc comment: 60 | 61 | ```javascript 62 | const WrapperPlugin = require('wrapper-webpack-plugin'); 63 | 64 | module.exports = { 65 | // other webpack config here 66 | 67 | plugins: [ 68 | new WrapperPlugin({ 69 | header: function (fileName) { 70 | return '/*! file: ' + fileName + ', created by dev123 */\n'; 71 | } 72 | }) 73 | ] 74 | }; 75 | ``` 76 | 77 | ## Example configuration #3 78 | 79 | Accessing file name, build hash, and chunk hash at runtime. 80 | 81 | ```javascript 82 | const WrapperPlugin = require('wrapper-webpack-plugin'); 83 | 84 | module.exports = { 85 | // other webpack config here 86 | 87 | output: { 88 | filename: '[name].[chunkhash].js' 89 | }, 90 | plugins: [ 91 | new WrapperPlugin({ 92 | header: `(function (FILE_NAME, BUILD_HASH, CHUNK_HASH) {`, 93 | footer(fileName, args) { 94 | return `})('${fileName}', '${args.hash}', '${args.chunkhash}');`; 95 | // note: args.hash and args.chunkhash correspond to the [hash] and [chunkhash] 96 | // placeholders you can specify in the output.filename option. 97 | } 98 | }) 99 | ] 100 | }; 101 | ``` 102 | 103 | ## Example configuration #4 104 | 105 | Keeping header in a separate file: 106 | 107 | file: `header.js` 108 | ```javascript 109 | /*! 110 | * my awesome app! 111 | */ 112 | ``` 113 | 114 | file: `webpack.config` 115 | ```javascript 116 | const fs = require('fs'); 117 | WrapperPlugin = require('wrapper-webpack-plugin'); 118 | 119 | const headerDoc = fs.readFileSync('./header.js', 'utf8'); 120 | 121 | module.exports = { 122 | // other webpack config here 123 | 124 | plugins: [ 125 | new WrapperPlugin({ 126 | header: headerDoc 127 | }) 128 | ] 129 | }; 130 | ``` 131 | 132 | ## Example configuration #5 133 | 134 | A slightly more complex example using `lodash` templates: 135 | 136 | ```javascript 137 | const WrapperPlugin = require('wrapper-webpack-plugin'); 138 | const template = require('lodash.template'); 139 | const pkg = require('./package.json'); 140 | 141 | const tpl = '/*! <%= name %> v<%= version %> | <%= author %> */\n'; 142 | 143 | module.exports = { 144 | // other webpack config here 145 | 146 | plugins: [ 147 | new WrapperPlugin({ 148 | header: template(tpl)(pkg) 149 | }) 150 | ] 151 | }; 152 | ``` 153 | 154 | ## Compatibility with other plugins 155 | 156 | This plugin should play nicely with most other plugins. 157 | E.g. adding the `webpack.optimize.UglifyJsPlugin` plugin to the plugins array *after* the `WrapperPlugin` will result in 158 | the wrapper text also being minified. 159 | 160 | ## License 161 | 162 | [ISC](https://opensource.org/licenses/ISC) 163 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wrapper-webpack-plugin", 3 | "version": "2.2.2", 4 | "description": "Wraps output files (chunks) with custom text or code.", 5 | "main": "wrapper-webpack-plugin.js", 6 | "types": "./type.d.ts", 7 | "author": "levp", 8 | "license": "ISC", 9 | "peerDependencies": { 10 | "webpack": ">=2" 11 | }, 12 | "files": [ 13 | "wrapper-webpack-plugin.js" 14 | ], 15 | "keywords": [ 16 | "webpack", 17 | "plugin" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/levp/wrapper-webpack-plugin.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/levp/wrapper-webpack-plugin/issues" 25 | }, 26 | "homepage": "https://github.com/levp/wrapper-webpack-plugin#readme" 27 | } 28 | -------------------------------------------------------------------------------- /type.d.ts: -------------------------------------------------------------------------------- 1 | import { Compiler } from "webpack"; 2 | 3 | type Factor = (filename: string, chunkHash: string) => string; 4 | 5 | export interface Options { 6 | /** Text that will be prepended to an output file. */ 7 | header?: string | Factor; 8 | /** Text that will be appended to an output file. */ 9 | footer?: string | Factor; 10 | /** Tested against output file names to check if they should be affected by this */ 11 | test?: string | RegExp; 12 | /** 13 | * Indicating whether this plugin should be activated before 14 | * (`false`) or after (`true`) the optimization stage. Example use case: Set this to true if you want to avoid 15 | * minification from affecting the text added by this plugin. 16 | */ 17 | afterOptimizations?: boolean; 18 | } 19 | 20 | export default class WrapperPlugin { 21 | constructor(options: Options); 22 | apply(compiler: Compiler): void; 23 | } 24 | -------------------------------------------------------------------------------- /wrapper-webpack-plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ModuleFilenameHelpers = require("webpack/lib/ModuleFilenameHelpers"); 4 | 5 | class WrapperPlugin { 6 | 7 | /** 8 | * @param {Object} args 9 | * @param {string | Function} [args.header] Text that will be prepended to an output file. 10 | * @param {string | Function} [args.footer] Text that will be appended to an output file. 11 | * @param {string | RegExp} [args.test] Tested against output file names to check if they should be affected by this 12 | * plugin. 13 | * @param {boolean} [args.afterOptimizations=false] Indicating whether this plugin should be activated before 14 | * (`false`) or after (`true`) the optimization stage. Example use case: Set this to true if you want to avoid 15 | * minification from affecting the text added by this plugin. 16 | */ 17 | constructor(args) { 18 | if (typeof args !== 'object') { 19 | throw new TypeError('Argument "args" must be an object.'); 20 | } 21 | 22 | this.header = args.hasOwnProperty('header') ? args.header : ''; 23 | this.footer = args.hasOwnProperty('footer') ? args.footer : ''; 24 | this.afterOptimizations = args.hasOwnProperty('afterOptimizations') ? !!args.afterOptimizations : false; 25 | this.test = args.hasOwnProperty('test') ? args.test : ''; 26 | } 27 | 28 | apply(compiler) { 29 | const ConcatSource = getSourceConcatenator(compiler); 30 | 31 | const header = this.header; 32 | const footer = this.footer; 33 | const tester = {test: this.test}; 34 | 35 | compiler.hooks.compilation.tap('WrapperPlugin', (compilation) => { 36 | if (this.afterOptimizations) { 37 | compilation.hooks.afterOptimizeChunkAssets.tap('WrapperPlugin', (chunks) => { 38 | wrapChunks(compilation, chunks, footer, header); 39 | }); 40 | } else { 41 | compilation.hooks.optimizeChunkAssets.tapAsync('WrapperPlugin', (chunks, done) => { 42 | wrapChunks(compilation, chunks, footer, header); 43 | done(); 44 | }); 45 | } 46 | }); 47 | 48 | function wrapFile(compilation, fileName, chunkHash) { 49 | const headerContent = (typeof header === 'function') ? header(fileName, chunkHash) : header; 50 | const footerContent = (typeof footer === 'function') ? footer(fileName, chunkHash) : footer; 51 | 52 | compilation.assets[fileName] = new ConcatSource( 53 | String(headerContent), 54 | compilation.assets[fileName], 55 | String(footerContent), 56 | ); 57 | } 58 | 59 | function wrapChunks(compilation, chunks) { 60 | for (const chunk of chunks) { 61 | const args = { 62 | hash: compilation.hash, 63 | chunkhash: chunk.hash, 64 | }; 65 | for (const fileName of chunk.files) { 66 | if (ModuleFilenameHelpers.matchObject(tester, fileName)) { 67 | wrapFile(compilation, fileName, args); 68 | } 69 | } 70 | } 71 | } // wrapChunks 72 | } 73 | } 74 | 75 | module.exports = WrapperPlugin; 76 | 77 | function getSourceConcatenator(compiler) { 78 | const webpack = compiler.webpack; 79 | if (webpack) { 80 | // webpack v5 81 | return webpack.sources.ConcatSource; 82 | } 83 | // webpack v4 84 | return require("webpack-sources").ConcatSource; 85 | } 86 | --------------------------------------------------------------------------------