Exclude unused dynamic modules from bundle using react-loadable - javascript

I've got an issue with react-loadable where I've got a large list of components that may or may not be rendered depending on user-generated content. I'm using a switch statement to render the correct ones.
A (simplified) list of user-generated content might look like this:
const content = ['Paragraph', 'Image', 'Paragraph', 'Canvas'];
Now, what I want to do is have ONLY the components that are used enter the bundle. Instead, ALL of them that get included in the following switch case are in the bundle. Why?
const collection = (name) => {
switch(name) {
case 'Paragraph':
return Loadable({
loader: () => import('dynamic-paragraph-component'),
loading(){ return null }
})
case 'Video':
return Loadable({
loader: () => import('dynamic-video-component'),
loading() { return null }
})
// etc
}
}
For example, dynamic-video-component ends up in the bundle even if it's not used. Is there a way to prevent this?
Current webpack setup with Webpack 4
//----------------------------------
//
// Bundler
//
//----------------------------------
import webpack from 'webpack';
import path from 'path';
import { ReactLoadablePlugin } from 'react-loadable/webpack';
module.exports = (files) => {
console.log(files);
return {
mode: 'production',
entry: './src/client/index.js',
output: {
filename: './main.pkgd.js',
chunkFilename: './[name].pkgd.js',
path: path.resolve(__dirname, 'tmp'),
publicPath: '/',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
babelrc: false,
presets: [
[
'env',
{
modules: false,
targets: {
browsers: ['last 2 versions'],
},
},
],
'flow',
'react',
],
plugins: [
'transform-class-properties',
'syntax-dynamic-import',
'react-loadable/babel',
],
},
},
],
},
optimization: {
splitChunks: {
cacheGroups: {
default: false,
vendors: false,
// vendor chunk
vendor: {
name: 'vendor',
chunks: 'all',
test: /node_modules/,
priority: 20,
reuseExistingChunk: true,
enforce: true,
},
common: {
name: 'main',
minChunks: 1,
chunks: 'initial',
priority: 10,
reuseExistingChunk: true,
enforce: true,
},
},
},
},
plugins: [
new webpack.DefinePlugin({
__isBrowser__: 'true',
env: {
NODE_ENV: JSON.stringify('production'),
},
}),
new ReactLoadablePlugin({
filename: './tmp/react-loadable.json',
}),
],
};
};

The way you have it set up looks correct, so I'd wager the problem is in your webpack.config.js file.
Assuming you are using Webpack 4, you need to reference the code-splitting docs.
Specifically, make sure you have configured the chunkFilename option. Also, you can add comment directives like /* webpackChunkName: "dynamic-video-component" */ for easier debugging.

Related

Cloudflare Deployment: Error: Cannot resolve module 'style-loader'

any idea how to fix this, the webpack config in the ckeditor folder is set like this:
const path = require("path");
const webpack = require("webpack");
const { bundler, styles } = require("#ckeditor/ckeditor5-dev-utils");
const CKEditorWebpackPlugin = require("#ckeditor/ckeditor5-dev-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
module.exports = {
devtool: "source-map",
performance: { hints: false },
entry: path.resolve(__dirname, "src", "ckeditor.js"),
output: {
// The name under which the editor will be exported.
library: "CKSource",
path: path.resolve(__dirname, "build"),
filename: "ckeditor.js",
libraryTarget: "umd",
libraryExport: "default",
},
optimization: {
minimizer: [
(compiler)=>{
const TerserPlugin = require('terser-webpack-plugin');
new TerserPlugin({
terserOptions: {
compress: {},
}
}).apply(compiler);
}
],
},
plugins: [
new CKEditorWebpackPlugin({
// UI language. Language codes follow the https://en.wikipedia.org/wiki/ISO_639-1 format.
// When changing the built-in language, remember to also change it in the editor's configuration (src/ckeditor.js).
language: "en",
additionalLanguages: "all",
}),
new webpack.BannerPlugin({
banner: bundler.getLicenseBanner(),
raw: true,
}),
],
module: {
rules: [
{
test: /\.svg$/,
use: ["raw-loader"],
},
{
test: /\.css$/,
use: [
{
loader: "style-loader",
options: {
injectType: "singletonStyleTag",
attributes: {
"data-cke": true,
},
},
},
{
loader: "css-loader",
},
{
loader: "postcss-loader",
options: {
postcssOptions: styles.getPostCssConfig({
themeImporter: {
themePath: require.resolve("#ckeditor/ckeditor5-theme-lark"),
},
minify: true,
}),
},
},
],
},
],
},
};
I tried to delete package-lock.json and delete node_modules and reinstall everything, but it did not work.
Tried other solutions from other Stack overflow questions that had similar issues, nothing worked
this happens for ckeditor by the way.

Read the contents of the module file/stream into a BLOB

I create a BLOB file and write JavaScipt code there, then create a URL and import the module from it.
const myJSFile = new Blob( [ 'export default true;' ], { type: 'application/javascript' } );
const myJSURL = URL.createObjectURL( myJSFile );
import( myJSURL ).then(async ( module ) => {
console.log( module.default );
});
This works great in the browser console. However, I am having a problem when building a project using Webpack.
I suspect the problem is with WebPack or Babel configuration.
Webpack common config:
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require("copy-webpack-plugin");
module.exports = {
// Where webpack looks to start building the bundle
entry: [
'core-js/modules/es6.promise',
'core-js/modules/es6.array.iterator',
'./src/main.js',
],
target: 'web',
// Where webpack outputs the assets and bundles
output: {
path: path.resolve(__dirname, 'dist'),
assetModuleFilename: '[name].[contenthash].[ext]',
filename: '[name].[contenthash].bundle.js',
chunkFilename: '[id].[chunkhash].bundle.js',
// publicPath: '/',
},
// Determine how modules within the project are treated
module: {
rules: [
{
test: /\.(gif|png|jpe?g)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'assets/images/'
}
}
]
},
// JavaScript: Use Babel to transpile JavaScript files
{ test: /\.js$/, use: ['babel-loader'] },
// Images: Copy image files to build folder
{ test: /\.(?:ico|gif|png|jpg|jpeg)$/i, type: 'asset/resource' },
// Fonts and SVGs: Inline files
{ test: /\.(woff(2)?|eot|ttf|otf|svg|)$/, type: 'asset/inline' },
],
},
// Customize the webpack build process
plugins: [
// Generates an HTML file from a template
new HtmlWebpackPlugin({
// template: path.resolve(__dirname, 'src/index.html'), // шаблон
template: 'src/index.html',
// filename: 'index.html', // название выходного файла
// inject: false, // true, 'head'
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true
},
// chunks: 'all',
// excludeChunks: [],
}),
new CopyWebpackPlugin(
{
patterns: [
{ from: 'src/assets', to: 'assets' },
// { from: 'src/components', to: 'components' },
]
}
),
],
resolve: {
// modules: [path.resolve(__dirname, 'src'), 'node_modules'],
extensions: ['.js', '.jsx', '.json', '.ts'],
alias: {
'#': [
path.resolve(__dirname, 'src'),
'./src/main.js'
],
},
},
}
Babel config
module.exports = {
presets: [
[
'#babel/preset-env',
{
targets: {
esmodules: true,
},
},
],
],
plugins: [
'#babel/plugin-proposal-class-properties',
'#babel/plugin-transform-runtime',
"#babel/plugin-syntax-dynamic-import"
]
};
I've used require-from-string before, which internally uses the native Module module to achieve this.
Speaking generally, Blobs don't really interoperate well between browser and node land, and modules are also not treated identically.. so it's not a surprise that blob+module has problems. :)

Webpack Build Speed performance

The Webpack Bulid runs pretty slow, project background is a community portal developed in Vue.js...
Can anybody tell if there is any potential for improvement and if so, what?
I wonder if the time for the build process of 37401ms can still be changed by changing the codebase?
const path = require('path');
const fs = require('fs');
const webpack = require('webpack');
const glob = require('glob');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const autoprefixer = require('autoprefixer');
const statsSettings = {
all: false,
modules: true,
maxModules: 0,
errors: true,
warnings: false,
moduleTrace: true,
errorDetails: true,
timings: true,
performance: true,
builtAt: true,
};
const {
rootDir,
srcDir,
assetsDir,
stylesDir,
buildDir,
sitepackageDir,
publicPath,
} = require('./config');
const chunks = glob.sync(path.join(rootDir, srcDir, 'pages/**/index.js'))
.reduce((obj, file) => {
const name = path.basename(path.dirname(file));
return {
...obj,
[name]: file,
};
}, {});
module.exports = env => {
return {
mode: env.production ? 'production' : 'development',
context: path.join(rootDir, srcDir),
entry: chunks,
output: {
path: path.join(rootDir, buildDir),
filename: '[name].js',
publicPath: env.production ? publicPath : '',
},
devtool: env.production ? false : 'cheap-module-eval-source-map',
devServer: {
contentBase: path.join(rootDir, buildDir),
inline: true,
proxy: {
'/api/v0': 'http://localhost:4000',
},
},
watchOptions: {
ignored: env.watch ? 'node_modules' : '',
aggregateTimeout: 300,
},
stats: env.watch ? statsSettings : 'normal',
module: {
rules: [
{
test: /\.vue$/,
include: [
path.join(rootDir, srcDir),
require.resolve('bootstrap-vue'),
],
loader: 'vue-loader',
},
{
test: /\.js$/,
include: [
path.join(rootDir, srcDir),
require.resolve('bootstrap-vue'),
],
loader: 'babel-loader',
},
{
test: /\.(css|scss)$/,
use: [
env.production ? MiniCssExtractPlugin.loader : 'vue-style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [
autoprefixer({
browsers: ['>1%', 'last 2 versions', 'not ie < 11'],
}),
],
},
},
{
loader: 'sass-loader',
options: {
includePaths: [
path.join(rootDir, srcDir, stylesDir),
],
},
},
],
},
{
test: /\.(jpg|jpeg|png|gif|webp|svg|eot|otf|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$/,
loader: 'file-loader',
options: {
name: `${assetsDir}/_processed_/[name].[hash:4].[ext]`,
},
},
],
},
optimization: {
runtimeChunk: {
name: '_runtime',
},
splitChunks: {
cacheGroups: {
// we need to figure out if it's worth having a common chunk
// or if each entry chunk should be somewhat self-contained
common: {
chunks: 'initial',
name: '_common',
minChunks: 2,
minSize: 0,
},
vendor: {
test: /node_modules/,
chunks: 'initial',
name: '_vendor',
enforce: true,
},
},
},
},
resolve: {
extensions: ['.js', '.json', '.vue'],
alias: {
'#app': path.join(rootDir, srcDir),
// although we're using single file components that can be pre-compiled,
// we want to dynamically mounting them in the DOM html.
// this is way we need to alias 'vue' to use the runtime + compiler build here.
// see: https://vuejs.org/v2/guide/installation.html#Runtime-Compiler-vs-Runtime-only
vue$: 'vue/dist/vue.esm.js',
},
},
plugins: [
new webpack.DefinePlugin({
PAGES: JSON.stringify(Object.keys(chunks)),
}),
new VueLoaderPlugin(),
...plugHtmlTemplates(),
...plugExtractCss(env),
...plugCopyAssets(),
],
};
};
function plugHtmlTemplates () {
return glob.sync(path.join(rootDir, srcDir, 'pages/**/template.html'))
.map(template => {
const name = path.basename(path.dirname(template));
return {
template,
filename: `${name}.html`,
chunks: ['_runtime', '_vendor', '_styles', '_common', name],
};
})
.map(htmlConfig => new HtmlWebpackPlugin(htmlConfig));
}
function plugExtractCss (env) {
if (!env.production) return [];
return [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].css',
}),
];
}
function plugCopyAssets () {
const assetsSrcPath = path.join(rootDir, srcDir, assetsDir);
if (!fs.existsSync(assetsSrcPath)) return [];
return [
new CopyWebpackPlugin([
{ from: assetsSrcPath, to: path.join(rootDir, buildDir, path.basename(assetsDir)) },
// this is required for the icons to be selectable in the backend
{ from: path.join(assetsSrcPath, 'icons/'), to: path.join(rootDir, sitepackageDir, 'Resources/Public/Icons/') },
// this is required for avatars to be available; we must not check them in in fileadmin since they would
// prevent having the dir linked by Deployer during a deployment
{ from: path.join(rootDir, '../packages/users/Resources/Private/Images/Avatars/'), to: path.join(rootDir, buildDir, 'static/avatars/') },
]),
];
}
The question is whether you can improve the performance by using different plugins or summarizing steps...
I have a similar configuration. My configuration built ~33 seconds. I added a cache-loader package and decrease build time to ~17 seconds.
npm install --save-dev cache-loader
config:
rules: [
//rules here
{
test: /\.vue$/,
use: [
{
loader: 'cache-loader',
options: {}
},
{
loader: 'vue-loader',
options: vueLoaderConfig
}
],
},
{
test: /\.js$/,
use: [
{
loader: 'cache-loader',
options: {}
},
{
loader: 'babel-loader'
}
],
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
}
]

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

How can I stop HTML being output to the compiled app.js?

I am using Webpack 2 for my portfolio website, but it's not an SPA - therefore, the intention is not to output everything into my bundled JS.
I have a few entry points for the .pug, .scss and .js files. Like so:
entry: {
app: [
path.resolve(__dirname, 'src/pug/app.pug'),
path.resolve(__dirname, 'src/js/app.js'),
path.resolve(__dirname, 'src/scss/app.scss')
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].js'
}
However, when looking at the source for the app.js, I see the rendered HTML from my .pug files.
For the .pug compiling, I'm using HtmlWebpackPlugin. I guess the easiest way for me to explain what's going on is to show you the webpack.config.babel.js file:
import webpack from 'webpack';
import path from 'path';
import autoprefixer from 'autoprefixer';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import SvgStorePlugin from 'external-svg-sprite-loader/lib/SvgStorePlugin';
import bourbon from 'bourbon';
import neat from 'bourbon-neat';
const extractScss = new ExtractTextPlugin({
filename: 'css/[name].css',
allChunks: true
});
const config = {
entry: {
app: [
path.resolve(__dirname, 'src/pug/app.pug'),
path.resolve(__dirname, 'src/js/app.js'),
path.resolve(__dirname, 'src/scss/app.scss')
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].js'
},
module: {
rules: [
{
test: /\.pug$/,
use: [
{
loader: 'html-loader'
},
{
loader: 'pug-html-loader',
options: {
pretty: false,
exports: false
}
}
]
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
['es2015']
]
}
}
]
},
{
test: /\.(jpe?g|png|gif)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '/images/[name].[ext]'
}
},
{
loader: 'image-webpack-loader',
options: {
progressive: false,
optipng: {
optimizationLevel: 7,
quality: '90',
speed: 5
},
mozjpeg: {
quality: 90
},
gifsicle: {
interlaced: false
}
}
}
]
},
{
test: /\.svg$/,
use: [
{
loader: 'external-svg-sprite-loader',
query: {
name: 'svg/sprite.svg',
iconName: '[name]'
}
}
]
},
{
test: /\.scss$/,
use: extractScss.extract([
{
loader: 'css-loader'
},
{
loader: 'postcss-loader',
options: {
plugins() {
return [
autoprefixer({
browsers: ['last 2 versions', 'Explorer >= 9', 'Android >= 4']
})
];
}
}
},
{
loader: 'sass-loader',
options: {
includePaths: [
bourbon.includePaths,
neat.includePaths
]
}
}
])
}
]
},
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
stats: 'errors-only',
open: false
},
plugins: [
new SvgStorePlugin(),
new HtmlWebpackPlugin({
title: 'Portfolio',
minify: {
collapseWhitespace: true
},
hash: true,
template: 'src/pug/app.pug',
filetype: 'pug',
filename: 'index.html'
}),
extractScss
]
}
process.traceDeprecation = false;
export default config;
I don't see any CSS in the app.js bundle and the entry point is setup just the same, so might it have something to do with the HtmlWebpackPlugin itself? Perhaps I'm not understanding how this works correctly and my configuration is wrong.
I'm new to Webpack (coming from Gulp), so please bear with me if the answers to my questions seem rather obvious.
Thanks for your help.
Update:
For reference, my project structure is as follows:
And I would call an image from my .pug file in /pug/components/example.pug with a path like img(src="../images/example.jpg"). This worked prior to removing .pug as an entry point in the Webpack config as user Tatsuyuki suggested below.
Do not add the template as an source:
app: [
// path.resolve(__dirname, 'src/pug/app.pug'),
path.resolve(__dirname, 'src/js/app.js'),
path.resolve(__dirname, 'src/scss/app.scss')
]
Instead, specify the template option for HtmlWebpackPlugin:
new HtmlWebpackPlugin({
template: 'src/index.pug'
})
Reference

Categories

Resources