Webpack run plug-in scripts on watch - javascript

I have webpack set up with a plug-in handling application files & css.
webpack.config.js
const path = require('path');
const glob = require('glob');
const uglify = require("uglify-js");
const cleanCSS = require("clean-css");
const merge = require('webpack-merge-and-include-globally');
const mode = 'dev';
const ext = mode === 'prod' ? '.min' : '';
module.exports = (event, arg) => {
let config = {
mode: mode === 'dev' ? 'development' : 'prod' ? 'production' : '',
entry: {
bundle : [ "./build/index.js" ]
},
output: {
path: __dirname + '/public',
filename: "js/[name].js"
},
plugins: [
new merge({
files: {
// concatenate angular app
'./app/app.js' : [
'./build/app/js/app.js',
'./build/app/js/controllers/*',
'./build/app/js/directives/*',
'./build/app/js/services/*',
],
// compile scripts & css
'./js/scripts.js' : [ './build/js/scripts.js' ],
'./css/style.css' : [ './build/css/*' ]
},
transform: { // minify in prod mode
'./app/app.js' : code => mode === 'prod' ? uglify.minify(code).code : code,
'./js/scripts.js' : code => mode === 'prod' ? uglify.minify(code).code : code,
'./css/style.css' : code => mode === 'prod' ? new cleanCSS({}).minify(code).styles : code
}
})
]
}
return config;
}
When I run npm run build the script works as expected building a bundle.js with my dependencies, an app.js with my application, and a style.css with my css.
However when I run npm run watch webpack only watches the entry files for bundle.js - index.js.
Is it possible to indicate that webpack watch entry files for the plugins as well? Or can I indicate that the build script is triggered on the changing of some arbitrary files?

I was able to find a solution using the webpack-watch-files plugin
const WatchExternalFilesPlugin = require('webpack-watch-files-plugin').default;
new WatchExternalFilesPlugin({
files: [
'./build/app/js/**/*.js',
'./build/js/*.js',
'./build/css/*.css',
// '!./src/*.test.js'
]
})
** Note: .default in the require statement is needed with Webpack 4

Related

How to configure a vue application (with vue-cli) to add nonce attributes to generated script tags?

I have a vue application that is compiled using vue-cli. My vue.config.js file looks like:
'use strict';
module.exports = {
publicPath: `${process.env.CDN_URL || ''}/dist/`,
lintOnSave: true,
transpileDependencies: [],
outputDir: '.tmp/dist',
pages: {
navigator: {
entry: 'vue/home/main.ts',
template: 'views/home/index.ejs',
// Will output to dist/views/home/index.ejs
filename: 'views/home/index.ejs',
},
},
chainWebpack(config) {
// Override the default loader for html-webpack-plugin so that it does not fallback to ejs-loader.
// ejs-loader will use ejs syntax against the template file to inject dynamic values before html-webpack-plugin runs
config.module
.rule('ejs')
.test(/\.ejs$/)
.use('html')
.loader('html-loader');
},
};
I would like to have webpack add nonce="<%= nonce %>" for each script tag that it generates. I see that webpack has a __webpack_nonce__ variable, but I've tried setting that in many parts of the vue.config.js file. I've tried adding it to chainWebpack() and configWebpack(). I've tried adding it to the vue/home/main.ts file. Nothing seems to work. How do I get nonce attributes added to the script tags?
This is vue 2.6.x and vue cli 4.5.x
I ended up writing my own webpack plugin since my use case was a bit more complex than what script-ext-html-webpack-plugin could support. I needed ${nonce} for header tags and <%= nonce %> for body tags. My plugin code is a simple and based off script-ext-html-webpack-plugin mentioned by #Sphinx
const HtmlWebpackPlugin = require('html-webpack-plugin');
class AddNonceToScriptTagsWebpackPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(this.constructor.name, (compilation) => {
const alterAssetTagGroups = compilation.hooks.htmlWebpackPluginAlterAssetTags || HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups;
alterAssetTagGroups.tap(this.constructor.name, (data) => {
data.head = this._addNonceAttribute(data.head || []);
data.body = this._addNonceAttribute(data.body || []);
return data;
});
});
}
_addNonceAttribute(tags) {
return tags.map((tag) => {
if (tag.tagName === 'script') {
tag.attributes = tag.attributes || {};
tag.attributes.nonce = '<%= nonce %>';
} else if (tag.tagName === 'link' && tag.attributes && tag.attributes.as === 'script') {
tag.attributes = tag.attributes || {};
// eslint-disable-next-line no-template-curly-in-string
tag.attributes.nonce = '${nonce}';
}
return tag;
});
}
}
The updated vue.config.js file then looks like:
'use strict';
module.exports = {
publicPath: `${process.env.CDN_URL || ''}/dist/`,
lintOnSave: true,
transpileDependencies: [],
outputDir: '.tmp/dist',
pages: {
navigator: {
entry: 'vue/home/main.ts',
template: 'views/home/index.ejs',
// Will output to dist/views/home/index.ejs
filename: 'views/home/index.ejs',
},
},
configureWebpack: {
plugins: [
new AddNonceToScriptTagsWebpackPlugin(),
],
},
chainWebpack(config) {
// Override the default loader for html-webpack-plugin so that it does not fallback to ejs-loader.
// ejs-loader will use ejs syntax against the template file to inject dynamic values before html-webpack-plugin runs
config.module
.rule('ejs')
.test(/\.ejs$/)
.use('html')
.loader('html-loader');
},
};
One solution should be adds script-ext-html-webpack-plugin into the plugin list of webpack.prod.conf file (or your own webpack configuration file).
new ScriptExtHtmlWebpackPlugin({
custom: {
test: /\.js$/, // adjust this regex based on your demand
attribute: 'nonce',
value: '<%= nonce %>'
}
}),

Webpack displays version rather than running code - Windows 7

I realise this question is a little out there and not very informative, but the webpack code runs perfectly on linux based systems.
Simply, the script takes an env variable which is a folder name (contains the js to be compiled), then compiles each of the js files inside this given folder. Creates some js from this, then passes that down to compile.js once webpack has finished its business.
package.json
"start_windows": "set BABEL_ENV=development/client&&npm run clean && webpack --watch",
I am most surprised that line 14 of webpack does not run console.info("sdhfjkzhsdkfhzsdjkfh================",env);
webpack.base.config.js
const path = require("path");
const getEntries = require("./scripts/build/getEntries");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const WebpackShellPlugin = require("webpack-shell-plugin");
const envGenerator = require("./scripts/build/handleEnv");
const LiveReloadPlugin = require("webpack-livereload-plugin");
console.info("aaaaaaaaaaaaaaaaaaaaaaa");
/**
* Webpack config
*/
module.exports = function() {
const optionsReload = {};
const env = envGenerator(process.env.npm_config_argv);
console.info("sdhfjkzhsdkfhzsdjkfh================",env);
return {
entry: getEntries(env),
plugins: [
new webpack.LoaderOptionsPlugin({
debug: true
}),
new ExtractTextPlugin("[name]/styles.css"),
new LiveReloadPlugin(optionsReload),
new WebpackShellPlugin({
onBuildEnd: ["node ./scripts/build/compile.js"],
dev: false // Allows script to run more than once (i.e on every watch)
})
],
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: ["css-loader", "postcss-loader", "sass-loader"]
})
}
]
},
output: {
path: path.join(__dirname, "dist"),
publicPath: "/dist/",
filename: "[name]/script.js"
}
};
};
cygwin
shruti#ASGLH-WL-12111 /cygdrive/c/Source/asos-mvt-framework
$ npm run build -- -env T1106-premier-upsell
> mvt-framework#0.0.2 build C:\Source\asos-mvt-framework
> npm run clean && webpack -p "-env" "T1106-premier-upsell"
npm WARN invalid config loglevel="notice"
> mvt-framework#0.0.2 clean C:\Source\asos-mvt-framework
> rm -rf ./dist/*
3.8.1 //output here where webpack should be running
> mvt-framework#0.0.2 postbuild C:\Source\asos-mvt-framework
> node ./scripts/build/compile.js
C:\Source\asos-mvt-framework\dist []
getEntries.js
const fs = require("fs");
const path = require("path");
// Directory where our Multi Variant Tests reside
const mvtFolderPath = path.resolve("./src/tests");
/**
* Loop through the JS files in the test folder root
* and define a new bundle for each one
*/
module.exports = function(testFolderName) {console.info(testFolderName);
const entries = {};
const testFolderPath = path.resolve(mvtFolderPath, testFolderName);
fs.readdirSync(testFolderPath).forEach(file => {
const fileName = path.parse(file).name;
const fileExt = path.parse(file).ext;
if (fileExt === ".js") {
entries[fileName] = path.resolve(testFolderPath, file);
}
});
return entries;
};
handleEnv.js
module.exports = function(env) {
console.info(env);
const envCooked = JSON.parse(env).cooked;
if (envCooked.length === 1) {
// eslint-disable-next-line no-console
console.error("\x1b[31m%s\x1b[0m", "ERROR: Please supply a test folder");
console.error(
"\x1b[31m%s\x1b[0m",
"For Example: `npm start T9999-somme-test`"
);
console.info(`
No env given
`);
process.exit();
}
return envCooked[3].substr(0, 1) === "T" ? envCooked[3] : envCooked[4];
};

Vim autoindent (gg=G) is terribly broken for JS indentation

My end goal here is to use gg=G to autoindent all of my JS code compliant to an eslintrc.js file.
So, currently I have syntastic and vim-javascript looking at my JS code with the following in my .vimrc
let g:syntastic_javascript_checkers=["eslint"]
Lets say that I have some decent JS like the following
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const PATHS = {
app : path.join(__dirname, 'app'),
build : path.join(__dirname, 'build'),
};
const commonConfig = {
entry : {
app : PATHS.app,
},
output : {
path : PATHS.build,
filename : '[name].js',
},
plugins : [
new HtmlWebpackPlugin({
title : 'Webpack Demo',
}),
],
};
The gg=G (normal mode) command mutilates the above into the following.
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const PATHS = {
app : path.join(__dirname, 'app'),
build : path.join(__dirname, 'build'),
};
const commonConfig = {
entry : {
app : PATHS.app,
},
output : {
path : PATHS.build,
filename : '[name].js',
},
plugins : [
new HtmlWebpackPlugin({
title : 'Webpack Demo',
}),
],
};
Which is not cool.
Btw, vim-js-indent and vim-jsx-improve didn't do anything either.
Any help is very welcome, many thanks are in advance.
Your "not cool" example is the result of the "generic" indenting you get when Vim didn't recognize your buffer as JavaScript and/or didn't apply JavaScript-specific indentation rules.
That code is indented correctly with this minimal setup:
$ vim -Nu NONE --cmd 'filetype indent on' filename.js
which:
detects that your buffer contains JavaScript,
applies JavaScript-specific indentation rules.
To ensure proper indenting, you must add this line to your vimrc:
filetype indent on

Using Webpack 2 and extract-text-webpack-plugin

I'm using extract-text-webpack-plugin 2.0.0-rc.3 with Webpack 2.2.1 and am getting this error when running the build:
/node_modules/extract-text-webpack-plugin/index.js:259
var shouldExtract = !!(options.allChunks || chunk.isInitial());
^
TypeError: chunk.isInitial is not a function
Here is my webpack.config.js:
'use strict';
const argv = require('yargs').argv;
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const webpack = require('webpack');
module.exports = (function () {
let config = {
entry : './' + process.env.npm_package_config_paths_source + '/main.js',
output : {
filename : 'main.js'
},
watch : !!argv.watch,
vue : {
loaders : {
js : 'babel-loader',
// Create separate CSS file to prevent unstyled content
sass : ExtractTextPlugin.extract("css!sass?sourceMap") // requires `devtool: "#source-map"`
}
},
module : {
rules : [
{
test : /\.js$/,
use : 'babel-loader',
exclude : '/node_modules/'
},
{
test : /\.vue$/,
use : 'vue-loader',
options : {
loaders : {
'scss' : 'vue-style-loader!css-loader!sass-loader',
'sass' : 'vue-style-loader!css-loader!sass-loader?indentedSyntax'
},
}
}
]
},
plugins : [
new webpack.DefinePlugin({
'process.env' : {
npm_package_config_paths_source : '"' + process.env.npm_package_config_paths_source + '"'
}
}),
new ExtractTextPlugin("style.css")
],
resolve : {
alias : {
'vue$' : 'vue/dist/vue.common.js'
}
},
babel : {
"presets" : ["es2015", "stage-2"],
"comments" : false,
"env" : {
"test" : {
"plugins" : ["istanbul"]
}
}
},
devtool : "#source-map" // #eval-source-map is faster but not compatible with ExtractTextPlugin
};
if (process.env.NODE_ENV === 'production') {
config.plugins = [
// short-circuits all Vue.js warning code
new webpack.DefinePlugin({
'process.env' : {
NODE_ENV : '"production"',
npm_package_config_paths_source : '"' + process.env.npm_package_config_paths_source + '"'
}
}),
// minify with dead-code elimination
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin("style.css")
];
config.devtool = "#source-map";
}
return config;
})();
When I remove new ExtractTextPlugin("style.css") from the plugins array the build runs fine, but doesn't create style.css.
If I add the allChunks: true option the error becomes this:
/node_modules/webpack/lib/Chunk.js:80
return this.entrypoints.length > 0;
^
TypeError: Cannot read property 'length' of undefined
In case you are writing style rules in .vue file or seprate .scss file, with below webpack configurations you can achieve what you're searching for:
webpack.confi.js:
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
....
....
module.exports = {
....
....
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
'scss': ExtractTextPlugin.extract('css-loader!sass-loader'),
'sass': ExtractTextPlugin.extract('css-loader!sass-loader?indentedSyntax')
}
}
},
....
....
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract('css-loader!sass-loader')
}
....
....
] // rules end
}, // module end
plugins: [
new ExtractTextPlugin('style.css')
],
....
}
Just make sure that you have installed these modules/loaders via NPM:
css-loader
sass-loader
node-sass
extract-text-webpack-plugin

Delete unused webpack chunked files

I'm looking for information on how to delete old webpack chunked files. Here is my current webpack configuration:
var path = require('path');
var webpack = require('webpack');
module.exports = {
debug: false,
outputPathinfo: true,
displayErrorDetails: true,
context: __dirname,
entry: {
common: ['./src/common.coffee'],
a: './src/a.cjsx',
b: './src/b.cjsx'
},
output: {
filename: '[name]-[chunkhash].js',
chunkFileName: '[name].[chunkhash].js',
path: path.join(__dirname, 'js')
},
plugins: [
new webpack.optimize.CommonsChunkPlugin('common', 'common-[chunkhash].js'),
new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false }
})
],
module: {
preLoaders: [
{
test: /\.coffee$/,
exclude: /node_modules/,
loader: 'coffeelint-loader'
}
],
loaders: [
{ test: /\.coffee/, loader: 'coffee' },
{ test: /\.cjsx$/, loaders: ['coffee', 'cjsx'] },
{ test: /\.js$/, loader: 'jsx-loader?harmony' }
]
}
}
If I am running $(npm bin)/webpack --config webpack.js --watch and make changes to a.cjsx, it compiles a newer version of that file with a new chunkedhash. However, the old one remains and I'd like it to be deleted right away.
How can I delete the old version of the chunked file?
Is there a way for me to hook into an after callback once watch finishes compiling?
There is a clean-webpack-plugin for those purposes, or you can write a simple bash script for npm:
"scripts": {
"build": "rm -r dist/* && webpack -p",
"clean": "rm -r dist/*"
}
Here is the webpack-clean-obsolete-chunks plugin, which do what you want. It searches for all updated chunks and deletes obsolete files after each webpack compilation.
The answer
I've decided to write an answer because others - although trying to answer the question directly - overlooked the most important part in my opinion.
And the most important part is: you shouldn't be doing it this way. Using [hash] placeholders in your development setup cause many headaches with other tooling (phpstorm's path autocomplete in symfony plugin for example). Also it's poor for webpack's incremental compilation performance and thus is not recommended by official webpack docs (reference).
So for future readers: just keep it simple for development config - define your filename as [name].js and move on.
Edit
There seems to be a confusion about what to do with the old chunk-files on the production server. Well, you don't do anything. Once a version is deployed it shouldn't be ever changed. You just keep creating new versions when deploying and keep previous as a backup. Why?
Because you want you're rollback to be reliable and for it to be possible your rollback needs to be extremely simple and atomic. If your rollback procedure is doing anything more than switching a symlink, rerouting to previous container (or similar simple operation) you're probably™ going to end up in trouble.
Rollback isn't a process of "re-deploying" the application again, but now to the previous version. It's a process of "un-doing" the deployment. So doing a git checkout to the previous version followed by a npm build --but-please-be-hurry --and-im-begging-you-dont-fail while your production app is hanging there, completely exploded doesn't cut here.
Rebuilding a previous version of the application - just like the deployment - may fail for many reasons. That's why a rollback should be switching/rerouting back to the exact same version-build that is proven to be working. Not ==-the-same, 100% ===-the-same. That's why you need to keep your previous version around, because that's the ===-same. A "regenerated" one is - in best case scenario - only ==-the-same, and so it is not proven to be working, only assumed.
And no, no amount of CI, staging environments or whatever will give you a guaranteed successful deployment. Part of doing it the right way is to be prepared for when things go wrong. And things will go wrong. Hopefully only from time to time, but still.
Of course once you have 3, 5 or <put-your-number-here> versions backed up you may start to remove the oldest ones as you probably won't ever need more than 3.
Since Webpack 5.20.0 you can use output.clean option
Take a look at this pull request:
https://github.com/johnagan/clean-webpack-plugin/pull/32/files
Open raw file view and copy it to index.js of clean webpack plugin.
Remember about config flag -> watch: true
I have solved that problem by adding below in webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
{
... your configs ...
plugins: [new CleanWebpackPlugin()]
}
You can solve the problem № 1 by using remove-files-webpack-plugin.
Use this plugin like this:
plugins: [
new RemovePlugin({
watch: {
test: [
{
folder: './js',
method: (absPath) => new RegExp(/(.*)-([^-\\\/]+)\.js/).test(absPath)
}
]
}
})
]
In "watch" mode (not normal compilation!) it grabs all files from ./js folder and tests them with this regular expression /(.*)-([^-\\\/]+)\.js/. Analyze this regular expression on regex101 (unit tests are included) if you have problems with understanding.
Note: i'm the creator of this plugin.
Looks like webpack#5.20.0+ has built-in support for this https://webpack.js.org/configuration/output/#outputclean. I use [chunkhash] in my chunk filenames and they get cleared out if I stop comment out dynamic imports and added back in if I uncomment them.
my case: webpack 5 + multipage application + themes.css via entry points
solution: https://github.com/webdiscus/webpack-remove-empty-scripts
this plugins don't work with webpack 5 entry points or with MiniCssExtractPlugin:
webpack-fix-style-only-entries,
webpack-extraneous-file-cleanup-plugin,
webpack-remove-empty-js-chunks-plugin,
webpack-delete-no-js-entries-plugin.
my webpack.config.js:
const fs = require('fs');
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts');
const isProd = process.env.NODE_ENV === 'production';
const isDev = !isProd;
const PAGES = ['app', 'help'];
const getThemes = (themePath, alias) => {
let themes = {};
const longPath = './' + alias + '/' + themePath;
fs.readdirSync(longPath).forEach(function(fileName) {
const fileNameWithPath = path.join(themePath, fileName);
const fileNameWithLongPath = path.join(longPath, fileName);
const stat = fs.lstatSync(fileNameWithLongPath);
if (stat.isDirectory()) return;
if (!/\.scss$/.test(fileName)) return;
const nameWithoutExt = path.basename(fileName, '.scss');
themes[nameWithoutExt] = ['./' + fileNameWithPath];
});
console.log(themes);
return themes;
};
const themes = getThemes('scss/themes', 'src');
const getFilename = (filename, ext) => {
let name = filename == 'index' ? 'bundle' : filename;
const isTheme = (ext == 'css' && name.startsWith('theme')) ? true : false;
const needHash = (isDev || isTheme) ? false : true;
return needHash ? name +`.[fullhash].` + ext : name+'.'+ext;
};
const getCSSDirname = filename => {
const isTheme = filename.startsWith('theme');
return !isTheme? '/css/' : '/css/theme/';
};
const getHTMLWebpackPlugins = arr => {
// this function config multipages names and add to html-pages
// inside <head> tag our themes via tag <link rel="stylesheet" href="....css" ...>
// and return array of HTMLWebpackPlugins
};
module.exports = {
// ... //
entry: {
// mutipage:
app: ['./index.js', './scss/app.scss'],
help: ['./help.js', './scss/help.scss'],
// multitheme:
...themes,
},
optimization: {
removeEmptyChunks: true, // not work!!!
},
// ... //
plugins: [
// ... //
...getHTMLWebpackPlugins(PAGES),
new RemoveEmptyScriptsPlugin({
ignore: PAGES,
enabled: isDev === false,
}),
new MiniCssExtractPlugin({
filename: pathdata => {
return getCSSDirname(pathdata.chunk.name) + getFilename(pathdata.chunk.name, 'css');
},
chunkFilename: isDev ? '[id].css' : '[id].[contenthash].css',
}),
],
};
my src files:
[src]:
- index.js
- index.html
- help.js
- help.html
- [scss]:
- - app.scss
- - help.scss
- - [themes]:
- - - light.scss
- - - dark.scss
- - - blue.scss
after build:
[dist]:
- app.js
- index.html
- help$hash.js
- help$hash.html
- [css]:
- - app$hash.css
- - help$hash.css
- - [themes]:
- - - light.css
- - - dark.css
- - - blue.css
For Windows users
"scripts": {
"build": "npm run clean && webpack --mode production",
"clean": "del /f /s /q dist 1>nul"
}
I just had to stop my server and run yarn serve again

Categories