-
-
Notifications
You must be signed in to change notification settings - Fork 8.7k
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
Comments
Correct is to split your application into two parts:
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... |
You could try this plugin https://www.npmjs.org/package/html-webpack-plugin |
Thanks. I forgot to mention that in the first post. Still, that's similar to |
@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 @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. |
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, module.exports = "<script src=\"" + __webpack_public_path__ +
JSON.stringify(urlToNewChunk) + "\"></script>"; It is, however, wrapped in a 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. 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" Current problems:
|
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 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 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 |
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 That's why the extract-text-webpack-plugin or the html-webpack-plugin is probably the right approach. |
Thank you for clarifying 👍 |
+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 Then the only problem is getting an actual |
@mogelbrod what is that apply loader you use in the html loader configuration? |
@wmertens Gist for a simple apply loader |
@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 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…</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:
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:
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 @sokra is this feasible? Any pointers on how to implement? |
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 I just don't understand how come the resulting chunks can load, do all Did you try adding the script:src tags to the things html should parse? Do Why do you name the files with the URL query syntax and not simply Good stuff, thanks! On Thu, Jun 25, 2015 at 8:41 AM Fenix notifications@github.com wrote:
Wout. |
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... |
Where's this voting page at? |
Is this voting page documented anywhere? Any time I've tried to click the up arrows, nothing happens. |
@chriscalo you have to sign in with GitHub to vote. You vote with your influence points. |
I am signed in because I see the up and down arrows. But when I click them nothing happens. |
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. |
@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 |
Using I think |
@Mitscherlich published a new |
Yeap it's okay to consider
|
Darn I really need this to work :( |
(If it's an option for you, HTML entry points work in both Vite and Parcel) |
@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. |
Hello @sokra, @alexander-akait, @TheLarkInn, @wmertens the html-bundler-webpack-plugin allows to use a HTML template as an entry point. For example, there is the source of <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 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 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 <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> |
@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 |
@webdiscus Substituting
and the file |
try my optimised fork of telnet-client. It works fine. Using the
The HTML contains JS code because you are using the |
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. |
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:
Problems:
specify the .html file as entry point and using
file-loader
for HTML filesProblems:
specify the .html file as entry point and using
html-loader
for HTML filesSee sample config here: https://gist.github.com/jampy/44faf0c18fd64b6dd1fd
..and the HTML file: https://gist.github.com/jampy/a2dd493901cd6dc5ae8b
Problems:
html-loader
apparently detects dependencies like images, but seems to ignore the.js
files referenced via<script>
tags..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).
The text was updated successfully, but these errors were encountered: