webpack / webpack

A bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through "loaders", modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.

Home Page:https://webpack.js.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Use a HTML file as an entry point?

jampy opened this issue · comments

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).

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...

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).

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

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?

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!

Related discussion #220

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?

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

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.

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.

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.

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?

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.

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.

+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...

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.

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

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

@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'
    }
  ]

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).

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.

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

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?

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"

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)

Answering my own question: http://webpack.github.io/docs/multiple-entry-points.html states that entry point chunks include the webpack loader.

@fenivana's code looks like a step in the right direction. I would like to have multiple html entry points though.

@wmertens It seems doesn't work with UglifyJsPlugin. Evaluating the uglified entry chunk results FALSE.

I tried { test: /app\.html$/, loader: 'html?attrs=link:href script:src' }, it would throw errors like "window is undefined". But I think css would work.

Naming the file with chunkhash as query string makes dist directory cleaner. It will overwrite outdated files, and the filename is more readable. Occasionally I have to look into the dist files to debug, the clean dist folder would help a lot.

Recently I came across this very problem, my current solution was to write a plugin that did the job for me:

I don't know how bad would it be to force a user to put a '!' as a prefix to every resource that you want to load. What do you people think?

It doesn't seem very different from the current html plugin?

Basically, an html entry plugin should convert all script requirements into
a single combined entry point. I don't know if you can add entry points
during compilation…

On Thu, Jul 9, 2015, 04:38 Nihey Luz Takizawa notifications@github.com
wrote:

Recently I came across this very problem, my current solution was to write
a plugin that did the job for me:

I don't know how bad would it be to force a user to put a '!' as a prefix
to every resource that you want to load. What do you people think?


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

Wout.
(typed on mobile, excuse terseness)

It doesn't seem very different from the current html plugin?

You mean html-webpack-plugin? They might appear to be similar, but their goals are different. The current plugin takes a template and compiles it (or create a simple html boilerplate for you).

My goal was to bundle files linked in <img> and <link> tags. This way no references are broken when building the html file.

Basically, an html entry plugin should convert all script requirements into a single combined entry point. (...)

Yes, my code does not handle any script requirements (I'm still leaving it to webpack) - its not the optimal solution. But it does handle <img>, which would also be required on a html entry plugin.

It will be great, if it can working something similar to:

webpack.config.js

plugins: [
    new HtmlPlugin([
        {
            entry: './src/entry1.html',
            output: './public/output1.html'
        },
        {
            entry: './src/entry2.hbs',
            output: './public/output2.html',
            loader: 'handlebars'
        },
        {
            entry: './src/entry3.jade',
            output: './public/output3.html',
            loader: 'jade'
        }])
    ]

@Friend-LGA Is this related to this issue? I was under the impression we were talking about taking in HTML files as entry points, not .hbs or .jade files.

@cowwoc Why don't use some template framework to generate entry HTML ?

@Friend-LGA I like my HTML files pure for readability and maintenance reasons. In any case, you need to get plain HTML files working before you can add template support.

Let's please focus on the original request here, and you can file a separate request to add templating support once that's done.

@sokra how can a plugin add an entry point during compilations? That is the missing puzzle piece.

@sokra how can a plugin add an entry point during compilations? That is the missing puzzle piece.

There is a make plugin hook on the compilation in which entry points should be added. The entry option is converted to this.addEntry calls in this hook. See

compiler.plugin("make", function(compilation, callback) {
var dep = new SingleEntryDependency(this.entry);
dep.loc = this.name;
compilation.addEntry(this.context, dep, this.name, callback);
}.bind(this));

commented

Is this the defacto solution for this? https://github.com/ampedandwired/html-webpack-plugin

@quantuminformation maybe - but not what this issue is really about though.
The idea is to start from the HTML files as entry - not generating them.

@quantuminformation I think so. And in an ideal world your html shouldn't have entry point specific stuff in them, except the script tags which can be achieved through the plugin you mentioned.

@nodkz that plugin produces html files - at least not quite what I am after.

Judging by the readme it does exactly what I was looking for, nice find @nodkz!
@tcurdt: According to the readme it does indeed allow you to use HTML entry points and will produce another HTML file with the correct script/stylesheet references, or am I missing something?

@tcurdt I agree with @mogelbrod. I think it depends on what the produced HTML file looks like. If the plugin lets you use HTML files as entry points, and the only thing it rewrites are the script filenames then it sounds reasonable to me.

At least I am looking for something that uses an existing html file (not produces one) and gathers further dependencies from the html.

Why don't you want a new file to be produced? You won't be able to use the filename rewriting feature in webpack without modifying the referenced file names.

@mongelbrod I want full control of the HTML.

HTML files are the entry points for the browser - so should they be for webpack. I would love to use webpack not just for SPA but also for other setups.

Assetgraph gets this right https://github.com/assetgraph/assetgraph ...but it does not have all the nice features webpack has.

But isn't full control what you get with the indexhtml-webpack-plugin? The html-webpack-plugin wasn't enough is this regard but the plugin which @nodkz linked seems like a perfect fit. I can't test it myself right now, but from what I can see you should just be able to specify HTML entry points, which the plugin will parse and produce new HTML files for in your webpack destination folder with everything but the script[src] and link[href] attributes kept intact.

@mogelbrod I haven't had the chance to try it yet - but if that's the case I misread the README and should maybe submit a PR for it :)

indexhtml currently has one big issue that I can't figure out how to work around that completely prevents its use: unbroken-dome/indexhtml-webpack-plugin#4

If anyone wants to help fix it, I'd be grateful :) I've provided an easy to run test case and I've narrowed down the source of the problem (options.publicPath isn't being set when evaluating the HTML).

I think if indexhtml could be fixed (and perhaps unbroken-dome/indexhtml-webpack-plugin#2 could be fixed too, although at least there's a workaround for that), we could have a very nice setup.

Hi, thanks for noting the plugin, I created it out of pretty much the same need that this issue describes. Forgot to mention it here myself, thanks for the link @nodkz :-)

I'm quite a newbie at both webpack and node, so any help is welcome. I just got it to work for my own project and thought I'd share, but I'll look into the issue @candrews

I found solution how to make HMR work with a html entry point with indexhtml-webpack-plugin. Not so good as I want, but may be somebody suggest better solution.
unbroken-dome/indexhtml-webpack-plugin#1

@tkrullmann I try found any solution with html file with entry point about 6 or 7 days. So at end I use google enhanced search with new pages for past week. And found your repo, which was created 1 days ago. Google rocks!

So where I found something about html entry point I post message about your repo (in this issue and webpack channel in slack with 700 peoples, you can join in http://www.reactiflux.com/). So let do this plugin by crowd. @tkrullmann please dont't disappear and accept PR ;) .

Any plans on fixing this or is this "out of scope"? I'd really prefer having an index.html as my app entry point. For now I'll fallback to generating the HTML.

+1

An html file is the entry point into the application for the user agent, it should be the entry point for webpack.

There is a make plugin hook on the compilation in which entry points should be added. The entry option is converted to this.addEntry calls in this hook. See

compiler.plugin("make", function(compilation, callback) {
var dep = new SingleEntryDependency(this.entry);
dep.loc = this.name;
compilation.addEntry(this.context, dep, this.name, callback);
}.bind(this));

@sokra is it possible to access loader loaded source at the point of "make" plugin? Otherwise I won't be able to extract script src from html entry.

I know this is not exactly what people are looking for in this issue but in case somebody is interested, I started building my own plugin for using React JSX files as entrypoints: https://github.com/mfellner/static-jsx-webpack-plugin. Any feedback is more than welcome!

@mfellner nice! How do you get the main script url into the html, do you run webpack in two stages?

@wmertens The plugin attaches to the 'additional-chunk-assets' phase. There I get the relevant 'source chunks' for each entrypoint from compilation.namedChunks. Then I add a new custom Source for each chunk to compilation.assets.

The chunk's files property holds the name of the output files. Adding them to the HTML is easy with React, since it's just a component that I can pass props into and then render the whole thing.

To get the React component I need to actually evaluate the complete webpack bundled code (compilation.mainTemplate.render on the source chunk) as a vm.Script.

The advantage of using JSX instead of HTML is that JSX can be just loaded as regular Javascript using an appropriate loader. So from webpack's perspective there's no difference. It just builds your output (bundle.js) as it normally would. The trick is then to add additional output assets for the static HTML.

The only downside of this approach that I can see is that you end up with React components inside your output JS. Which is fine, I think, if you want to use React anyway.

@mfelltner it sounds like exactly what I want, but I'm still confused on
how to use it.

How do you know which scripts to load in the html?

I'm also confused about your comment about react being in output JS, isn't
that only used for building the html?

It would be great if you had an example, the test fixture confuses me.

On Sun, Oct 18, 2015 at 10:41 PM Maximilian Fellner <
notifications@github.com> wrote:

@wmertens https://github.com/wmertens The plugin attaches to the
'additional-chunk-assets' phase. There I get the relevant 'source chunks'
for each entrypoint from compilation.namedChunks. Then I add a new custom
Source for each chunk to compilation.assets.

The chunk's files property holds the name of the output files. Adding
them to the HTML is easy with React, since it's just a component that I can
pass props into and then render the whole thing.

To get the React component I need to actually evaluate the complete
webpack bundled code (compilation.mainTemplate.render on the source chunk)
as a vm.Script.

The advantage of using JSX instead of HTML is that JSX can be just loaded
as regular Javascript using an appropriate loader. So from webpack's
perspective there's no difference. It just builds your output (bundle.js)
as it normally would. The trick is then to add additional output assets for
the static HTML.

The only downside of this approach that I can see is that you end up with
React components inside your output JS. Which is fine, I think, if you want
to use React anyway.


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

Wout.
(typed on mobile, excuse terseness)

@sokra would it be easy to add a way to refer to something as an entrypoint, maybe with a loader? Then the entry html could refer to the main script with "!!file!entry!src/main.js" for example.

Then using "!!file!html!index.html" would work…

It seems that hasn't a perfect solution..

I'm found a plugin that may fit the requirement: https://github.com/skozin/webpack-path-rewriter

Although it doesn't allow HTML files as entries, you can create an entry.js as easy as:

require('js/index.js')  // Needed for "generated assets", see the README for details
require('templates/index.jade')

and use it as entry. It'll automatically place the output HTML files, handle the asserts dependencies, and rewrite the assert paths. I also find it easier to handle multiple-HTML-entries cases than the indexhtml-webpack-plugin.

I made a proof of concept webpack-loader to dynamically add script:src as entry point. I think this approach can maximize the flexibility. Really love to hear some feedback.

@d6u nice! (although the name would be better as html-entry-loader or so)

You can get the html file from the js file with val-loader, I think.

What happens with recursive dependencies though, doesn't the webpack loader configuration turn every js file into an entry point?

One small thing: Can't you use {...webpackLoader, {entry}} instead of lodash's assign?

@wmertens It will handle recursive dependencies well. Because webpack-loader won't use the same loader config. You will specify new loader configs in webpackLoader property. There you can handle js with babel-loader or other loaders.

I guess my main question is what exactly we want to achieve here? Using HTML as an entry point so all the assets presented on the HTML, e.g. images, can be handled by the loader we desire, e.g. file-loader, url-loader? So the goal for webpack-loader would be ensure script:src can be bundled first before handed to file-loader or url-loader. What about CSS then? For people who using extract text plugin, CSS will become a separate file while webpack-loader bundling JS files. But when html-loader looking for link:href, the CSS file might not be available. Is it possible for html-loader to defer handling of link:href and wait for webpack-loader to finish it's job?

@d6u looks good 👍. I'm impressed that it's not much code at all.

However, this approach is imho exactly the same as the ExtractTextPlugin, so I don't really see the benefit here.

I think, the main problem here is that webpack requires the final module to be JS to build the dependency graph. That's why every file must be valid JS at the end of the loader chain.

Maybe the whole process would be more flexible if we'd introduce an intermediate JSON-like format which could look like:

helloWorld.js

{
    "type": "js",
    "dependencies": [
        "events",
        "../otherModule.js"
    ],
    "source": [
        "'use strict';\nconst EventEmitter = require('",
        0,
        "');\nconst otherModule = require('"
        1,
        "');\nfunction hi() { console.log('hello world') }"
    ]
}

index.html

{
    "type": "html",
    "dependencies": [
        "css/styles.css"
    ],
    "source": [
        "<!doctype html>\n<html>\n<head>\n<link rel=\"stylesheet\" href=\"",
        0,
        "\">\n</head>\n<body>Hello World</body>\n</html>"
    ]
}

Than webpack could easily decide, how to format the resulting output file. Imho, there are two main benefits here:

  • The AST parsing via esprima/acorn could be extracted into an own js-loader which would improve modularity
  • These generated files could (probably) be cached, so that future compilations would not need to parse and compile everything again #250

One challenge here is that we need to put webpack's infrastructure code somewhere which is reponsible to set up the CommonJS environment. But I think, this would be managable somehow.

This change would probably have a big impact on internal webpack stuff, but I think it would be possible without disrupting the community written loaders, since we only would need to introduce a JS-loader and change the html- and css-loader to not output JS.

What do you think? @sokra

Maybe that's also an elegant solution for #378

Instead of providing complicated meta information, they would just provide this intermediate format which can easily be understood by any build tool/bundler.

@jhnns

  • No, ExtractTextPlugin won't add new entry point for dependencies
  • Yes, the file saving part can be treated exactly like ExtractTextPlugin. Maybe webpack-loader should just return a string of bundled content and let ExtractTextPlugin create the file.
  • Still don't know how to handle ExtractTextPlugin extract CSS files within webpack-loader.
  • Love the proposal you made about flexible format.

ExtractTextPlugin won't add new entry point for dependencies

I don't know, if we're talking about the same thing, but you can use the ExtractTextPlugin to extract HTML + dependencies. You just need to apply the html-loader to parse the HTML.

@jhnns Maybe we are talking about different things. Do you mean using ExtractTextPlugin can achieve the goal of using HTML as entry point here? If so can you give me an example config?

I've created an example how the webpack-extract-text-plugin can be used to extract the index.html.

The current implementation allows to:

  • Add the index.html as entry to the webpack.config.js
  • Reference assets like images or stylesheets that are loaded via the file-loader
  • Reference the resulting bundle.js. This can only be done by replacing % BUNDLE % with the final filename after the compilation has been finished because the hash is unknown before that. You can remove that replacing stuff if you're not hashing the output filename.

I've also tried to reference a stylesheet which is transformed via the css-loader and then extracted. This, however, is currently not working. The resulting URL of the stylesheet is [object Object], maybe @sokra can give us some insight here.

@jhnns Thanks for sharing. I think your ExtractTextPlugin (ETP) example works, but the main differences are:

  1. using ETP, you need to specify a JS entry for any JS file your want to bundle. Normally this is not a problem since most of us only have one JS file on a website. But this is not dependency driven bundling.
  2. You have to use some template syntax to be able to put the final url % BUNDLE %. What if the HTML is already using some template language, we mixing two template syntax a problem?

I for one want to avoid any magic (templates) in the html file if possible.

you need to specify a JS entry for any JS file your want to bundle. Normally this is not a problem since most of us only have one JS file on a website. But this is not dependency driven bundling.

That's correct. But that's the way how webpack works today for most users. And usually you don't have too many entries. Personally I only add init.js and use require.ensure() or the promise-loader to require other stuff asynchronously.

You have to use some template syntax to be able to put the final url % BUNDLE %. What if the HTML is already using some template language, we mixing two template syntax a problem?

That's also correct. But you only need % BUNDLE % if you're going to hash your final bundle (which most people do, I assume). There is no other way to solve this, because you only know the final hash when the build has finished – and then you can't change the index.html anymore. You need to str.replace() that manually after the build process has been finished. So this is a chicken egg problem. I think, the webpack-loader does not address the % BUNDLE % problem either.

There is no other way to solve this, because you only know the final hash when the build has finished – and then you can't change the index.html anymore

Sounds like something that is best corrected in Webpack core. Shouldn't the hash be calculated over the input files, as opposed to the output files? If it was, Webpack core could provide this hash earlier on in the build process.

Shouldn't the hash be calculated over the input files, as opposed to the output files

Mhmm ... no I don't think so, because that would produce to much "false positives". Think about whitespace changes which change the hash but would not have an impact on the output files (assumed they are minified).

Instead of using templates, how about having the user specify an HTML id in the Webpack configuration and the latter could inject the bundle path into the DOM object with that id? Same end-result without the use of templates.

The hash should be calculated over the output files. Let's say you haven't changed the source files, but updated your minifier which now works better. You want to serve the newer minified file to the user.

@jhnns

you need to specify a JS entry for any JS file your want to bundle. Normally this is not a problem since most of us only have one JS file on a website. But this is not dependency driven bundling.

That's correct. But that's the way how webpack works today for most users. And usually you don't have too many entries. Personally I only add init.js and use require.ensure() or the promise-loader to require other stuff asynchronously.

A single entry works for me. But this is a limitation without a good justification. People all have different usage cases and limited to a single number doesn't sound like a good approach.

You have to use some template syntax to be able to put the final url % BUNDLE %. What if the HTML is already using some template language, we mixing two template syntax a problem?

That's also correct. But you only need % BUNDLE % if you're going to hash your final bundle (which most people do, I assume). There is no other way to solve this, because you only know the final hash when the build has finished – and then you can't change the index.html anymore. You need to str.replace() that manually after the build process has been finished. So this is a chicken egg problem. I think, the webpack-loader does not address the % BUNDLE % problem either.

I think this is related with the first comment. Because the ExtractTextPlugin example is not dependency driven. The connection between index.html and init.js is not clear. They are only tied together by the % BUNDLE %. This breaks the pipeline of loaders. Normally one loader should do one thing, hashing file is the job of file-loader (or similar one).

webpack-loader indeed will not solve the hashing issue. Because this is not a issue webpack-loader want to solve. The url hashing should be handled by file-loader. webpack-loader merely just treat script src as new entry point and return a JavaScript file, so the other loader can pick the stuff webpack-loader returned and do whatever they want, e.g. inline JavaScript or just replace an hashing url.


I agree both solution works now, but ultimately, I'd like a solution that's more generic, dependency driven and can integrate with any other loaders.

As an aside, the hash should be calculated over ALL the inputs, so the source files and the modules, as well as the transpilers and any scripts and programs used to generate the output.

That way, if any dependency changes, the output can be rebuilt, and if nothing changes, you don't have to rebuild.

If you doubt this, know that http://nixos.org uses this method to completely define all the files and packages for a linux distribution according to the desired configuration, and applying a change and building the changed files and creating the new system version typically takes only a few seconds.

I agree both solution works now, but ultimately, I'd like a solution that's more generic, dependency driven and can integrate with any other loaders.

Yes, I agree. That would be good.

As an aside, the hash should be calculated over ALL the inputs, so the source files and the modules, as well as the transpilers and any scripts and programs used to generate the output.

That's an interesting suggestion. Then we would be able to calculate the hash before the final file would have been rendered. However, I don't think that this is do-able ... what if a loader requires another file, how would we track that?

That's an interesting suggestion. Then we would be able to calculate the hash before the final file would have been rendered. However, I don't think that this is do-able ... what if a loader requires another file, how would we track that?

Perhaps loaders could encapsulate this logic? Meaning, loaders could provide an API that returns a hash of their inputs, or a list of their inputs so others could include it in their hash.

In NixOS the build scripts have to declare their inputs and they run in a sandbox.

Loaders could pinkie-promise adhere to the same semantics, or the node file methods could get monkeypatched to be a sandbox…

That sounds very fragile to me with a bunch of loaders from the community that don't care about hashes 😀...

Well, that's what issues and PRs are for :-) if they don't properly declare their inputs, they won't properly update when those inputs change…

Another thing that NixOS has going for it is that every package (the whole OS up to and including individual configuration files) is store read-only in a directory named by its hash, so it can trust that inputs didn't change.

The node_modules dir doesn't do that so webpack would have to read each file, or make a copy at build time. (with hardlinks that takes no space)

@sokra Is it possible to access compilation.addEntry in a loader?

For anyone tracking this issue still, here is something interesting, look at the part about childCompiler: jantimon/html-webpack-plugin#171

This may help make @d6u's webpack-loader approach work with WDS.

@ALL Here is a standalone HTML app compiler using webpack https://github.com/resistdesign/rdx

@resistdesign wow, nice! Am I correct in finding https://github.com/resistdesign/rdx/blob/master/src/Config/WebPack/Utils/HTMLConfig.js as where the magic of parsing a html file and bundling its dependencies happens?

@wmertens Yes, that's the "Secret Weapon"! :)

I didn't realize this, but the problem has been solved in a different way by https://www.npmjs.com/package/html-webpack-plugin (in v2). Basically, you configure what your index.html should look like, and then it creates one based on your entry points.

For me that is a fine solution, so IMHO this issue can be closed.

@wmertens having the index file being created was always available as option IIRC.

I personally want NOT to have the index file created for me. I guess one could just put everything into the html-webpack-plugin template - but that's a hack.

IMO this issue should not be closed.

@tcurdt the problem was not knowing the names of the assets that should go in the html until everything is created.

By declaring your intention (what you want in the index.html), you get automatic bugfixes and performance improvements to the generated html. If you write the html manually, not so much.

@wmertens Actually, what we want is to make index.html as an entry point (one entry point and it is html file); so the css, javascript and images inside the html file will go thought loaders. This way, the small images can be inlined, assets from index.html will be copied (and hashed etc.) to the right directory with right name.

Anything else is an hack. Seriously.

@buraktamturk I believe all these are possible with the html plugin and maybe with some extra plugins.

Therefore it must be possible to write a tool that maps from an index.html file to a webpack config.

@wmertens then by all means, present that solution here and everybody can be happy.

@Tragetaschen open source does not mean I work for you :) Like I said, I'm happy with the plugin as-is, I'm just pointing out a likely approach to implement index as an entry point. I won't be pursuing it.

It's not easy, webpack fights you on it, here is the best solution I could muster, with all sorts of supporting code to deal with all kinds of caveates: https://github.com/resistdesign/rdx/blob/master/src/Config/WebPack/Utils/HTMLConfig.js#L62

And that's just the heart of the matter. You have to look at the whole solution to see what HTML as an entrypoint really entails. Though I still beleive that HTML is the most natural manifest for a web app.