Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a HTML file as an entry point? #536

Open
jampy opened this issue Oct 14, 2014 · 148 comments
Open

Use a HTML file as an entry point? #536

jampy opened this issue Oct 14, 2014 · 148 comments

Comments

@jampy
Copy link

jampy commented Oct 14, 2014

I'm trying to figure out what's the best way to make webpack aware of the main HTML file (for a single page app).

AFAIK i have these options:

specify an .js file as entry point

That JS file needs to have require("./index.html"); so that the HTML file is identified as an dependency and included in the build.

To support that I have the following Webpack configuration:

loaders : {
  { test: /\.html/, loader: 'file?name=[name].[ext]' }
}

Problems:

  • Seems awkward to me to define the HTML file as a dependency for a JS file (instead of the other way round)
  • Additional dependencies in the HTML code (like images) are not being identified.

specify the .html file as entry point and using file-loader for HTML files

Problems:

  • Dependencies aren't being detected and thus no JavaScript code is bundled.

specify the .html file as entry point and using html-loader for HTML files

See sample config here: https://gist.github.com/jampy/44faf0c18fd64b6dd1fd
..and the HTML file: https://gist.github.com/jampy/a2dd493901cd6dc5ae8b

Problems:

  • The html-loader apparently detects dependencies like images, but seems to ignore the .js files referenced via <script> tags.
  • The .html file itself is converted to JavaScript and included in the .js file, which is useless.

What's correct?

What am I supposed to do to let webpack handle the HTML file and it's dependencies (including JavaScript).

@sokra
Copy link
Member

sokra commented Oct 17, 2014

Correct is to split your application into two parts:

  • client-side javascript (target: "web")
  • (pre)rending on server-side (target: "node") (this generates the HTML)

You can do this by exporting an array of configurations from the webpack.config.js.

For a simple static website you can do this:

module.exports = [
  {
    name: "client",
    target: "web",
    /* your client side configuration */
  },
  {
    name: "rendering",
    target: "node",
    entry: {
      "index.html": "./app/index.html",
    },
    output: {
      path: path.resolve("build")
      filename: "rendering/[name].js"
    }
  }
]
webpack && node -e "console.log(require('./build/render/index.html.js'))" > build/public/index.html

Maybe the script part can be moved into a webpack plugin. This would be more usable and dev-server combatible...

@nelix
Copy link

nelix commented Oct 19, 2014

You could try this plugin https://www.npmjs.org/package/html-webpack-plugin

@jampy
Copy link
Author

jampy commented Oct 20, 2014

Thanks. I forgot to mention that in the first post.

Still, that's similar to file-loader, meaning that additional dependencies in the HTML file (like images) aren't bundled (when using a custom template).

@randymorris
Copy link

@jampy did you ever come up with an acceptable solution? I'm trying to wrap my head around this as well.

@jhnns
Copy link
Member

jhnns commented Jan 11, 2015

I think this would require a change in the html-loader. It needed to turn this html:

<script src="./some/module.js"></script>

into

module.exports = "<script src=\"" + __webpack_public_path__ +
    JSON.stringify(urlToNewChunk) + "\"></script>";

while emitting a new chunk to the output folder with ./some/module.js as entry point.

@sokra Is that possible?

@kurtharriger
Copy link
Contributor

While webpack is great at generating javascript if you want to use hashes for caching you need to do post build processing to update the html. It seems like this should be easier.
Having the html-loader be the entry point rather than the javascript makes a lot more sense to me for brower targets, since ultimately the entry point from a browser is as an html page.
It seems like generating processed html would make it easy to link to cachable resources and maybe even automatically apply extract text plugin for referanced css and such.
I tried using the html-webpack-plugin with some success. It doesnt seem to hot reload in the dev server, but I really like the idea of including l javascript source files straight from html and have webpack update the links automatically!

@jhnns
Copy link
Member

jhnns commented Jan 12, 2015

Related discussion #220

@mako-taco
Copy link

I am running in to the same issue, where I'd like to inline scripts into an HTML entry point. As @jhnns brought up, html-loader could be modified to produce output that looks like

module.exports = "<script src=\"" + __webpack_public_path__ +
    JSON.stringify(urlToNewChunk) + "\"></script>";

It is, however, wrapped in a webpackJsonp function. For my purposes, i'd like to get the output of this as plain HTML (This is for an iframe which gets requested 80,000 times a second, so it's important to not have the iframe link to external resources). Changing around the html loader's output is easy enough, but then I am still left with the resulting webpackJsonP function wrapping my output...

Does anyone know how to prevent that so I can output plain HTML?

@kurtharriger
Copy link
Contributor

Might be able to use and/or copy bits from extract-text-webpack-plugin

@mogelbrod
Copy link

Since HTML files are the entry points for browsers being able to do this just seems logical.
I'm experimenting with different ways of doing this, and have so far been able to compile a jade file (html like) entry point, with some issues.

webpack.config.ls

require! <[ webpack path ]>
ExtractTextPlugin = require "extract-text-webpack-plugin"

# Webpack plugins
css-extractor  = new ExtractTextPlugin "css", "[name].[id].css"
html-extractor = new ExtractTextPlugin "html", "[name].html"

module.exports = do
  output:
    path: path.join __dirname, "build"
    public-path: "/assets"
    filename: "[name].js"
    chunk-filename: "[name].id.js"

  entry:
    index: "./client/index.jade"

  devtool: "source-map"

  plugins: [css-extractor, html-extractor]

  resolve:
    extensions: ['', ".js", ".ls", ".css", ".styl", ".html", ".jade"]

  module:
    loaders:
      * test: /\.ls$/
        loader: 'livescript'
      * test: /\.styl$/
        loader: css-extractor.extract "style", "css!stylus"
      * test: /\.jade$/
        loader: html-extractor.extract "html", "apply!jade"

Gist for apply loader

Current problems:

  • Compiled html includes //# sourceMappingURL=index.html.map due to devtool option. Haven't found a way to disable this without modifying the ExtractTextPlugin. It's inserted by the Webpack EvalDevToolModulePlugin, which is injected before any loader code is executed.
  • Can't figure out how to require() scripts in index.jade:
    • script(src=require('./common.ls')) inserts src="[Object object]"
    • script(src=require('file?name=[name]-[hash].js!./common.ls')) correctly inserts src="/assets/common-938e5a0f3a70d579b6d485dbb56f5fa9.js", but require()s inside it doesn't seem to be resolved.

@AndrewRayCode
Copy link

Since HTML files require scripts, css files, etc that are built by webpack, there needs to be a way to manage HTML files this way with webpack. There should be a way to treat <script src="../local/script.js"></script> as any other webpack require where it inlines the hashed name of that file and builds it to an html file.

This plugin: https://github.com/skozin/webpack-path-rewriter comes close, except it doesn't reload when you change files and webpack is watching (so your html doesn't update until you entirely re-run webpack), and the way you require entry point files is by having to prefix them with something-[hash] and then in the html file doing <script src="[[something-*]]"></script> which is less than ideal.

The HTML file a likely candidate for a true entry point to an application, so it should be easy for it to access the dependency graph.

@tcurdt
Copy link

tcurdt commented May 4, 2015

I've spent a whole afternoon on this until I realized this all breaks down with the current webpack-dever-server implementation generating the html.

@sokra I am willing to work on this if you could provide some guidance. This should really be easier.

@screendriver
Copy link

Same here. At the moment my build/ directory contains only a bundle.js file but not my index.html.
Fortunately there is a plugin for this: html-webpack-plugin. But why we have to use a plugin? Is there a different approach in webpack I am missing?

@jhnns
Copy link
Member

jhnns commented May 15, 2015

This is not as trivial as you think, because it breaks one major assumption in webpack: Every "webmodule" can be represented as JavaScript.

Sure, it is possible, to represent HTML as JavaScript. The html-loader does that, it turns something like

<img src="./some-image.jpg">

into

module.exports = "<img src=\"" + require("./some-image.jpg") + "\">";

which can be interpreted by webpack easily. It's another case with index.html. It must stay HTML.

That's why the extract-text-webpack-plugin or the html-webpack-plugin is probably the right approach.

@screendriver
Copy link

Thank you for clarifying 👍
Could you add a description or a example or something to the webpack website that explains the wohle thing? For beginners it's a little bit confusing.

@tony-kerz
Copy link

+1 on a little info around this in the documentation, i just burned an hour or two trying to grok this before stumbling across this thread...

@wmertens
Copy link
Contributor

I had the same idea, using index.html as the entry point for the app is the most logical. It would also allow changing some URLs to CDN URLs and basically do everything that grunt/gulp does, but dependency-driven.

@mogelbrod I think you need to do "file!js?path/to/app.js" inside the html so that it parses the js as js first, resolving all require calls. It would be cool if you could specify sub-loaders, meaning a loader configuration that only applies for files that are matched in the regular loader configuration.

Then the only problem is getting an actual index.html file out of it, I'm looking at extract-text-webpack-plugin now. Alternatively I could make an entry.js file that simply requires the index.html file.

@wmertens
Copy link
Contributor

@mogelbrod what is that apply loader you use in the html loader configuration?

@mogelbrod
Copy link

@wmertens Gist for a simple apply loader
Looking forward to reading about any findings you do! =)

@wmertens
Copy link
Contributor

@mogelbrod I ended up not needing it, I used your text extractor configuration and configured my main script to be written to file after converting. Note that the html loader doesn't support a loader configuration in the source attributes so I had to do it in the webpack configuration.

This setup almost works, but the included script doesn't have the webpack bootstrap code 😢. So the html text extractor should in fact replace all to-be-bundled script tags with a single script tag that loads the bundle js and then requires all the removed scripts in the same order.

Right now, my resulting index.html has <script src="a66188bc09c9607710dbfa1c54f85a98.coffee"> and the bundled "index.html" entry point is eval("// removed by extract-text-webpack-plugin\n\n/*****************\n ** WEBPACK FOOTER\n ** ./client/index.html\n ** module id = 3\n ** module chunks = 0\n **/\n//# sourceURL=webpack:///./client/index.html?");. The script is simply the webpackified script.

So it's feasible by combining the html loader and text extractor plugin, provided the loader knows what the js bundle path will be and the text extractor can leave some require statements in place. Then when visiting index.html the resulting bundle js will be loaded, executed, require the index.html entry point which loads the desired scripts.

It would be really cool if this worked, because it opens the door for lots of html, script and image processing using minimal configuration.

My setup:

index.html:

<!DOCTYPE html>
<html lang=en>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <link href='//fonts.googleapis.com/css?family=Roboto:500,300,400' rel='stylesheet' type='text/css'>
  <body>
    <div id="app">
        <p>LOADING&hellip;</p>
    </div>
    <script type="text/javascript" src="./main.coffee" charset="utf-8"></script>
  </body>
</html>

webpack.config.coffee:

webpack = require('webpack')
ExtractTextPlugin = require "extract-text-webpack-plugin"

# Webpack plugins
cssExtractor  = new ExtractTextPlugin "css", "[name].[id].css"
htmlExtractor = new ExtractTextPlugin "html", "[name].html"

isProd = (process.env.NODE_ENV is "production")
console.log "Configuring webpack for #{if isProd then "production" else "dev"}"

entries = if isProd then [] else [
    'webpack-dev-server/client?http://0.0.0.0:8080'
    'webpack/hot/only-dev-server'
]

module.exports =
  # use eval-source-map if you want coffeescript in the browser dev tools
  devtool: if not isProd then 'eval' # 'eval-source-map'
  devServer:
    # allow everyone in
    host: '0.0.0.0'
  entry: app: entries.concat [
    # './client/main.coffee'
    './client/index.html'
  ]
  output:
    pathinfo: true
    path: './build'
    filename: 'bundle-[name].js'
    "chunk-filename": "[name].id.js"
  plugins: [cssExtractor, htmlExtractor]
  resolve:
    modulesDirectories: [ 'node_modules', 'client']
    # List of automatically tested extensions
    extensions: [
      ''
      '.js'
      '.json'
      '.cson'
      '.coffee'
    ]
  module: loaders: [
    {
      test: /\.jsx$/
      loader: 'react-hot!jsx?harmony'
      # exclude: /node_modules/
    }
    {
      test: /\.coffee$/
      loader: 'react-hot!coffee!cjsx'
      exclude: /node_modules/
    }
    {
      test: /main\.coffee$/
      loader: 'file!react-hot!coffee!cjsx'
      exclude: /node_modules/
    }
    {
      test: /\.json$/
      loader: 'json'
    }
    {
      test: /\.cson$/
      loader: 'cson'
    }
    {
      test: /\.css$/
      loader: 'style!css'
    }
    {
      test: /\.less$/
      loader: 'style!css!less'
    }
    {
      test: /\.html$/
      loader: htmlExtractor.extract 'html?attrs=script:src'
      #  'file?name=[path][name].[ext]&context=./client!html?attrs=script:src'
    }
    {
      # Just reference all the rest
      test: /\.(png|otf|eot|svg|ttf|woff2?)(\?.*)?$/
      loader: 'url?limit=8192&name=[path][name].[ext]&context=./client'
    }
  ]

@wmertens
Copy link
Contributor

So as a TL;DR: You cannot use the HTML as an entry point right now.

To do it, there needs to be a loader+plugin, performing these steps:

  • loader: like html loader
    • create javascript code that emits the html as a string
    • convert resource tags into require calls
      • script tags should be stripped and replaced with a single placeholder script tag loading the bundle
        • the html loader does not do this
      • other resources should be forced to pass through the file loader, or optionally converted to inline (e.g. font css)
        • the html loader does not do this
      • optionally: leave third-party scripts alone or change their URL (e.g. libraries on CDN) or inline them (e.g. google font loader), as long as they don't use require.
      • obviously this allows transforming less, sass, coffeescript, images, ...
  • plugin: like extract-text plugin
    • Replace the placeholder script tag src with the path to the relevant bundle file (possibly chunked)
      • the extract-text plugin does not do this
    • Emit the HTML as a file and in the module source require() all the script resources
      • the extract-text plugin removes everything from the module source

The webpack configuration would only require setting up the plugin since that can set up the loader.

I don't feel up to making this but I would be super grateful if someone would. This is IMHO the missing functionality to completely replace gulp/grunt (npm run allows running scripts).

@mogelbrod
Copy link

Nice findings @wmertens! I'm not yet familiar enough with Webpack to implement what you're suggesting, but totally agree on your last point and am very interested in any further progress on this issue.

@wmertens
Copy link
Contributor

Extra puzzle piece: In a plugin you can access the filenames of chunks, see https://github.com/sporto/assets-webpack-plugin/blob/master/index.js#L62

@wmertens
Copy link
Contributor

Ideally you'd be able to do something like:

  • assets/index.html:

    <!DOCTYPE html>
    <html>
    <!-- browser assets -->
    <link rel="apple-touch-icon" sizes="114x114" href="apple-touch-icon-114.png">
    <meta name="msapplication-TileImage" content="windows-tile.png">
    <link rel="icon" sizes="any" mask href="favicon.svg">
    <link rel="icon" type="image/x-icon" href="favicon.ico">
    <link href="../src/style.css" media="all" rel="stylesheet" />
    
    The content
    
    <script src="../src/app.js"></script>
    </html>
  • src/style.css:

    /* special entry that text-extract-plugin should place extracted CSS in */
    /* text-extract-plugin "style.css" begin */
    /* text-extract-plugin "style.css" end */
  • src/app.js:

    // Your app

and then in webpack you make the entry be "file?name=index.html!val!html!assets/index.html", which would result in a html like:

<!DOCTYPE html>
<html>
    <!-- browser assets -->
    <link rel="apple-touch-icon" sizes="114x114" href="hash1.png">
    <meta name="msapplication-TileImage" content="hash2.png">
    <link rel="icon" sizes="any" mask href="hash3.svg">
    <link rel="icon" type="image/x-icon" href="hash4.ico">
    <link href="hash5.css" media="all" rel="stylesheet" />

    The content

    <script src="hash6.js"></script>
</html>

and the hash6.js should be a bundle with the webpack preamble. Then you can copy the entire build folder onto your web server (index.html last) and the hashes will make sure browsers get the correct version of everything, no caching issues.

The html loader should allow processing urls so you can CDNify them, inline them, ...

@sokra is this feasible? Any pointers on how to implement?

@jiangfengming
Copy link

Though not very convenient, you can do it without a plugin.

It doesn't work with UglifyJsPlugin. I had to add extract-text plugin back..

Here is my build script:

process.chdir(__dirname);
var fs = require('fs');
var util = require('util');
var path = require('path');
var Getopt = require('node-getopt');
var webpack = require('webpack');
var crypto = require('crypto');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var child_process = require('child_process');

var opt = new Getopt([
  ['c', 'config=CONFIG', 'Specify the config file name in the conf folder. Default: default'],
  ['r', 'run=COMMAND', 'The command to run after the compilation is complete.'],
  ['h', 'help', 'Display this help.']
]).bindHelp().parseSystem();

var conf = require('./conf/' + (opt.options.config || 'default'));

var webpackOpts = {
  resolve: {
    root: [path.join(__dirname, 'bower_components')],
  },

  entry: {
    'app.html': './app.html',
    app: './app.js',
    vendor: './vendor.js'
  },

  output: {
    path: './dist',
    publicPath: conf.resource + 'dist/',
    filename: '[name].js?[chunkhash]',
    chunkFilename: '[name].js?[chunkhash]',
    sourceMapFilename: '[file].map'
  },

  module: {
    loaders: [
      { test: /app\.html$/, loader: ExtractTextPlugin.extract('html?attrs=link:href') },
      { test: /favicon\.png$/, loader: 'file' },
      { test: /\.css$/, loader: 'style!css' },
      { test: /\.styl$/, loader: 'style!css!stylus' },
      { test: /\.html$/, exclude: /app\.html$/, loader: 'html' },
      { test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/, exclude: /favicon\.png$/, loader: 'url?limit=10000' }
    ]
  },

  stylus: {
    use: [require('nib')()],
    import: ['nib']
  },

  devtool: 'source-map',
  debug: conf.debug,

  plugins: [
    new ExtractTextPlugin('app.html'),

    new webpack.DefinePlugin({
      __CONF__: JSON.stringify(conf)
    }),

    new webpack.ResolverPlugin(
        new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin('bower.json', ['main'])
    ),

    new webpack.optimize.OccurenceOrderPlugin(),

    // long-term caching
    // http://webpack.github.io/docs/long-term-caching.html
    function() {
      this.plugin('done', function(stats) {
        var chunks = stats.toJson().assetsByChunkName;

        // replace the js file path of app.html
        appHtml = fs.readFileSync('./dist/app.html', { encoding: 'utf8' });
        for (var entry in chunks) {
          var src = entry + '.js';

          // Code splitting and chunkhash problem
          // https://github.com/webpack/webpack/issues/1209
          chunkhash = crypto.createHash('md5').update(fs.readFileSync('./dist/' + src)).digest('hex');
          var dest = conf.resource + 'dist/' + src + '?' + chunkhash;
          appHtml = appHtml.replace(src, dest);
        }
        fs.writeFileSync('./dist/app.html', appHtml);
      });
    }
  ]
};

if (!conf.debug)
  webpackOpts.plugins.push(new webpack.optimize.UglifyJsPlugin());

var compiler = webpack(webpackOpts);

if (conf.debug) {
  compiler.watch(null, compilerCb);
} else {
  // Bug: UglifyJsPlugin will compile <input type="text" requred="{{foo}}"> to <input type="text" required>
  // https://github.com/webpack/webpack/issues/752
  // Webpack 2.0 will fix this issue
  compiler.plugin("compilation", function(compilation) {
    compilation.plugin("normal-module-loader", function(context) {
      context.minimize = false;
    });
  });

  compiler.run(compilerCb);
}

function compilerCb(err, stats) {
  if (err)
    return console.error(err);

  var jsonStats = stats.toJson();

  if (jsonStats.errors.length > 0) {
    jsonStats.errors.forEach(function(err) {
      console.error(err);
    });
    return;
  }

  if (jsonStats.warnings.length > 0) {
    jsonStats.warnings.forEach(function(err) {
      console.error(err);
    });
  }

  jsonStats.modules.forEach(function(module) {
    console.log(module.name);
  });

  if (opt.options.run) {
    child_process.execSync(opt.options.run, {
      stdio: 'inherit'
    });
  }

  console.log(new Date().toLocaleString());
}

conf/default.js:

module.exports = {
  base: '/',
  resource: '//app-dev-res.example.com/',
  api: 'https://app-dev-api.example.com/',
  debug: true
};

app.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title v-text="title"></title>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <link rel="icon" type="image/png" href="favicon.png">
  </head>

  <body>
    <script src="vendor.js"></script>
    <script src="app.js"></script>
  </body>
</html>

run:

node build -r "rsync -rptz --exclude .git* --exclude .DS_Store ./ USER@app-dev.example.com:/home/USER/app-frontend"

@wmertens
Copy link
Contributor

looks to me like you wrote a plugin :)

It doesn't handle my entire wishlist (coalesce script tags etc) but it does
make the js point to hashed files, which is great.

I just don't understand how come the resulting chunks can load, do all
chunks include the webpack loader stub? Is it because they are entries?

Did you try adding the script:src tags to the things html should parse? Do
those then not include the stub?

Why do you name the files with the URL query syntax and not simply
file-hash.js?

Good stuff, thanks!

On Thu, Jun 25, 2015 at 8:41 AM Fenix notifications@github.com wrote:

Though not very convenient, you can do it without a plugin. Here is my
build script:

process.chdir(__dirname);
var fs = require('fs');
var util = require('util');
var path = require('path');
var Getopt = require('node-getopt');
var webpack = require('webpack');
var crypto = require('crypto');
var child_process = require('child_process');

var opt = new Getopt([
['c', 'config=CONFIG', 'Specify the config file name in the conf folder. Default: default'],
['r', 'run=COMMAND', 'The command to run after the compilation is complete.'],
['h', 'help', 'Display this help.']
]).bindHelp().parseSystem();

var conf = require('./conf/' + (opt.options.config || 'default'));

var webpackOpts = {
resolve: {
root: [path.join(__dirname, 'bower_components')],
},

entry: {
'app.html': './app.html',
app: './app.js',
vendor: './vendor.js'
},

output: {
path: './dist',
publicPath: conf.resource + 'dist/',
filename: '[name].js?[chunkhash]',
chunkFilename: '[name].js?[chunkhash]',
sourceMapFilename: '[file].map'
},

module: {
loaders: [
{ test: /app.html$/, loader: 'html?attrs=link:href' },
{ test: /.css$/, loader: 'style!css' },
{ test: /.styl$/, loader: 'style!css!stylus' },
{ test: /.html$/, exclude: /app.html$/, loader: 'html' },
{ test: /.(png|jpg|jpeg|gif|ico|eot|ttf|woff|woff2|svg|svgz)(?.+)?$/, loader: 'url?limit=10000' }
]
},

stylus: {
use: [require('nib')()],
import: ['nib']
},

devtool: 'source-map',
debug: conf.debug,

plugins: [
new webpack.DefinePlugin({
CONF: JSON.stringify(conf)
}),

new webpack.ResolverPlugin(
    new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin('bower.json', ['main'])
),

new webpack.optimize.OccurenceOrderPlugin(),

// long-term caching
// http://webpack.github.io/docs/long-term-caching.html
function() {
  this.plugin('done', function(stats) {
    var chunks = stats.toJson().assetsByChunkName;

    // replace the js file path of app.html
    appHtml = eval(fs.readFileSync('./dist/app.html.js', { encoding: 'utf8' }));
    for (var entry in chunks) {
      var src = entry + '.js';
      var dest = conf.resource + 'dist/' + chunks[entry][0];
      appHtml = appHtml.replace(src, dest);
    }
    fs.writeFileSync('./dist/app.html', appHtml);
  });
}

]
};

if (!conf.debug)
webpackOpts.plugins.push(new webpack.optimize.UglifyJsPlugin());

var compiler = webpack(webpackOpts);

if (conf.debug) {
compiler.watch(null, compilerCb);
} else {
// Bug: UglifyJsPlugin will compile to
// #752
// Webpack 2.0 will fix this issue
compiler.plugin("compilation", function(compilation) {
compilation.plugin("normal-module-loader", function(context) {
context.minimize = false;
});
});

compiler.run(compilerCb);
}

function compilerCb(err, stats) {
if (err)
return console.error(err);

var jsonStats = stats.toJson();

if (jsonStats.errors.length > 0) {
jsonStats.errors.forEach(function(err) {
console.error(err);
});
return;
}

if (jsonStats.warnings.length > 0) {
jsonStats.warnings.forEach(function(err) {
console.error(err);
});
}

jsonStats.modules.forEach(function(module) {
console.log(module.name);
});

if (opt.options.run) {
child_process.execSync(opt.options.run, {
stdio: 'inherit'
});
}

console.log(new Date().toLocaleString());
}

app.html:

<title v-text="title"></title> <script src="vendor.js"></script> <script src="app.js"></script>

run:

node build -r "rsync -rptz --exclude .git* --exclude .DS_Store ./ USER@app-dev.example.com:/home/USER/app-frontend"


Reply to this email directly or view it on GitHub
#536 (comment).

Wout.
(typed on mobile, excuse terseness)

@sokra
Copy link
Member

sokra commented May 31, 2020

Yes as @evilebottnawi said this is planned similar to parcel. If you think this should get higher priority, cast your vote on the voting page...

@monokrome
Copy link

Where's this voting page at?

@sokra
Copy link
Member

sokra commented Jun 6, 2020

https://webpack.js.org/vote/

@chriscalo
Copy link

Is this voting page documented anywhere? Any time I've tried to click the up arrows, nothing happens.

@resistdesign
Copy link
Contributor

@chriscalo you have to sign in with GitHub to vote. You vote with your influence points.

@chriscalo
Copy link

I am signed in because I see the up and down arrows. But when I click them nothing happens.

@kevincox
Copy link

kevincox commented Jun 6, 2020

Can we please take this discussion elsewhere? It would good to keep this bug focused, it isn't the best place to discuss the voting system.

@trusktr
Copy link

trusktr commented Jan 28, 2021

@pribilinskiy mentioned Poi (a "zero-config" wrapper on top of Webpack) in the above comment which apparently everyone skipped because it has zero thumbs-up.

Poi has a built-in html-entry-loader. Looks really easy to use or adapt. Maybe someone can publish it to NPM separately from Poi...

@Airkro
Copy link

Airkro commented Jul 5, 2021

Using html-webpack-plugin needs too many plugins to handle custom tags inject, they have many mutex logic. and that list keep became longer.

I think vite is doing good about HTML handing. It proves HTML entry could really save the day.

@trusktr
Copy link

trusktr commented Oct 9, 2021

@Mitscherlich published a new html-entry-loader on npm.

@Mitscherlich
Copy link

Mitscherlich commented Oct 9, 2021

@Mitscherlich published a new html-entry-loader on npm.

Yeap it's okay to consider html-entry-loader as a possible solution but not hurry to use it in production since it will only process <script> and <link> and may generate wrong html fragment while transforming.

html-entry-loader is a personal try-out and simply combined html-loader and html-webpack-plugin but add a little bit presume, which may not meet general propose. Any PR is welcome. 😄

@cmonti-bc
Copy link

Darn I really need this to work :(

@chriscalo
Copy link

(If it's an option for you, HTML entry points work in both Vite and Parcel)

@wmertens
Copy link
Contributor

wmertens commented Mar 9, 2022

@cmonti-bc actually, having a server endpoint that outputs the HTML entry dynamically is very nice, it plays well with SSR and allows you to fine-tune every detail. It's also straightforward to cache so performance can be similar to static files.

@cmonti-bc
Copy link

@cmonti-bc actually, having a server endpoint that outputs the HTML entry dynamically is very nice, it plays well with SSR and allows you to fine-tune every detail. It's also straightforward to cache so performance can be similar to static files.

Sorry but the project I'm working on cannot afford to have an endpoint to serve the html. Thanks anyway.

@TheLarkInn TheLarkInn moved this from Maybe to Move to Main Board in webpack 5 Apr 25, 2023
@webdiscus
Copy link
Contributor

webdiscus commented Aug 13, 2023

Hello @sokra, @alexander-akait, @TheLarkInn, @wmertens

the html-bundler-webpack-plugin allows to use a HTML template as an entry point.
In a HTML template can be referenced any source files of styles, scripts, images, fonts, etc.
The bundler plugin detects all source files referenced in a HTML template and extracts processed assets to the output directory.

For example, there is the source of index.html:

<html>
<head>
  <!-- specify source style files -->
  <link href="./styles.scss" rel="stylesheet">
  <!-- specify source script files here and/or in body -->
  <script src="./main.ts" defer="defer"></script>
</head>
<body>
  <h1>Hello World!</h1>
  <!-- specify source image files -->
  <img src="./picture.png">
</body>
</html>

You can specify script, style and image source files in HTML using a relative path or Webpack aliases. Of cause, you can import styles in JavaScript. Imported styles will be extracted and the <link href="..."> tag will be injected in a HTML automatically.

The Webpack config is very simple:

const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
  entry: {
     index: 'src/views/home.html', // => dist/index.html
     'news/sport': 'src/views/news/sport/index.html', // => dist/news/sport.html
  },
  plugins: [
    new HtmlBundlerPlugin(),
  ],
  // ... loaders for ts, sass, images, etc.
};

Or you can use the entry plugin option to locate all relevant options at the same place:

const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
  plugins: [
    new HtmlBundlerPlugin({
      // automatically processing HTML templates in the path
      entry: 'src/views/',
      // -OR- define templates manually
      entry: {
        index: { // => dist/index.html
          import: 'src/views/home.html', // template file
          data: { title: 'Homepage', name: 'Heisenberg' } // pass variables into template
        },
        'news/sport': 'src/views/news/sport/index.html', // => dist/news/sport.html
      },
      js: {
        // output filename of compiled JavaScript
        filename: 'js/[name].[contenthash:8].js',
        inline: 'auto', // `auto`, true, false
      },
      css: {
        // output filename of extracted CSS
        filename: 'css/[name].[contenthash:8].css',
        inline: 'auto', // inlines CSS into HTML in development mode or save to file in production mode
      },
    }),
  ],
  // ... loaders for ts, sass, images, etc.
};

The generated dist/index.html contains the output filenames of the processed files:

<html>
<head>
  <link href="css/styles.05e4dd86.css" rel="stylesheet">
  <script src="js/main.f4b855d8.js" defer="defer"></script>
</head>
<body>
  <h1>Hello World!</h1>
  <img src="img/picture.58b43bd8.png">
</body>
</html>

Try in a browser how it works:
Open in StackBlitz

@guest271314
Copy link

@webdiscus I have a case where I am trying to omit loading any JavaScript as an entry point.

This https://github.com/GoogleChromeLabs/telnet-client winds up creating an HTML file that also includes two <script defer="defer"> elements with src set to main.js and runtime.js that I am not using. I am writing <script> elements in the HTML directly before runnin npm build run. How do I instruct Webpack to not build those scripts or emit those <script> elements at all in the HTML?

@guest271314
Copy link

@webdiscus Substituting HtmlBundlerPlugin for HtmlWebpackPlugin does remove the <script> tags, though the HTML is basically a script now

// Module
var code = " <html> <head> ...";
// Exports
export default code;

and the file runtime.js is written to src as well as main.html where the idea was to just emit a single HTML file without writing runtime.js or main(.js/html) at all.

@webdiscus
Copy link
Contributor

webdiscus commented Aug 14, 2023

@guest271314

try my optimised fork of telnet-client. It works fine.
This version generate html file with inlined JS and CSS. JS or CSS files are not generated in dist/.

Using the HtmlBundlerPlugin:

  • you not need define JS files in the Webpack entry option, you can specify source files directly in HTML or write self JS code in <script> tag
  • you can write your JS code to a file and inline it in the generated HTML using the js.inline plugin option
  • you can inline CSS into HTML using the css.inline plugin option
  • don't use: html-loader, style-loader, html-webpack-plugin, mini-css-extract-plugin, because the HtmlBundlerPlugin covers their functionality

@webdiscus Substituting HtmlBundlerPlugin for HtmlWebpackPlugin does remove the <script> tags, though the HTML is basically a script now

var code = " <html> <head> ...";
export default code;

The HTML contains JS code because you are using the html-loader. Just remove it, it gets in the way.

@webdiscus
Copy link
Contributor

Using the html-bundler-webpack-plugin you can use the source file paths in all dependencies, similar to how it works in Vite or Parcel.

assets graph

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
webpack 5
Move to Main Board
Status: Priority - Medium
Status: To be discussed
Development

No branches or pull requests