├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── codeql.yml │ └── release.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .lintstagedrc ├── .npmignore ├── .nvmrc ├── .prettierrc ├── .versionrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __tests__ ├── HtmlInlineScriptPlugin.test.ts └── cases │ ├── escape-script-end-tag │ ├── expected │ │ └── index.html │ ├── fixtures │ │ ├── index.html │ │ └── index.js │ └── webpack.config.ts │ ├── filename-with-special-characters │ ├── expected │ │ └── index.html │ ├── fixtures │ │ ├── index.html │ │ └── index.js │ └── webpack.config.ts │ ├── html-inside-subfolder │ ├── expected │ │ └── frontend │ │ │ └── index.html │ ├── fixtures │ │ ├── index.html │ │ └── index.js │ └── webpack.config.ts │ ├── ignore-htmls │ ├── expected │ │ ├── index.html │ │ ├── index.js │ │ ├── page2.html │ │ └── page2.js │ ├── fixtures │ │ ├── index.html │ │ ├── index.js │ │ ├── page2.html │ │ └── page2.js │ └── webpack.config.ts │ ├── ignore-scripts-and-htmls │ ├── expected │ │ ├── index.html │ │ ├── index.js │ │ ├── page2.html │ │ └── page2.js │ ├── fixtures │ │ ├── index.html │ │ ├── index.js │ │ ├── page2.html │ │ └── page2.js │ └── webpack.config.ts │ ├── ignore-scripts │ ├── expected │ │ ├── index.html │ │ └── non-inline.js │ ├── fixtures │ │ ├── index.html │ │ ├── index.js │ │ └── non-inline.js │ └── webpack.config.ts │ ├── js-with-import │ ├── expected │ │ └── index.html │ ├── fixtures │ │ ├── app.js │ │ ├── index.html │ │ └── index.js │ └── webpack.config.ts │ ├── multiple-instance │ ├── expected │ │ ├── index.html │ │ └── page2.html │ ├── fixtures │ │ ├── index.html │ │ ├── index.js │ │ ├── page2.html │ │ └── page2.js │ └── webpack.config.ts │ ├── preserveAsset │ ├── expected │ │ ├── index.html │ │ └── ui.js │ ├── fixtures │ │ ├── index.html │ │ └── index.js │ └── webpack.config.ts │ ├── simple │ ├── expected │ │ └── index.html │ ├── fixtures │ │ ├── index.html │ │ └── index.js │ └── webpack.config.ts │ └── web-worker │ ├── expected │ ├── index.html │ └── test.worker.js │ ├── fixtures │ ├── index.html │ ├── index.js │ └── worker.js │ └── webpack.config.ts ├── commitlint.config.js ├── docs └── publish.md ├── jest.config.js ├── package.json ├── src ├── HtmlInlineScriptPlugin.ts ├── constants.ts └── index.ts ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | max_line_length = 0 13 | indent_size = 4 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | README.md 2 | __tests__/cases/**/dist 3 | __tests__/cases/**/expected 4 | dist 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": [ 4 | "@typescript-eslint" 5 | ], 6 | "env": { 7 | "browser": true, 8 | "node": true 9 | }, 10 | "extends": [ 11 | "eslint:recommended", 12 | "airbnb-base", 13 | "plugin:@typescript-eslint/eslint-recommended", 14 | "plugin:@typescript-eslint/recommended" 15 | ], 16 | "settings": { 17 | "import/resolver": { 18 | "node": { 19 | "extensions": [ 20 | ".js", 21 | ".ts", 22 | ".d.ts" 23 | ] 24 | } 25 | } 26 | }, 27 | "rules": { 28 | "no-param-reassign": "off", 29 | "comma-dangle": ["error", "never"], 30 | "import/no-extraneous-dependencies": ["error", { 31 | "optionalDependencies": false 32 | }], 33 | "import/extensions": ["error", { 34 | "js": "never", 35 | "ts": "never" 36 | }], 37 | "class-methods-use-this": "off" 38 | }, 39 | "overrides": [ 40 | { 41 | "plugins": ["jest"], 42 | "files": ["__tests__/**/*.test.js", "__tests__/**/*.test.ts"], 43 | "settings": { 44 | "import/resolver": { 45 | "node": { 46 | "extensions": [".js", ".json", ".ts"] 47 | } 48 | } 49 | }, 50 | "extends": [ 51 | "eslint:recommended", 52 | "airbnb-base", 53 | "plugin:jest/recommended", 54 | "plugin:@typescript-eslint/eslint-recommended", 55 | "plugin:@typescript-eslint/recommended" 56 | ], 57 | "env": { "node": true }, 58 | "rules": { 59 | "@typescript-eslint/no-var-requires": ["off"], 60 | "no-console": ["off"], 61 | "import/extensions": ["error", { 62 | "js": "never", 63 | "ts": "never" 64 | }] 65 | } 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## Environment and package version 5 | 6 | 7 | 8 | ## Reproduction link / code sample 9 | 10 | 11 | 12 | ## Steps to reproduce 13 | 14 | 15 | 16 | ## What is expected? 17 | 18 | 19 | 20 | ## What is actually happening? 21 | 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## Description 5 | 6 | 7 | 8 | ## How has this been tested? 9 | 10 | 11 | 12 | ## Types of changes 13 | 14 | - [ ] New feature - `feat` 15 | - [ ] Bug fix - `fix` 16 | - [ ] Refactor - `refactor` 17 | - [ ] Test cases - `test` 18 | - [ ] Other(s): 19 | 20 | ## Remarks 21 | 22 | N/A 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Basic dependabot.yml file with 2 | # https://docs.github.com/en/github/administering-a-repository/enabling-and-disabling-version-updates#example-dependabotyml-file 3 | 4 | version: 2 5 | updates: 6 | # checking for develop branch which contains v2 code of this plugin 7 | - package-ecosystem: "npm" 8 | directory: "/" 9 | schedule: 10 | interval: "monthly" 11 | target-branch: "develop" 12 | labels: 13 | - "dependencies" 14 | commit-message: 15 | prefix: "chore" 16 | prefix-development: "chore" 17 | include: "scope" 18 | reviewers: 19 | - "icelam" 20 | open-pull-requests-limit: 10 21 | - package-ecosystem: "github-actions" 22 | directory: "/" 23 | target-branch: "develop" 24 | schedule: 25 | interval: "monthly" 26 | commit-message: 27 | prefix: "ci" 28 | rebase-strategy: "auto" 29 | reviewers: 30 | - "icelam" 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - develop 7 | tags-ignore: 8 | - '**' #prevent double build on release tag 9 | pull_request: 10 | branches: 11 | - '**' 12 | 13 | jobs: 14 | test: 15 | name: Test 16 | strategy: 17 | matrix: 18 | node-version: [14.x, 16.x, 18.x, 20.x] 19 | # FIXME: replace macos-12 with macos-latest when https://github.com/actions/setup-node/issues/1017 is resolved 20 | os: [ubuntu-latest, windows-latest, macos-12] 21 | runs-on: ${{ matrix.os }} 22 | env: 23 | HUSKY: 0 24 | steps: 25 | - name: Checkout repo 26 | uses: actions/checkout@v4 27 | - name: Setup Node.js version ${{ matrix.node-version }} 28 | uses: actions/setup-node@v4 29 | with: 30 | node-version: ${{ matrix.node-version }} 31 | - name: Get yarn cache directory path 32 | id: yarn-cache-dir-path 33 | shell: bash 34 | run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT 35 | - name: Restore yarn cache 36 | uses: actions/cache@v4 37 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) 38 | with: 39 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 40 | key: ${{ runner.os }}-node${{ matrix.node-version }}-yarn-${{ hashFiles('**/yarn.lock') }} 41 | restore-keys: | 42 | ${{ runner.os }}-yarn- 43 | - name: Install dependencies 44 | run: | 45 | yarn install --frozen-lockfile 46 | - name: Build package 47 | run: | 48 | yarn build 49 | - name: Test 50 | run: | 51 | yarn test:ci 52 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - develop 8 | tags-ignore: 9 | - '**' #prevent double build on release tag 10 | pull_request: 11 | branches: 12 | - '**' 13 | schedule: 14 | - cron: "14 14 * * 0" 15 | 16 | jobs: 17 | analyze: 18 | name: Analyze 19 | runs-on: ubuntu-latest 20 | permissions: 21 | actions: read 22 | contents: read 23 | security-events: write 24 | 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | language: [javascript-typescript] 29 | 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | 34 | - name: Initialize CodeQL 35 | uses: github/codeql-action/init@v3 36 | with: 37 | languages: ${{ matrix.language }} 38 | queries: +security-and-quality 39 | 40 | - name: Autobuild 41 | uses: github/codeql-action/autobuild@v3 42 | 43 | - name: Perform CodeQL Analysis 44 | uses: github/codeql-action/analyze@v3 45 | with: 46 | category: "/language:${{ matrix.language }}" 47 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | 7 | jobs: 8 | release: 9 | name: Create release 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout repo 14 | uses: actions/checkout@v4 15 | - name: Setup Node.js version 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version-file: '.nvmrc' 19 | 20 | - name: Generate release body 21 | run: | 22 | npx rexreplace "(#+ \[\d+\.\d+\.\d+].*?)#+ \[?\d+\.\d+\.\d+]?" "_" -s -M -G -m -o "CHANGELOG.md" > RELEASE_BODY.md 23 | result=$(cat RELEASE_BODY.md) 24 | 25 | if [[ $? != 0 ]]; then 26 | echo "Command failed." 27 | exit 1; 28 | elif [[ $result ]]; then 29 | echo "Release body generated." 30 | else 31 | echo "This is the first release, using different command to generate release body." 32 | npx rexreplace "(#+ \[?\d+\.\d+\.\d+]?.*)" "_" -s -M -G -m -o "CHANGELOG.md" > RELEASE_BODY.md 33 | fi 34 | 35 | - name: Create release 36 | id: create-release 37 | uses: actions/create-release@v1 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | with: 41 | tag_name: ${{ github.ref }} 42 | release_name: ${{ github.ref }} 43 | draft: false 44 | prerelease: false 45 | body_path: RELEASE_BODY.md 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | /dist 8 | /__tests__/**/*/dist 9 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "src/**/*.{json,md}": [ 3 | "prettier --single-quote --write" 4 | ], 5 | "src/**/*.{js,ts}": [ 6 | "eslint --report-unused-disable-directives --fix --quiet" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /src 2 | tsconfig.json 3 | .editorconfig 4 | .eslintrc 5 | .eslintignore 6 | .huskyrc 7 | .lintstagedrc 8 | .nvmrc 9 | .versionrc 10 | commitlint.config.js 11 | yarn-error.log 12 | yarn.lock 13 | .github 14 | /__tests__ 15 | .husky 16 | /scripts 17 | jest.config.js 18 | docs 19 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 14.20.0 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "trailingComma": "es5", 4 | "tabWidth": 2, 5 | "semi": true, 6 | "singleQuote": true 7 | } 8 | -------------------------------------------------------------------------------- /.versionrc: -------------------------------------------------------------------------------- 1 | { 2 | "header": "# Changelog\n" 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### [3.2.1](https://github.com/icelam/html-inline-script-webpack-plugin/compare/v3.2.0...v3.2.1) (2023-08-09) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * resolve public path base on html-webpack-plugin and webpack config ([c333304](https://github.com/icelam/html-inline-script-webpack-plugin/commit/c333304722aa8a9f594bbcc7d48944b7720ec499)) 9 | 10 | ## [3.2.0](https://github.com/icelam/html-inline-script-webpack-plugin/compare/v3.1.0...v3.2.0) (2023-06-02) 11 | 12 | 13 | ### Features 14 | 15 | * support preserving inlined assets by regex ([#434](https://github.com/icelam/html-inline-script-webpack-plugin/issues/434)) ([239e7b9](https://github.com/icelam/html-inline-script-webpack-plugin/commit/239e7b9e31dabf6699089357af214951c6209918)) 16 | 17 | ## [3.1.0](https://github.com/icelam/html-inline-script-webpack-plugin/compare/v3.0.1...v3.1.0) (2022-08-05) 18 | 19 | 20 | ### Features 21 | 22 | * escape all that appears inside source ([cdf4b31](https://github.com/icelam/html-inline-script-webpack-plugin/commit/cdf4b3198f4ff927fe03a25b6f35eacff6080ff2)) 23 | 24 | ### [3.0.1](https://github.com/icelam/html-inline-script-webpack-plugin/compare/v3.0.0...v3.0.1) (2022-07-24) 25 | 26 | 27 | ### Bug Fixes 28 | 29 | * unable to match assets when filename contains special characters ([ce6be9c](https://github.com/icelam/html-inline-script-webpack-plugin/commit/ce6be9cc09c40cfd85377a62b119bacdf590c67b)) 30 | 31 | ## [3.0.0](https://github.com/icelam/html-inline-script-webpack-plugin/compare/v2.0.3...v3.0.0) (2022-02-27) 32 | 33 | 34 | ### Features 35 | 36 | * new option for defining HTML templates the plugin should process ([1ffe025](https://github.com/icelam/html-inline-script-webpack-plugin/commit/1ffe025618685b99a1c37ef6d925b422ae5918c7)) 37 | 38 | ### [2.0.3](https://github.com/icelam/html-inline-script-webpack-plugin/compare/v2.0.2...v2.0.3) (2021-10-30) 39 | 40 | 41 | ### Bug Fixes 42 | 43 | * delete only script files which has been processed by plugin ([4e40c19](https://github.com/icelam/html-inline-script-webpack-plugin/commit/4e40c19ee11688f9ee9bbb25196cc30a0f7372ce)) 44 | 45 | ### [2.0.2](https://github.com/icelam/html-inline-script-webpack-plugin/compare/v2.0.1...v2.0.2) (2021-07-04) 46 | 47 | 48 | ### Bug Fixes 49 | 50 | * invalid logic on missing script check ([#198](https://github.com/icelam/html-inline-script-webpack-plugin/issues/198)) ([e9f9f26](https://github.com/icelam/html-inline-script-webpack-plugin/commit/e9f9f26a247df31a0f1b4849410a775b477cca76)) 51 | 52 | ### [2.0.1](https://github.com/icelam/html-inline-script-webpack-plugin/compare/v2.0.0...v2.0.1) (2021-05-05) 53 | 54 | 55 | ### Bug Fixes 56 | 57 | * chage the default test scope to avoid targeting map files or gzip files ([7539425](https://github.com/icelam/html-inline-script-webpack-plugin/commit/75394251d96d94dbb35ae9b353b9ca6f24c6cec8)) 58 | 59 | ## [2.0.0](https://github.com/icelam/html-inline-script-webpack-plugin/compare/v1.1.2...v2.0.0) (2021-03-21) 60 | 61 | 62 | ### Features 63 | 64 | * support for webpack 5 ([a015679](https://github.com/icelam/html-inline-script-webpack-plugin/commit/a0156798e6a80d58d4db52d0fb614bc673cac9f3)) 65 | 66 | ### [1.1.2](https://github.com/icelam/html-inline-script-webpack-plugin/compare/v1.1.1...v1.1.2) (2021-03-06) 67 | 68 | 69 | ### Bug Fixes 70 | 71 | * fix no construct signatures error throw by typescript ([d00e1a7](https://github.com/icelam/html-inline-script-webpack-plugin/commit/d00e1a7eb79bb81642246ac81d68807b72bcb06e)) 72 | 73 | ### [1.1.1](https://github.com/icelam/html-inline-script-webpack-plugin/compare/v1.1.0...v1.1.1) (2021-03-01) 74 | 75 | 76 | ### Bug Fixes 77 | 78 | * ignore .husky and scripts folder when publishing package ([f056fa2](https://github.com/icelam/html-inline-script-webpack-plugin/commit/f056fa26f242cdca73254f5a0a260a98c4e477d7)) 79 | 80 | ## [1.1.0](https://github.com/icelam/html-inline-script-webpack-plugin/compare/v1.0.1...v1.1.0) (2021-03-01) 81 | 82 | 83 | ### Features 84 | 85 | * enhance typings ([9da9cec](https://github.com/icelam/html-inline-script-webpack-plugin/commit/9da9ceca3477a5f82b416a69704b9c790212a738)) 86 | 87 | ### [1.0.1](https://github.com/icelam/html-inline-script-webpack-plugin/compare/v1.0.0...v1.0.1) (2021-01-22) 88 | 89 | 90 | ### Bug Fixes 91 | 92 | * assets source become undefined in some scenario ([d60c640](https://github.com/icelam/html-inline-script-webpack-plugin/commit/d60c640eb7eaf0673a4930fc9cf7777b89a2c0ab)), closes [#1](https://github.com/icelam/html-inline-script-webpack-plugin/issues/1) 93 | 94 | ## 1.0.0 (2020-07-09) 95 | 96 | 97 | ### Features 98 | 99 | * html inline script webpack plugin ([979d3c8](https://github.com/icelam/html-inline-script-webpack-plugin/commit/979d3c8bf9699235209f3852c2600756b9a8281c)) 100 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ice Lam 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 | # HTML Inline Script Webpack Plugin for webpack (html-inline-script-webpack-plugin) 2 | 3 | [![Latest version](https://img.shields.io/github/v/release/icelam/html-inline-script-webpack-plugin.svg?sort=semver&label=latest)](https://github.com/icelam/html-inline-script-webpack-plugin/releases) 4 | [![Download count](https://img.shields.io/npm/dm/html-inline-script-webpack-plugin)](https://www.npmjs.com/package/html-inline-script-webpack-plugin) 5 | [![Install size](https://packagephobia.com/badge?p=html-inline-script-webpack-plugin)](https://packagephobia.com/result?p=html-inline-script-webpack-plugin) 6 | ![ci](https://github.com/icelam/html-inline-script-webpack-plugin/workflows/ci/badge.svg) 7 | [![Package quality](https://npm.packagequality.com/shield/html-inline-script-webpack-plugin.svg)](https://packagequality.com/#?package=html-inline-script-webpack-plugin) 8 | 9 | [![NPM](https://nodei.co/npm/html-inline-script-webpack-plugin.png?compact=true)](https://npmjs.org/package/html-inline-script-webpack-plugin) 10 | 11 | A webpack plugin for converting external script files `` to inline script block ``. Requires [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin) to work. 12 | 13 | Inspired by [react-dev-utils](https://github.com/facebook/create-react-app/blob/master/packages/react-dev-utils/InlineChunkHtmlPlugin.js) created by [Facebook](https://github.com/facebook/). 14 | 15 | ## Install 16 | 17 | ### Webpack5 18 | 19 | ```bash 20 | npm i html-inline-script-webpack-plugin -D 21 | ``` 22 | 23 | ### Webpack4 24 | 25 | ```bash 26 | npm i html-inline-script-webpack-plugin@^1 -D 27 | ``` 28 | 29 | ## Usage 30 | 31 | By default, the plugin will convert all the external script files to inline script block, and remove the original script file from build assets. 32 | 33 | ```js 34 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 35 | const HtmlInlineScriptPlugin = require('html-inline-script-webpack-plugin'); 36 | 37 | module.exports = { 38 | plugins: [new HtmlWebpackPlugin(), new HtmlInlineScriptPlugin()], 39 | }; 40 | ``` 41 | 42 | ## Options 43 | 44 | Below are lists of options supported by this plugin: 45 | 46 | | Name | Description | Type | 47 | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | 48 | | scriptMatchPattern | List of script files that should be processed and inject as inline script. This will be filtered using the output file name. | RegExp[] | 49 | | htmlMatchPattern | List of HTML template files that should be processed by this plugin. Useful when you have multiple `html-webpack-plugin` initialized. This will be filtered using the [`options?.filename`](https://github.com/jantimon/html-webpack-plugin#options) provided by `html-webpack-plugin`. | RegExp[] | 50 | | assetPreservePattern | List of script files that should be preserved by this plugin after inserting them inline. This will be filtered using the output file name. | RegExp[] | 51 | 52 | Here are some examples illustrating how to use these options: 53 | 54 | ##### Process only script files that have file name start with `runtime~` and `app~` 55 | 56 | ```js 57 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 58 | const HtmlInlineScriptPlugin = require('html-inline-script-webpack-plugin'); 59 | 60 | module.exports = { 61 | plugins: [ 62 | new HtmlWebpackPlugin(), 63 | new HtmlInlineScriptPlugin({ 64 | scriptMatchPattern: [/runtime~.+[.]js$/, /app~.+[.]js$/], 65 | }), 66 | ], 67 | }; 68 | ``` 69 | 70 | ##### Process any script files but only have them inlined in `index.html` 71 | 72 | ```js 73 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 74 | const HtmlInlineScriptPlugin = require('html-inline-script-webpack-plugin'); 75 | 76 | module.exports = { 77 | plugins: [ 78 | new HtmlWebpackPlugin({ 79 | filename: 'index.html', 80 | template: 'static/index.webos.html', 81 | }), 82 | new HtmlWebpackPlugin({ 83 | filename: 'page2.html', 84 | template: 'page2.html', 85 | }), 86 | new HtmlInlineScriptPlugin({ 87 | htmlMatchPattern: [/index.html$/], 88 | }), 89 | ], 90 | }; 91 | ``` 92 | 93 | ##### Process script files that have file name start with `runtime~` and `app~` and inject only to `index.html` 94 | 95 | ```js 96 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 97 | const HtmlInlineScriptPlugin = require('html-inline-script-webpack-plugin'); 98 | 99 | module.exports = { 100 | plugins: [ 101 | new HtmlWebpackPlugin({ 102 | filename: 'index.html', 103 | template: 'static/index.webos.html', 104 | }), 105 | new HtmlWebpackPlugin({ 106 | filename: 'page2.html', 107 | template: 'page2.html', 108 | }), 109 | new HtmlInlineScriptPlugin({ 110 | scriptMatchPattern: [/runtime~.+[.]js$/, /app~.+[.]js$/], 111 | htmlMatchPattern: [/index.html$/], 112 | }), 113 | ], 114 | }; 115 | ``` 116 | ##### Process any script files but preserve `main.js` from build assets 117 | 118 | ```js 119 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 120 | const HtmlInlineScriptPlugin = require('html-inline-script-webpack-plugin'); 121 | 122 | module.exports = { 123 | plugins: [ 124 | new HtmlWebpackPlugin(), 125 | new HtmlInlineScriptPlugin({ 126 | assetPreservePattern: [/main.js$/], 127 | }), 128 | ], 129 | }; 130 | ``` 131 | 132 | ## Known limitations 133 | 1. This plugin does not transform Web Worker syntax like `new Worker(new URL('./worker.js', import.meta.url));``. It simply embeds the source code processed by webpack into HTML files, and emits any JavaScript files that is not processed by the plugin. 134 | 2. This plugin is designed to embed script content into HTML files for deployment to environments where only a single file can be uploaded, or where the script file itself is small enough that it doesn't warrant an additional HTTP request. It is not intended for use in development, and may fail if HMR is enabled. 135 | 3. This plugin does not support inlining scripts that are dynamically imported (the `import()` syntax). The reason is explained in [this issue](https://github.com/icelam/html-inline-script-webpack-plugin/issues/494#issuecomment-2016902280). 136 | 137 | ## Contributors 138 | 139 | Thanks goes to these wonderful people: 140 | 141 | 142 | 143 | 144 | 151 | 158 | 159 | 160 |
145 | 146 | @kmalakoff 147 |
148 | @kmalakoff 149 |
150 |
152 | 153 | @SorsOps 154 |
155 | @SorsOps 156 |
157 |
161 | -------------------------------------------------------------------------------- /__tests__/HtmlInlineScriptPlugin.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import webpack from 'webpack'; 4 | 5 | import Self from '../dist'; 6 | 7 | import simpleConfig from './cases/simple/webpack.config'; 8 | import preserveConfig from './cases/preserveAsset/webpack.config'; 9 | import multipleInstanceConfig from './cases/multiple-instance/webpack.config'; 10 | import jsWithImportConfig from './cases/js-with-import/webpack.config'; 11 | import webWorkerConfig from './cases/web-worker/webpack.config'; 12 | import ignoreScriptsConfig from './cases/ignore-scripts/webpack.config'; 13 | import ignoreHtmlsConfig from './cases/ignore-htmls/webpack.config'; 14 | import ignoreScriptsAndHtmlsConfig from './cases/ignore-scripts-and-htmls/webpack.config'; 15 | import filenameWithSpecialCharactersConfig from './cases/filename-with-special-characters/webpack.config'; 16 | import escapeScriptTagEndConfig from './cases/escape-script-end-tag/webpack.config'; 17 | import htmlInsideSubfolderConfig from './cases/html-inside-subfolder/webpack.config'; 18 | 19 | describe('HtmlInlineScriptPlugin', () => { 20 | it('should build simple webpack config without error', async () => { 21 | const webpackPromise = new Promise((resolve) => { 22 | const compiler = webpack(simpleConfig); 23 | 24 | compiler.run((error, stats) => { 25 | expect(error).toBeNull(); 26 | 27 | const statsErrors = stats?.compilation.errors; 28 | expect(statsErrors?.length).toBe(0); 29 | 30 | const result = fs.readFileSync( 31 | path.join(__dirname, 'cases/simple/dist/index.html'), 32 | 'utf8', 33 | ); 34 | 35 | const expected = fs.readFileSync( 36 | path.join(__dirname, 'cases/simple/expected/index.html'), 37 | 'utf8', 38 | ); 39 | expect(result).toBe(expected); 40 | 41 | const expectedFileList = fs.readdirSync(path.join(__dirname, 'cases/simple/expected/')); 42 | const generatedFileList = fs.readdirSync(path.join(__dirname, 'cases/simple/dist/')); 43 | expect(expectedFileList.sort()).toEqual(generatedFileList.sort()); 44 | 45 | resolve(true); 46 | }); 47 | }); 48 | 49 | await webpackPromise; 50 | }); 51 | 52 | it('should preserve the output of an asset if requested', async () => { 53 | const webpackPromise = new Promise((resolve) => { 54 | const compiler = webpack(preserveConfig); 55 | console.log(preserveConfig) 56 | 57 | compiler.run((error, stats) => { 58 | expect(error).toBeNull(); 59 | 60 | const statsErrors = stats?.compilation.errors; 61 | expect(statsErrors?.length).toBe(0); 62 | 63 | const result = fs.readFileSync( 64 | path.join(__dirname, 'cases/preserveAsset/dist/index.html'), 65 | 'utf8', 66 | ); 67 | 68 | const expected = fs.readFileSync( 69 | path.join(__dirname, 'cases/preserveAsset/expected/index.html'), 70 | 'utf8', 71 | ); 72 | expect(result).toBe(expected); 73 | 74 | const expectedFileList = fs.readdirSync(path.join(__dirname, 'cases/preserveAsset/expected/')); 75 | const generatedFileList = fs.readdirSync(path.join(__dirname, 'cases/preserveAsset/dist/')); 76 | expect(expectedFileList.sort()).toEqual(generatedFileList.sort()); 77 | 78 | resolve(true); 79 | }); 80 | }); 81 | 82 | await webpackPromise; 83 | }); 84 | 85 | 86 | it('should build webpack config having multiple HTML webpack plugin instance without error', async () => { 87 | const webpackPromise = new Promise((resolve) => { 88 | const compiler = webpack(multipleInstanceConfig); 89 | 90 | compiler.run((error, stats) => { 91 | expect(error).toBeNull(); 92 | 93 | const statsErrors = stats?.compilation.errors; 94 | expect(statsErrors?.length).toBe(0); 95 | 96 | const result1 = fs.readFileSync( 97 | path.join(__dirname, 'cases/multiple-instance/dist/index.html'), 98 | 'utf8', 99 | ); 100 | 101 | const expected1 = fs.readFileSync( 102 | path.join(__dirname, 'cases/multiple-instance/expected/index.html'), 103 | 'utf8', 104 | ); 105 | 106 | expect(result1).toBe(expected1); 107 | 108 | const result2 = fs.readFileSync( 109 | path.join(__dirname, 'cases/multiple-instance/dist/page2.html'), 110 | 'utf8', 111 | ); 112 | 113 | const expected2 = fs.readFileSync( 114 | path.join(__dirname, 'cases/multiple-instance/expected/page2.html'), 115 | 'utf8', 116 | ); 117 | 118 | expect(result2).toBe(expected2); 119 | 120 | const expectedFileList = fs.readdirSync(path.join(__dirname, 'cases/multiple-instance/expected/')); 121 | const generatedFileList = fs.readdirSync(path.join(__dirname, 'cases/multiple-instance/dist/')); 122 | expect(expectedFileList.sort()).toEqual(generatedFileList.sort()); 123 | 124 | resolve(true); 125 | }); 126 | }); 127 | 128 | await webpackPromise; 129 | }); 130 | 131 | it('should build webpack config having JS file with import without error', async () => { 132 | const webpackPromise = new Promise((resolve) => { 133 | const compiler = webpack(jsWithImportConfig); 134 | 135 | compiler.run((error, stats) => { 136 | expect(error).toBeNull(); 137 | 138 | const statsErrors = stats?.compilation.errors; 139 | expect(statsErrors?.length).toBe(0); 140 | 141 | const result1 = fs.readFileSync( 142 | path.join(__dirname, 'cases/js-with-import/dist/index.html'), 143 | 'utf8', 144 | ); 145 | 146 | const expected1 = fs.readFileSync( 147 | path.join(__dirname, 'cases/js-with-import/expected/index.html'), 148 | 'utf8', 149 | ); 150 | 151 | expect(result1).toBe(expected1); 152 | 153 | const expectedFileList = fs.readdirSync(path.join(__dirname, 'cases/js-with-import/expected/')); 154 | const generatedFileList = fs.readdirSync(path.join(__dirname, 'cases/js-with-import/dist/')); 155 | expect(expectedFileList.sort()).toEqual(generatedFileList.sort()); 156 | 157 | resolve(true); 158 | }); 159 | }); 160 | 161 | await webpackPromise; 162 | }); 163 | 164 | it('should build webpack config having web worker without error', async () => { 165 | const webpackPromise = new Promise((resolve) => { 166 | const compiler = webpack(webWorkerConfig); 167 | 168 | compiler.run((error, stats) => { 169 | expect(error).toBeNull(); 170 | 171 | const statsErrors = stats?.compilation.errors; 172 | expect(statsErrors?.length).toBe(0); 173 | 174 | const result1 = fs.readFileSync( 175 | path.join(__dirname, 'cases/web-worker/dist/index.html'), 176 | 'utf8', 177 | ); 178 | 179 | const expected1 = fs.readFileSync( 180 | path.join(__dirname, 'cases/web-worker/expected/index.html'), 181 | 'utf8', 182 | ); 183 | 184 | expect(result1).toBe(expected1); 185 | 186 | const result2 = fs.readFileSync( 187 | path.join(__dirname, 'cases/web-worker/dist/test.worker.js'), 188 | 'utf8', 189 | ); 190 | 191 | const expected2 = fs.readFileSync( 192 | path.join(__dirname, 'cases/web-worker/expected/test.worker.js'), 193 | 'utf8', 194 | ); 195 | 196 | expect(result2).toBe(expected2); 197 | 198 | const expectedFileList = fs.readdirSync(path.join(__dirname, 'cases/web-worker/expected/')); 199 | const generatedFileList = fs.readdirSync(path.join(__dirname, 'cases/web-worker/dist/')); 200 | expect(expectedFileList.sort()).toEqual(generatedFileList.sort()); 201 | 202 | resolve(true); 203 | }); 204 | }); 205 | 206 | await webpackPromise; 207 | }); 208 | 209 | it('should build webpack config that outputs html file inside subfolder without error', async () => { 210 | const webpackPromise = new Promise((resolve) => { 211 | const compiler = webpack(htmlInsideSubfolderConfig); 212 | 213 | compiler.run((error, stats) => { 214 | expect(error).toBeNull(); 215 | 216 | const statsErrors = stats?.compilation.errors; 217 | expect(statsErrors?.length).toBe(0); 218 | 219 | const result = fs.readFileSync( 220 | path.join(__dirname, 'cases/html-inside-subfolder/dist/frontend/index.html'), 221 | 'utf8', 222 | ); 223 | 224 | const expected = fs.readFileSync( 225 | path.join(__dirname, 'cases/html-inside-subfolder/expected/frontend/index.html'), 226 | 'utf8', 227 | ); 228 | expect(result).toBe(expected); 229 | 230 | const expectedParentFileList = fs.readdirSync(path.join(__dirname, 'cases/html-inside-subfolder/expected/')); 231 | const generatedParentFileList = fs.readdirSync(path.join(__dirname, 'cases/html-inside-subfolder/dist/')); 232 | expect(expectedParentFileList.sort()).toEqual(generatedParentFileList.sort()); 233 | 234 | const expectedChildFileList = fs.readdirSync(path.join(__dirname, 'cases/html-inside-subfolder/expected/')); 235 | const generatedChildFileList = fs.readdirSync(path.join(__dirname, 'cases/html-inside-subfolder/dist/')); 236 | expect(expectedChildFileList.sort()).toEqual(generatedChildFileList.sort()); 237 | 238 | resolve(true); 239 | }); 240 | }); 241 | 242 | await webpackPromise; 243 | }); 244 | 245 | it('should inline filename with spacial characters without error', async () => { 246 | const webpackPromise = new Promise((resolve) => { 247 | const compiler = webpack(filenameWithSpecialCharactersConfig); 248 | 249 | compiler.run((error, stats) => { 250 | expect(error).toBeNull(); 251 | 252 | const statsErrors = stats?.compilation.errors; 253 | expect(statsErrors?.length).toBe(0); 254 | 255 | const result = fs.readFileSync( 256 | path.join(__dirname, 'cases/filename-with-special-characters/dist/index.html'), 257 | 'utf8', 258 | ); 259 | 260 | const expected = fs.readFileSync( 261 | path.join(__dirname, 'cases/filename-with-special-characters/expected/index.html'), 262 | 'utf8', 263 | ); 264 | expect(result).toBe(expected); 265 | 266 | const expectedFileList = fs.readdirSync(path.join(__dirname, 'cases/filename-with-special-characters/expected/')); 267 | const generatedFileList = fs.readdirSync(path.join(__dirname, 'cases/filename-with-special-characters/dist/')); 268 | expect(expectedFileList.sort()).toEqual(generatedFileList.sort()); 269 | 270 | resolve(true); 271 | }); 272 | }); 273 | 274 | await webpackPromise; 275 | }); 276 | 277 | it('should respect plugin options on script matching pattern', async () => { 278 | const webpackPromise = new Promise((resolve) => { 279 | const compiler = webpack(ignoreScriptsConfig); 280 | 281 | compiler.run((error, stats) => { 282 | expect(error).toBeNull(); 283 | 284 | const statsErrors = stats?.compilation.errors; 285 | expect(statsErrors?.length).toBe(0); 286 | 287 | const result1 = fs.readFileSync( 288 | path.join(__dirname, 'cases/ignore-scripts/dist/index.html'), 289 | 'utf8', 290 | ); 291 | 292 | const expected1 = fs.readFileSync( 293 | path.join(__dirname, 'cases/ignore-scripts/expected/index.html'), 294 | 'utf8', 295 | ); 296 | 297 | expect(result1).toBe(expected1); 298 | 299 | const expectedFileList = fs.readdirSync(path.join(__dirname, 'cases/ignore-scripts/expected/')); 300 | const generatedFileList = fs.readdirSync(path.join(__dirname, 'cases/ignore-scripts/dist/')); 301 | expect(expectedFileList.sort()).toEqual(generatedFileList.sort()); 302 | 303 | resolve(true); 304 | }); 305 | }); 306 | 307 | await webpackPromise; 308 | }); 309 | 310 | it('should respect plugin options on html template matching pattern', async () => { 311 | const webpackPromise = new Promise((resolve) => { 312 | const compiler = webpack(ignoreHtmlsConfig); 313 | 314 | compiler.run((error, stats) => { 315 | expect(error).toBeNull(); 316 | 317 | const statsErrors = stats?.compilation.errors; 318 | expect(statsErrors?.length).toBe(0); 319 | 320 | const result1 = fs.readFileSync( 321 | path.join(__dirname, 'cases/ignore-htmls/dist/index.html'), 322 | 'utf8', 323 | ); 324 | 325 | const expected1 = fs.readFileSync( 326 | path.join(__dirname, 'cases/ignore-htmls/expected/index.html'), 327 | 'utf8', 328 | ); 329 | 330 | expect(result1).toBe(expected1); 331 | 332 | const result2 = fs.readFileSync( 333 | path.join(__dirname, 'cases/ignore-htmls/dist/page2.html'), 334 | 'utf8', 335 | ); 336 | 337 | const expected2 = fs.readFileSync( 338 | path.join(__dirname, 'cases/ignore-htmls/expected/page2.html'), 339 | 'utf8', 340 | ); 341 | 342 | expect(result2).toBe(expected2); 343 | 344 | const expectedFileList = fs.readdirSync(path.join(__dirname, 'cases/ignore-htmls/expected/')); 345 | const generatedFileList = fs.readdirSync(path.join(__dirname, 'cases/ignore-htmls/dist/')); 346 | expect(expectedFileList.sort()).toEqual(generatedFileList.sort()); 347 | 348 | resolve(true); 349 | }); 350 | }); 351 | 352 | await webpackPromise; 353 | }); 354 | 355 | it('should respect plugin options on both script matching pattern and html template matching pattern', async () => { 356 | const webpackPromise = new Promise((resolve) => { 357 | const compiler = webpack(ignoreScriptsAndHtmlsConfig); 358 | 359 | compiler.run((error, stats) => { 360 | expect(error).toBeNull(); 361 | 362 | const statsErrors = stats?.compilation.errors; 363 | expect(statsErrors?.length).toBe(0); 364 | 365 | const result1 = fs.readFileSync( 366 | path.join(__dirname, 'cases/ignore-scripts-and-htmls/dist/index.html'), 367 | 'utf8', 368 | ); 369 | 370 | const expected1 = fs.readFileSync( 371 | path.join(__dirname, 'cases/ignore-scripts-and-htmls/expected/index.html'), 372 | 'utf8', 373 | ); 374 | 375 | expect(result1).toBe(expected1); 376 | 377 | const result2 = fs.readFileSync( 378 | path.join(__dirname, 'cases/ignore-scripts-and-htmls/dist/page2.html'), 379 | 'utf8', 380 | ); 381 | 382 | const expected2 = fs.readFileSync( 383 | path.join(__dirname, 'cases/ignore-scripts-and-htmls/expected/page2.html'), 384 | 'utf8', 385 | ); 386 | 387 | expect(result2).toBe(expected2); 388 | 389 | const expectedFileList = fs.readdirSync(path.join(__dirname, 'cases/ignore-scripts-and-htmls/expected/')); 390 | const generatedFileList = fs.readdirSync(path.join(__dirname, 'cases/ignore-scripts-and-htmls/dist/')); 391 | expect(expectedFileList.sort()).toEqual(generatedFileList.sort()); 392 | 393 | resolve(true); 394 | }); 395 | }); 396 | 397 | await webpackPromise; 398 | }); 399 | 400 | it('should escape any "" appears in source', async () => { 401 | const webpackPromise = new Promise((resolve) => { 402 | const compiler = webpack(escapeScriptTagEndConfig); 403 | 404 | compiler.run((error, stats) => { 405 | expect(error).toBeNull(); 406 | 407 | const statsErrors = stats?.compilation.errors; 408 | expect(statsErrors?.length).toBe(0); 409 | 410 | const result = fs.readFileSync( 411 | path.join(__dirname, 'cases/escape-script-end-tag/dist/index.html'), 412 | 'utf8', 413 | ); 414 | 415 | const expected = fs.readFileSync( 416 | path.join(__dirname, 'cases/escape-script-end-tag/expected/index.html'), 417 | 'utf8', 418 | ); 419 | expect(result).toBe(expected); 420 | 421 | const expectedFileList = fs.readdirSync(path.join(__dirname, 'cases/escape-script-end-tag/expected/')); 422 | const generatedFileList = fs.readdirSync(path.join(__dirname, 'cases/escape-script-end-tag/dist/')); 423 | expect(expectedFileList.sort()).toEqual(generatedFileList.sort()); 424 | 425 | resolve(true); 426 | }); 427 | }); 428 | 429 | await webpackPromise; 430 | }); 431 | 432 | it('should build throw error if options passed to plugin is old options format', async () => { 433 | const initializedPlugin = () => { 434 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-new 435 | new Self([/runtime~.+[.]js$/, /app~.+[.]js$/] as any); 436 | }; 437 | expect(initializedPlugin).toThrow(); 438 | }); 439 | }); 440 | -------------------------------------------------------------------------------- /__tests__/cases/escape-script-end-tag/expected/index.html: -------------------------------------------------------------------------------- 1 | webpack test

This is minimal code to demonstrate webpack usage

-------------------------------------------------------------------------------- /__tests__/cases/escape-script-end-tag/fixtures/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webpack test 10 | 11 | 12 |

This is minimal code to demonstrate webpack usage

13 | 14 | 15 | -------------------------------------------------------------------------------- /__tests__/cases/escape-script-end-tag/fixtures/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | document.write(''); 3 | document.write(''); 4 | -------------------------------------------------------------------------------- /__tests__/cases/escape-script-end-tag/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import type { Configuration } from 'webpack'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import Self from '../../../dist'; 5 | 6 | const config: Configuration = { 7 | mode: 'production', 8 | entry: path.join(__dirname, './fixtures/index.js'), 9 | output: { 10 | path: path.join(__dirname, './dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | new HtmlWebpackPlugin({ 15 | template: path.resolve(__dirname, './fixtures/index.html'), 16 | inject: 'body' 17 | }), 18 | new Self() 19 | ] 20 | }; 21 | 22 | export default config; 23 | -------------------------------------------------------------------------------- /__tests__/cases/filename-with-special-characters/expected/index.html: -------------------------------------------------------------------------------- 1 | webpack test

This is minimal code to demonstrate webpack usage

-------------------------------------------------------------------------------- /__tests__/cases/filename-with-special-characters/fixtures/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webpack test 10 | 11 | 12 |

This is minimal code to demonstrate webpack usage

13 | 14 | 15 | -------------------------------------------------------------------------------- /__tests__/cases/filename-with-special-characters/fixtures/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log('Hello world'); 3 | -------------------------------------------------------------------------------- /__tests__/cases/filename-with-special-characters/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import type { Configuration } from 'webpack'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import Self from '../../../dist'; 5 | 6 | const config: Configuration = { 7 | mode: 'production', 8 | entry: path.join(__dirname, './fixtures/index.js'), 9 | output: { 10 | path: path.join(__dirname, './dist'), 11 | filename: '[name]@production.js' 12 | }, 13 | plugins: [ 14 | new HtmlWebpackPlugin({ 15 | template: path.resolve(__dirname, './fixtures/index.html') 16 | }), 17 | new Self() 18 | ] 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /__tests__/cases/html-inside-subfolder/expected/frontend/index.html: -------------------------------------------------------------------------------- 1 | webpack test

This is minimal code to demonstrate webpack usage

-------------------------------------------------------------------------------- /__tests__/cases/html-inside-subfolder/fixtures/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webpack test 10 | 11 | 12 |

This is minimal code to demonstrate webpack usage

13 | 14 | 15 | -------------------------------------------------------------------------------- /__tests__/cases/html-inside-subfolder/fixtures/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log('Hello world'); 3 | -------------------------------------------------------------------------------- /__tests__/cases/html-inside-subfolder/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import type { Configuration } from 'webpack'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import Self from '../../../dist'; 5 | 6 | const config: Configuration = { 7 | mode: 'production', 8 | entry: path.join(__dirname, './fixtures/index.js'), 9 | output: { 10 | path: path.join(__dirname, './dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | new HtmlWebpackPlugin({ 15 | template: path.resolve(__dirname, './fixtures/index.html'), 16 | filename: path.join(__dirname, './dist/frontend/index.html') 17 | }), 18 | new Self() 19 | ] 20 | }; 21 | 22 | export default config; 23 | -------------------------------------------------------------------------------- /__tests__/cases/ignore-htmls/expected/index.html: -------------------------------------------------------------------------------- 1 | webpack test

This is minimal code to demonstrate webpack usage

-------------------------------------------------------------------------------- /__tests__/cases/ignore-htmls/expected/index.js: -------------------------------------------------------------------------------- 1 | console.log("Hello world"); -------------------------------------------------------------------------------- /__tests__/cases/ignore-htmls/expected/page2.html: -------------------------------------------------------------------------------- 1 | webpack test

This is minimal code to demonstrate webpack usage

-------------------------------------------------------------------------------- /__tests__/cases/ignore-htmls/expected/page2.js: -------------------------------------------------------------------------------- 1 | console.log("Page 2"); -------------------------------------------------------------------------------- /__tests__/cases/ignore-htmls/fixtures/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webpack test 10 | 11 | 12 |

This is minimal code to demonstrate webpack usage

13 | 14 | 15 | -------------------------------------------------------------------------------- /__tests__/cases/ignore-htmls/fixtures/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log('Hello world'); 3 | -------------------------------------------------------------------------------- /__tests__/cases/ignore-htmls/fixtures/page2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webpack test 10 | 11 | 12 |

This is minimal code to demonstrate webpack usage

13 | 14 | 15 | -------------------------------------------------------------------------------- /__tests__/cases/ignore-htmls/fixtures/page2.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log('Page 2'); 3 | -------------------------------------------------------------------------------- /__tests__/cases/ignore-htmls/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import type { Configuration } from 'webpack'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import Self from '../../../dist'; 5 | 6 | const config: Configuration = { 7 | mode: 'production', 8 | entry: { 9 | index: path.join(__dirname, './fixtures/index.js'), 10 | page2: path.join(__dirname, './fixtures/page2.js') 11 | }, 12 | output: { 13 | path: path.join(__dirname, './dist'), 14 | filename: '[name].js' 15 | }, 16 | plugins: [ 17 | new HtmlWebpackPlugin({ 18 | template: path.resolve(__dirname, './fixtures/index.html'), 19 | filename: 'index.html' 20 | }), 21 | new HtmlWebpackPlugin({ 22 | template: path.resolve(__dirname, './fixtures/page2.html'), 23 | filename: 'page2.html' 24 | }), 25 | new Self({ 26 | htmlMatchPattern: [/index.html$/] 27 | }) 28 | ] 29 | }; 30 | 31 | export default config; 32 | -------------------------------------------------------------------------------- /__tests__/cases/ignore-scripts-and-htmls/expected/index.html: -------------------------------------------------------------------------------- 1 | webpack test

This is minimal code to demonstrate webpack usage

-------------------------------------------------------------------------------- /__tests__/cases/ignore-scripts-and-htmls/expected/index.js: -------------------------------------------------------------------------------- 1 | console.log("Hello world"); -------------------------------------------------------------------------------- /__tests__/cases/ignore-scripts-and-htmls/expected/page2.html: -------------------------------------------------------------------------------- 1 | webpack test

This is minimal code to demonstrate webpack usage

-------------------------------------------------------------------------------- /__tests__/cases/ignore-scripts-and-htmls/expected/page2.js: -------------------------------------------------------------------------------- 1 | console.log("Page 2"); -------------------------------------------------------------------------------- /__tests__/cases/ignore-scripts-and-htmls/fixtures/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webpack test 10 | 11 | 12 |

This is minimal code to demonstrate webpack usage

13 | 14 | 15 | -------------------------------------------------------------------------------- /__tests__/cases/ignore-scripts-and-htmls/fixtures/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log('Hello world'); 3 | -------------------------------------------------------------------------------- /__tests__/cases/ignore-scripts-and-htmls/fixtures/page2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webpack test 10 | 11 | 12 |

This is minimal code to demonstrate webpack usage

13 | 14 | 15 | -------------------------------------------------------------------------------- /__tests__/cases/ignore-scripts-and-htmls/fixtures/page2.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log('Page 2'); 3 | -------------------------------------------------------------------------------- /__tests__/cases/ignore-scripts-and-htmls/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import type { Configuration } from 'webpack'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import Self from '../../../dist'; 5 | 6 | const config: Configuration = { 7 | mode: 'production', 8 | entry: { 9 | index: path.join(__dirname, './fixtures/index.js'), 10 | page2: path.join(__dirname, './fixtures/page2.js') 11 | }, 12 | output: { 13 | path: path.join(__dirname, './dist'), 14 | filename: '[name].js' 15 | }, 16 | plugins: [ 17 | new HtmlWebpackPlugin({ 18 | template: path.resolve(__dirname, './fixtures/index.html'), 19 | filename: 'index.html' 20 | }), 21 | new HtmlWebpackPlugin({ 22 | template: path.resolve(__dirname, './fixtures/page2.html'), 23 | filename: 'page2.html' 24 | }), 25 | new Self({ 26 | scriptMatchPattern: [/index.js$/], 27 | htmlMatchPattern: [/index.html$/] 28 | }) 29 | ] 30 | }; 31 | 32 | export default config; 33 | -------------------------------------------------------------------------------- /__tests__/cases/ignore-scripts/expected/index.html: -------------------------------------------------------------------------------- 1 | webpack test

This is minimal code to demonstrate webpack usage

-------------------------------------------------------------------------------- /__tests__/cases/ignore-scripts/expected/non-inline.js: -------------------------------------------------------------------------------- 1 | console.log("This should not be inlined"); -------------------------------------------------------------------------------- /__tests__/cases/ignore-scripts/fixtures/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webpack test 10 | 11 | 12 |

This is minimal code to demonstrate webpack usage

13 | 14 | 15 | -------------------------------------------------------------------------------- /__tests__/cases/ignore-scripts/fixtures/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log('This should be inlined'); 3 | -------------------------------------------------------------------------------- /__tests__/cases/ignore-scripts/fixtures/non-inline.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log('This should not be inlined'); 3 | -------------------------------------------------------------------------------- /__tests__/cases/ignore-scripts/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import type { Configuration } from 'webpack'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import Self from '../../../dist'; 5 | 6 | const config: Configuration = { 7 | mode: 'production', 8 | entry: { 9 | index: path.join(__dirname, './fixtures/index.js'), 10 | 'non-inline': path.join(__dirname, './fixtures/non-inline.js') 11 | }, 12 | output: { 13 | path: path.join(__dirname, './dist'), 14 | filename: '[name].js' 15 | }, 16 | plugins: [ 17 | new HtmlWebpackPlugin({ 18 | template: path.resolve(__dirname, './fixtures/index.html'), 19 | filename: 'index.html' 20 | }), 21 | new Self({ 22 | scriptMatchPattern: [/index.js$/] 23 | }) 24 | ] 25 | }; 26 | 27 | export default config; 28 | -------------------------------------------------------------------------------- /__tests__/cases/js-with-import/expected/index.html: -------------------------------------------------------------------------------- 1 | webpack test

This is minimal code to demonstrate webpack usage

-------------------------------------------------------------------------------- /__tests__/cases/js-with-import/fixtures/app.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log('Hello world'); 3 | -------------------------------------------------------------------------------- /__tests__/cases/js-with-import/fixtures/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webpack test 10 | 11 | 12 |

This is minimal code to demonstrate webpack usage

13 | 14 | 15 | -------------------------------------------------------------------------------- /__tests__/cases/js-with-import/fixtures/index.js: -------------------------------------------------------------------------------- 1 | import './app'; 2 | -------------------------------------------------------------------------------- /__tests__/cases/js-with-import/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import type { Configuration } from 'webpack'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import Self from '../../../dist'; 5 | 6 | const config: Configuration = { 7 | mode: 'production', 8 | entry: path.join(__dirname, './fixtures/index.js'), 9 | output: { 10 | path: path.join(__dirname, './dist'), 11 | filename: '[name].js' 12 | }, 13 | optimization: { 14 | chunkIds: 'named', 15 | moduleIds: 'named' 16 | }, 17 | plugins: [ 18 | new HtmlWebpackPlugin({ 19 | template: path.resolve(__dirname, './fixtures/index.html') 20 | }), 21 | new Self() 22 | ] 23 | }; 24 | 25 | export default config; 26 | -------------------------------------------------------------------------------- /__tests__/cases/multiple-instance/expected/index.html: -------------------------------------------------------------------------------- 1 | webpack test

This is minimal code to demonstrate webpack usage

-------------------------------------------------------------------------------- /__tests__/cases/multiple-instance/expected/page2.html: -------------------------------------------------------------------------------- 1 | webpack test

This is minimal code to demonstrate webpack usage

-------------------------------------------------------------------------------- /__tests__/cases/multiple-instance/fixtures/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webpack test 10 | 11 | 12 |

This is minimal code to demonstrate webpack usage

13 | 14 | 15 | -------------------------------------------------------------------------------- /__tests__/cases/multiple-instance/fixtures/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log('Hello world'); 3 | -------------------------------------------------------------------------------- /__tests__/cases/multiple-instance/fixtures/page2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webpack test 10 | 11 | 12 |

This is minimal code to demonstrate webpack usage

13 | 14 | 15 | -------------------------------------------------------------------------------- /__tests__/cases/multiple-instance/fixtures/page2.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log('Page 2'); 3 | -------------------------------------------------------------------------------- /__tests__/cases/multiple-instance/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import type { Configuration } from 'webpack'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import Self from '../../../dist'; 5 | 6 | const config: Configuration = { 7 | mode: 'production', 8 | entry: { 9 | index: path.join(__dirname, './fixtures/index.js'), 10 | page2: path.join(__dirname, './fixtures/page2.js') 11 | }, 12 | output: { 13 | path: path.join(__dirname, './dist'), 14 | filename: '[name].js' 15 | }, 16 | plugins: [ 17 | new HtmlWebpackPlugin({ 18 | template: path.resolve(__dirname, './fixtures/index.html'), 19 | filename: 'index.html' 20 | }), 21 | new HtmlWebpackPlugin({ 22 | template: path.resolve(__dirname, './fixtures/page2.html'), 23 | filename: 'page2.html' 24 | }), 25 | new Self() 26 | ] 27 | }; 28 | 29 | export default config; 30 | -------------------------------------------------------------------------------- /__tests__/cases/preserveAsset/expected/index.html: -------------------------------------------------------------------------------- 1 | webpack test

This is minimal code to demonstrate webpack usage

-------------------------------------------------------------------------------- /__tests__/cases/preserveAsset/expected/ui.js: -------------------------------------------------------------------------------- 1 | console.log("Hello world"); -------------------------------------------------------------------------------- /__tests__/cases/preserveAsset/fixtures/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webpack test 10 | 11 | 12 |

This is minimal code to demonstrate webpack usage

13 | 14 | 15 | -------------------------------------------------------------------------------- /__tests__/cases/preserveAsset/fixtures/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log('Hello world'); 3 | -------------------------------------------------------------------------------- /__tests__/cases/preserveAsset/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import type { Configuration } from 'webpack'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import Self from '../../../dist'; 5 | 6 | const config: Configuration = { 7 | mode: 'production', 8 | entry: { 9 | ui: path.join(__dirname, './fixtures/index.js') 10 | }, 11 | output: { 12 | path: path.join(__dirname, './dist'), 13 | filename: '[name].js' 14 | }, 15 | plugins: [ 16 | new HtmlWebpackPlugin({ 17 | chunks: ['ui'], 18 | template: path.resolve(__dirname, './fixtures/index.html') 19 | }), 20 | new Self({ 21 | assetPreservePattern: [/^ui[.]js$/] 22 | }) 23 | ] 24 | }; 25 | 26 | export default config; 27 | -------------------------------------------------------------------------------- /__tests__/cases/simple/expected/index.html: -------------------------------------------------------------------------------- 1 | webpack test

This is minimal code to demonstrate webpack usage

-------------------------------------------------------------------------------- /__tests__/cases/simple/fixtures/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webpack test 10 | 11 | 12 |

This is minimal code to demonstrate webpack usage

13 | 14 | 15 | -------------------------------------------------------------------------------- /__tests__/cases/simple/fixtures/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log('Hello world'); 3 | -------------------------------------------------------------------------------- /__tests__/cases/simple/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import type { Configuration } from 'webpack'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import Self from '../../../dist'; 5 | 6 | const config: Configuration = { 7 | mode: 'production', 8 | entry: path.join(__dirname, './fixtures/index.js'), 9 | output: { 10 | path: path.join(__dirname, './dist'), 11 | filename: '[name].js' 12 | }, 13 | plugins: [ 14 | new HtmlWebpackPlugin({ 15 | template: path.resolve(__dirname, './fixtures/index.html') 16 | }), 17 | new Self() 18 | ] 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /__tests__/cases/web-worker/expected/index.html: -------------------------------------------------------------------------------- 1 | webpack test

This is minimal code to demonstrate webpack usage

-------------------------------------------------------------------------------- /__tests__/cases/web-worker/expected/test.worker.js: -------------------------------------------------------------------------------- 1 | onmessage=function(s){const e={timestamp:Date.now(),...s.data};e.onmessage=!0,postMessage(e)}; -------------------------------------------------------------------------------- /__tests__/cases/web-worker/fixtures/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webpack test 10 | 11 | 12 |

This is minimal code to demonstrate webpack usage

13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /__tests__/cases/web-worker/fixtures/index.js: -------------------------------------------------------------------------------- 1 | const worker = new Worker(new URL(/* webpackChunkName: 'test.worker' */'./worker.js', import.meta.url)); 2 | 3 | let result; 4 | 5 | worker.onmessage = function (event) { 6 | if (!result) { 7 | result = document.createElement('div'); 8 | result.setAttribute('id', 'result'); 9 | 10 | document.body.append(result); 11 | } 12 | 13 | const record = document.createElement('pre'); 14 | record.innerHTML = JSON.stringify(event.data); 15 | 16 | result.append(record); 17 | }; 18 | 19 | window.addEventListener('load', () => { 20 | const button = document.getElementById('button'); 21 | 22 | button.addEventListener('click', () => { 23 | worker.postMessage({ postMessage: true }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /__tests__/cases/web-worker/fixtures/worker.js: -------------------------------------------------------------------------------- 1 | onmessage = function (event) { 2 | const workerResult = { timestamp: Date.now(), ...event.data }; 3 | 4 | workerResult.onmessage = true; 5 | 6 | postMessage(workerResult); 7 | }; 8 | -------------------------------------------------------------------------------- /__tests__/cases/web-worker/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import type { Configuration } from 'webpack'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import Self from '../../../dist'; 5 | 6 | const config: Configuration = { 7 | mode: 'production', 8 | entry: path.join(__dirname, './fixtures/index.js'), 9 | output: { 10 | path: path.join(__dirname, './dist'), 11 | filename: '[name].js', 12 | publicPath: './' 13 | }, 14 | optimization: { 15 | chunkIds: 'named', 16 | moduleIds: 'named' 17 | }, 18 | plugins: [ 19 | new HtmlWebpackPlugin({ 20 | template: path.resolve(__dirname, './fixtures/index.html') 21 | }), 22 | new Self() 23 | ] 24 | }; 25 | 26 | export default config; 27 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'] 3 | }; 4 | -------------------------------------------------------------------------------- /docs/publish.md: -------------------------------------------------------------------------------- 1 | # Publish procedure 2 | 3 | ## Version 1 4 | For publishing v1 of the plugin which supports webpack 4, checkout `v1` branch, and run the following commands: 5 | ```bash 6 | # checkout branch 7 | git checkout v1 8 | 9 | # pull the latest code 10 | git pull 11 | 12 | # make sure dependencies are in correct version 13 | yarn 14 | 15 | # bump package version 16 | yarn release 17 | 18 | # create build 19 | yarn build 20 | 21 | # publish to npm (OTP needed) 22 | npm publish 23 | 24 | # push code and trigger github release creation 25 | git push --follow-tags 26 | ``` 27 | 28 | ## Version 2 29 | For publishing v2 of the plugin which supports webpack 5, checkout `develop` branch, and run the following commands: 30 | ```bash 31 | # checkout branch 32 | git checkout develop 33 | 34 | # pull the latest code 35 | git pull 36 | 37 | # switch to master branch 38 | git checkout master 39 | 40 | # pull the latest code from master 41 | git pull 42 | 43 | # merge code from develop branch 44 | git merge --no-ff develop 45 | 46 | # make sure dependencies are in correct version 47 | yarn 48 | 49 | # bump package version 50 | yarn release 51 | 52 | # create build 53 | yarn build 54 | 55 | # publish to npm (OTP needed) 56 | npm publish 57 | 58 | # push code and trigger github release creation 59 | git push --follow-tags 60 | 61 | # merge change logs back to develop 62 | git checkout develop 63 | git merge --no-ff master 64 | git push 65 | ``` 66 | 67 | ## Pointing distribution tags `latest` to a specific version 68 | Since npm automatically tag newest published version with distribution tags `latest`, it might be end up in some scenerio where a smaller semver is being taged as `latest`. To point a specific version of package back to `latest`, run the command: 69 | 70 | ```bash 71 | # Point the distribution tags `latest` to a specific version (OTP needed) 72 | npm dist-tag add html-inline-script-webpack-plugin@ latest 73 | ``` 74 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverageFrom: ['src/**/*.[tj]s'], 3 | testMatch: ['__tests__/**/*.test.[jt]s'], 4 | moduleFileExtensions: [ 5 | 'ts', 6 | 'js', 7 | 'json' 8 | ], 9 | transform: { 10 | '^.+\\.ts$': 'ts-jest' 11 | }, 12 | globals: { 13 | 'ts-jest': { 14 | tsconfig: '/tsconfig.json' 15 | } 16 | }, 17 | testEnvironment: 'node' 18 | }; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html-inline-script-webpack-plugin", 3 | "version": "3.2.1", 4 | "description": "A webpack plugin for converting external script files to inline script block. Requires 'html-webpack-plugin' to work.", 5 | "main": "./dist/index.js", 6 | "scripts": { 7 | "build": "rm -rf ./dist && tsc --build --force --verbose", 8 | "lint": "eslint --ext .ts,js src", 9 | "prepare": "husky install && install-peers", 10 | "first-release": "npx standard-version --commit-all --tag-prefix v --first-release", 11 | "release": "npx standard-version --commit-all --tag-prefix v", 12 | "test": "jest --verbose --watchAll --config ./jest.config.js --silent", 13 | "test:ci": "yarn test --ci --watchAll=false --runInBand --detectOpenHandles" 14 | }, 15 | "author": "Ice Lam", 16 | "repository": { 17 | "url": "https://github.com/icelam/html-inline-script-webpack-plugin", 18 | "type": "git" 19 | }, 20 | "license": "MIT", 21 | "keywords": [ 22 | "webpack", 23 | "webpack4", 24 | "webpack5", 25 | "webpack-plugin", 26 | "html-webpack-plugin", 27 | "inline", 28 | "internal", 29 | "embedded", 30 | "source", 31 | "inline-source", 32 | "script", 33 | "inline-script" 34 | ], 35 | "devDependencies": { 36 | "@commitlint/cli": "^17.0.3", 37 | "@commitlint/config-conventional": "^17.0.3", 38 | "@types/html-webpack-plugin": "^3.2.3", 39 | "@types/jest": "^27.0.2", 40 | "@typescript-eslint/eslint-plugin": "^4.0.0", 41 | "@typescript-eslint/parser": "^4.15.2", 42 | "eslint": "^7.4.0", 43 | "eslint-config-airbnb-base": "^15.0.0", 44 | "eslint-plugin-import": "^2.22.0", 45 | "eslint-plugin-jest": "^27.1.4", 46 | "husky": "^8.0.1", 47 | "install-peers-cli": "^2.2.0", 48 | "jest": "^26.6.3", 49 | "lint-staged": "^13.0.3", 50 | "pinst": "^3.0.0", 51 | "prettier": "^3.0.1", 52 | "ts-jest": "^26.5.3", 53 | "typescript": "^4.1.3" 54 | }, 55 | "peerDependencies": { 56 | "html-webpack-plugin": "^5.0.0", 57 | "webpack": "^5.0.0" 58 | }, 59 | "engines": { 60 | "npm": ">=6.0.0", 61 | "node": ">=14.0.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/HtmlInlineScriptPlugin.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { Compilation } from 'webpack'; 3 | import type { Compiler, WebpackPluginInstance } from 'webpack'; 4 | import htmlWebpackPlugin from 'html-webpack-plugin'; 5 | import type { HtmlTagObject } from 'html-webpack-plugin'; 6 | import { PLUGIN_PREFIX } from './constants'; 7 | 8 | export type PluginOptions = { 9 | scriptMatchPattern?: RegExp[]; 10 | htmlMatchPattern?: RegExp[]; 11 | assetPreservePattern?: RegExp[]; 12 | }; 13 | 14 | class HtmlInlineScriptPlugin implements WebpackPluginInstance { 15 | scriptMatchPattern: NonNullable; 16 | 17 | htmlMatchPattern: NonNullable; 18 | 19 | processedScriptFiles: string[]; 20 | 21 | ignoredHtmlFiles: string[]; 22 | 23 | assetPreservePattern: NonNullable; 24 | 25 | constructor(options: PluginOptions = {}) { 26 | if (options && Array.isArray(options)) { 27 | // eslint-disable-next-line no-console 28 | console.error( 29 | '\x1b[35m%s \x1b[31m%s %s\x1b[0m', 30 | '[html-inline-script-webpack-plugin]', 31 | 'Options is now an object containing `scriptMatchPattern` and `htmlMatchPattern` in version 3.x.', 32 | 'Please refer to documentation for more information.' 33 | ); 34 | 35 | throw new Error('OPTIONS_PATTERN_UNMATCHED'); 36 | } 37 | 38 | const { 39 | scriptMatchPattern = [/.+[.]js$/], 40 | htmlMatchPattern = [/.+[.]html$/], 41 | assetPreservePattern = [] 42 | } = options; 43 | 44 | this.scriptMatchPattern = scriptMatchPattern; 45 | this.htmlMatchPattern = htmlMatchPattern; 46 | this.processedScriptFiles = []; 47 | this.assetPreservePattern = assetPreservePattern; 48 | this.ignoredHtmlFiles = []; 49 | } 50 | 51 | isFileNeedsToBeInlined( 52 | assetName: string 53 | ): boolean { 54 | return this.scriptMatchPattern.some((test) => assetName.match(test)); 55 | } 56 | 57 | isFileNeedsToBePreserved( 58 | assetName: string 59 | ): boolean { 60 | return this.assetPreservePattern.some((test) => assetName.match(test)); 61 | } 62 | 63 | shouldProcessHtml( 64 | templateName: string 65 | ): boolean { 66 | return this.htmlMatchPattern.some((test) => templateName.match(test)); 67 | } 68 | 69 | processScriptTag( 70 | publicPath: string, 71 | assets: Compilation['assets'], 72 | tag: HtmlTagObject 73 | ): HtmlTagObject { 74 | if (tag.tagName !== 'script' || !tag.attributes?.src) { 75 | return tag; 76 | } 77 | 78 | // Decoded is needed for special characters in filename like '@' since they will be escaped 79 | const scriptName = decodeURIComponent((tag.attributes.src as string).replace(publicPath, '')); 80 | 81 | if (!this.isFileNeedsToBeInlined(scriptName)) { 82 | return tag; 83 | } 84 | 85 | const asset = assets[scriptName]; 86 | 87 | if (!asset) { 88 | return tag; 89 | } 90 | 91 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 92 | const { src, ...attributesWithoutSrc } = tag.attributes; 93 | this.processedScriptFiles.push(scriptName); 94 | 95 | return { 96 | tagName: 'script', 97 | // escape '' appears in source 98 | innerHTML: (asset.source() as string).replace(/(<)(\/script>)/g, '\\x3C$2'), 99 | voidTag: false, 100 | attributes: attributesWithoutSrc, 101 | meta: { plugin: 'html-inline-script-webpack-plugin' } 102 | }; 103 | } 104 | 105 | getPublicPath( 106 | compilation: Compilation, 107 | htmlFileName: string, 108 | customPublicPath: string 109 | ): string { 110 | const webpackPublicPath = compilation.getAssetPath( 111 | compilation.outputOptions.publicPath as string, 112 | { hash: compilation.hash } 113 | ); 114 | // Webpack 5 introduced "auto" as default value 115 | const isPublicPathDefined = webpackPublicPath !== 'auto'; 116 | 117 | let publicPath = ''; 118 | 119 | if (customPublicPath !== 'auto') { 120 | // If the html-webpack-plugin options contain a custom public path uset it 121 | publicPath = customPublicPath; 122 | } else if (isPublicPathDefined) { 123 | // If a hard coded public path exists in webpack config use it 124 | publicPath = webpackPublicPath; 125 | } else if (compilation.options.output.path) { 126 | // If no public path for webpack and html-webpack-plugin was set get a relative url path 127 | publicPath = path.relative( 128 | path.resolve(compilation.options.output.path, path.dirname(htmlFileName)), 129 | compilation.options.output.path 130 | ).split(path.sep).join('/'); 131 | } 132 | 133 | if (publicPath && !publicPath.endsWith('/')) { 134 | publicPath += '/'; 135 | } 136 | 137 | return publicPath; 138 | } 139 | 140 | apply(compiler: Compiler): void { 141 | compiler.hooks.compilation.tap(`${PLUGIN_PREFIX}_compilation`, (compilation) => { 142 | const hooks = htmlWebpackPlugin.getHooks(compilation); 143 | 144 | hooks.alterAssetTags.tap(`${PLUGIN_PREFIX}_alterAssetTags`, (data) => { 145 | const htmlFileName = data.plugin.options?.filename; 146 | const publicPath = this.getPublicPath(compilation, data.outputName, data.publicPath); 147 | 148 | if (htmlFileName && !this.shouldProcessHtml(htmlFileName)) { 149 | this.ignoredHtmlFiles.push(htmlFileName); 150 | return data; 151 | } 152 | 153 | data.assetTags.scripts = data.assetTags.scripts.map( 154 | (tag: HtmlTagObject) => this.processScriptTag(publicPath, compilation.assets, tag) 155 | ); 156 | return data; 157 | }); 158 | 159 | compilation.hooks.processAssets.tap({ 160 | name: `${PLUGIN_PREFIX}_PROCESS_ASSETS_STAGE_SUMMARIZE`, 161 | stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE 162 | }, (assets) => { 163 | if (this.ignoredHtmlFiles.length === 0) { 164 | this.processedScriptFiles.forEach((assetName) => { 165 | if (!this.isFileNeedsToBePreserved(assetName)) { 166 | delete assets[assetName]; 167 | } 168 | }); 169 | } 170 | }); 171 | }); 172 | } 173 | } 174 | 175 | export default HtmlInlineScriptPlugin; 176 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/prefer-default-export 2 | export const PLUGIN_PREFIX = 'HtmlInlineScriptPlugin'; 3 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { version as webpackVersion } from 'webpack'; 2 | import htmlWebpackPlugin from 'html-webpack-plugin'; 3 | import HtmlInlineScriptPlugin from './HtmlInlineScriptPlugin'; 4 | 5 | const isHtmlWebpackPluginV3 = !('getHooks' in htmlWebpackPlugin); 6 | const isHtmlWebpackPluginV4 = webpackVersion.startsWith('4.'); 7 | const isUnsupportedVersion = isHtmlWebpackPluginV3 || isHtmlWebpackPluginV4; 8 | 9 | if (isUnsupportedVersion) { 10 | // eslint-disable-next-line no-console 11 | console.error( 12 | '\x1b[35m%s \x1b[31m%s\x1b[0m', 13 | '[html-inline-script-webpack-plugin]', 14 | 'Version 3.x only supports webpack 5. If you are using webpack 4, please downgrade this plugin to version 1.x instead.' 15 | ); 16 | 17 | throw new Error('VERSION_INCOMPATIBLE'); 18 | } 19 | 20 | export = HtmlInlineScriptPlugin; 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "declaration": true, 5 | "module": "UMD", 6 | "outDir": "./dist", 7 | "downlevelIteration": true, 8 | "strict": true, 9 | "noImplicitReturns": true, 10 | "moduleResolution": "node", 11 | "allowSyntheticDefaultImports": true, 12 | "esModuleInterop": true, 13 | "skipLibCheck": true, 14 | "forceConsistentCasingInFileNames": true 15 | }, 16 | "include": [ 17 | "./src" 18 | ], 19 | "exclude": [ 20 | "node_modules" 21 | ] 22 | } 23 | --------------------------------------------------------------------------------