In another question, an accepted answer with 11 upvotes suggests using webpack's html-webpack-plugin to merge HTML+JS+CSS.
Not a single word on how though, so that's my question. I have a documentation file, in HTML. It includes two external CSS files and one javascript file (syntax highlighter). I would like to bundle it all into one HTML file for easy distribution.
I tried to use webpack without packing any JS with following webpack.config.js:
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: 'Documentation',
template: "index.html",
filename: 'index_bundle.html'
})
]
};
But that caused an error because webpack requires input and output script path:
Error: 'output.filename' is required, either in config file or as --output-filename
So how do I do this. Is webpack even the right choice?
What's tricky here is that webpack and HtmlWebpackPlugin each have their own entry-output pipeline. You don't care about webpack's entry point and output; you care about HtmlWebpackPlugin's, but in order for HtmlWebpackPlugin to run at all, you have to let webpack do its thing.
There's a clever trick that I stole from https://stackoverflow.com/a/43556756/152891 that you could do here -- tell webpack to output its bundle at index.html, and then tell HtmlWebpackPlugin to output to the same location. Webpack will run first, and then HtmlWebpackPlugin will overwrite its output. You still need to define an entry point for webpack, but this can be a blank index.js.
webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'none',
entry: './src/index.js', // Default value, can omit
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html', // Default value, can omit
inject: false,
minify: false
})
],
output: {
filename: "index.html",
path: path.resolve(__dirname, 'dist')
}
};
You can either keep a blank ./src/index.js file in your repo, or you could define a little inline plugin in your webpack.config.js to create the file on run, and add it to your .gitignore.
plugins: [
{
apply: (compiler) => {
compiler.hooks.beforeRun.tap('NoOpPlugin', () => {
fs.writeFileSync('./src/index.js');
});
}
}
]
Related
In official doc says when applying Code Splitting and generating chunk files, if chunk code changes, then the filename of it will change. However index.html which uses the chunk code files can't change the filename in its <script> tag, so in this case the manifest.json which is generated by webpack-manifest-plugin will help mapping [name].js to [name].[hash].js.
But opposing to what the doc says, it seems that every time I run webpack to build my codes, new codes are generated with new hash value in its file(in case something in code changed), and HTML-Webpack-Plugin will autometically inject <script> tag with new name of code's file. This seems that there is no need to use webpack-manifest-plugin, I don't even see where the manifest.json is used.
In case if you are looking for my webpack.config.js:
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HTMLWebpackPlugin = require('html-webpack-plugin')
const ManifestPlugin = require('webpack-manifest-plugin')
module.exports = {
entry: ["core-js/stable", "regenerator-runtime/runtime", "./src/index.jsx"],
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(`${__dirname}/build`)
},
mode: "none",
optimization: {
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: "/node_modules",
use: ['babel-loader']
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HTMLWebpackPlugin({
template: './public/index.html'
}),
new ManifestPlugin({
fileName: 'manifest.json'
})
// need this plugin for SSR?
]
}
What is the detailed usage of webpack-manifest-plugin and manifest.json?
HtmlWebpackPlugin "knows" that your asset bundle.js maps to bundle.some-hash.js because it uses the Webpack's Manifest. This manifest is not emitted though. It's just data that Webpack uses to keep track of how all the modules map to the output bundles.
WebpackManifestPlugin uses Webpack's manifest data data to emit a JSON file (that you can call manifest.json or whatever you want).
Since you are using HtmlWebpackPlugin with the inject: true option (it's the default one), HtmlWebpackPlugin injects your bundle bundle.some-hash.js into your template. So you probably have no need to use WebpackManifestPlugin for your application.
If you did not use HtmlWebpackPlugin, or if you used it with inject: false, then you would need to find a way to "manually" inject the assets generated by Webpack.
So, the manifest.json is not for Webpack. It's for you.
Let's say for example that you have a Python Flask web application and your web pages are built with Jinja templates. You could use Webpack to generate all of your static assets, and use the manifest.json to resolve the asset generated by Webpack. This Flask extension does just that. This means that in your jinja template you can write this:
<img src="{{ asset_for('images/hamburger.svg') }}" alt="Hamburger">
and get this:
<img src="images/hamburger.d2cb0dda3e8313b990e8dcf5e25d2d0f.svg" alt="Hamburger">
Another use case would be if you want fine control where the bundles are injected into your templates. For that, have a look at this example in the html-webpack-plugin repo.
I am currently working on a library which should dynamically load a JavaScript from a remote host and instantiate that.
I am writing the library in TypeScript and my plan is to use Webpack as a bundler.
On another host (remote system) runs a provider which should serve JavaScript code (see here: https://stubs.d-koppenhagen.de/stubs/SimpleStub.js).
The library will dynamically resolve "Identitys" via Webfinger. These Identitys represented by an object and they having a property pointing to a "Stub Provider" which will serve JavaScript code (the link I mentioned before). My library should load this script during runtime (s the library don't know the target for this stubs before) and should use it.
currently my webpack.config.js looks like the following:
var path = require('path');
var webpack = require('webpack');
var WebpackBuildNotifierPlugin = require('webpack-build-notifier');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const PATHS = {
src: path.join(__dirname, './src'),
build: path.join(__dirname, './dist')
};
module.exports = {
entry: {
wonder: PATHS.src + '/wonder'
},
output: {
path: PATHS.build,
filename: '[name].js',
library: 'Wonder',
libraryTarget: 'umd'
},
devtool: 'source-map',
module: {
loaders: [
{
test: /\.ts$/,
loader: 'ts-loader'
}
]
},
resolve: {
extensions: ['.ts', '.js']
},
plugins: [
new WebpackBuildNotifierPlugin()
]
};
And here is a part of the library code:
require.ensure([], function() {
require(localMsgStubUrl);
});
When I am now including the bundled library in an example app, I will get the following error:
Error: loading chunk failed
So is there a way to tell webpack not to bundle the code which is required from a external resource loaded from a URL so that I can use that code like it is?
I don't want to tell webpack the URL statically in a config as maybe other stubs I am loading are located on a different target.
Thanks in advance for your help!
I was reading this webpack tutorial:
https://webpack.github.io/docs/usage.html
It says it bundles the src files and node_modules. If I want to add another .js file there, how can I do this? This is a thirdpartyjs file that is not part of the source and not part of the node_modules files. This is my current webpack.config.js:
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
'./app/app.js'
],
output: {
path: path.resolve(__dirname, "dist"),
publicPath: "/dist/",
filename: "dist.js",
sourceMapFilename: "dist.map"
},
devtool: 'source-map',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('development')
}
}),
],
module: {
loaders: [{
loader: 'babel',
exclude: /node_modules/
}]
},
devServer: {
inline: true
},
node: {
fs: "empty"
},
watch: false
}
The start point for code is the entry field in config. In your config entry point is the list of files. Webpack gets all, resolve their dependencies and output in one file.
You have two options for adding third party script:
add the file path to entry list before app.js
require this file from app.js
In response to Dmitry's answer:
add the file path to entry list before app.js
This has the effect that you will get a bundled .js file for each entry point, which you might not want.
require this file from app.js
You might not have access to app.js if it is written dynamically, or for whatever reason you might not want to edit app.js.
Another option:
You can use webpack-inject-plugin to inject any JS code as string into the resulting .js bundle created by webpack. This way you can read the File you want to inject as a string (e.g. fs.readFile in nodejs) and inject it with the plugin.
Another solution but without using any extra plugins:
//Webpack.config.js
entry: {
main: './src/index',
/**
/* object is passed to load script at global scope and exec immediately
/* but if you don't need then simply do:
/* myCustomScriptEntry: './src/myCustomScript'
*/
myCustomScriptEntry: {
import: './src/myCustomScript',
library: {
name: 'myCustomScriptEntry',
type: 'var',
},
},
},
new HtmlWebpackPlugin({
template: './public/index.html',
excludeChunks: ['myCustomScriptEntry'], //exclude it from being autoreferenced in script tag
favicon: './public/favicon.svg',
title: 'Alida',
}),
and
//index.html
<script type="text/javascript" src="<%= compilation.namedChunks.get('myCustomScriptEntry').files[0] %>"></script>
Electron 1.6.5, Webpack 2.4.1
I'm using electron-react-boilerplate with a webview component. I inject a preload script into the webview that does something like this:
const { ipcRenderer } = require('electron');
const doSomething = require('./utils/do-some-thing.js');
document.addEventListener('DOMContentLoaded', event => {
doSomeThing()
// tell scraper to get started
ipcRenderer.sendToHost('THING IS DONE', [{ url: document.URL }]);
});
webview needs this script passed as a file:// path like so:
<webview
preload={'./some/folder/preload.js''}
{...props}
/>
The problem is that my webpack setup doesn't transpile preload.js because it isn't explicitly called via require(). Then, when I build the app, the path ./some/folder/ doesn't exist.
I've tried setting webpack to create a second compiled script like so:
entry: [
'babel-polyfill',
'./app/index',
'./some/folder/preload.js'
],
output: {
path: path.join(__dirname, 'app/dist'),
publicPath: '../dist/'
},
But this leads to a JavaScript heap out of memory error, which leads me to believe this isn't correct.
Also: wouldn't this approach duplicate electron in the ./dist folder since it's require()'d by both preload.js and index.js ?
You could use the electron-main and electron-preload configurations of webpack:
const path = require('path');
module.exports = [
{
entry: './src/index.js',
target: 'electron-main',
output: {
path: path.join(__dirname, 'dist'),
filename: 'index.bundled.js'
},
node: {
__dirname: false,
}
},
{
entry: './src/preload.js',
target: 'electron-preload',
output: {
path: path.join(__dirname, 'dist'),
filename: 'preload.bundled.js'
}
},
]
You get two bundles after building, but Electron is not in either of them, so no duplicates.
Notice the __dirname: false, which is required since otherwise webpack replaces __dirname always replaced by / by webpack, leading to an unexpected behaviour in almost all cases (see here for further information, should be false by default, but wasn't for me).
We had a similar issue where we had several preload scripts instead of one. Our solution was to use the CopyPlugin. So for us, the config looks like this:
const CopyPlugin = require("copy-webpack-plugin");
plugins.push(new CopyPlugin([{ from: "src/container-with-scripts/", to: "preloadScripts/" }]));
module.exports = {
module: { rules }, // imported from elsewhere
target: "electron-renderer",
node: { global: true },
plugins,
resolve: {
extensions: [".js", ".ts", ".jsx", ".tsx", ".css", ".scss"]
}
};
So we just take the folder containing all our scripts and copy them to the autogenerated .webpack folder.
I was having the same problem and here's solution that worked for me:
Electron-webpack documentation now contains example how to add additional entries.
"electronWebpack": {
"main": {
"extraEntries": ["#/preload.js"]
}
}
Note that you can use the # alias to reference your main.sourceDirectory
in this case it's path to your main process script which will be src/main by default
This adds preload script from src/main/preload.js into webpack entries.
Then you need to add preload script into window with
new BrowserWindow({
webPreferences: {
nodeIntegration: true,
preload: path.resolve(path.join(__dirname, "preload.js")),
}
})
That's all. __dirname was giving me relative path and electron requires absolute path for preload script so it was failing. path.resolve will get you absolute path needed to make it work.
Electron provides an option to load a script before any other execution in your DOM.
You have to provide your pre-load script file path while creating a browser window and file path of the script should be the absolute.
You can find reference here
I guess I'll start with my webpack config.
const webpack = require('webpack');
const path = require('path');
/**
* Environment
*/
const nodeEnv = process.env.NODE_ENV || 'development';
const isProduction = nodeEnv === 'production';
const isDevelopment = !isProduction;
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const sourcePath = path.join(__dirname, 'assets');
const buildPath = path.join(__dirname, 'dist');
const extractSass = new ExtractTextPlugin({
filename: '[name].[contenthash].css',
disable: isDevelopment
});
/**
* Plugins
*/
const plugins = [
new HtmlWebpackPlugin({
template: path.join(sourcePath, 'index.html'),
}),
extractSass
];
if (isProduction) {
} else {
plugins.concat([
new webpack.HotModuleReplacementPlugin(),
]);
}
module.exports = {
entry: ['./assets/app.js', './assets/app.scss'],
devtool: isProduction ? 'eval' : 'source-map',
plugins: plugins,
module: {
rules: [{
test: /\.scss$/,
use: extractSass.extract({
use: [{
loader: "css-loader"
}, {
loader: "sass-loader"
}],
// use style-loader in development
fallback: "style-loader"
})
}]
},
output: {
filename: 'bundle.js',
path: buildPath
},
devServer: {
contentBase: buildPath,
port: 9000
}
};
This all works fine when running on the webpack dev server but I'm trying to figure out how this fits together on a production environment.
As you can see, as per the sass-loader documentation, I'm creating a file called [name].[contenthash].css if NODE_ENV is set to production. I love the idea of serving files based on the content hash because I love integrity.
The difficulty I'm having is understanding how I can pass that file name, that content hash into the index.html template I'm creating so that I can <link> the stylesheet.
Is it a server side thing?
Is there any way to pass that file name into the HTML template on
production?
Is it intentional that I do it manually or script it out?
I just don't understand how these two components come together to produce a publishable build. HtmlWebpackPlugin produced a .html in the output directory but obviously it has no innate understanding of where to find it's styles.
Your config seems correct.
Is there any way to pass that file name into the HTML template on production?
What should be happening is that the HtmlWebpackPlugin should be creating a new index.html file in your buildPath directory, which has the generated bundles automatically injected in it (for example the generated CSS bundle will be injected in the head tag and the generated script bundles at the bottom of the body tag)
Beyond that it is just a matter of serving that dist/index.html to whoever visits your site/app. So the answer to
Is it a server side thing?
is yes.
Try doing a build without the dev server, by simply running webpack, so you can see the output of your configuration (the dev server builds things in memory, so you do not actually get to see them)