javascriptwebpackgoogle-chrome-extension

Insert script into HTML head section when in development and not in production


I want to insert the below script tag into my popup.html and options.html when compiled in dist folder:

<script src="http://localhost:8097"></script>

I need this to be the first script before any other scripts. Below is how I expect the script to look in my HTML:

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
 <script src="http://localhost:8097"></script>
 <script src="example.js"></script>
 <script src="react.js"></script>
</head>
<body>
  
</body>
</html>

How can I go about injecting this script for local development only, when working with Webpack?

Here's my set-up so far:

webpack.dev.js:

const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: 'development',
    devtool: 'cheap-module-source-map'
});

webpack.common.js:

const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');
const HtmlPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
    entry: {
        popup: path.resolve('src/popup/popup.tsx'),
        options: path.resolve('src/options/options.tsx'),
        background: path.resolve('src/background/background.ts'),
        contentScript: path.resolve('src/contentScript/contentScript.js')
    },
    module: {
        rules: [
            {
                use: 'ts-loader',
                test: /\.tsx?$/,
                exclude: /node_modules/
            },
            {
                use: ['style-loader', 'css-loader'],
                test: /\.css$/i
            },
            {
                type: 'asset/resource',
                test: /\.(jpg|jpeg|png|woff|woff2|eot|ttf|svg)$/
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin({
            cleanStaleWebpackAssets: false
        }),
        new CopyPlugin({
            patterns: [
                {
                    from: path.resolve('src/static'),
                    to: path.resolve('dist/')
                }
            ]
        }),
        ...getHtmlPlugin(['popup', 'options'])
    ],
    resolve: {
        extensions: ['.tsx', '.ts', '.js']
    },
    output: {
        filename: '[name].js',
        path: path.resolve('dist')
    },
    optimization: {
        splitChunks: {
            chunks: 'all'
        }
    }
};

function getHtmlPlugin(chunks) {
    return chunks.map(
        (chunk) =>
            new HtmlPlugin({
                title: 'Test',
                filename: `${chunk}.html`,
                chunks: [chunk]
            })
    );
}

Package.json

{
 ...
 "devDependencies": {
  "webpack": "^5.94.0",
  "webpack-cli": "^5.1.4",
  "webpack-merge": "^6.0.1"
  }
 ...
}

Solution

  • You can inject a custom script (like http://localhost:8097) into your generated HTML files only in development using HtmlWebpackPlugin with the templateParameters or inject options, or by using a custom HTML template with logic to conditionally add that script only in dev.

    I think you could even just duplicate your files with other names and adjust your scripts to use them in development mode (popup-dev.html , options-dev.html)

    If going for the HtmlWebpackPlugin:

    With EJS you could use a custom template.ejs for popup.html and options.html

    1. Create a shared template.ejs file Inside your project (e.g., src/templates/template.ejs):
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title><%= htmlWebpackPlugin.options.title %></title>
      <% if (htmlWebpackPlugin.options.devMode) { %>
        <script src="http://localhost:8097"></script>
      <% } %>
    </head>
    <body>
      <div id="app"></div>
    </body>
    </html>
    

    This checks for devMode, and if true, injects your dev script.

    1. Modify getHtmlPlugin() in webpack.common.js

    Update your helper function to include the template and pass the dev flag dynamically:

    function getHtmlPlugin(chunks) {
        const isDev = process.env.NODE_ENV === 'development';
    
        return chunks.map(
            (chunk) =>
                new HtmlPlugin({
                    title: 'Test',
                    filename: `${chunk}.html`,
                    chunks: [chunk],
                    template: path.resolve(__dirname, 'src/templates/template.ejs'),
                    templateParameters: {
                        devMode: isDev
                    },
                    inject: 'body'
                })
        );
    }
    

    1. Set NODE_ENV correctly

    Make sure you're setting NODE_ENV=development when running webpack-dev-server or building locally:

    cross-env NODE_ENV=development webpack --config webpack.dev.js
    

    Add "cross-env" to your dev dependencies if needed:

    npm install --save-dev cross-env
    

    If you want separate templates for popup and options

    You can pass different templates to HtmlWebpackPlugin in getHtmlPlugin() like this:

    template: path.resolve(__dirname, `src/${chunk}/${chunk}.ejs`)
    

    Then make popup.ejs and options.ejs with the same if (htmlWebpackPlugin.options.devMode) logic.