Requiring an object from another file currently strips out Regex key values - javascript

I'm currently iterating over a few different directories, requiring Webpack Configuration objects from these different directories, and starting a child process in node that runs the webpack compiler with that given configuration object.
When I use require( webpackConfigPath ), it strips out the regex values for module: { rules : [ { test: /\.js$/, ...} ]}, and replaces them with an empty object, bringing over something that looks like this: module: { rules : [ { test: {}, ...} ]}
Can anybody provide me direction on how to deep clone a variable from another file without these RegExp key values being stripped out and replaced with empty objects?
Example in usage:
const webpackConfigDir = path.resolve( __dirname, themeDir + '/webpack.config.js' )
let config = require( webpackConfigDir );

I ended up solving this by requiring a constructor function for the webpack configuration instead.
Example:
webpack.config.js
const path = require("path");
const entryPath = path.resolve( __dirname, 'source/scripts/app.js' );
const outputPath = path.resolve( __dirname, 'dist/js' )
const constructConfig = function ( entryPath, outputPath ) {
const config = {
entry: ["#babel/polyfill", entryPath],
output: {
path: outputPath,
filename: "scripts.js"
},
module: {
rules: [
{
test: /\.js$/,
use: "babel-loader",
exclude: /node_modules/
}
]
},
plugins: [],
devtool: 'source-map',
externals: {
jquery: 'jQuery'
}
}
return config
}
module.exports = {
constructor: constructConfig,
entry: entryPath,
output: outputPath
}
I then converted the constructor function to a string, since node child processes don't allow functions to be passed in, only objects, as far as I could tell.
scripts.js:
let configConstructor = require( webpackConfigDir ).constructor
let entry = require( webpackConfigDir ).entry
let output = require( webpackConfigDir ).output
let processOptions = {
constructor: configConstructor.toString(),
entry: entry,
output: output
}
process.send( processOptions )
Within the child process being ran, I converted the string back into a function that gets called once returned.
build.js:
const webpack = require("webpack");
const chalk = require('chalk');
async function build(config) {
await webpack( config, (err, stats) => {
if ( stats.hasErrors() ) {
const errors = stats.toJson().errors
errors.forEach( (error, i) => {
console.log( chalk.white.bgRed.bold(`Error #${i + 1}: `) + chalk.white.bgRed(`${error}`) )
})
}
})
}
process.on("message", async options => {
const constructor = new Function( 'return ' + options.constructor )();
const config = await constructor( options.entry, options.output )
await build(config);
});
This solution worked for the problem at hand, figured I would share in the event anybody runs into a similar problem. In this case, I was trying to share project dependencies but allow a different webpack configuration on a per-directory basis.

Related

Option to get a single line main.js output with only references (webpack)?

I'm new to webpack and I have been put on a project where someone was already coding something before me. I have all the source code and I have also a webpack.config.js file.
I want to rebuild the output main.js from those files but I think I might be missing some steps to get there. I use the loader babel by the way and the react library.
When I run npx webpack in the folder where the .config.js file is I have something with this structure:
/*! For license information please see main.js.LICENSE.txt */
(() => {
var e = {
418: e => {
"use strict";
var t = Object.getOwnPropertySymbols,
n = Object.prototype.hasOwnProperty,
r = Object.prototype.propertyIsEnumerable;
function o(e) {...
I want to get something that looks like this (I modified it so it is more readable but normally everything is on one line):
() => {
var __webpack_modules__ = {
"./src/components/app.js": (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
If anyone has an idea feel free to tell me, I just started using webpack two weeks ago and I might have misunderstood something.
My Webpack.config.js file
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "./static/frontend"),
filename: "[name].js",
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
},
],
},
],
},
optimization: {
minimize: true,
},
plugins: [
new webpack.DefinePlugin({
"process.env": {
// This has effect on the react lib size
NODE_ENV: JSON.stringify("production"),
},
}),
new HtmlWebpackPlugin({
favicon: "./src/small-logo-orange.svg"
}),
],
};
Ok after some research I found out what was my problem.
The previous guy gave me a webpack.config.js file with no option for the mode field in :
module.exports = {
};
To get the same thing as him I need to have the mode set to 'development'
module.exports = {
mode: 'development', ...
};
See https://webpack.js.org/configuration/mode
Hope this will help someone one day.

Compiling a typescript project with webpack

I have a question for you - how is it possible to implement multi-file compilation while preserving the tree of folders and documents, while not writing each file into entry in this way
entry: {
index:'./src/index.ts',
'bot/main':'./src/bot/main.ts'
}
But at the same time, the files had their names and their position, as before compilation in js, only instead of the src folder, they were in the dist folder?
My current config webpack.config.js
const path = require('path')
const nodeExternals = require('webpack-node-externals')
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')
module.exports = {
context: __dirname,
entry: {
index:'./src/index.ts',
'bot/main':'./src/bot/main.ts'
},
externals: [nodeExternals()],
module: {
rules: [
{
exclude: /node_modules/,
test: /.ts$/,
use: {
loader: 'ts-loader'
}
}
]
},
node: {
__dirname: false
},
resolve: {
extensions: ['.ts', '.js'],
plugins: [
new TsconfigPathsPlugin({
baseUrl: './src'
})
]
},
output: {
filename: '[name]js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/dist/'
},
target: 'node'
}
And when building in production mode, all this was compiled into one file, taking into account all URLs, imports, etc.
Is it even possible?
Webpack itself won't do that for you. You will need to write litter helper function to achieve this. The basic idea is to crawl the directory, find all the files and then provide them to Webpack:
const path = require('path');
const glob = require('glob');
const extension = 'ts';
// Recursively find all the `.ts` files inside src folder.
const matchedFiles = glob.sync(`./src/**/*.${extension}`, {
nodir: true
});
const entry = {};
matchedFiles.forEach((file) => {
const SRC_FOLDER = path.join(__dirname, 'src');
const ABS_PATH = path.join(__dirname, file);
// Generates relative file paths like `src/test/file.ts`
const relativeFile = path.relative(SRC_FOLDER, ABS_PATH);
// fileKey is relative filename without extension.
// E.g. `src/test/file.ts` becomes `src/test/file`
const fileKey = path.join(path.dirname(relativeFile), path.basename(relativeFile, extension));
entry[fileKey] = relativeFile;
});
module.exports = {
context: __dirname,
// Use the entry object generated above.
entry,
// ... rest of the configuration
};

Webpack problem generating sourcemaps -- only generated when a particular plugin is disabled

I'm using my own custom plugin to strip some optional code from a build. This works well, but for some reason it seems to be blocking generation of source maps.
My best guess is that the fact that I'm modifying the index.js output file interferes with the ability to generate a map of for that file. If I comment out the plugin, my source maps come back.
Is there perhaps something I can do to change order of plugin execution that will fix this? Or perhaps a way to strip code from source file input streams (not from the files themselves) rather than from the generated output?
I've tried explicitly adding SourceMapDevToolPlugin to my plugins, but that didn't help.
Here's my webpack.config.cjs file:
const { Compilation, sources } = require('webpack');
const { resolve } = require('path');
module.exports = env => {
const esVersion = env?.esver === '5' ? 'es5' : 'es6';
const dir = env?.esver === '5' ? 'web5' : 'web';
const chromeVersion = env?.esver === '5' ? '23' : '51';
// noinspection JSUnresolvedVariable,JSUnresolvedFunction,JSUnresolvedFunction
return {
mode: env?.dev ? 'development' : 'production',
target: [esVersion, 'web'],
entry: {
index: './dist/index.js'
},
output: {
path: resolve(__dirname, 'dist/' + dir),
filename: `index.js`,
libraryTarget: 'umd',
library: 'tbTime'
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: { presets: [['#babel/preset-env', { targets: { chrome: chromeVersion } }]] }
},
resolve: { fullySpecified: false }
}
]
},
resolve: {
mainFields: ['esm2015', 'es2015', 'module', 'main', 'browser']
},
externals: { 'by-request': 'by-request' },
devtool: 'source-map',
plugins: [
new class OutputMonitor {
// noinspection JSUnusedGlobalSymbols
apply(compiler) {
compiler.hooks.thisCompilation.tap('OutputMonitor', (compilation) => {
compilation.hooks.processAssets.tap(
{ name: 'OutputMonitor', stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE },
() => {
const file = compilation.getAsset('index.js');
let contents = file.source.source();
// Strip out dynamic import() so it doesn't generate warnings.
contents = contents.replace(/return import\(.*?\/\* webpackIgnore: true \*\/.*?tseuqer-yb.*?\.join\(''\)\)/s, 'return null');
// Strip out large and large-alt timezone definitions from this build.
contents = contents.replace(/\/\* trim-file-start \*\/.*?\/\* trim-file-end \*\//sg, 'null');
compilation.updateAsset('index.js', new sources.RawSource(contents));
}
);
});
}
}()
]
};
};
Full project source can be found here: https://github.com/kshetline/tubular_time/tree/development
I think using RawSource would disable the source map. The right one for devtool is supposed to be SourceMapSource so the idea looks like following:
const file = compilation.getAsset('index.js');
const {devtool} = compiler.options;
let contents = file.source.source();
const {map} = file.source.sourceAndMap();
// your replace work
// ...
compilation.updateAsset(
'index.js',
devtool
// for devtool we have to pass map file but this the original one
// it would be wrong since you have already changed the content
? new sources.SourceMapSource(contents, 'index.js', map)
: new sources.RawSource(contents)
);

Why is webpack not running all plugins when a file change is detected?

I'm using webpack with watch=true to compile handlebars files into HTML files in a /dist folder.
The HandlebarsWebpackPlugin runs when .hbs files are modified, but I also want it to run when JSON files are modified. The JSON files contain data that is passed to the handlebars templates when they are compiled into HTML.
I added a plugin called webpack-watch-files-plugin thinking that it would watch the JSON files and re-run the whole build when a change is detected, but it doesn't.
Can somebody please explain how does webpack work in this regard? How can I make all the plugins re-run (or at least the handlebars to HTML compilation) when I change a JSON file?
webpack.config.js
const webpack = require('webpack');
const glob = require("glob");
const path = require("path");
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HandlebarsPlugin = require("handlebars-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const ImageminPlugin = require("imagemin-webpack-plugin").default;
const WatchFilesPlugin = require('webpack-watch-files-plugin').default;
const mergeJSON = require("./src/utils/mergeJSON");
const handlebarsHelpers = require("handlebars-helpers");
const lang = process.env.npm_config_lang; // lang variable passed as argument to script
const webpackConfig = {
watch: true,
watchOptions: {
ignored: ['node_modules/**', 'dist']
},
entry: path.join(__dirname, 'src'),
plugins: [
new CleanWebpackPlugin(),
new HandlebarsPlugin({
// path to hbs entry file(s). Also supports nested directories if write path.join(process.cwd(), "app", "src", "**", "*.hbs"),
entry: path.join(__dirname, "src", "emails", "**/*.hbs"),
// output path and filename(s). This should lie within the webpacks output-folder
// if ommited, the input filepath stripped of its extension will be used
output: path.join(__dirname, "dist", "[path]", "[name].html"),
// you can also add a [path] variable, which will emit the files with their relative path, like
// output: path.join(process.cwd(), "build", [path], "[name].html"),
// data passed to main hbs template: `main-template(data)`
data: mergeJSON(path.join(__dirname, 'src/emails/**/*.json')),
// globbed path to partials, where folder/filename is unique
partials: [
path.join(__dirname, "src", "partials", "*.hbs")
],
// register custom helpers. May be either a function or a glob-pattern
helpers: {
projectHelpers: handlebarsHelpers()
},
// hooks
// getTargetFilepath: function (filepath, outputTemplate) {},
// getPartialId: function (filePath) {}
onBeforeSetup: function (Handlebars) {},
onBeforeAddPartials: function (Handlebars, partialsMap) {},
onBeforeCompile: function (Handlebars, templateContent) {},
onBeforeRender: function (Handlebars, data, filePath) {
const mergedData = mergeJSON(path.join(__dirname, 'src/emails/**/*.json'));
const filePathArray = filePath.split('/');
const fileName = filePathArray[filePathArray.length-1].split('.hbs')[0];
if(mergedData[fileName]) {
// only json specific to this file and language will be used as data for this file
return {...mergedData[fileName].config, ...mergedData[fileName][lang]};
} else if(!lang) {
const errorText = `The language code is required to build the emails. Pass it as an argument with this format "--lang=en-US".`;
console.log('\n', '\x1b[41m\x1b[37m', errorText, '\x1b[0m');
throw new Error(errorText);
}
},
onBeforeSave: function (Handlebars, resultHtml, filename) {},
onDone: function (Handlebars, filename) {}
}),
new CopyWebpackPlugin([{
from: path.join(__dirname, "src/emails/**/imgs/*"),
to: "[1]/[2].[ext]", // [1] and [2] are groups matched by the regex below
test: /emails\/([^/]+)\/(.+)\.(jpe?g|png|gif|svg|)$/,
}]),
new ImageminPlugin({test: /\.(jpe?g|png|gif|svg)$/i}),
new WatchFilesPlugin({
files: [
path.join(__dirname, "src", "emails", "**/data/*.json")
],
verbose: true
})
]
};
module.exports = webpackConfig;

Webpack loaders in multiple config files

I have a Webpack flow where multiple configurations are merged depending on the type of build. I'm still a webpack newbie but getting the hang of it - but ran into a problem.
With the setup I have som css loaders which are used in my common flow - that is on every build. Now I need some loaders only used for production builds. With my current setup the loaders for production is never used - but if I outcomment the loaders in my common setup the production loaders are run.
Is there a way to merge rules for css from different configurations?
My webpack.config.js
const path = require('path');
const webpackMerge = require('webpack-merge');
const commonPartial = require('./webpack/webpack.common');
const clientPartial = require('./webpack/webpack.client');
const serverPartial = require('./webpack/webpack.server');
const prodPartial = require('./webpack/webpack.prod');
const { getAotPlugin } = require('./webpack/webpack.aot');
module.exports = function (options, webpackOptions) {
options = options || {};
webpackOptions = webpackOptions || {};
if (options.aot) {
console.log(`Running build for ${options.client ? 'client' : 'server'} with AoT Compilation`)
}
let serverConfig = webpackMerge({}, commonPartial, serverPartial, {
entry: options.aot ? { 'main-server' : './Client/main.server.aot.ts' } : serverPartial.entry, // Temporary
plugins: [
getAotPlugin('server', !!options.aot)
]
});
let clientConfig = webpackMerge({}, commonPartial, clientPartial, {
plugins: [
getAotPlugin('client', !!options.aot)
]
});
if (options.prod) {
// Change api calls prior to packaging due to the /web root on production
clientConfig = webpackMerge({}, prodPartial, clientConfig);
serverConfig = webpackMerge({}, prodPartial, serverConfig);
}
const configs = [];
if (!options.aot) {
configs.push(clientConfig, serverConfig);
} else if (options.client) {
configs.push(clientConfig);
} else if (options.server) {
configs.push(serverConfig);
}
return configs;
}
My webpack.common.js
const { root } = require('./helpers');
const path = require('path');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
/**
* This is a common webpack config which is the base for all builds
*/
const extractBeneath = new ExtractTextPlugin('../assets/stylesheets/beneath.css');
const extractSkolePlan = new ExtractTextPlugin('../assets/stylesheets/skoleplan.css');
const source = path.resolve(__dirname, 'Client');
const appDirectory = path.resolve(source, 'app');
module.exports = {
devtool: 'source-map',
resolve: {
extensions: ['.ts', '.js']
},
output: {
filename: '[name].js',
publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
module: {
rules: [
{ test: /\.ts$/, loader: '#ngtools/webpack' },
{
//***** This is working nicely *****
test: /\.css$/,
exclude: appDirectory,
use: extractSkolePlan.extract({
fallback: 'to-string-loader',
use: 'css-loader?sourcemap'
})
},
{
//***** This is working nicely too *****
test: /\.css$/,
include: appDirectory,
use: 'raw-loader'
},
{ test: /\.html$/, loader: 'html-loader' },
{
test: /\.less$/,
use: extractBeneath.extract({
fallback: 'to-string-loader',
use: ['css-loader', 'less-loader']
})
},
{ test: /\.(woff2?|ttf|eot|svg)$/, loader: 'url-loader?limit=10000' },
{ test: /\.(png|jpg|jpeg|gif)$/, loader: 'url-loader?limit=25000' }
]
}
,
plugins: [
extractSkolePlan,
extractBeneath
]
};
And my webpack.prod.js
const { root } = require('./helpers');
const path = require('path');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const StringReplacePlugin = require("string-replace-webpack-plugin");
const source = path.resolve(__dirname, 'Client');
const appDirectory = path.resolve(source, 'app');
/**
* This is a prod config to be merged with the Client config
*/
module.exports = {
module: {
rules: [
{
test: /\.ts$/,
loader: StringReplacePlugin.replace({
replacements: [
{
pattern: /return window.location.origin;/ig,
replacement: function() {
console.log(' Her er javascript replaced');
return 'return window.location.origin + \'/web\'';
}
}
]
})
},
{
//***** This is never loaded *****
test: /\.css$/,
exclude: appDirectory,
use: StringReplacePlugin.replace({
replacements: [
{
pattern: /assets/ig,
replacement: function() {
console.log('Her er css skiftet');
return '/web/assets/martin';
}
}
]
})
}
]
},
plugins: [
// an instance of the plugin must be present
new StringReplacePlugin()
]
};
Any help is appreciate - thanks :-)
I am not familiar with the package you are using, webpack-merge, but when testing, it is similar to:
Object.assign({}, {foo: 'a'}, {foo: 'b'}) // => {foo: 'b'}
It gives priority to the objects from right to left. So in your example, it should be:
if (options.prod) {
// Change api calls prior to packaging due to the /web root on production
clientConfig = webpackMerge({}, clientConfig, prodPartial);
serverConfig = webpackMerge({}, serverConfig, prodPartial);
}
With prodPartial at the right, in order to get preference over the commonPartial inside serverConfig and clientConfig.
However, I would advise to make these three configuration files easier to reason about by having several imports, e.g. module.exports.output, module.exports.rules, and do the merging manually in the main config file.

Categories

Resources