Is there a way to expose module globally from Webpack DllPlugin vendor bundle? - javascript

Using Webpack 4, I'm creating a bundle and a vendors bundle. Vendor bundle contains jQuery and the code bundle references jquery using 'import * from jquery'. This works perfectly.
However, I now need to use a 3rd party (already minified) javascript file. That file expects jquery to be exposed as '$' on the global window object. My bundle needs to be loaded after the new minified file, also.
So I have:
<script src='./libs/jquery-3.2.1.min.js'></script>
<script src='../vendor.js'></script>
<script src="./libs/newMinifiedFile.js"></script>
<script src="../bundle.js"></script>
as my current workaround. The static jquery file links the $ objects to the global namespace, and then the vendor.js file allows me to keep using 'import' in my bundle.
So, how do I only load jquery once, and use it in both ways? This seems to be a slightly different problem than most I've seen online because of how I'm loading things.
Here's a small example of my configs right now:
const config = (isDebug) => {
const isDevBuild = isDebug;
const extractCSS = new MiniCssExtractPlugin({filename: 'vendor.css'});
const sharedConfig = {
mode: isDevBuild ? 'development' : 'production',
stats: { modules: false },
resolve: {
extensions: [ '.js' ]
},
module: {
rules: [
{ test: /\.(png|woff|woff2|eot|ttf|svg)(\?|$)/, use: 'url-loader?limit=100000' },
]
},
entry: {
vendor: [
'jquery'
],
},
output: {
publicPath: 'dist/',
filename: '[name].js',
library: '[name]_[hash]',
},
plugins: [
new webpack.NormalModuleReplacementPlugin(/\/iconv-loader$/, require.resolve('node-noop')) // Workaround for https://github.com/andris9/encoding/issues/16
]
};
const clientBundleConfig = merge(sharedConfig, {
output: {
path: path.join(__dirname, 'wwwroot', 'dist'),
pathinfo: false
},
module: {
rules: [
{
test: /\.css(\?|$)/, include: path.resolve(__dirname, "client"), exclude: [/webviewer/, /redux/, /helpers/],
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
minimize: isDevBuild,
sourceMap: isDevBuild
}
}
]
}
]
},
plugins: [
extractCSS,
new webpack.DllPlugin({
path: path.join(__dirname, 'wwwroot', 'dist', '[name]-manifest.json'),
name: '[name]_[hash]'
})
],
optimization: {
minimize: !isDevBuild,
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false,
},
});
And in my normal config I use:
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require(path.join(__dirname, 'wwwroot', 'dist', 'vendor-manifest.json'))
})
So, I can find jquery in the global 'vendor_hash' object, but I can't get it to map it to $ no matter what I try (Most plugins seem to see that $ is never used in any of the code I'm supplying for the bundle and thus don't seem to include it). If I add something like this in my 'entry.js' file then it still doesn't work:
window.$ = window.jQuery = require("jquery");

Related

minify with raw bundler plugin in webpack

Objective is to concatenate src files to create a library file, and then create another version that's minified with sources maps. I'm using RawBundlerPlugin.
Following works like a charm! What's missing is how to generate another minified library.
const path = require("path");
const RawBundlerPlugin = require("webpack-raw-bundler");
module.exports = {
entry: "./src/header.txt",
output: {
filename: "MyLib.js",
path: path.resolve(__dirname, "dist")
},
module: {
rules: [
{ test: /\.txt$/, use: "raw-loader" }
]
},
plugins: [
new RawBundlerPlugin({
readEncoding: "utf-8",
bundles: [ "MyLib.js" ],
"MyLib.js": [
"src/header.txt",
"src/Product.js",
"src/Customer.js",
"src/copyright.txt"
]
})
],
mode: "none"
};
Question: How to include a minification build with sources maps using above configuration?
Or
maybe take the dist/MyLib.js and minify it to dist/MyLib.min.js through a subsequent process.
attempt https://webpack.js.org/plugins/uglifyjs-webpack-plugin/
optimization: {
minimizer: [new UglifyJsPlugin({
test: /\.js(\?.*)?$/i,
})],
},

mini-css-extract-plugin generates two additional JS files

I'm using the following webpack.config.js file to build two CSS files (editor.css and style.css) and a JS file (block.build.js) making use of the mini-css-extract-plugin plugin:
// Load webpack for use of certain webpack tools and methods
const webpack = require( 'webpack' );
// For extracting CSS (and SASS) into separate files
const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' );
// Define JavaScript entry points
const entryPointNames = [ 'blocks', 'frontend' ];
// Setup externals
const externals = {};
// Setup external for each entry point
entryPointNames.forEach( entryPointName => {
externals[ '#/lg6' + entryPointName ] = {
this: [ 'lg6', entryPointName ]
}
} );
// Define WordPress dependencies
const wpDependencies = [ 'components', 'element', 'blocks', 'utils', 'date' ];
// Setup externals for all WordPress dependencies
wpDependencies.forEach( wpDependency => {
externals[ '#wordpress/' + wpDependency ] = {
this: [ 'wp', wpDependency ]
};
});
// Start of main webpack config
const config = {
// Set mode
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
// Go through each entry point and prepare for use with externals
entry: {
index: './index.js',
style: './style.scss',
editor: './editor.scss',
},
// Include externals
externals,
// Set output
output: {
// Place all bundles JS in current directory
filename: 'block.build.js',
path: __dirname,
library: [ 'pluginnamespace', '[name]' ],
libraryTarget: 'this'
},
// Fall back to node_modules for file resolution
resolve: {
modules: [ __dirname, 'node_modules' ]
},
optimization: {
splitChunks: {
cacheGroups: {
editor: {
name: 'editor',
test: /editor\.(sc|sa|c)ss$/,
chunks: 'all',
enforce: true,
},
style: {
name: 'style',
test: /style\.(sc|sa|c)ss$/,
chunks: 'all',
enforce: true,
},
default: false,
},
},
},
module: {
rules: [
{
// Run JavaScript files through Babel
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
{
// Setup SASS (and CSS) to be extracted
test: /\.(sc|sa|c)ss$/,
exclude: /node_modules/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: 'css-loader',
options: {
sourceMap: process.env.NODE_ENV !== 'production',
},
},
{
loader: 'postcss-loader',
options: {
plugins: [ require( 'autoprefixer' ) ]
}
},
{
loader: 'sass-loader',
options: {
sourceMap: process.env.NODE_ENV !== 'production',
},
},
],
},
]
},
plugins: [
// Setup environment conditions
new webpack.DefinePlugin( {
'process.env.NODE_ENV': JSON.stringify(
process.env.NODE_ENV || 'development'
)
} ),
new MiniCssExtractPlugin( {
filename: './css/[name].css',
} ),
// For migrations from webpack 1 to webpack 2+
new webpack.LoaderOptionsPlugin( {
minimize: process.env.NODE_ENV === 'production',
debug: process.env.NODE_ENV !== 'production',
} )
],
// Do not include information about children in stats
stats: {
children: false
}
};
module.exports = config;
Everything is working as expected but, for some reason, in addition to the block.build.js file, I'm getting two more JS files named 0.block.build.js and 2.block.build.js with the following content:
(window.webpackJsonp=window.webpackJsonp||[]).push([[0],[,function(n,w,o){}]]);
My question is, why are these two additional files are being created and how can I avoid this?
Thanks in advance
You should remove these 2 line
style: './style.scss',
editor: './editor.scss',
Also you can import those 2 scss file in your index.js
import "style.scss";
import "editor.scss";
And mini-css-extract-plugin will take care the rest for you
As an alternative, if you don't want to import the scss files in your js files, I found you can use a webpack plugin such as Ignore Emit Webpack in your webpack.config.js file to prevent the creation of the extra js files:
const IgnoreEmitPlugin = require('ignore-emit-webpack-plugin');
module.exports = {
// ...
plugins: [
new IgnoreEmitPlugin(['0.block.build.js', '2.block.build.js'])
]
// ...
};

Webpack style-loader / css-loader: url() path resolution not working

There are a few SO posts about style-loader and css-loader, but despite this I have not been able to find a solution to my problem.
In short summary, when I #import css files in other css files, and the imported css contains url()s with relative paths, the paths are not resolved correctly.
Basically, the error message shows that Webpack ends up thinking the url() paths in the imported css are relative to src (main entry point), rather than being relative to the css file it it is imported into:
// css-one.scss
#import "./assets/open-iconic-master/font/css/open-iconic-bootstrap.css";
// open-iconic-bootstrap.css
#font-face {
src: url('../fonts/open-iconic.eot');
}
Error:
ERROR in ./src/main.scss
(./node_modules/css-loader??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./node_modules/sass-loader/lib/loader.js??ref--5-3!./src/main.scss)
Module not found: Error: Can't resolve '../fonts/open-iconic.eot' in
'C:\Users\...\src'
# ./src/main.scss
(./node_modules/css-loader??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./node_modules/sass-loader/lib/loader.js??ref--5-3!./src/main.scss) 7:106-141 7:172-207 # ./src/main.scss # ./src/index.js
What I Have Tried:
I have tried to use the convertToAbsoluteUrls flag in style-loader
I have tried to turn off all source maps (mentioned in style-loader docs)
My Webpack Config File (loaders are at the bottom):
const path = require('path');
const webpack = require('webpack'); // for webpack built-in plugins
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
// const WriteFilePlugin = require('write-file-webpack-plugin');
// const ManifestPlugin = require('webpack-manifest-plugin');
// const InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin');
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const PATHS = {
// when using __dirname, resolve and join gives same result,
// because __dirname is absolute path to directory of this file.
// OK to use no slashes,
// both resolve and join adds platform-specific separators by default
src: path.resolve(__dirname, 'src'),
dist: path.resolve(__dirname, 'dist'),
build: path.resolve(__dirname, 'build'),
test: path.resolve(__dirname, 'test')
};
const NAMES = {
// JS FILES
index: 'index',
print: 'print',
// Chrome Extension Development
popup: 'popup',
options: 'options',
background: 'background',
contentScript: 'contentScript',
// FOLDERS
assets: 'assets',
utilities: 'utilities',
images: 'images',
fonts: 'fonts',
include: 'include'
};
const FILE_PATHS = {
// JS
indexJs: `${path.join(PATHS.src, NAMES.index)}.js`,
printJs: `${path.join(PATHS.src, NAMES.print)}.js`,
// Chrome Extension Development
popupJs: `${path.join(PATHS.src, NAMES.popup)}.js`,
optionsJs: `${path.join(PATHS.src, NAMES.options)}.js`,
backgroundJs: `${path.join(PATHS.src, NAMES.background)}.js`,
contentScriptJs: `${path.join(
PATHS.src,
NAMES.include,
NAMES.contentScript
)}.js`,
// HTML
indexHtml: `${path.join(PATHS.src, NAMES.index)}.html`,
printHtml: `${path.join(PATHS.src, NAMES.print)}.html`,
// Chrome Extension Development
popupHtml: `${path.join(PATHS.src, NAMES.popup)}.html`,
optionsHtml: `${path.join(PATHS.src, NAMES.options)}.html`,
backgroundHtml: `${path.join(PATHS.src, NAMES.background)}.html`
};
// Third-party (vendor) libraries to include
// const VENDORS = ['react', 'bootstrap', 'lodash', 'jQuery']; // Relative paths to node_modules
// Note: These are relative
const ASSETS = {
images: path.join(NAMES.assets, NAMES.images),
fonts: path.join(NAMES.assets, NAMES.fonts)
};
// CleanWebpackPlugin config
const pathsToClean = [PATHS.dist, PATHS.build];
const cleanOptions = {
root: __dirname,
exclude: ['shared.js'],
verbose: true,
dry: false
};
// CopyWebpackPlugin config
const copyPattern = [
// {
// from: NAMES.assets,
// to: NAMES.assets
// },
// {
// from: path.join(NAMES.include, 'contentScript.css')
// },
// {
// from: 'manifest.json',
// transform(content, copyPath) {
// // generates the manifest file using the package.json informations
// return Buffer.from(
// JSON.stringify({
// ...JSON.parse(content.toString())
// // description: env.npm_package_description,
// // version: env.npm_package_version
// })
// );
// }
// }
];
const copyOptions = {
// ignore: ['*.js'],
context: PATHS.src
};
module.exports = (env = {}) => {
// webpack injects env variable, into webpack config.
// perfect to check for production.
// remember to specify --env.production in command
// (if in production mode).
const isProduction = env.production === true;
return {
entry: {
index: FILE_PATHS.indexJs
// Chrome Extension Development
// popup: FILE_PATHS.popupJs,
// contentScript: FILE_PATHS.contentScriptJs
// options: FILE_PATHS.optionsJs,
// background: FILE_PATHS.backgroundJs,
// vendor: VENDORS
},
mode: isProduction ? 'production' : 'development',
devtool: isProduction ? 'source-map' : 'inline-source-map',
optimization: {
splitChunks: {
chunks: 'all'
}
},
output: {
filename: isProduction ? '[name].[chunkhash:8].js' : '[name].js',
// chunkFilename determine name of non-entry chunk files,
// for example dynamic imports in the app
chunkFilename: isProduction ? '[name].[chunkhash:8].js' : '[name].js',
path: PATHS.dist
},
plugins: [
// new webpack.SourceMapDevToolPlugin({
// filename: '[file].map',
// exclude: ['vendor', 'runtime']
// }),
new webpack.DefinePlugin({
// specifies environment variable for dependencies.
// does not apply to browser runtime environment
// (process.env is provisioned by Node)
'process.env.NODE_ENV': isProduction ?
JSON.stringify('production') :
JSON.stringify('development')
}),
// new BundleAnalyzerPlugin(),
new CleanWebpackPlugin(pathsToClean, cleanOptions),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
// does not work with Hot Module Replacement (HMR)
// allows HMR in development (will only use this plugin in production)
filename: isProduction ? '[name].[contenthash].css' : '[name].css',
chunkFilename: isProduction ? '[id].[contenthash].css' : '[id].css'
}),
new webpack.HashedModuleIdsPlugin(),
isProduction ?
new UglifyJSPlugin({
cache: true,
parallel: true,
sourceMap: true // set to true if you want JS source maps
}) :
() => {},
new CopyWebpackPlugin(copyPattern, copyOptions),
// new WriteFilePlugin(),
new HtmlWebpackPlugin({
template: FILE_PATHS.indexHtml,
filename: `${NAMES.index}.html`
})
// new HtmlWebpackPlugin({
// template: FILE_PATHS.popupHtml,
// filename: `${NAMES.popup}.html`,
// excludeChunks: [NAMES.contentScript]
// In dev mode, chunks excluded vendor chunk (which holds CSS).
// Above check fixes it.
// }),
// new HtmlWebpackPlugin({
// filename: `${NAMES.contentScript}.html`,
// excludeChunks: [NAMES.popup, 'runtime'] // Runtime only needed in one HTML
// }),
// new HtmlWebpackPlugin({
// template: FILE_PATHS.optionsHtml,
// filename: `${NAMES.options}.html`,
// chunks: isProduction ? [NAMES.options] : ''
// }),
// new HtmlWebpackPlugin({
// template: FILE_PATHS.backgroundHtml,
// filename: `${NAMES.background}.html`,
// chunks: isProduction ? [NAMES.background] : ''
// }),
// no need for CSS minimization here <-- Done by PostCSS (cssnano)
// new InlineManifestWebpackPlugin(),
// new ManifestPlugin({fileName: 'webpack-manifest.json'}),
],
module: {
rules: [{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
},
{
test: /\.s?[ac]ss$/,
exclude: /node_modules/,
use: [
isProduction ?
MiniCssExtractPlugin.loader :
{
// creates style nodes from JS strings
loader: 'style-loader',
options: {
sourceMap: true,
convertToAbsoluteUrls: true
}
},
{
// CSS to CommonJS (resolves CSS imports into exported CSS strings)
loader: 'css-loader',
options: {
sourceMap: true,
importLoaders: 2
}
},
{
loader: 'postcss-loader',
options: {
config: {
ctx: {
cssnext: {},
cssnano: {},
autoprefixer: {}
}
},
sourceMap: true
}
},
{
// compiles Sass to CSS
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[hash:4].[ext]',
outputPath: ASSETS.images
}
}]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[hash:4].[ext]',
outputPath: ASSETS.fonts
}
}]
},
{
test: /\.(csv|tsv)$/,
use: ['csv-loader']
},
{
test: /\.xml$/,
use: ['xml-loader']
},
{
test: /\.(html)$/,
use: {
loader: 'html-loader',
options: {
interpolate: 'require',
minimize: true
}
}
}
// {
// test: /\.tsx?$/,
// exclude: /(node_modules|bower_components)/,
// use: 'ts-loader'
// }
]
},
devServer: {
// contentBase: path.join(__dirname, 'dist'),
contentBase: PATHS.dist,
compress: false,
port: 8080,
open: false
}
};
};
it took me around 5 days of work to understand how this webpack mess works. I have to be honest I can say that this is one of those things that I really do not understand why they are "defacto" tools of the moment. I can't understand how difficult it can be just to make the config files work as it should, in gulp took me 1 hour to do the same.
My problem was that all the url() rules (including fonts and images) were being loaded by css-loader as [object Module], and they where exported by file-loader but never loaded, so if I added ?url=false to the css-loader it never copied the files and export them. I have to say this was a totally PITA, but I got it working, and I hope it works for somebody else in the world, this was made with webpack 4.
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const ImageminPlugin = require('imagemin-webpack-plugin').default;
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
entry: "./src/index.js",
mode: "development",
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules|bower_components)/,
loader: "babel-loader",
options: { presets: ["#babel/env"] }
},
{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
gifsicle: {
interlaced: false,
},
webp: {
quality: 75
},
}
},
{
loader: 'file-loader',
options:{
name: '[name].[ext]',
outputPath: 'images/',
publicPath: 'images/'
}
},
'url-loader?limit=100000'
],
},
{
test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
}
}
]
},
{
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader?url=false'},
{ loader: 'sass-loader', options: { sourceMap: true } }
],
},
]
},
resolve: { extensions: ["*", ".js", ".jsx"] },
output: {
path: path.resolve(__dirname, "dist/"),
publicPath: "",
filename: "bundle.js"
},
devServer: {
contentBase: path.join(__dirname, "dist/"),
port: 3000,
publicPath: "http://localhost:3000/dist/",
hotOnly: true
},
plugins: [ new MiniCssExtractPlugin(),
new CopyPlugin([{ from: 'src/images/', to: 'images/' }]),
new CopyPlugin([{ from: 'src/fonts/', to: 'fonts/' }]),
new ImageminPlugin({ test: /\.(jpe?g|png|gif|svg)$/i }),
new HtmlWebpackPlugin({
hash: true,
template: './src/index.html',
filename: './index.html' //relative to root of the application
}),
]
};
You can turn off processing of url() rules, btw. I have no idea, why this is a default behavior.
{
loader: 'css-loader',
options: {
...
url: false,
}
},
I was able to solve the problem myself. In case it could help others in the future, please find the solution below.
First of all, if you are using both postcss-loader with the postcss-import plugin, AND css-loader, turn off / delete the postcss-import plugin. You do not need more than one tool that resolves #import rules. This is not really a problem if the order of loaders is correct, but you might as well remove it.
In the sass-loader docs, you can read the following:
Since Sass/libsass does not provide url rewriting, all linked assets
must be relative to the output.
If you're just generating CSS without passing it to the css-loader, it must be relative to your web root.
If you pass the generated CSS on to the css-loader, all urls must be relative to the entry-file (e.g. main.scss).
More likely you will be disrupted by this second issue. It is natural to expect relative references to be resolved against the .scss file in which they are specified (like in regular .css files). Thankfully there are two solutions to this problem:
Add the missing url rewriting using the resolve-url-loader. Place it before the sass-loader in the loader chain.
Library authors usually provide a variable to modify the asset path. bootstrap-sass for example has an $icon-font-path. Check out this working bootstrap example.
I decided to follow bullet two, and add in resolve-url-loader above sass-loader in the Webpack config. It now works as expected.
My final Webpack config (for now) looks like this:
{
test: /\.s?[ac]ss$/,
exclude: /node_modules/,
use: [
isProduction
? MiniCssExtractPlugin.loader
: {
// creates style nodes from JS strings
loader: 'style-loader',
options: {
sourceMap: true,
// convertToAbsoluteUrls: true
}
},
{
// CSS to CommonJS (resolves CSS imports into exported CSS strings)
loader: 'css-loader',
options: {
sourceMap: true,
importLoaders: 2
// url: false,
// import: false
}
},
{
loader: 'postcss-loader',
options: {
config: {
ctx: {
cssnext: {},
cssnano: {},
autoprefixer: {}
}
},
sourceMap: true
}
},
{
loader: 'resolve-url-loader',
options: {
attempts: 1,
sourceMap: true
}
},
{
// compiles Sass to CSS
loader: 'sass-loader',
options: { sourceMap: true }
}
]
},
Side Notes
I noticed that source map paths under "no domain" in Chrome's debugger are repeated. If anyone figures out why, please do share
Remember to include the below side effects in package.json, so tree shaking, which happens in production mode, does not delete the extracted css
"sideEffects": [
".css",
".scss"
],
In webpack 5, you should refrain from using raw-loader and file-loader, instead use asset/source and asset/resource.
{
test: /\.txt$/,
type: 'asset/source',
},
{
test: /\.png$/,
type: 'asset/resource',
},
You can read more about assets on: https://webpack.js.org/guides/asset-modules/. It's described how for Webpack 4 and earlier you should use file-loader, and for Webpack5 and later you should use asset modules.
If anyone is struggling with this using Webpack 5 and is trying to upgrade from css-loader 5 to css-loader 6, you might need to check this issue where the poster has a similar problem to the OP:
With css-loader 5.2.7 the images in the input stylus were embedded as data-
URL in the output CSS. With css-loader 6, the images are instead moved
to the output directory.
See Notes here for how to upgrade to css-loader 6 - the salient points are:
using ~ is deprecated when the esModules option is enabled ... and can
be removed from your code
and
file-loader and url-loader are deprecated, please migrate on asset
modules, since v6 css-loader is generating new URL(...) syntax, it
enables by default built-in assets modules, i.e. type: 'asset' for all
url()
I've therefore done the following:
removed any '~' in my .scss files
i.e.
$font-path: "~/src/fonts" !default;
becomes
$font-path: "/src/fonts" !default;
I've also removed the 'file-loader' module completely.
All NPM packages are now up to date and URLs in CSS are working correctly.
IE is not compatible with new URL()
My issue was that webpack 5 changed everything with "Asset modules" apparently and broke file-loader.
Official docs explain it well: https://webpack.js.org/guides/asset-modules/
TL;DR you can fix it by adding type: 'javascript/auto' to the rule/loader
{
test: /\.(eot|ttf|woff2?)($|\?)/i,
loader: 'file-loader',
options: {
name: '[name]-[sha1:hash:hex:10].[ext]',
esModule: false,
},
type: 'javascript/auto'
},

Webpack: Common chunks for code shared between Webworker and Web code?

I have lots of code shared between web and web worker parts of my browser app.
How can I tell webpack to split my code up into common chunks so that the result is garanteed to work 100%?
The webworker code breaks (fails at runtime) after I tell webpack to generate the common chunks (which it does). Even after I fix the trivial "window not defined" error the worker just does nothing.
I believe this has to do with the webpack "target" option, which per default is set to "web". But I need "web" target because I don't have purely webworker code.
I also cannot do multiple webpack configs because I cannot do the common chunks thing with multiple configs...
What should I do?
If anybody is interested: I am trying build a minimal sized build for my app which includes the monaco editor (which provides the workers):
https://github.com/Microsoft/monaco-editor/blob/master/docs/integrate-esm.md
You can see here (at the bottom of the page) that the entry points consist of 1 main entry file + the workers.
Currently at least 6 MB is wasted because of duplicate code I am using and currently can not be split up because of this problem. That is a lot of wasted traffic.
Any ideas? :)
my webpack 4.1.1 config is basically:
module.exports = (env, options) => {
const mode = options.mode;
const isProduction = mode === 'production';
const outDir = isProduction ? 'build/release' : 'build/debug';
return {
entry: {
"app": "./src/main.tsx",
"editor.worker": 'monaco-editor/esm/vs/editor/editor.worker.js',
"ts.worker": 'monaco-editor/esm/vs/language/typescript/ts.worker.js'
},
output: {
filename: "[name].bundle.js",
path: `${__dirname}/${outDir}`,
libraryTarget: 'umd',
globalObject: 'this',
library: 'app',
umdNamedDefine: true
},
node: {
fs: 'empty'
},
devtool: isProduction ? undefined : "source-map",
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"],
alias: {
"#components": path.resolve(__dirname, "src/components"),
"#lib": path.resolve(__dirname, "src/lib"),
"#common": path.resolve(__dirname, "src/common"),
"#redux": path.resolve(__dirname, "src/redux"),
"#services": path.resolve(__dirname, "src/services"),
"#translations": path.resolve(__dirname, "src/translations"),
"#serverApi": path.resolve(__dirname, "src/server-api")
}
},
optimization: isProduction ? undefined : {
splitChunks: {
minSize: 30000,
minChunks: 1,
name: true,
maxAsyncRequests: 100,
maxInitialRequests: 100,
cacheGroups: {
default: {
chunks: "all",
priority: -100,
test: (module) => {
const req = module.userRequest;
if (!req) return false;
return (!/node_modules[\\/]/.test(req));
},
},
vendor: {
chunks: "all",
test: (module) => {
const req = module.userRequest;
if (!req) return false;
if (!/[\\/]node_modules[\\/]/.test(req)) return false;
return true;
},
priority: 100,
}
}
},
},
module: {
rules: [...(isProduction ? [] : [
{
enforce: "pre", test: /\.js$/, loader: "source-map-loader",
exclude: [
/node_modules[\\/]monaco-editor/
]
}
]),
{
test: require.resolve('jquery.hotkeys'),
use: 'imports-loader?jQuery=jquery'
},
{
test: /\.tsx?$/,
loader: "awesome-typescript-loader",
options: {
configFileName: 'src/tsconfig.json',
getCustomTransformers: () => {
return {
before: [p => keysTransformer(p)]
};
}
}
},
{
test: /\.(css|sass|scss)$/,
use: extractSass.extract({
use: [
{
loader: 'css-loader',
options: {
minimize: isProduction
}
},
{
loader: "postcss-loader",
options: {
plugins: () => [autoprefixer({
browsers: [
'last 3 version',
'ie >= 10'
]
})]
}
},
{ loader: "sass-loader" }
],
fallback: "style-loader"
})
},
{
test: /node_modules[\/\\]font-awesome/,
loader: 'file-loader',
options: {
emitFile: false
}
},
{
test: { not: [{ test: /node_modules[\/\\]font-awesome/ }] },
rules: [
{
test: { or: [/icomoon\.svg$/, /fonts[\/\\]seti\.svg$/] },
rules: [
{ loader: 'file-loader?mimetype=image/svg+xml' },
]
}, {
test: { not: [/icomoon\.svg$/, /fonts[\/\\]seti\.svg$/] },
rules: [
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'svg-url-loader',
options: {}
}
},
]
},
{
test: /\.(png|jpg|gif)$/,
loader: 'url-loader'
},
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?mimetype=application/font-woff" },
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?mimetype=application/font-woff" },
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?mimetype=application/octet-stream" },
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader" },
]
},
]
},
plugins: [
new HardSourceWebpackPlugin({
cacheDirectory: '../node_modules/.cache/hard-source/[confighash]', configHash: function (webpackConfig) {
return require('node-object-hash')({ sort: false }).hash(Object.assign({}, webpackConfig, { devServer: false }));
},
environmentHash: {
root: process.cwd(),
directories: [],
files: ['../package-lock.json'],
}
}),
new webpack.ProvidePlugin({
"window.$": "jquery"
}),
new CleanWebpackPlugin(outDir),
extractSass,
new HtmlWebpackPlugin({
title: 'my title',
filename: 'index.html',
minify: isProduction ? {
collapseWhitespace: true,
collapseInlineTagWhitespace: true,
removeComments: true,
removeRedundantAttributes: true
} : false,
template: 'index_template.html',
excludeChunks: ['ts.worker', "editor.worker"]
}),
new webpack.IgnorePlugin(/^((fs)|(path)|(os)|(crypto)|(source-map-support))$/, /vs[\\\/]language[\\\/]typescript[\\\/]lib/)
].concat(isProduction ? [new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
})] : [])
}
};
EDIT: Alright I wrote a webpack plugin based on everyone's knowledge just put together.
https://www.npmjs.com/package/worker-injector-generator-plugin
You can ignore the content below, and use the plugin or if you want to understand how the plugin came to be and do it by hand yourself (so you don't have to depend on my code) you can keep reading.
=====================================================
Alright after so much researching I figured out this solution, you need to create an injection file, for a simple case you need the https://github.com/webpack-contrib/copy-webpack-plugin as it works pretty well... so let's say your setup is:
entry: {
"worker": ["./src/worker.ts"],
"app": ["./src/index.tsx"],
},
And you have setup your common plugins already let's say this example.
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: 'commons',
chunks: 'initial',
minChunks: 2
},
}
}
},
You need to now create an injection "Vanilla JS" which might look like this:
var base = location.protocol + "//" + location.host;
window = self;
self.importScripts(base + "/resources/commons.js", base + "/resources/worker.js");
Then you can add that alongside your worker, say in src/worker-injector.js
And using the copy plugin
new CopyPlugin([
{
from: "./src/worker-injector.js",
to: path.resolve(__dirname, 'dist/[name].js'),
},
]),
Make sure your output is set to umd.
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
libraryTarget: "umd",
globalObject: "this",
}
This is nothing but a hack, but allows you to use everything as it is without having to do something as overblown.
If you need hashing (so that copy plugin doesn't work) functionality you would have to generate this file (rather than copying it), refer to this.
How to inject Webpack build hash to application code
For that you would have to create your own plugin which would generate the vanilla js file and consider the hash within itself, you would pass the urls that you want to load together, and it would attach the hash to them, this is more tricky but if you need hashes it should be straightforward to implement with your custom plugin.
Sadly so far there doesn't seem to be other way.
I could probably write the plugin myself that would do the workaround and create the injectors, but I do think this is more of a hack and shouldn't be the accepted solution.
I might later go around and write the injector plugin, it could be something as:
something like new WorkerInjectorGeneratorPlugin({name: "worker.[hash].injector.js", importScripts: ["urlToLoad.[hash].js", secondURLToLoad.[hash].js"])
refer to this issues for reference, and why it should be fixed within webpack and something as a WorkerInjectorGeneratorPlugin would be pretty much a hack plugin.
https://github.com/webpack/webpack/issues/6472
This is really bad answer, but i've managed to share chunks between workers and main thread.
The clue is that
globalObject has to be defined as above to (self || this):
output: {
globalObject: "(self || this)"
}
Webpack loads chunks with document.createElement('script') and document.head.appendChild() sequence, which is not available in worker context, but we have self.importScript. So it's just a matter of "polyfiling" it.
Here is working "polyfill" (straight from the hell):
console.log("#faking document.createElement()");
(self as any).document = {
createElement(elementName: string): any {
console.log("#fake document.createElement", elementName);
return {};
},
head: {
appendChild(element: any) {
console.log("#fake document.head.appendChild", element);
try {
console.log("loading", element.src);
importScripts(element.src);
element.onload({
target: element,
type: 'load'
})
} catch(error) {
element.onerror({
target: element,
type: 'error'
})
}
}
}
};
Ensure, that your real code is resolved after polyfill is installed, by using dynamic import, which will. Assuming, that normal "worker main" is in "./RealWorkerMain", that would be "main worker script":
// so, typescript recognizes this as module
export let dummy = 2;
// insert "polyfill from hell" from here
import("./RealWorkerMain").then(({ init }) => {
init();
});
You may need to configure dynamic import in webpack, as documented here is not easy too, this answer was very helpful.
You're looking for universal library target, aka umd.
This exposes your library under all the module definitions, allowing
it to work with CommonJS, AMD and as global variable.
To make your Webpack bundle compile to umd you should configure output property like this:
output: {
filename: '[name].bundle.js',
libraryTarget: 'umd',
library: 'yourName',
umdNamedDefine: true,
},
There is an issue with Webpack 4, but if you still want to use it, you can workaround the issue by adding globalObject: 'this' to the configuration:
output: {
filename: '[name].bundle.js',
libraryTarget: 'umd',
library: 'yourName',
umdNamedDefine: true,
globalObject: 'this'
},
Native Worker support is introduced in webpack 5. With this feature, you can share chunks between app code and webwokers with simple splitChunk options like
{
optimization: {
splitChunks: {
chunks: 'all',
minChunks: 2,
},
},
}
When combining new URL for assets with new Worker/new SharedWorker/navigator.serviceWorker.register webpack will automatically create a new entrypoint for a web worker.
new Worker(new URL("./worker.js", import.meta.url))
The syntax was chosen to allow running code without bundler too. This syntax is also available in native ECMAScript modules in the browser.
https://webpack.js.org/blog/2020-10-10-webpack-5-release/#native-worker-support

Uncaught ReferenceError: require is not defined - Webpack2

I'm updating an app to use webpack 2 from webpack 1 and the regular build works fine. The issue seems to arise when using the devServer and requiring just one of the chunks generated (it's an electron app so I have main and renderer chunks - both are included in a regular build, and with the dev server only the renderer chunk is included)
Everything worked on webpack 1, but for some reason the runtime isn't being included in my chunks? I've tried reordering them but to no avail.
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const path = require('path');
module.exports = {
context: __dirname,
entry: {
main: './main.js',
renderer: './app/index.jsx'
},
output: {
path: path.resolve(__dirname, './dist'),
filename: "[name].js"
},
node: {
__dirname: false,
__filename: false
},
devtool: 'cheap-eval-source-map',
target: 'electron',
module: {
loaders: [
{
test: /(\.js$|\.jsx$)/,
exclude: /(node_modules|dist)/,
loader: 'babel'
},
{ test: /\.scss$/, loader: "style!css?modules!sass" },
{ test: /\.png$/, loader: "url?limit=100000" },
{ test: /\.jpg$/, loader: "file" }
]
},
resolve: {
extensions: ['.js', '.jsx', '.scss'],
modules: [path.resolve('./app'), 'node_modules']
},
plugins: [
new HtmlWebpackPlugin({
template: './app/index.html',
chunks: ['renderer'],
inject: 'body',
hash: 'true'
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': '"dev"'
}
})
],
devServer: {
contentBase: __dirname
},
externals: {
'cheerio': 'window',
'react/addons': true,
'react/lib/ExecutionEnvironment': true,
'react/lib/ReactContext': true
}
};
It seems one of the chunks isn't included properly:
/***/ },
/* 26 */
/***/ function(module, exports) {
module.exports = require("url");
Why is this?
So it seems the issue was with me setting the target to electron but building for web. This seemed to work in webpack 1, but after updating to webpack 2 it no longer includes the runtime in both bundles.
The solution I took was to have two configs - one for electron (my main build), and one for web (specify the target in the webpack config)

Categories

Resources