├── .all-contributorsrc ├── .autorc ├── .circleci ├── config.yml └── semver-check.sh ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .npmrc ├── CHANGELOG.md ├── README.md ├── example ├── README.md ├── entry.js └── webpack.config.js ├── husky.config.js ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── __tests__ │ └── main.test.ts ├── main.ts └── webpack-inject-plugin.loader.ts └── tsconfig.json /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "webpack-inject-plugin", 3 | "projectOwner": "adierkens", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": true, 11 | "contributors": [ 12 | { 13 | "login": "adierkens", 14 | "name": "Adam Dierkens", 15 | "avatar_url": "https://avatars1.githubusercontent.com/u/13004162?v=4", 16 | "profile": "https://adamdierkens.com", 17 | "contributions": [ 18 | "code" 19 | ] 20 | }, 21 | { 22 | "login": "YellowKirby", 23 | "name": "YellowKirby", 24 | "avatar_url": "https://avatars1.githubusercontent.com/u/1654019?v=4", 25 | "profile": "https://github.com/YellowKirby", 26 | "contributions": [ 27 | "code" 28 | ] 29 | }, 30 | { 31 | "login": "chvin", 32 | "name": "Chvin", 33 | "avatar_url": "https://avatars2.githubusercontent.com/u/5383506?v=4", 34 | "profile": "https://github.com/chvin", 35 | "contributions": [ 36 | "code" 37 | ] 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /.autorc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Adam Dierkens", 3 | "email": "adam@dierkens.com" 4 | } 5 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | 7 | general: 8 | artifacts: coverage/ 9 | 10 | defaults: &defaults 11 | working_directory: ~/inject 12 | docker: 13 | - image: circleci/node:latest-browsers 14 | 15 | jobs: 16 | build: 17 | <<: *defaults 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | # Find a cache corresponding to this specific package.json checksum 26 | # when this file is changed, this key will fail 27 | - inject-{{ .Branch }}-{{ checksum "package-lock.json" }}-{{ checksum ".circleci/config.yml" }} 28 | - inject-{{ .Branch }}-{{ checksum "package-lock.json" }} 29 | - inject-{{ .Branch }} 30 | # Find the most recent cache used from any branch 31 | - inject-master 32 | - inject- 33 | 34 | - run: npm ci 35 | 36 | - run: npm run build 37 | # - run: bash ./.circleci/semver-check.sh 38 | 39 | - save_cache: 40 | key: inject-{{ .Branch }}-{{ checksum "package-lock.json" }}-{{ checksum ".circleci/config.yml" }} 41 | paths: 42 | - ~/.cache/yarn 43 | - node_modules 44 | - persist_to_workspace: 45 | root: . 46 | paths: 47 | - . 48 | 49 | lint: 50 | <<: *defaults 51 | steps: 52 | - attach_workspace: 53 | at: ~/inject 54 | - run: 55 | name: 'Lint' 56 | command: npm run lint 57 | 58 | test: 59 | <<: *defaults 60 | steps: 61 | - attach_workspace: 62 | at: ~/inject 63 | - run: 64 | name: 'Test' 65 | command: npm run test 66 | environment: 67 | JEST_JUNIT_OUTPUT: 'coverage/junitjunit.xml' 68 | - store_test_results: 69 | path: coverage/junit 70 | - store_artifacts: 71 | path: coverage/junit 72 | - run: 73 | name: Send CodeCov Results 74 | command: bash <(curl -s https://codecov.io/bash) -t $CODECOV_KEY 75 | 76 | integration: 77 | <<: *defaults 78 | steps: 79 | - attach_workspace: 80 | at: ~/inject 81 | - run: 82 | name: 'Integration Test' 83 | command: npm run test:example 84 | 85 | release: 86 | <<: *defaults 87 | steps: 88 | - attach_workspace: 89 | at: ~/inject 90 | - run: mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config 91 | - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 92 | - run: 93 | name: Release 94 | command: npm run release 95 | 96 | workflows: 97 | version: 2 98 | build_and_test: 99 | jobs: 100 | - build: 101 | filters: 102 | tags: 103 | only: /.*/ 104 | - lint: 105 | requires: 106 | - build 107 | filters: 108 | tags: 109 | only: /.*/ 110 | - test: 111 | requires: 112 | - build 113 | filters: 114 | tags: 115 | only: /.*/ 116 | - integration: 117 | requires: 118 | - build 119 | filters: 120 | tags: 121 | only: /.*/ 122 | - release: 123 | requires: 124 | - integration 125 | - test 126 | filters: 127 | branches: 128 | only: 129 | - master 130 | -------------------------------------------------------------------------------- /.circleci/semver-check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ ! -z "$CIRCLE_PR_NUMBER" ]; then 4 | auto pr-check --pr "$CIRCLE_PR_NUMBER" --url "$CIRCLE_PULL_REQUEST" 5 | fi 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { "node": true, "es6": true }, 3 | "extends": ["eslint:recommended", "xo", "prettier"], 4 | "parser": "@typescript-eslint/parser", 5 | "parserOptions": { 6 | "project": "./tsconfig.json", 7 | "sourceType": "module" 8 | }, 9 | "plugins": [ 10 | "@typescript-eslint", 11 | "prettier", 12 | "eslint-plugin-no-explicit-type-exports" 13 | ], 14 | "settings": { 15 | "import/resolver": { 16 | "node": { 17 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 18 | } 19 | } 20 | }, 21 | "rules": { 22 | "no-explicit-type-exports/no-explicit-type-exports": 2, 23 | "no-unused-vars": ["off"], 24 | "no-undef": ["off"], 25 | "no-unused-expressions": ["off"] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | *.log 3 | .idea 4 | .DS_Store 5 | node_modules 6 | coverage 7 | .nyc_output 8 | *.log 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /* 2 | !src/ 3 | !dist/**/* 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.5.5 (Thu Nov 19 2020) 2 | 3 | #### 🐛 Bug Fix 4 | 5 | - move types packages to dev dependencies [#51](https://github.com/adierkens/webpack-inject-plugin/pull/51) ([@spentacular](https://github.com/spentacular)) 6 | 7 | #### Authors: 1 8 | 9 | - Spencer Hamm ([@spentacular](https://github.com/spentacular)) 10 | 11 | --- 12 | 13 | # v1.5.4 (Mon Jan 06 2020) 14 | 15 | #### 🐛 Bug Fix 16 | 17 | - Fix async and dynamic entry functions [#42](https://github.com/adierkens/webpack-inject-plugin/pull/42) (adierkens@users.noreply.github.com) 18 | 19 | #### Authors: 1 20 | 21 | - adierkens@users.noreply.github.com 22 | 23 | --- 24 | 25 | # v1.5.3 (Thu Aug 01 2019) 26 | 27 | #### 🐛 Bug Fix 28 | 29 | - Fix if entry is a function [#31](https://github.com/adierkens/webpack-inject-plugin/pull/31) ([@chvin](https://github.com/chvin)) 30 | 31 | #### Authors: 1 32 | 33 | - Chvin ([@chvin](https://github.com/chvin)) 34 | 35 | --- 36 | 37 | # v1.5.2 (Fri Jun 14 2019) 38 | 39 | #### 🐛 Bug Fix 40 | 41 | - fix: export ENTRY_ORDER [#30](https://github.com/adierkens/webpack-inject-plugin/pull/30) ([@deini](https://github.com/deini)) 42 | 43 | #### Authors: 1 44 | 45 | - Daniel Almaguer ([@deini](https://github.com/deini)) 46 | 47 | --- 48 | 49 | # v1.5.1 (Wed Jun 12 2019) 50 | 51 | #### 🐛 Bug Fix 52 | 53 | - fix: entryOrder First [#29](https://github.com/adierkens/webpack-inject-plugin/pull/29) ([@deini](https://github.com/deini)) 54 | 55 | #### 📝 Documentation 56 | 57 | - Add loaderID to README [#28](https://github.com/adierkens/webpack-inject-plugin/pull/28) ([@adierkens](https://github.com/adierkens)) 58 | 59 | #### Authors: 2 60 | 61 | - Daniel Almaguer ([@deini](https://github.com/deini)) 62 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 63 | 64 | --- 65 | 66 | # v1.5.0 (Thu Apr 25 2019) 67 | 68 | #### 🚀 Enhancement 69 | 70 | - feat(plugin): Change how ID are handled for the injected loader [#27](https://github.com/adierkens/webpack-inject-plugin/pull/27) ([@mems](https://github.com/mems)) 71 | 72 | #### 📝 Documentation 73 | 74 | - fix(doc): rename entry order option name [#26](https://github.com/adierkens/webpack-inject-plugin/pull/26) ([@mems](https://github.com/mems)) 75 | 76 | #### ⚠️ Pushed to master 77 | 78 | - Fix type resolution for id ([@adierkens](https://github.com/adierkens)) 79 | 80 | #### Authors: 2 81 | 82 | - Memmie Lenglet ([@mems](https://github.com/mems)) 83 | - [@adierkens](https://github.com/adierkens) 84 | 85 | --- 86 | 87 | # v1.2.0 (Fri Apr 12 2019) 88 | 89 | #### 🚀 Enhancement 90 | 91 | - Add support for a function as an entryName [#23](https://github.com/adierkens/webpack-inject-plugin/pull/23) ([@adierkens](https://github.com/adierkens)) 92 | 93 | #### ⚠️ Pushed to master 94 | 95 | - bump pkg.version ([@adierkens](https://github.com/adierkens)) 96 | - Update deps ([@adierkens](https://github.com/adierkens)) 97 | - Bump auto. Fix pkg version ([@adierkens](https://github.com/adierkens)) 98 | - Fix the pkg version ([@adierkens](https://github.com/adierkens)) 99 | 100 | #### Authors: 1 101 | 102 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) 103 | 104 | --- 105 | 106 | # v1.2.0 (Fri Apr 12 2019) 107 | 108 | #### 🚀 Enhancement 109 | 110 | - Add support for a function as an entryName [#23](https://github.com/adierkens/webpack-inject-plugin/pull/23) ([@adierkens](https://github.com/adierkens)) 111 | 112 | #### ⚠️ Pushed to master 113 | 114 | - Update deps ([@adierkens](https://github.com/adierkens)) 115 | - Bump auto. Fix pkg version ([@adierkens](https://github.com/adierkens)) 116 | - Fix the pkg version ([@adierkens](https://github.com/adierkens)) 117 | 118 | #### Authors: 1 119 | 120 | - Adam Dierkens ([@adierkens](https://github.com/adierkens)) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webpack-inject-plugin 2 | 3 | [![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors) 4 | [![CircleCI](https://circleci.com/gh/adierkens/webpack-inject-plugin/tree/master.svg?style=svg)](https://circleci.com/gh/adierkens/webpack-inject-plugin/tree/master) [![npm version](https://badge.fury.io/js/webpack-inject-plugin.svg)](https://badge.fury.io/js/webpack-inject-plugin) [![npm](https://img.shields.io/npm/dt/webpack-inject-plugin.svg)](https://www.npmjs.com/package/webpack-inject-plugin) [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/xojs/xo) [![codecov](https://codecov.io/gh/adierkens/webpack-inject-plugin/branch/master/graph/badge.svg)](https://codecov.io/gh/adierkens/webpack-inject-plugin) 5 | 6 | A webpack plugin to dynamically inject code into the bundle. 7 | 8 | You can check out an example [here](./example) 9 | 10 | ## Usage 11 | 12 | ```javascript 13 | # webpack.config.js 14 | 15 | const InjectPlugin = require('webpack-inject-plugin').default; 16 | 17 | 18 | module.exports = { 19 | // Other stuff in your webpack config 20 | 21 | plugins: [ 22 | new InjectPlugin(function() { 23 | return "console.log('Hello World');" 24 | }); 25 | ] 26 | }; 27 | ``` 28 | 29 | This webpack plugin accepts a single argument, a function to which returns the code to inject into the bundle. 30 | 31 | The function is called using the same context as the loader, so everything [here](https://webpack.js.org/api/loaders/#the-loader-context) applies. 32 | 33 | You can either return the raw content to load, or a `Promise` which resolves to the content, if you wish to be async. 34 | 35 | 36 | ### options 37 | 38 | You can also pass in more options: 39 | 40 | ```javascript 41 | import InjectPlugin, { ENTRY_ORDER } from 'webpack-inject-plugin'; 42 | 43 | new InjectPlugin(loader, { 44 | entryName: 'entry name', // Limit the injected code to only the entry w/ this name 45 | entryOrder: ENTRY_ORDER.First // Make the injected code be the first entry point 46 | ENTRY_ORDER.Last // Make the injected code be the last entry point 47 | ENTRY_ORDER.NotLast // Make the injected code be second to last. (The last entry module is the API of the bundle. Useful when you don't want to override that.) This is the default. 48 | }); 49 | ``` 50 | 51 | ### options.entryName 52 | 53 | > `string` | `function` 54 | 55 | A filter for which entries to inject code into. 56 | If a `string`, only an entry with the same name will be used. 57 | If a `function`, it will be called with each entry name -- and only inject code for each _truthy_ response 58 | 59 | ex. 60 | 61 | ```javascript 62 | new InjectPlugin(loader, { 63 | // This will inject code into every entry that's not named `foo` 64 | entryName: key => key !== 'foo' 65 | }); 66 | ``` 67 | 68 | ### options.loaderID 69 | 70 | > `string` 71 | 72 | An optional uniquie ID for the injected loader. If omitted, one will automatically be generated for you. 73 | 74 | ## Additional Use Cases 75 | 76 | Though this could be used as a standalone plugin, you could also use it to create other webpack plugins, such as injecting code into the build based on a config file. 77 | 78 | Example: 79 | 80 | ```javascript 81 | import InjectPlugin from 'webpack-inject-plugin'; 82 | 83 | function customLoader(options) { 84 | return () => { 85 | return "console.log('My custom code generated from `options`');"; 86 | }; 87 | } 88 | 89 | export default class MyPlugin { 90 | constructor(options) { 91 | this.options = options; 92 | } 93 | 94 | apply(compiler) { 95 | new InjectPlugin(customLoader(this.options)).apply(compiler); 96 | } 97 | } 98 | ``` 99 | 100 | ## Contributors 101 | 102 | Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)): 103 | 104 | 105 | 106 | | [Adam Dierkens
Adam Dierkens](https://adamdierkens.com)
[💻](https://github.com/adierkens/webpack-inject-plugin/commits?author=adierkens "Code") | [YellowKirby
YellowKirby](https://github.com/YellowKirby)
[💻](https://github.com/adierkens/webpack-inject-plugin/commits?author=YellowKirby "Code") | [Chvin
Chvin](https://github.com/chvin)
[💻](https://github.com/adierkens/webpack-inject-plugin/commits?author=chvin "Code") | 107 | | :---: | :---: | :---: | 108 | 109 | 110 | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! 111 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | A small sample project to show off `webpack-inject-plugin` in action. 2 | 3 | 4 | - Run `webpack` in this directory 5 | 6 | - Inspect `dist/main.js` for the: 7 | ```javascript 8 | console.log('hello world'); 9 | ``` 10 | statement at the end of the bundle: 11 | 12 | ```bash 13 | cat dist/main.js | grep 'console' 14 | ``` 15 | -------------------------------------------------------------------------------- /example/entry.js: -------------------------------------------------------------------------------- 1 | // Insert some good codes here 2 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const InjectPlugin = require('..').default; 2 | 3 | module.exports = { 4 | entry: './entry.js', 5 | mode: 'development', 6 | plugins: [ 7 | new InjectPlugin(() => `console.log('hello world');`), 8 | new InjectPlugin(() => `console.log('second injected code');`) 9 | ] 10 | }; 11 | -------------------------------------------------------------------------------- /husky.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'pre-commit': 'lint-staged' 3 | }; 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['ts', 'js'], 3 | transform: { 4 | '^.+\\.(ts|tsx)$': 'ts-jest' 5 | }, 6 | globals: { 7 | 'ts-jest': { 8 | tsConfig: 'tsconfig.json' 9 | } 10 | }, 11 | collectCoverage: true, 12 | testPathIgnorePatterns: ['node_modules', 'dist'], 13 | coverageDirectory: './coverage', 14 | coverageReporters: ['cobertura', 'html', 'lcov'] 15 | }; 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-inject-plugin", 3 | "version": "1.5.5", 4 | "description": "A webpack plugin to dynamically inject code into the bundle.", 5 | "main": "dist/main.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "build:watch": "tsc --watch", 9 | "lint": "eslint ./src --ext .js,.ts", 10 | "test": "jest", 11 | "test:example": "cd example && webpack && npm run test:example:first && npm run test:example:second", 12 | "test:example:first": "cd example && cat dist/main.js | grep \"console.log('hello world')\"", 13 | "test:example:second": "cd example && cat dist/main.js | grep \"console.log('second injected code')\"", 14 | "test:watch": "npm run test -- --watch", 15 | "contributors:add": "all-contributors add", 16 | "contributors:generate": "all-contributors generate", 17 | "release": "auto shipit -v" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/adierkens/webpack-inject-plugin.git" 22 | }, 23 | "keywords": [ 24 | "webpack", 25 | "plugin", 26 | "loader", 27 | "inject" 28 | ], 29 | "publishConfig": { 30 | "registry": "https://registry.npmjs.org" 31 | }, 32 | "author": "Adam Dierkens ", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/adierkens/webpack-inject-plugin/issues" 36 | }, 37 | "homepage": "https://github.com/adierkens/webpack-inject-plugin#readme", 38 | "peerDependencies": { 39 | "webpack": ">=4.0.0" 40 | }, 41 | "dependencies": { 42 | "loader-utils": "~1.2.3" 43 | }, 44 | "devDependencies": { 45 | "@types/jest": "^23.3.5", 46 | "@types/loader-utils": "^1.1.3", 47 | "@types/webpack": "^4.4.17", 48 | "@typescript-eslint/parser": "2.2.0", 49 | "@typescript-eslint/eslint-plugin": "^2.5.0", 50 | "all-contributors-cli": "^5.4.0", 51 | "auto": "^4.8.14", 52 | "husky": "^1.1.2", 53 | "jest": "^24.7.1", 54 | "lint-staged": "^7.3.0", 55 | "prettier": "^1.14.3", 56 | "ts-jest": "^23.10.4", 57 | "eslint": "6.3.0", 58 | "eslint-config-prettier": "6.2.0", 59 | "eslint-plugin-no-explicit-type-exports": "0.10.10", 60 | "eslint-plugin-prettier": "^3.1.1", 61 | "eslint-config-xo": "0.26.0", 62 | "tsutils": "~3.1.0", 63 | "typescript": "~3.1.3", 64 | "webpack": "~4.21.0", 65 | "webpack-cli": "~3.1.2" 66 | }, 67 | "lint-staged": { 68 | "*.{js,json,css,md}": [ 69 | "prettier --write", 70 | "git add" 71 | ], 72 | "*.{ts,tsx}": [ 73 | "prettier --parser typescript --write", 74 | "npm run lint", 75 | "git add" 76 | ] 77 | }, 78 | "prettier": { 79 | "singleQuote": true 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/__tests__/main.test.ts: -------------------------------------------------------------------------------- 1 | import { EntryFunc } from 'webpack'; 2 | import { injectEntry, ENTRY_ORDER } from '../main'; 3 | 4 | describe('injectEntry', () => { 5 | it('appends to the entry config correctly', () => { 6 | expect(injectEntry(undefined, 'foo', {})).toEqual('foo'); 7 | expect(injectEntry(['original'], 'added', {})).toEqual([ 8 | 'added', 9 | 'original' 10 | ]); 11 | expect(injectEntry('original', 'added', {})).toEqual(['added', 'original']); 12 | expect(injectEntry(['foo', 'bar'], 'baz', {})).toEqual([ 13 | 'foo', 14 | 'baz', 15 | 'bar' 16 | ]); 17 | expect(injectEntry(['foo', 'bar', 'baz', 'blah'], 'aaa', {})).toEqual([ 18 | 'foo', 19 | 'bar', 20 | 'baz', 21 | 'aaa', 22 | 'blah' 23 | ]); 24 | expect( 25 | injectEntry( 26 | { 27 | foo: 'bar', 28 | another: ['an', 'array'] 29 | }, 30 | 'added', 31 | {} 32 | ) 33 | ).toEqual({ 34 | foo: ['added', 'bar'], 35 | another: ['an', 'added', 'array'] 36 | }); 37 | 38 | // This dynamic entry function will return {foo: bar} on first call, then {foo: baz} on the next call 39 | let first = true; 40 | const entryFunc = injectEntry( 41 | async () => { 42 | return { foo: first ? 'bar' : 'baz' }; 43 | }, 44 | 'added', 45 | {} 46 | ) as EntryFunc; 47 | 48 | expect(entryFunc()).resolves.toEqual({ foo: ['added', 'bar'] }); 49 | first = false; 50 | expect(entryFunc()).resolves.toEqual({ foo: ['added', 'baz'] }); 51 | }); 52 | 53 | it('appends to only the specified entry', () => { 54 | expect(injectEntry(undefined, 'foo', { entryName: 'bar' })).toBe('foo'); 55 | expect( 56 | injectEntry({ foo: 'bar', bar: 'baz' }, 'added', { entryName: 'bar' }) 57 | ).toEqual({ 58 | foo: 'bar', 59 | bar: ['added', 'baz'] 60 | }); 61 | }); 62 | 63 | it('supports a filter function', () => { 64 | expect( 65 | injectEntry({ foo: 'bar', bar: 'baz', baz: 'blah' }, 'added', { 66 | entryName: e => e !== 'bar' 67 | }) 68 | ).toEqual({ 69 | foo: ['added', 'bar'], 70 | bar: 'baz', 71 | baz: ['added', 'blah'] 72 | }); 73 | }); 74 | 75 | it('throws error for unknown filter type', () => { 76 | expect(() => { 77 | injectEntry('bar', 'foo', { 78 | entryName: { not: 'a function ' } as any 79 | }); 80 | }).toThrowError(); 81 | }); 82 | 83 | it('respects the config for ordering', () => { 84 | expect( 85 | injectEntry(['foo', 'bar'], 'baz', { entryOrder: ENTRY_ORDER.First }) 86 | ).toEqual(['baz', 'foo', 'bar']); 87 | expect( 88 | injectEntry(['foo', 'bar'], 'baz', { entryOrder: ENTRY_ORDER.Last }) 89 | ).toEqual(['foo', 'bar', 'baz']); 90 | expect( 91 | injectEntry(['foo', 'bar'], 'baz', { entryOrder: ENTRY_ORDER.NotLast }) 92 | ).toEqual(['foo', 'baz', 'bar']); 93 | }); 94 | 95 | it('order config for strings', () => { 96 | expect( 97 | injectEntry('original', 'new', { entryOrder: ENTRY_ORDER.First }) 98 | ).toEqual(['new', 'original']); 99 | expect( 100 | injectEntry('original', 'new', { entryOrder: ENTRY_ORDER.Last }) 101 | ).toEqual(['original', 'new']); 102 | expect( 103 | injectEntry('original', 'new', { entryOrder: ENTRY_ORDER.NotLast }) 104 | ).toEqual(['new', 'original']); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { randomBytes } from 'crypto'; 2 | import path from 'path'; 3 | import { Compiler, Entry, EntryFunc } from 'webpack'; 4 | 5 | type EntryType = string | string[] | Entry | EntryFunc; 6 | type EntryFilterFunction = (entryName: string) => boolean; 7 | type EntryFilterType = string | EntryFilterFunction; 8 | 9 | const FAKE_LOADER_NAME = 'webpack-inject-plugin.loader'; 10 | 11 | export type Loader = () => string; 12 | 13 | export const registry: { 14 | [key: string]: Loader; 15 | } = {}; 16 | 17 | let uniqueIDCounter = 0; 18 | function getUniqueID() { 19 | const id = (++uniqueIDCounter).toString(16); 20 | 21 | return `webpack-inject-module-${id}`; 22 | } 23 | 24 | export enum ENTRY_ORDER { 25 | First = 1, 26 | Last, 27 | NotLast 28 | } 29 | 30 | export interface IInjectOptions { 31 | entryName?: EntryFilterType; 32 | entryOrder?: ENTRY_ORDER; 33 | loaderID?: string; 34 | } 35 | 36 | function injectToArray( 37 | originalEntry: string[], 38 | newEntry: string, 39 | entryOrder = ENTRY_ORDER.NotLast 40 | ): string[] { 41 | if (entryOrder === ENTRY_ORDER.First) { 42 | return [newEntry, ...originalEntry]; 43 | } 44 | 45 | if (entryOrder === ENTRY_ORDER.Last) { 46 | return [...originalEntry, newEntry]; 47 | } 48 | 49 | return [ 50 | ...originalEntry.splice(0, originalEntry.length - 1), 51 | newEntry, 52 | ...originalEntry.splice(originalEntry.length - 1) 53 | ]; 54 | } 55 | 56 | function createEntryFilter( 57 | filterOption?: EntryFilterType 58 | ): EntryFilterFunction { 59 | if (filterOption === null || filterOption === undefined) { 60 | return () => true; 61 | } 62 | 63 | if (typeof filterOption === 'string') { 64 | return (entryName: string) => filterOption === entryName; 65 | } 66 | 67 | if (typeof filterOption === 'function') { 68 | return filterOption; 69 | } 70 | 71 | throw new Error(`Unknown entry filter: ${typeof filterOption}`); 72 | } 73 | 74 | export function injectEntry( 75 | originalEntry: EntryType | undefined, 76 | newEntry: string, 77 | options: IInjectOptions 78 | ): EntryType { 79 | if (originalEntry === undefined) { 80 | return newEntry; 81 | } 82 | 83 | const filterFunc = createEntryFilter(options.entryName); 84 | 85 | // Last module in an array gets exported, so the injected one must not be 86 | // last. https://webpack.github.io/docs/configuration.html#entry 87 | 88 | if (typeof originalEntry === 'string') { 89 | return injectToArray([originalEntry], newEntry, options.entryOrder); 90 | } 91 | 92 | if (Array.isArray(originalEntry)) { 93 | return injectToArray(originalEntry, newEntry, options.entryOrder); 94 | } 95 | 96 | if (typeof originalEntry === 'function') { 97 | // The entry function is meant to be called on each compilation (when using --watch, webpack-dev-server) 98 | // We wrap the original function in our own function to reflect this behavior. 99 | return async () => { 100 | const callbackOriginEntry = await originalEntry(); 101 | 102 | // Safe type-cast here because callbackOriginEntry cannot be an EntryFunc, 103 | // so the injectEntry call won't return one either. 104 | return injectEntry(callbackOriginEntry, newEntry, options) as Exclude 105 | }; 106 | } 107 | 108 | if (Object.prototype.toString.call(originalEntry).slice(8, -1) === 'Object') { 109 | return Object.entries(originalEntry).reduce( 110 | (a: Record, [key, entry]) => { 111 | if (filterFunc(key)) { 112 | a[key] = injectEntry(entry, newEntry, options); 113 | } else { 114 | a[key] = entry; 115 | } 116 | 117 | return a; 118 | }, 119 | {} 120 | ); 121 | } 122 | 123 | return originalEntry; 124 | } 125 | 126 | export default class WebpackInjectPlugin { 127 | private readonly options: IInjectOptions; 128 | 129 | private readonly loader: Loader; 130 | 131 | constructor(loader: Loader, options?: IInjectOptions) { 132 | this.loader = loader; 133 | this.options = { 134 | entryName: (options && options.entryName) || undefined, 135 | entryOrder: (options && options.entryOrder) || ENTRY_ORDER.NotLast, 136 | loaderID: (options && options.loaderID) || getUniqueID() 137 | }; 138 | } 139 | 140 | apply(compiler: Compiler) { 141 | const id = this.options.loaderID!; 142 | const newEntry = path.resolve(__dirname, `${FAKE_LOADER_NAME}?id=${id}!`); 143 | 144 | registry[id] = this.loader; 145 | 146 | compiler.options.entry = injectEntry( 147 | compiler.options.entry, 148 | newEntry, 149 | this.options 150 | ); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/webpack-inject-plugin.loader.ts: -------------------------------------------------------------------------------- 1 | import loaderUtils from 'loader-utils'; 2 | import { loader } from 'webpack'; 3 | import { registry, Loader } from './main'; 4 | 5 | const injectLoader: loader.Loader = function(source: string | Buffer) { 6 | const options = loaderUtils.getOptions(this); 7 | 8 | let func: Loader = () => ''; 9 | if (registry[options.id]) { 10 | func = registry[options.id]; 11 | } 12 | 13 | const rtn: string | Promise = func.call(this, source); 14 | 15 | if (rtn instanceof Promise) { 16 | const callback = this.async(); 17 | rtn 18 | .then(result => { 19 | callback && callback(null, result); 20 | }) 21 | .catch(err => { 22 | callback && callback(err, undefined); 23 | }); 24 | return undefined; 25 | } 26 | 27 | return rtn; 28 | }; 29 | 30 | export default injectLoader; 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "target": "es5", 5 | "module": "commonjs", 6 | "strict": true, 7 | "allowSyntheticDefaultImports": true, 8 | "sourceMap": true, 9 | "declaration": true, 10 | "lib": ["es6", "es2017"], 11 | "esModuleInterop": true 12 | }, 13 | "include": ["./src/**/*"] 14 | } 15 | --------------------------------------------------------------------------------