Giter VIP home page Giter VIP logo

html-replace-webpack-plugin's Introduction

A Webpack plugin for replace HTML contents with custom pattern string or regex.

Examples

https://github.com/iminif/html-replace-webpack-plugin-howto

๐Ÿ’š Special Note! ๐Ÿ‘€

This plugin works together with html-webpack-plugin!

Usage

First of all, you need both html-webpack-plugin and html-replace-webpack-plugin.

npm i -D html-webpack-plugin html-replace-webpack-plugin

Then, add it to your webpack.config.js file:

In your webpack.config.js file:

๐Ÿ’š Please ensure that html-webpack-plugin was placed before html-replace-webpack-plugin in your Webpack config if you were working with Webpack 4.x!

var webpack = require('webpack')
var HtmlReplaceWebpackPlugin = require('html-replace-webpack-plugin')

// file types & file links
const resource = {
  js: { bootstrap: '//cdn/bootstrap/bootstrap.min.js' },
  css: { bootstrap: '//cdn/bootstrap/bootstrap.min.css' },
  img: { 'the-girl': '//cdn/img/the-girl.jpg' }
}

const tpl = {
  img: '<img src="%s">',
  css: '<link rel="stylesheet" type="text/css" href="%s">',
  js: '<script type="text/javascript" src="%s"></script>'
}

module.exports = {
  // Definition for Webpack plugins
  plugin: [
    new HtmlWebpackPlugin({
      /* configs */
    }),
    // Replace html contents with string or regex patterns
    new HtmlReplaceWebpackPlugin([
      {
        pattern: 'foo',
        replacement: '`foo` has been replaced with `bar`'
      },
      {
        pattern: '@@title',
        replacement: 'html replace webpack plugin'
      },
            {
        pattern: /<p>(.+?)<\/p>/g, // /g => replace all
        replacement: '<div>$1</div>'
      },
      {
        pattern: /(<!--\s*|@@)(css|js|img):([\w-\/]+)(\s*-->)?/g,
        replacement: function(match, $1, type, file, $4, index, input) {
          // those formal parameters could be:
          // match: <-- css:bootstrap-->
          // type: css
          // file: bootstrap
          // Then fetch css link from some resource object
          // var url = resources['css']['bootstrap']

          var url = resource[type][file]

          // $1==='@@' <--EQ--> $4===undefined
          return $4 == undefined ? url : tpl[type].replace('%s', url)
        }
      }
    ])
  ]
}

In your src/index.html file:

<!DOCTYPE html>
<html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>@@title</title>
      <!-- css:bootstrap -->
    </head>
    <body>
      <div>foo</div>
      <p>I wanna be in a div</p>
      <!-- js:bootstrap -->
    </body>
</html>

After replacing, in the dist/index.html file:

<html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>html replace webpack plugin</title>
      <link rel="stylesheet" type="text/css" href="//cdn/bootstrap/bootstrap.min.css">
    </head>
    <body>
      <div>`foo` has been replaced with `bar`</div>
      <div>I wanna be in a div</div>
      <script type="text/javascript" src="//cdn/bootstrap/bootstrap.min.js"></script>
    </body>
</html>

API

html-replace-webpack-plugin can be called with an objects array or an object.

Options for html-replace-webpack-plugin

new HtmlReplaceWebpackPlugin([obj1[, obj2[, obj3[, ...[, objN]]]]] | obj)

[obj1[, obj2[, obj3[, ...[, objN]]]]] | obj

Type: Objects Array | Object

obj1, obj2, obj3, ..., objN | obj

Type: Object

obj.pattern

Type: String | RegExp

string or regex pattern for matching HTML content. See the MDN documentation for RegExp for details.

obj.replacement

Type: String | Function

string with which the matching string be replaced, or function which returns a string for replacing. See the MDN documentation for String.replace for details.

html-replace-webpack-plugin's People

Contributors

asulaiman avatar helgenlechner avatar iminif avatar scullman avatar tpavard avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

html-replace-webpack-plugin's Issues

How do you specify your index.html file?

I don't see anything in the docs about how to specify where the html file you want to copy lives. Do you plan on adding support for that? It's kind of limiting my usage of this plugin.

Doesn't work in `mode: production`

I'm trying to use that plugin but it only works in development mode. As soon as I switch to production mode it stop to work.

My wepback version:

12:40 $ yarn list webpack
yarn list v1.17.3
warning Resolution field "[email protected]" is incompatible with requested version "chokidar@^2.0.0"
warning Resolution field "[email protected]" is incompatible with requested version "chokidar@^1.4.1"
warning Resolution field "[email protected]" is incompatible with requested version "chokidar@^2.1.8"
warning Resolution field "[email protected]" is incompatible with requested version "chokidar@^2.1.8"
warning Filtering by arguments is deprecated. Please use the pattern option instead.
โ”œโ”€ [email protected]
โ”‚  โ””โ”€ [email protected]
โ””โ”€ [email protected]
โœจ  Done in 2.96s.

Here my webpack.config.js

/* global __dirname */
const path = require('path');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const webpack = require('webpack');
const isDevelopment = process.argv.filter(el => el.includes('--development')).length > 0;
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackTagsPlugin = require('html-webpack-tags-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const HtmlReplaceWebpackPlugin = require('html-replace-webpack-plugin');
const CONTENT_BASE = path.join(__dirname, 'www');

const argv = (() => {
    const tmp = {};
    process.argv.slice(2).map( (element) => {
        const matches = element.match( '--([a-zA-Z0-9\-]+)=?(.*)');
        if ( matches ) {
            tmp[matches[1]] = matches[2]
            .replace(/^['"]/, '').replace(/['"]$/, '');
        }
    });
    return tmp;
})();

let entry = [path.resolve(__dirname, 'src/index.js')];
let jsLoaders = [{
    loader: 'babel-loader',
    options: {
        cacheDirectory: true,
        presets: ['@babel/preset-env'],
        plugins: [
            '@babel/plugin-proposal-optional-chaining',
            'angularjs-annotate',
        ].concat(isDevelopment ? ['@babel/plugin-transform-modules-commonjs'] : []),
    },
}];

if (isDevelopment) {
    jsLoaders = (argv['full-reload'] ? [] : [
        {
            loader: 'ng-hot-reload-loader',
            options: {
                requireAngular: 'angular',
            },
        },
    ]).concat(jsLoaders);

    entry = [
        'webpack-dev-server/client?http://localhost:9000',
        'webpack/hot/only-dev-server',
    ].concat(entry);
}

let mixPanelScripts = (url) => `
<script>
    MIXPANEL_CUSTOM_LIB_URL = '${url}/resources/js/m_p_l_2.js';
</script>
<script src="${url}/resources/js/m_p_l.js"></script>
`;

let nodeModulesDependencies = [
   .... several files 
    'regenerator-runtime/runtime.js',
];

const moduleAssets = [
    'ionic/fonts/**/*',
    'ngDialog/css/**/*',
];


module.exports = {
    mode: isDevelopment ? 'development' : 'production',
    resolve: {
        modules: [
            path.resolve(__dirname, 'src'),
            path.resolve(__dirname, 'node_modules'),
        ],
    },
    entry:  entry,
    output: {
        chunkFilename: 'js/[name].[hash].js',
        path: CONTENT_BASE,
        filename: isDevelopment ? 'js/app.min.js' : 'js/app.[hash].min.js',
    },
    optimization: {
        minimizer: isDevelopment ? [] : [new UglifyJsPlugin()],
    },
    devServer: {
        contentBase: CONTENT_BASE,
        port: 9000,
        hot: true,
        writeToDisk: false,
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'css/app.min.css',
        }),
        new HtmlWebpackTagsPlugin({
            tags: nodeModulesDependencies.map(v => 'lib/'+v).concat(['js/template.module.js', 'js/httpMock.module.js']),
            useHash: true,
            append: false,
        }),
        new HtmlWebpackTagsPlugin({
            tags: ['js/config.override.module.js'].concat(argv['mobile-build'] ? ['cordova.js'] : []),
            useHash: true,
            append: true,
        }),
        new HtmlWebpackPlugin({
            template: 'src/index.html',
            filename: 'index.html',
        }),
        new HtmlReplaceWebpackPlugin([
            {
                pattern: '<!-- replace:Google Tag Manager Head -->', // /g => replace all
                replacement: argv['mobile-build'] ? '' : `<!-- Google Tag Manager -->
                    <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
                            new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
                        j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
                        'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
                    })(window,document,'script','dataLayer','GTM-W4LRF6B');</script>
                    <!-- End Google Tag Manager -->`,
            }, {
                pattern: '<!-- replace:Google Tag Manager Body -->',
                replacement: argv['mobile-build'] ? '' : `<!-- Google Tag Manager (noscript) -->
                    <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-W4LRF6B" height="0" width="0"
                     style="display:none;visibility:hidden"></iframe></noscript>
                    <!-- End Google Tag Manager (noscript) -->`,
            }, {
                pattern: '<!-- replace:mixpanel_scripts -->',
                replacement: argv['mobile-build'] !== undefined ? mixPanelScripts('https://portal.mywebsite.com') : mixPanelScripts('..'),
            },
        ]),
        new CopyPlugin({
            patterns: nodeModulesDependencies
            .map(v => ({
                from: v,
                to: './lib/'+v,
                context: path.join(__dirname, 'node_modules'),
            }))
            .concat(moduleAssets.map( v => ({
                from: v,
                to: 'lib/',
                context: 'node_modules',
            })))
            .concat([{
                from: 'js/http/httpMock.module.js',
                to: 'js/',
                context: 'src',
            }, {
                from: 'js/utils/template.module.js',
                to: 'js',
                context: 'src',
            }]),
        }),
        new webpack.DefinePlugin({
            SOFTWARE_VERSION: JSON.stringify(require('./package.json').version),
        }),
    ].concat(isDevelopment ? [new webpack.HotModuleReplacementPlugin()] : []),
    module: {
        rules: [
            {
                test: /\.js$/,
                use: [
                    {
                        loader: 'angularjs-template-loader',
                        options: {
                            relativeTo: path.resolve(__dirname, 'src'),
                        },
                    },
                ],
                exclude: [/\.(spec|e2e)\.js$/],
            },
            {
                test: /\.html$/,
                use: [{
                    loader: 'raw-loader',
                    options: {
                        esModule: false,
                    },
                }],
            },
            {
                test: path.resolve(
                    __dirname, 'src', 'js/config/config.json',
                ),
                use: {
                    loader: 'ng-package-constants-loader',
                    options: {
                        moduleName: 'app.config',
                        configKey: argv.environment || 'webapp',
                        wrap: 'es6',
                    },
                },
                type: 'javascript/auto',
            },
            {
                test: /\.s?[ac]ss$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            publicPath: 'www/css/',
                            hmr: isDevelopment,
                            reloadAll: true,
                        },
                    },
                    { loader: 'css-loader', options: { url: false, sourceMap: false } },
                    { loader: 'sass-loader', options: { sourceMap: false } },
                ],
            },
            {
                test: /\.(png|jpe?g|gif)$/i,
                loader: 'file-loader',
                options: {
                    name () {
                        if (process.env.NODE_ENV === 'development') {
                            return 'resources/images/[path][name].[ext]';
                        }

                        return 'resources/images/[contenthash].[ext]';
                    },
                },
            },
            {
                test: /\.m?js$/,
                exclude: function(modulePath) {
                    return /node_modules/.test(modulePath);
                },
                use: jsLoaders,
            },
        ],
    },
    devtool: isDevelopment ? 'cheap-module-source-map' : undefined,
};

Any idea why it doesn't work on production mode?

Use in a template

Your plugin is simple and awesome, but how can I use it just in a template, for example:

template.html

<h1><%= title %></h1>
<p><%= description %></p>
<a href="evil.com" @blank></a>

index.js

import template from './template.html';

setTimeout(() => {
   const html = microTmpl(template, {
      title: 'The Title',
      description: 'Best Description Ever',
   });

   console.log(html);
}, 1000);

webpack.config.js

new HtmlReplaceWebpackPlugin([{
     pattern: '@blank',
     replacement: 'target="_blank" rel="noopener noreferrer"',
}]),

htmlContent.replace is not function

Unhandled rejection TypeError: htmlContent.replace is not a
function
at HtmlResWebpackPlugin.apply.compiler.plugin.compiler.plugin.Promise.resolv
e.then.then.HtmlResWebpackPlugin.buildStatsHtmlMode.HtmlResWebpackPlugin.printCh
unkName.HtmlResWebpackPlugin.buildStats.HtmlResWebpackPlugin.checkResource.HtmlR
esWebpackPlugin.injectAssets.injectChunks.map [as injectAssets] (E:\git\erp\node
_modules\html-res-webpack-plugin\index.js:475:28)

Global patterns doesn't work in some cases

let's say I want to replace '1234' by 'ab'.
if my source is '12341234', the pattern /1234/g will only be applied via the plugin to the first item (cursor issue with the while/replace loop and Match object tracking the position of the current match) -> 'ab1234'
'1234123412341234', the first and 2nd items only are gonna be replaced -> 'ab1234ab123'

May I suggest this code for the replace function:
It should also increase the performances (while/replace could be very slow if the block to replace is too big)

    this.replace = function(htmlPluginData, callback) {
        options.forEach(option => {
            if (typeof option.replacement === 'function') {
                try {
                    new RegExp(option.pattern)
                } catch (e) {
                    // (small improvement): throwing the error in the catch avoids the isValid variable declaration
                    throw new Error('Invalid `pattern` option provided, it must be a valid regex.')
                }
                // Fix: The while loop + all regex match stuffs are replaced by this single line of code
                htmlPluginData.html = htmlPluginData.html.replace(option.pattern, option.replacement);
            } else {
                if (option.pattern instanceof RegExp)
                    htmlPluginData.html = htmlPluginData.html.replace(option.pattern, option.replacement)
                else htmlPluginData.html = htmlPluginData.html.split(option.pattern).join(option.replacement)
            }
        })

        callback(null, htmlPluginData)
    }

Support Regex Patterns

e.g.

        {
            pattern: /<link.*\shref\=(?:\"|\')(.+?)(?:\"|\').*>/g,
            replacement: '[Css:{ path: "$1"}]'
        },

Error with webpack 4.12.0

When using this plugin, the following error is thrown to the terminal:

node_modules/html-replace-webpack-plugin/index.js:41
      compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing.tapAsync('html-webpack-plugin-before-html-processing', this.replace)
                                                              ^

TypeError: Cannot read property 'tapAsync' of undefined

The webpack config just has it configured with

new HtmlReplaceWebpackPlugin([
      {
        pattern: '@@version',
        replacement: '1.0.0'
      }
    ])

Is there some required dependency for working with webpack 4.x?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.