html-webpack-plugin multiple entry points adding all scripts - javascript

I'm building a project on an ejected react cli. The reason I ejected it is because there will be multiple pages being generated and some stand alone scripts that I wanted to make and have them take advantage of the ES6 and debugging tools provided by the boilerplate.
My problem is that while using the technique to build out multiple pages with html-webpack-plugin builds out the generated HTML files with both scripts from each page.
So, lets have a look at the basic entry point
Here's my basic web pack config.
...
entry: {
devServerClient : require.resolve('react-dev-utils/webpackHotDevClient'),
// Finally, this is your app's code:
...pages,
},
...
As you can see, I'm using the same hot module reloading stuff that came with the boiler plate, but then I deviate to spread in values from the pages variable which is being required from another page:
const glob = require("glob");
const path = require("path");
const input_path = path.resolve(__dirname, "../src/apps/*/index.js");
const apps_directories = glob.sync(input_path);
let pages = {};
// Loop through them and append them to the pages object for processing.
apps_directories.map(function (app_directory) {
const split_name = app_directory.split("/");
pages[split_name[split_name.length - 2]] = app_directory;
});
module.exports = pages;
Thats how I generate multiple entries dynamically. This portion of the code works fine, but I thought I'd post it just in case something needs to happen here that I'm missing.
Next We have the actual plugin section. I take a similar approach in modularizing the code, so here's that portion in the config (root level of the web pack object):
...
plugins: [
// Generates an `index.html` file with the <script> injected.
...HtmlWebpackPlugins,
...
]
...
Again, I spread them in, the script that generates these looks like so:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const pages = require("./multiplePaths");
const paths = require("./paths");
const NODE_ENV = process.env.NODE_ENV;
let HtmlWebpackPlugins = [];
Object.keys(pages).map(function (fileName) {
if (NODE_ENV === "development") {
HtmlWebpackPlugins.push( new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
filename: `${fileName}.html`,
}));
return;
}
HtmlWebpackPlugins.push(new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
filename: `${fileName}.html`,
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}));
});
module.exports = HtmlWebpackPlugins;
This script here generates the multiple instances of the HtmlWebpackPlugin class per entry, and we also name the html file based on the folder the name of the folder the app resides in. This satisfies the technique in their issues section.
The problem then happens in the generated HTML page. Notice that the scripts for all the folders are injected in each app:
As you can see, each app's CSS and JS are added. This happens to both pages. I only want the relevant CSS and JS to each page.
Any idea what's going on here?

If you want to add only some of the chunks inside each page you need to specify which chunks exactly you want to have inside your html as scripts:
HtmlWebpackPlugins.push(new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
filename: `${fileName}.html`,
chunks: [filename], // add any chunks you need here (for example, chunk with libraries
....
});

I used something like this to create entries for HtmlWebpackPlugin based on webpack entries:
// Webpack entries
const entry = {
"nameA": "./xy/nameA/main.js",
"nameB": "./xy/nameB/main.js",
};
// Plugins
let plugins = [];
// HTML injection
Object.keys(entry).forEach((ent) => {
const htmlPlugin = new HtmlWebpackPlugin({
inject: true,
title: "Your Title",
template: "./html/index.html",
filename: `formats/${ent}/index.html`,
chunks: [ent],
});
plugins.push(htmlPlugin);
});
// Insert more plugins
plugins = plugins.concat([
... your plugins
]);
// Return Config
return {
// define entry point
entry,
// define output file
output: {
...
},
...
// define Plugins
plugins,
...
};

Related

Reload Webpack Html Template on Certain Files Change

I'm using revealjs to create responsive presentations. The problem with revealjs is that all the slides code is written in a single HTML file which can be messy to some level (Some presentations' HTML code reached about 3500 lines of HTML in that single file).
I'm now restructuring this system and I would like to have a directory named slides that contains each slide HTML file. Each of these files is named slidenumber.html. Finally, I want to bundle all of the files with webpack 5 into a single HTML file in dist. I managed to achieve this but it has an issue with the dev server.
webpack.config.js
// ... imports ....
module.exports = {
...,
plugins: [
....,
new HtmlWebpackPlugin({
filename: "index.html",
inject: true,
templateContent: getTemplate(),
}),
new WatchExternalFilesPlugin({
files: ["./slides/*.html"],
}),
],
module: {
rules: [...],
},
devServer: {
port: 8080,
},
};
The getTemplate function loops over the HTML files in the slides directory and returns them wrapped with the template boilerplate
This is the function for reference
getTemplate.js
const fs = require("fs/promises");
const path = require("path");
const { parse } = require("node-html-parser");
module.exports = async () => {
const template = parse(
await fs.readFile(path.join(__dirname, "../templates/index.html"))
);
const files = await fs.readdir(path.join(__dirname, "../slides"));
for await (const fileName of files) {
const slide = parse(
await fs.readFile(path.join(__dirname, `../slides/${fileName}`))
);
template.querySelector("#slides").appendChild(slide);
}
return template.toString();
};
all of the above code is working fine on build but when running the dev server, I can't get the HtmlWebpackPlugin to re-execute the templateContent: getTemplate() on the change of any HTML slide file in the slides directory and as a result, when I edit any file of the slides HTML files in the slides directory, I don't get any update.
I'm aware that templateContent is supposed to run only on the start of the server but I'm asking if there is any other feature that can get me to the required behavior.
Thanks if you made it to here and excuse my English, I'm not a native speaker.
I could achieve the behavior I described in the question by setting a middleware from the dev server options that catches any request and returns the output of the getTemplate function.
This is the configurations for the dev server
webpack.config.dev.js
// ...imports...
module.exports = {
mode: "development",
entry: { index: "./main.js" },
output: {...},
module: {
rules: [...],
},
devServer: {
port: 8080,
watchFiles: ["./slides/*.html"],
hot: true,
onBeforeSetupMiddleware: function (devServer) {
devServer.app.get("/", async function (req, res) {
res.send(await getTemplate());
});
},
},
};
These are the configurations for the production server
webpack.config.production.js
// ...imports...
module.exports = {
mode: "production",
entry: { index: "./main.js" },
output: {...},
plugins: [
new HtmlWebpackPlugin({
filename: "index.html",
templateContent: getTemplate(),
inject: false,
}),
],
module: {
rules: [...],
},
};
I used the webpackHtmlPlugin in production as usual but in development, I didn't use it at all since it can't reload the templates on the build
In development, though I lost the ability to add the hash number to the compiled js file as I won't be able to predict the hash and inject its script tag. The compiled file had the same name as the original file and I added the script tag manually in the HTML template file.
Hope this helps anyone!

Optimal webpack.config with webpack 2 for AngularJS 1.x app?

I want to set up an Angular 1.x app from scratch using webpack 2.
I am having trouble finding the best configuration for webpack.config, with optimal entry and output for production (meaning, all code, style and templating minified and gziped with no code repetition).
My main problem is how to set up webpack.config so that it recognizes all partials within the folder structure of my project, like these:
My current config file, for reference (which can't see subfolders):
var HtmlWebpackPlugin = require( 'html-webpack-plugin' );
var ExtractTextPlugin = require( 'extract-text-webpack-plugin' );
var path = require( 'path' );
module.exports = {
devServer: {
compress: true,
contentBase: path.join( __dirname, '/dist' ),
open: true,
port: 9000,
stats: 'errors-only'
},
entry: './src/app.js',
output: {
path: path.join( __dirname, '/dist' ),
filename: 'app.bundle.js'
},
module: {
rules: [ {
test: /\.scss$/,
use: ExtractTextPlugin.extract( {
fallback: 'style-loader',
use: [
'css-loader',
'sass-loader'
],
publicPath: '/dist'
} )
} ]
},
plugins: [
new HtmlWebpackPlugin( {
hash: true,
minify: { collapseWhitespace: true },
template: './src/index.html',
title: 'Prov'
} ),
new ExtractTextPlugin( {
filename: 'main.css',
allChunks: true
} )
]
};
Note that this isn't an exhaustive solution, as there are many optimizations one can make in the frontend, and I've kept the code snippets fairly short.
With webpack, there are a few routes that you can take to include partials into your app.js.
Solution 1
You can import/require your partials within app.js as such:
app.js
var angular = require('angular');
var proverbList = require('./proverb/list/proverb.list');
// require other components
// set up your app as normal
This allows the app.bundle.js to include your component js files in the main bundle. You can also use html-loader to include templates in the final bundle.
This isn't ideal, as all it does is create a large bundle.js (which doesn't leverage multiple downloads with http2 nor does it allow loading of components/files when the user explicitly requires it).
Solution 2
Importing partials as separate entry files into your webpack bundle:
webpack.config.js
const globby = require('globby');
const sourceDir = 'src';
var webpackentry = {
app: `${__dirname}/src/app.js`
};
const glob = globby.sync(`${__dirname}/${sourceDir}/**/*.js`)
.map((file)=>{
let name = file.split('/').pop().replace('.js', '');
webpackentry[name] = file;
});
const config = {
entry: webpackentry,
...
}
The second solution is unorthodox but it can be useful if you wanted to split all your partials as <script> tags in your html (for example if your company/team uses that as a means to include your directive/components/controllers), or if you have an app-2.bundle.js.
Solution 3
Use CommonsChunkPlugin:
webpack.config.js
let webpackentry = {
vendor: [
'module1',
'module2',
'module3',
]
}
...
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor'] //... add other modules
})
]
CommonsChunkPlugin allows webpack to scrawl through your entry files and discern common modules that are shared among them. This means that even if you are importing module1 in different files, they will be compiled only once in your final bundle.

Webpack Never finishes

I'm working on a React project and I suddenly realize that when I starting my server, webpack never finishes(CPU usage is full and no output prints on the screen from webpack, I check it with -v switch and nothing changed.
How should id find the problem, Since I am unable to get any usable information from npm/webpack.
Edit:
when running with production Configuration it works fine, But in Development Configuration, it never stops. blow is my dev/prod configuration:
Dev:
/**
* DEVELOPMENT WEBPACK CONFIGURATION
*/
const path = require('path');
const fs = require('fs');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const logger = require('../../server/logger');
const cheerio = require('cheerio');
const pkg = require(path.resolve(process.cwd(), 'package.json'));
const dllPlugin = pkg.dllPlugin;
const plugins = [
new webpack.HotModuleReplacementPlugin(), // Tell webpack we want hot reloading
new webpack.NoErrorsPlugin(),
new HtmlWebpackPlugin({
inject: true, // Inject all files that are generated by webpack, e.g. bundle.js
templateContent: templateContent(), // eslint-disable-line no-use-before-define
}),
];
module.exports = require('./webpack.base.babel')({
// Add hot reloading in development
entry: [
'script!jquery/dist/jquery.js',
// 'script!materialize-css/dist/js/materialize.js',
'script!style/js/materialize.js',
'script!style/js/init.js',
'eventsource-polyfill', // Necessary for hot reloading with IE
'webpack-hot-middleware/client',
path.join(process.cwd(), 'app/app.js'), // Start with js/app.js
],
// Don't use hashes in dev mode for better performance
output: {
filename: '[name].js',
chunkFilename: '[name].chunk.js',
},
// Add development plugins
plugins: dependencyHandlers().concat(plugins).concat(new CircularDependencyPlugin({
// exclude detection of files based on a RegExp
exclude: /a\.js/,
// add errors to webpack instead of warnings
failOnError: true
})
), // eslint-disable-line no-use-before-define
// Load the CSS in a style tag in development
cssLoaders: 'style-loader!css-loader?localIdentName=[local]__[path][name]__[hash:base64:5]&importLoaders=1',
// Tell babel that we want to hot-reload
babelQuery: {
presets: ['react-hmre'],
},
// Emit a source map for easier debugging
// devtool: 'cheap-module-eval-source-map',
devtool: 'source-map',
});
/**
* Select which plugins to use to optimize the bundle's handling of
* third party dependencies.
*
* If there is a dllPlugin key on the project's package.json, the
* Webpack DLL Plugin will be used. Otherwise the CommonsChunkPlugin
* will be used.
*
*/
function dependencyHandlers() {
// Don't do anything during the DLL Build step
if (process.env.BUILDING_DLL) {
return [];
}
// If the package.json does not have a dllPlugin property, use the CommonsChunkPlugin
if (!dllPlugin) {
return [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
children: true,
minChunks: 2,
async: true,
}),
];
}
const dllPath = path.resolve(process.cwd(), dllPlugin.path || 'node_modules/react-boilerplate-dlls');
/**
* If DLLs aren't explicitly defined, we assume all production dependencies listed in package.json
* Reminder: You need to exclude any server side dependencies by listing them in dllConfig.exclude
*
* #see https://github.com/mxstbr/react-boilerplate/tree/master/docs/general/webpack.md
*/
if (!dllPlugin.dlls) {
const manifestPath = path.resolve(dllPath, 'reactBoilerplateDeps.json');
if (!fs.existsSync(manifestPath)) {
logger.error('The DLL manifest is missing. Please run `npm run build:dll`');
process.exit(0);
}
return [
new webpack.DllReferencePlugin({
context: process.cwd(),
manifest: require(manifestPath), // eslint-disable-line global-require
}),
];
}
// If DLLs are explicitly defined, we automatically create a DLLReferencePlugin for each of them.
const dllManifests = Object.keys(dllPlugin.dlls).map((name) => path.join(dllPath, `/${name}.json`));
return dllManifests.map((manifestPath) => {
if (!fs.existsSync(path)) {
if (!fs.existsSync(manifestPath)) {
logger.error(`The following Webpack DLL manifest is missing: ${path.basename(manifestPath)}`);
logger.error(`Expected to find it in ${dllPath}`);
logger.error('Please run: npm run build:dll');
process.exit(0);
}
}
return new webpack.DllReferencePlugin({
context: process.cwd(),
manifest: require(manifestPath), // eslint-disable-line global-require
});
});
}
/**
* We dynamically generate the HTML content in development so that the different
* DLL Javascript files are loaded in script tags and available to our application.
*/
function templateContent() {
const html = fs.readFileSync(
path.resolve(process.cwd(), 'app/index.html')
).toString();
if (!dllPlugin) {
return html;
}
const doc = cheerio(html);
const body = doc.find('body');
const dllNames = !dllPlugin.dlls ? ['reactBoilerplateDeps'] : Object.keys(dllPlugin.dlls);
dllNames.forEach((dllName) => body.append(`<script data-dll='true' src='/${dllName}.dll.js'></script>`));
return doc.toString();
}
Prod:
// Important modules this config uses
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const OfflinePlugin = require('offline-plugin');
module.exports = require('./webpack.base.babel')({
// In production, we skip all hot-reloading stuff
entry: [
'script!jquery/dist/jquery.min.js',
// 'script!materialize-css/dist/js/materialize.min.js',
'script!style/js/materialize.min.js',
'script!style/js/init.js',
path.join(process.cwd(), 'app/app.js'),
],
// Utilize long-term caching by adding content hashes (not compilation hashes) to compiled assets
output: {
filename: '[name].[chunkhash].js',
chunkFilename: '[name].[chunkhash].chunk.js',
},
// We use ExtractTextPlugin so we get a separate CSS file instead
// of the CSS being in the JS and injected as a style tag
cssLoaders: ExtractTextPlugin.extract({
fallbackLoader: 'style-loader',
loader: 'css-loader?modules&-autoprefixer&importLoaders=1!postcss-loader',
}),
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
children: true,
minChunks: 2,
async: true,
}),
// OccurrenceOrderPlugin is needed for long-term caching to work properly.
// See http://mxs.is/googmv
new webpack.optimize.OccurrenceOrderPlugin(true),
// Merge all duplicate modules
new webpack.optimize.DedupePlugin(),
// Minify and optimize the JavaScript
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false, // ...but do not show warnings in the console (there is a lot of them)
},
}),
// Minify and optimize the index.html
new HtmlWebpackPlugin({
template: 'app/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
inject: true,
}),
// Extract the CSS into a separate file
new ExtractTextPlugin('[name].[contenthash].css'),
// Put it in the end to capture all the HtmlWebpackPlugin's
// assets manipulations and do leak its manipulations to HtmlWebpackPlugin
new OfflinePlugin({
relativePaths: false,
publicPath: '/',
// No need to cache .htaccess. See http://mxs.is/googmp,
// this is applied before any match in `caches` section
excludes: ['.htaccess'],
caches: {
main: [':rest:'],
// All chunks marked as `additional`, loaded after main section
// and do not prevent SW to install. Change to `optional` if
// do not want them to be preloaded at all (cached only when first loaded)
additional: ['*.chunk.js'],
},
// Removes warning for about `additional` section usage
safeToUseOptionalCaches: true,
AppCache: false,
}),
],
});

Aurelia bundler does not delete references to old versioned bundles

I am switching my SPA web app from Durandal to Aurelia and am now taking a look at the bundling process.
I use gulp to bundle the app and I followed the instructions on this Aurelia documentation page and other resources on the web. It works but there are some unclear things to me.
This is my gulpfile.js
var gulp = require('gulp'),
bundler = require('aurelia-bundler'),
uglify = require('gulp-uglify'),
htmlmin = require('gulp-htmlmin'),
del = require('del');
var config = {
force: true,
baseURL: '.', // baseURL of the application
configPath: './config.js', // config.js file. Must be within `baseURL`
bundles: {
"Scripts/build/app-build": { // bundle name/path. Must be within `baseURL`. Final path is: `baseURL/dist/app-build.js`.
includes: [
'[Scripts/app/**/*.js]',
'Scripts/app/**/*.html!text',
'Content/*.css!text'
],
options: {
inject: true,
minify: true,
depCache: true,
rev: true
}
},
"Scripts/build/vendor-build": {
includes: [
'jspm_packages/npm/babel-runtime#5.8.38/helpers/class-call-check.js',
'jspm_packages/npm/babel-runtime#5.8.38/helpers/create-class.js',
'jspm_packages/npm/babel-runtime#5.8.38/core-js/object/define-property.js',
'jspm_packages/npm/core-js#1.2.7/library/fn/object/define-property.js',
'jspm_packages/npm/babel-runtime#5.8.38/core-js/object/define-properties.js',
'jspm_packages/npm/core-js#1.2.7/library/fn/object/define-properties.js',
'npm:aurelia-framework#1.0.7',
'npm:aurelia-loader-default#1.0.0',
'npm:aurelia-logging-console#1.0.0',
'npm:aurelia-templating-binding#1.0.0',
'npm:aurelia-templating-resources#1.1.1',
'npm:aurelia-templating-router#1.0.0',
'npm:aurelia-knockout#1.0.2',
'npm:aurelia-history-browser#1.0.0',
'npm:aurelia-bootstrapper#1.0.0',
'npm:aurelia-fetch-client#1.0.1',
'npm:aurelia-router#1.0.6',
'npm:aurelia-animator-css#1.0.1',
'npm:babel-core#5.8.38',
'npm:babel-runtime#5.8.38',
'npm:core-js#1.2.7',
'github:systemjs/plugin-text#0.0.9'
],
options: {
inject: true,
minify: true,
depCache: true,
rev: true
}
}
}
};
gulp.task('build', ['minify'], function () {
return bundler.bundle(config);
});
And this is config.js
System.config({
baseURL: "/",
defaultJSExtensions: true,
transpiler: "babel",
babelOptions: {
"optional": [
"es7.decorators",
"es7.classProperties",
"runtime"
],
"compact": true
},
paths: {
"github:*": "jspm_packages/github/*",
"npm:*": "jspm_packages/npm/*"
},
bundles: {
},
map: //some mappings
}
If I run the gulp task to bundle, it works, and I can load my app using the bundled files, but there are some things I don't understand:
After the bundled files are created, the config.js file is updated within the "bundles:" property with the files which have been created for the bundle, and a random versioning number (since I had set 'rev: true' in the options)
bundles: [
"Scripts/build/vendor-build-4c2789cace.js": [
//list of files
]
]
When I run the task again, maybe after some changes, the new bundled file has been added to the config.js file like that:
bundles: [
"Scripts/build/vendor-build-4c2789cace.js": [
//list of files
],
"Scripts/build/vendor-build-t67uj8e5f4.js": [
//list of files
]
]
but as you can see, the old one is still there. How do I tell him to "clear" the bundles property when I create a new bundle?

Gulp task to create a DEV and PROD version of main.js

Im new to Gulp and have been playing with gulp-minify and gulp-uglify to create an overall main.js file via gulp-requirejs.
However, i need to create 2 versions of this main.js file, one for my DEV environment, and one for PROD.
Reason being, in some .js files, i have listed URLs, for example:
currentPage == '/products/chairs.html'
However, when moving these html files into my .Net structure, the actual URL is:
currentPage == 'goodsandservices/products/our-chairs.html'
My current rJS task is as follows:
gulp.task('rjs', function() {
rjs({
baseUrl: config.src.js,
out: 'main.js',
name: 'main',
mainConfigFile: config.src.js + '/main.js',
exclude: [ 'angular']
})
.pipe(prod ? uglify({ mangle: false, compress: { drop_console: true } }) : gutil.noop())
.pipe(gulp.dest(config.dest.js))
});
I have tried using gulp-replace, and updated my rjs task as:
gulp.task('rjs', function() {
rjs({
baseUrl: config.src.js,
out: 'main.js',
name: 'main',
mainConfigFile: config.src.js + '/main.js',
exclude: [ 'angular']
})
.pipe(prod ? uglify({ mangle: false, compress: { drop_console: true } }) : gutil.noop())
.pipe(replace(/NETStructure/g, 'goodsandservices'))
.pipe(gulp.dest(config.dest.js))
});
And in my .js files, i have updated the URLs to:
currentPage == /NETStructure/g'/products/chairs.html'
However this didnt appear to work.
I suggest you to keep those strings in an object and have two different files for the same object, like:
constants.dev.js
var constants = {
chairs: '/products/chairs.html'
};
constants.prod.js
var constants = {
chairs: 'goodsandservices/products/our-chairs.html'
};
Then you only need to include the correct file (or concatenate wrapped in a closure) and:
currentPage == constants.chairs

Categories

Resources