Content Hashes, ExtractTextPlugin and HtmlWebpackPlugin - javascript

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)

Related

Pack HTML with html-webpack-plugin without packing any JavaScript

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');
});
}
}
]

Sourcemaps not mapping correctly except "entry" specified in webpack

I'm using ASP.NET Core 2.0. If anyone wants to see detailed code or run it themselves, the code can be found here: https://github.com/jakelauer/BaseballTheater/tree/master/BaseballTheaterCore
My basic problem is that I'm expecting each generated js file in my project to have a sourcemap back to the original .ts or .tsx file. That is not working except for my entry file (./ClientApp/boot.tsx).
Here is my webpack.config.js:
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
const bundleOutputDir = './wwwroot/dist';
module.exports = (env) => {
const isDevBuild = !(env && env.prod);
return [{
stats: { modules: false },
entry: { 'main': './ClientApp/boot.tsx' },
resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'] },
output: {
path: path.join(__dirname, bundleOutputDir),
filename: '[name].js',
publicPath: 'dist/'
},
module: {
rules: [
{ test: /\.tsx?$/, include: /ClientApp/, use: 'awesome-typescript-loader?silent=true' },
{ test: /\.css$/, use: isDevBuild ? ['style-loader', 'css-loader'] : ExtractTextPlugin.extract({ use: 'css-loader?minimize' }) },
{
test: /\.scss/,
use: ["style-loader", "css-loader", "sass-loader"]
},
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
]
},
plugins: [
new CheckerPlugin(),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(bundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin('site.css')
])
}];
};
Based on my interpretation of this file and limited understanding of webpack, this should work. Each of my files does generated a .js.map file, and it appears to be referenced in the generated .js file. However, none of them actually load except the one for boot.tsx when debugging in Chrome.
An example of one of the js files in Chrome:
And that file does have the correct files to load:
When I open main.js.map in /wwwroot/dist/ and Ctrl+F for ts inside there, I only find boot.tsx and none of the other .ts or .tsx files I would expect to find.
I am no webpack expert, so I'm not sure what else to do!
From the comments, we've come to the solution by:
upgrading to the newest webpack (v4 in this case) and
installing the source-map loader via npm install --save-dev source-map-loader and
setting devtool: 'source-map' in the webpack.config.js.
The source-map option tells webpack to emit a full separate source map file. This is from the webpack docs:
source-map - A full SourceMap is emitted as a separate file. It adds a reference comment to the bundle so development tools know where to find it.

Webpack: load js modules dynamically from a URL at runtime

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!

Concat and minify all less files with Webpack without importing them

I have a folder of around 20 separate less files that I need to concatenate into a single file via Webpack and store this in my /dist folder. My current Webpack config file is as follows:
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
const bundleOutputDir = './wwwroot/dist';
module.exports = (env) => {
const isDevBuild = !(env && env.prod);
return [{
stats: { modules: false },
entry: { 'main': './ClientApp/boot.ts' },
resolve: { extensions: ['.js', '.ts'] },
output: {
path: path.join(__dirname, bundleOutputDir),
filename: '[name].js',
publicPath: '/dist/'
},
module: {
rules: [
{ test: /\.ts$/, include: /ClientApp/, use: 'awesome-typescript-loader?silent=true' },
{ test: /\.html$/, use: 'raw-loader' },
{ test: /\.css$/, use: isDevBuild ? ['style-loader', 'css-loader'] : ExtractTextPlugin.extract({ use: 'css-loader' }) },
{ test: /\.less/, use: ExtractTextPlugin.extract('style-loader', 'css-loader!less-loader') },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
]
},
plugins: [
new CheckerPlugin(),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(bundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin('site.less'),
new ExtractTextPlugin('site.css')
])
}];
};
If I try and import each single .less file into the boot.ts entry file, I get a less error stating that the less variables that I've declared are not being recognised, which is how I came to the conclusion that I need to concat these files beforehand. I come from a gulp background, so any help to get me up and running with this would be greatly appreciated.
If there is an alternative way to get all less compiled to css and working correctly, without the need for concat, then I'm open to suggestions.
Webpack is a module bundler and uses the module syntax for JavaScript (ES6, CommonJS, AMD..), CSS (#import, url) and even HTML (through src attribute) to build the app's dependency graph and then serialize it in several bundles.
In your case, when you import the *.less files the errors are because you miss CSS modules. In other words, on the places where you have used variables defined in other file, that file was not #import-ed.
With Webpack it's recommended to modularize everything, therefore I would recommend to add the missing CSS modules. I had the same issue when I was migrating a project from Grunt to Webpack. Other temporary solution is to create an index.less file where you will #import all the less files (note: the order is important) and then import that file in app's entry file (ex. boot.ts).

webpack did not output file to correct directory

I try to implement hot reloading in a laravel app. But I have problem doing output using webpack. When I do http://localhost/bundle.js it's 404 not found. I wonder why. In my terminal I did see bundle.js is bundled by webpack, but where has it gone to?
gulpfile.js
var elixir = require('laravel-elixir');
var webpackDevMiddleware = require('webpack-dev-middleware');
var webpackHotMiddleware = require('webpack-hot-middleware');
var webpackConfig = require('./webpack.dev.config');
var bundler = webpack(webpackConfig);
gulp.task('hot', function(){
elixir(function(mix) {
browserSync({
proxy: 'somewhere.local',
middleware: [
webpackDevMiddleware(bundler, {
publicPath: '/'
}),
webpackHotMiddleware(bundler)
]
});
});
});
webpack.dev.config.js The problem is here
module.exports = {
debug: true,
context: path.join(__dirname, 'resources/assets/bundle/entries'),
entry: ['./feed.js'],
output: {
path : path.join(__dirname, 'public/bundle'),
publicPath : '/',
filename : 'bundle.js'
}
resolve: {
extensions: ['', '.js', '.jsx']
}
}
my laravel view
I also included
<script src="http://localhost:3000/bundle.js"></script>
Something is wrong in my webpack config file. Here's my site directory :
webpack dev server doesn't write the bundle to disk.
This modified bundle is served from memory at the relative path specified in publicPath (see API). It will not be written to your configured output directory. Where a bundle already exists at the same URL path, the bundle in memory takes precedence (by default).
you can find out more here: https://webpack.github.io/docs/webpack-dev-server.html
depending on your exact needs, you might consider using a different webpack configuration

Categories

Resources