AngularJS + webpack how to hash template htmls - javascript

I have migrated our AngularJS application to use webpack - before it used gulp. in the gulp version I have used rev plugin to rev all the files (css,js and html) however in the webpack mode , I cannot find a way to add hash to the html templates - which cause issues as the browser serve old html files. How can it be fixed? below is by webpack conf file
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const OpenBrowserPlugin = require('open-browser-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const RemoteServer = process.env.REMOTE_SERVER;
const appEnv = process.env.NODE_ENV || 'development';
const isProduction = appEnv === 'production';
const patterns = require('../server/src/main/resources/regex.json');
const appPath = path.join(__dirname, 'app');
const buildPath = path.join(__dirname, 'artifacts');
const config = {
entry: [path.join(appPath, 'index.js')],
output: {
path: buildPath,
filename: '[name].[hash].js',
chunkFilename: '[name].[hash].js'
},
resolve: {
modules: ['node_modules', appPath],
alias: {
'ui-select-css': path.resolve('./node_modules/ui-select/dist/select.css'),
fonts: path.resolve(__dirname, 'assets/fonts')
}
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
options: {
emitWarning: true,
quiet: true
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'resolve-url-loader'
]
},
{
test: /\.less$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader', options: {
url: false,
sourceMap: true
}
}, {
loader: 'less-loader', options: {
relativeUrls: false,
sourceMap: true
}
}]
},
{
test: /\.(jpe?g|png|gif)(\?.*)?$/i,
use: [{
loader: 'file-loader',
options: {name: '[path][name].[hash].[ext]'}
}]
},
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
}
}
},
{
test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'url-loader',
options: {limit: 10000, mimetype: 'application/octet-stream'}
}
]
},
{
test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'file-loader'
}
]
},
{
test: /\.svg$/i,
loader: 'raw-loader'
},
{
test: require.resolve('angular'),
use: [
{loader: 'expose-loader', options: 'angular'},
]
},
{
test: require.resolve('jquery'),
use: [
{loader: 'expose-loader', options: '$'},
{loader: 'expose-loader', options: 'jQuery'},
]
},
{
test: require.resolve('lodash'),
use: [
{loader: 'expose-loader', options: '_'},
]
},
{
test: require.resolve('moment'),
use: [
{loader: 'expose-loader', options: 'moment'},
]
},
{
test: /\.html$/,
use: [{
loader: 'raw-loader',
options: {name: '[path][name].[hash].[ext]'}
}]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(appPath, 'index.html')
}),
new webpack.DefinePlugin({
INJECT_REGEX_HERE: JSON.stringify(patterns)
}),
new CopyWebpackPlugin([
{from: 'app/images', to: 'assets/images'},
{from: 'app/fonts', to: 'assets/fonts'},
{from: 'app/templates', to: 'assets/templates'},
{from: 'app/silent-callback.html', to: 'silent-callback.html'},
{from: 'node_modules/font-awesome/css', to: 'assets/font-awesome/css'},
{from: 'node_modules/font-awesome/fonts', to: 'assets/font-awesome/fonts'},
{from: 'node_modules/angular-ui-grid/fonts', to: 'assets/fonts'},
{from: 'node_modules/d3/d3.min.js', to: 'assets/d3'}
]),
new MiniCssExtractPlugin({
filename: '[name].[hash].css',
chunkFilename: '[id].[hash].css'
}),
new OpenBrowserPlugin({url: 'http://localhost:1337'})
],
devtool: isProduction ? 'source-map' : 'inline-source-map',
devServer: {
port: 1337
},
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
if (RemoteServer) {
console.log('running with remote server', RemoteServer);
config.devServer.proxy = {
'/occm/*': 'http://' + RemoteServer
};
}
if (isProduction) {
config.plugins.push(
new CleanWebpackPlugin(buildPath)
);
}
module.exports = config;

One of the main benefits of using Webpack is reducing the number of requests a browser has to perform for rendering your application and making your app start faster.
To achieve this, it groups related resources together in "chunks", that get loaded together in one request. Separate loading of required single files like HTML-templates (without a specific reason) could be considered an anti-pattern here.
Best practice is grouping all related JS, HTML and CSS code together in one big bundle that gets loaded once, sometimes (for bigger apps) having a second 'vendor' bundle for the code from node_modules, speeding up development, because this chunk won't change as often.
Alternative solution:
So in your case, if there is no specific reason for keeping things separate (which you didn't write about), I would rather recommend serving HTML together with the controlling code in one chunk instead of loading the HTML files separately.
A good and simple starting point would be to build just two chunks. Replace your whole optimization block by the following code:
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all'
}
}
}
},
That will build two chunks: One main chunk for all of your JS and HTML files and another one, explicitly for everything from the node_modules folder.
That way you won't have to worry about browser caches for HTML files anymore, because they are built in your main chunk, and, as a benefit, your app will start faster.

I did a similar migration a couple of year and I didn't need that type of solution. My goal was encapsulate each component as a module and then lazy load as much as possible.
// webpack config
{
test: /\.html$/,
use: ['html-loader'],
},
Then in each component I just require styles and template like:
require('./_proposal-page.scss');
(function() {
'use strict';
angular.module('component.myComponent', []).component('myComponent', {
template: require('./proposal-page.html'),
controller: MyController,
});
/** #ngInject */
function MyController($log) {
const $ctrl = this;
$ctrl.$onInit = function() {
$log.log('$onInit myComponent');
}
}
})();
if (typeof module !== 'undefined' && typeof exports !== 'undefined' && module.exports === exports) {
module.exports = 'component.myComponent';
}
Webpack detect the requires and export each .html file as a module, it works smoothly.

Related

Vue SFC - Webpack maybe not including style block

I have webpack set up to process my single file Vue components, but it seems to ignore the <style> block. Here is my full config file:
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const webpack = require('webpack');
const path = require('path');
module.exports = {
watch: true,
mode: 'development',
entry: path.resolve(__dirname + '/src/webpack-entry.js'),
output: {
path: path.resolve(__dirname + '/assets/'),
filename: 'vue.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
},
{
test: /\.scss$/,
use: [
'vue-style-loader',
{ loader: 'css-loader', options: { sourceMap: true } },
{
loader: 'sass-loader',
options: {
sourceMap: true,
additionalData: `
#import "./src/scss/_variables.scss";
`
}
}
]
}
]
},
optimization: {
minimize: false
},
plugins: [
new VueLoaderPlugin()
]
};
The generated .js files work fine. They just don't have the scoped style. I want to say it's a webpack configuration problem as I'm not super versed in webpack configuration. Let me know if there is any context that needs to be added or described.

Flash of unstyled content with react and scss

After migrating my CSS files to SCSS, I can see FOUC for my layout elements at the first load (After each reloads of page).
I guess it has something to do with my webpack config so I tried to fix the problem by using mini-css-extract-plugin, but I can still see the problem.
Here is the content of my webpack.config.js file:
const HtmlWebPackPlugin = require("html-webpack-plugin");
const path = require('path');
const MomentLocalesPlugin = require('moment-locales-webpack-plugin');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const devMode = process.env.NODE_ENV !== 'production';
module.exports = {
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,{
loader: "css-loader"
}, {
loader: "sass-loader",
}
]
},
{
test: /\.html$/,
use: [
{
loader: "html-loader"
}
]
},
{
test: /(\.(?:le|c)ss)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: false,
},
},
{
loader: 'less-loader',
options: {
sourceMap: false,
javascriptEnabled: true,
},
}
]
},
{
test: /\.(png|jpe?g|gif)$/,
use: [
{
loader: 'file-loader',
},
]
}
]
},
output: {
publicPath: "/"
},
devServer: {
historyApiFallback: true,
},
plugins: [
new HtmlWebPackPlugin({
template: "./src/index.html",
filename: "./index.html"
}),
new MiniCssExtractPlugin(),
new MomentLocalesPlugin(),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
}),
new webpack.optimize.AggressiveMergingPlugin()
],
resolve: {
alias: {
"#ant-design/icons/lib/dist$": path.resolve(__dirname, "./src/icons.js")
}
}
};
The problem wasn't because of Webpack. It was because of me being stupid :)
I imported the CSS file inside of a child component, so when I reload the page, it first shows the layout without any styles and then loads the child component and gives the layout a proper style. So basically, the only thing I did to fix the problem was to import the style in the layout component.
import '../style.scsc'

Copy files from node_modules to dist dir

I am trying to copy library from node_modules to dist folder using html-webpack-plugin, the library name is wsrpc-python
That's my webpack config, I set file-loader but it doesn't help.
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const { VueLoaderPlugin } = require('vue-loader')
const vueLoaderConfig = require('./vue-loader.conf')
function resolve(dir) {
return path.join(__dirname, '..', dir)
}
const createLintingRule = () => ({
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [resolve('src'), resolve('test')],
options: {
formatter: require('eslint-friendly-formatter'),
emitWarning: !config.dev.showEslintErrorsInOverlay
}
})
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: './src/main.js',
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath:
process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
externals: {
"wsrpc-python": "WSRPC",
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'#': resolve('src')
}
},
module: {
rules: [
...(config.dev.useEslint ? [createLintingRule()] : []),
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader?cacheDirectory',
include: [
resolve('src'),
resolve('test'),
resolve('node_modules/webpack-dev-server/client')
]
},
{
test: /\.svg$/,
loader: 'svg-sprite-loader',
include: [resolve('src/icons')],
options: {
symbolId: 'icon-[name]'
}
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
exclude: [resolve('src/icons')],
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
},
{
test: /wsrpc-python\/js\//,
loader: 'file-loader'
},
]
},
plugins: [new VueLoaderPlugin()],
node: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
}
Adding onto #tony19's answer, you need to set a to and from path in copy-webpack-plugin.
It should look like this:
new CopyWebpackPlugin([
{ from: 'node_modules/wsrpc-python/*.js', to: '/dist' },
]),
By default CopyWebpackPlugin will look for the file in the current dir, to provide the right dir we need to use require.resolve('your-package-name/dir/file-name')
plugins: [
new CopyWebpackPlugin({
patterns: [{
from: require.resolve('my-package-name/dist/index.css'),
to: '[name].min.css'
}]
})
]
Use the CopyWebpackPlugin instead:
webpack.config.js:
const CopyPlugin = require('copy-webpack-plugin')
module.exports = {
plugins: [
...
new CopyPlugin([
'node_modules/wsrpc-python/*.js',
]),
],
}
GitHub demo
CopyWebpackPlugin didn't work for me either.
I just use a custom inline plugin, like so: https://gist.github.com/Venryx/c473c60ea0f10a2620f2d9d00c06e6c7

Click to open PDF - You may need an appropriate loader to handle this file type

I'm trying to import a PDF into a .js file in order to have a Click to open pdf into my render.
Importing
import myFile from './assets/files/myfile.pdf';
Rendering
render() {
return (
...
<a href={myFile}>
<span>Click to open PDF</span>
</a>
...
)}
Error
myProject.bundle.js:88481 ./assets/files/myfile.pdf 1:0
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
(Source code omitted for this binary file)
Webpack.config.js
I tried several PDF loader without success, the webpack below uses url-loader.
const path = require('path'),
webpack = require('webpack'),
CleanWebpackPlugin = require('clean-webpack-plugin'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractPlugin = new ExtractTextPlugin({ filename: './assets/css/app.css' });
const config = {
mode: 'production',
// absolute path for project root with the 'src' folder
context: path.resolve(__dirname, 'src'),
entry: ['babel-polyfill', './main.js'],
// entry: {
// // relative path declaration
// app: './main.js'
// },
output: {
// absolute path declaration
path: path.resolve(__dirname, 'dist'),
filename: 'myProject.bundle.js',
publicPath: '/'
},
module: {
rules: [
// ************
// * SEE HERE *
// ************
{ test: /\.pdf$/, use: 'url-loader' },
// babel-loader with 'env' preset
{ test: /\.jsx?$/, include: /src/, exclude: /node_modules/, use: { loader: "babel-loader", options: { presets: ['env', 'es2015', 'react', 'stage-0'] } } },
// html-loader
{ test: /\.html$/, use: ['html-loader'] },
// sass-loader with sourceMap activated
{
test: /\.scss$/,
include: [path.resolve(__dirname, 'src', 'assets', 'scss')],
use: extractPlugin.extract({
use: [
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
],
fallback: 'style-loader'
})
},
// file-loader(for images)
{ test: /\.(jpg|png|gif|svg)$/, use: [ { loader: 'file-loader', options: { name: '[name].[ext]', outputPath: './assets/images/' } } ] },
// file-loader(for fonts)
{ test: /\.(woff|woff2|eot|ttf|otf)$/, use: ['file-loader'] },
]
},
plugins: [
// cleaning up only 'dist' folder
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
template: 'index.html'
}),
// extract-text-webpack-plugin instance
extractPlugin
],
};
module.exports = config;
Note: I'm testing this on localhost if that makes any difference.
SOLUTION
The problem came from the fact that I wasn't restarting the dev server by running npm run startdev after making some changes.
Include it in the file loader, as you do with images:
{ test: /\.(jpg|png|gif|svg|pdf)$/, use: [ { loader: 'file-loader', options: { name: '[name].[ext]', outputPath: './assets/images/' } } ] },
Note that I have added |pdf after the SVG declaration. Webpack should then process the file as it does with images.
config.module.rules.push({
test: /\.pdf$/,
use: {
loader: 'file-loader',
options: {
name: '[path][name].[ext]',
},
},
});
// vue.config.js configureWebpack
config.module
.rule('pdf')
.test(/\.pdf$/)
.use('pdf')
.loader('file-loader')
.end();
config.module
.rule('pdf')
.test(/\.pdf$/)
.use('file-loader?name=[path][name].[ext]')
.loader('file-loader')
.end();

Webpack - Sass loader not compiling Sass

I'm trying to introduce Sass-loader to an existing project so as to be able to use Sass in my React components. I've set this up successfully before in my own boiler plates and had no issues but for some reason with the current configuration it doesn't seem to play. It doesn't give any errors but rather doesn't do anything with my .scss file i'm trying to import.
I can see that there is some loaders in the cssLoaders config for Webpack which could be the culprit but I am only targeting .scss/ .sass files with the rule
test: /\.s(a|c)ss$/
so I wouldn't have thought this would affect it at all.
I have webpack setup with a common file and one for development. Here are both those files:
webpack.common.js
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const ErrorOverlayPlugin = require('error-overlay-webpack-plugin');
const webpack = require('webpack');
const path = require('path');
require('babel-polyfill');
const Dotenv = require('dotenv-webpack');
module.exports = {
entry: {
main: ['babel-polyfill', './src/index.js']
},
output: {
filename: '[name].[hash].js',
path: path.resolve('./dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: ['node_modules'],
use: [{ loader: 'babel-loader' }]
},
{
test: /\.s(a|c)ss$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader'
},
{
loader: 'sass-loader'
}
]
},
{
test: /\.(png|jpg|gif|svg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
outputPath: 'images/'
}
}
]
},
{
test: /\.(woff(2)?|ttf|eot|otf)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
}
}
]
}
]
},
plugins: [
new ErrorOverlayPlugin(),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebPackPlugin({
template: 'index.html'
}),
new Dotenv(),
new CleanWebpackPlugin(['dist'])
]
};
And here is webpack.dev.js
const path = require('path');
const webpack = require('webpack');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const CircularDependencyPlugin = require('circular-dependency-plugin');
const paths = require('../paths');
module.exports = require('./webpack.config.base')({
bail: false,
devtool: 'cheap-module-eval-source-map',
stats: 'errors-only',
performance: {
hints: false
},
entry: {
main: [
require.resolve('react-dev-utils/webpackHotDevClient'),
paths.appPolyfillsJs,
require.resolve('react-error-overlay'),
paths.appIndexJs
]
},
output: {
pathinfo: true,
filename: 'static/js/[name].js',
chunkFilename: 'static/js/[name].chunk.js',
devtoolModuleFilenameTemplate: info =>
path.resolve(info.absoluteResourcePath)
},
// Load the CSS in a style tag in development
cssLoaders: [
{
loader: require.resolve('style-loader'),
options: {
sourceMap: true
}
},
{
loader: require.resolve('css-loader'),
options: {
modules: false,
sourceMap: true,
importLoaders: 1
}
},
{
loader: require.resolve('postcss-loader'),
options: { sourceMap: true }
}
],
babelQuery: {
cacheDirectory: true
},
plugins: [].concat([
new ProgressBarPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new CircularDependencyPlugin({
exclude: /a\.js|node_modules/,
failOnError: true
})
])
});
The part that I have added to the configuration is in webpack.common:
{
test: /\.s(a|c)ss$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader'
},
{
loader: 'sass-loader'
}
]
},
I then just import it in the module like so:
import React, { PureComponent } from 'react';
import './test.scss';
...
Can anyone spot why the configuration is wrong?
The sass-loader requires node-sass and webpack as peerDependency.
Make sure node-sass is installed and edit the loader webpack.dev.js like so:
{
loader: "sass-loader",
options: {
includePaths: ["absolute/path/a", "absolute/path/b"]
}
}

Categories