├── .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 | [](#contributors)
4 | [](https://circleci.com/gh/adierkens/webpack-inject-plugin/tree/master) [](https://badge.fury.io/js/webpack-inject-plugin) [](https://www.npmjs.com/package/webpack-inject-plugin) [](https://github.com/xojs/xo) [](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](https://adamdierkens.com)
[💻](https://github.com/adierkens/webpack-inject-plugin/commits?author=adierkens "Code") | [
YellowKirby](https://github.com/YellowKirby)
[💻](https://github.com/adierkens/webpack-inject-plugin/commits?author=YellowKirby "Code") | [
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 |
--------------------------------------------------------------------------------