Vue Webpack Build Breaks Bulma CSS - javascript

I scaffold a webpack template for my vue project using the vue-cli. Subsequently, I added Bulma's CSS. When I run the program normally (npm run dev), the CSS renders properly. However, upon building the project for production (npm run build) with the default webpack configuration, the styling of the web app is now misaligned. Does anyone know how to resolve this issue?
For comparison:
Production Build of Vue with broken CSS
Dev version of Vue rendering CSS properly
The following is my webpack config (webpack.prod.conf.js)
var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var CopyWebpackPlugin = require('copy-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var env = config.build.env
var webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
if (config.build.productionGzip) {
var CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig

I was facing the same issue as yours. After some debugging, I realize that this is due to the order of css rules. It seems that the built css file doesn't have the same rules order as the dev server.
I searched a little bit on this topic. On the webpack documentation I have found this:
Keep in mind that it’s difficult to manage the execution order of modules, so design your stylesheets so that order doesn’t matter. (You can, however, rely on the order within a given CSS file.)
I solved my problem by increasing the specificity of my custom css selectors by using #id instead of .class.
ex:
// my custom css rules
#navbar a {
color: white
}
instead of:
// my custom css rules
.navbar a {
color: white
}
Thus the order of your custom rules versus the bulma ones won't matter because the priority will be always for the id over the class selector.
I hope this is useful

Related

Webpack dynamic import each imported module

This may be an x/y question, so here goes!
Background:
I'm trying to run a comparison of two versions of a JS library to measure the benefits of its side-effect free tree-shaking modules.
My plan was to make two .html pages, one with old.js, and another importing specific modules (i.e. import {mod1, mod2} from "new.js")
Webpack Chunk Names
Ideally, I'd like each individual module to be placed into its own chunk so I can document how much each module "weighs".
I see webpack has an option to add /* webpackChunkName: "my-chunk-name" */ inside of an import.
Question:
Is it possible to dynamically import an individual property/module while specifying its name to generate its own chunk?
I've tried using this code below, but it combines them into a single chunk based on the first mod1 chunkname.
document.getElementById('mod1').onclick = function () {
import(/* webpackChunkName: "mod1" */ 'new.js').then(
(lib) => {
lib.mod1()
}
);
};
document.getElementById('mod2')!.onclick = function () {
import(/* webpackChunkName: "mod2" */ 'new.js').then(
(lib) => {
lib.mod2()
}
);
};
webpack.config.js
// Generated using webpack-cli https://github.com/webpack/webpack-cli
import { Configuration } from 'webpack';
import 'webpack-dev-server';
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const isProduction = process.env.NODE_ENV == 'production';
const config = {
// An entry point is the root JS file associated with a HTML route
entry: {
old: './src/old.ts',
new: './src/new.ts',
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
},
devServer: {
open: false,
host: 'localhost',
},
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
// get the name. E.g. node_modules/packageName/not/this/part.js
// or node_modules/packageName
const packageName = module.context.match(
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
)[1];
// npm package names are URL-safe, but some servers don't like # symbols
return `npm.${packageName.replace('#', '')}`;
},
},
},
},
},
plugins: [
new HtmlWebpackPlugin({
// output name (URL path)
filename: 'old.html',
// the template property to the HTML template
template: path.resolve(__dirname, 'src', 'old.html'),
// associate it with one or more of the entry points with the chunks property.
chunks: ['old'],
}),
new HtmlWebpackPlugin({
// output name (URL path)
filename: 'new.html',
// the template property to the HTML template
template: path.resolve(__dirname, 'src', 'new.html'),
// associate it with one or more of the entry points with the chunks property.
chunks: ['new'],
}),
new HtmlWebpackPlugin({
// output name (URL path)
filename: 'index.html',
// the template property to the HTML template
template: path.resolve(__dirname, 'index.html'),
// associate it with one or more of the entry points with the chunks property.
chunks: [],
}),
// Add your plugins here
// Learn more about plugins from https://webpack.js.org/configuration/plugins/
],
performance: {
hints: false,
},
module: {
rules: [
{
test: /\.(ts|tsx)$/i,
loader: 'ts-loader',
exclude: ['/node_modules/'],
},
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
type: 'asset',
},
// Add your rules for custom modules here
// Learn more about loaders from https://webpack.js.org/loaders/
],
},
resolve: {
extensions: ['.ts', '.js'],
},
experiments: {
topLevelAwait: true,
},
};
module.exports = () => {
if (isProduction) {
config.mode = 'production';
} else {
config.mode = 'development';
}
return config;
};
I think the reason webpack combines the same module(new.js) into a single chunk is because MergeDuplicateChunksPlugin is used.
Its functionality is very well described by its name and in this situation it can be seen in action: the mod1 and mod2 chunks are using the same new.js module, so they're fundamentally the same.
Fortunately, this plugin is behind a flag and it can be deactivated by modifying your configuration as follows:
config = {
/* ... */
optimization: {
mergeDuplicateChunks: false,
},
/* ... */
}
With the above configuration, you should now see the new.js module being duplicated in two different chunks - mod1 and mod2.

How can I set webpack to translate asset paths to another path?

I have a Vue.js app, which is going to be used as a theme for a WordPress site, and wordpress serves the assets on a diffrent path than Vue.js would expect, this is the path that would work normally:
<img src="src/assets/images/image.svg"/>
But this is what works:
<img src="/wp-content/themes/theme-name/src/assets/images/image.svg"/>
So my question is that how would I go about this? Since I have almost null knowledge about how wordpress operates, except for the REST API part, what could I do. I would like WebPack to translate these paths if possible, and i have tried the publicPath: '/wp-content/pathtoassets' trick, but it does not seem to work. Also if this is not possible with WebPack, can i do something in php to serve this path as a global variable to my Vue theme?
Here is my vue.config.js:
const {
WebpackManifestPlugin
} = require('webpack-manifest-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
const gifsicle = require('imagemin-gifsicle');
const jpegtran = require('imagemin-jpegtran');
const optipng = require('imagemin-optipng');
module.exports = {
runtimeCompiler: true,
// filenameHashing: false,
productionSourceMap: false,
chainWebpack: config => {
config.plugins.delete('preload');
config.plugins.delete('prefetch');
config.module.rule('images').use('url-loader');
config.module.rule('svg').use('file-loader');
},
configureWebpack: config => {
config.output.filename = 'scripts/[name].js';
config.output.chunkFilename = 'scripts/[name].js';
config.plugins = [
...config.plugins,
new CopyWebpackPlugin({
patterns: [{
from: 'src/assets/fonts',
to: 'fonts/[name].[ext]',
},
{
from: 'src/assets/images',
to: 'images/[name].[ext]',
},
],
}),
new ImageMinimizerPlugin({
severityError: 'warning',
minimizerOptions: {
plugins: [
[gifsicle, {
interlaced: true
}],
[jpegtran, {
progressive: true
}],
[optipng, {
optimizationLevel: 5
}],
],
},
}),
new WebpackManifestPlugin({
fileName: 'manifest.json',
basePath: '/dist/',
}),
];
},
css: {
extract: {
filename: 'styles/[name].css',
chunkFilename: 'styles/[name].css',
},
},
};
Also I, have been able to use the manifest.json to enqueue my scripts and styles, i just dont know how to make these assets like images or fonts work like they should.
Furthermore, if anyone who sees this and has worked with Vue.js as a WordPress theme, and has any suggestions to what should/could do more to help me along the way, i would appreciate it real much!
Thank you in advance!
Welp, it turns out it's as easy as adding publicPatch property to your loaders, like:
config.module
.rule('svg')
.use('file-loader')
.options({
name: 'images/[name].[ext]',
publicPath,
});
The publicPath property being the following:
const [, themeName] = __dirname.match(/\/wp-content\/themes\/([^/]+)/);
const publicPath = isProduction ? `/wp-content/themes/${themeName}/dist/` : '/';
Which will compile down to my .svg's being resolved to this path:
/wp-content/themes/${themeName}/dist/images/[name].[ext]

Webpack - Excluding node_modules with also keep a separated browser and server management

(webpack.config.js file content below)
I'm trying to make a webpack exclusion on node modules.
I found that using webpack-node-externals works for it but using that on my common config causes this other error:
Require is not defined on reflect-metadata - __webpack_require__ issue
So... I was wondering how can i exclude webpack bundling also on the browser side without getting any issue.
My webpack version: 3.11.0
webpack-config.js
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const AotPlugin = require('#ngtools/webpack').AotPlugin;
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
var nodeExternals = require('webpack-node-externals');
module.exports = (env) => {
// Configuration in common to both client-side and server-side bundles
const isDevBuild = !(env && env.prod);
const sharedConfig = {
//externals: [nodeExternals()], // in order to ignore all modules in node_modules folder
stats: { modules: false },
context: __dirname,
resolve: { extensions: [ '.js', '.ts' ] },
output: {
filename: '[name].js',
publicPath: 'dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
module: {
rules: [
{ test: /\.ts$/, use: isDevBuild ? ['awesome-typescript-loader?silent=true', 'angular2-template-loader', 'angular2-router-loader'] : '#ngtools/webpack' },
{ test: /\.html$/, use: 'html-loader?minimize=false' },
{ test: /\.css$/, use: [ 'to-string-loader', 'style-loader', isDevBuild ? 'css-loader' : 'css-loader?minimize' ] },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
]
},
plugins: [new CheckerPlugin()]
};
// Configuration for client-side bundle suitable for running in browsers
const clientBundleOutputDir = './wwwroot/dist';
const clientBundleConfig = merge(sharedConfig, {
entry: { 'main-client': './ClientApp/boot.browser.ts' },
output: { path: path.join(__dirname, clientBundleOutputDir) },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(clientBundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin(),
new AotPlugin({
tsConfigPath: './tsconfig.json',
entryModule: path.join(__dirname, 'ClientApp/app/app.browser.module#AppModule'),
exclude: ['./**/*.server.ts']
})
])
});
// Configuration for server-side (prerendering) bundle suitable for running in Node
const serverBundleConfig = merge(sharedConfig, {
resolve: { mainFields: ['main'] },
entry: { 'main-server': './ClientApp/boot.server.ts' },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./ClientApp/dist/vendor-manifest.json'),
sourceType: 'commonjs2',
name: './vendor'
})
].concat(isDevBuild ? [] : [
// Plugins that apply in production builds only
new AotPlugin({
tsConfigPath: './tsconfig.json',
entryModule: path.join(__dirname, 'ClientApp/app/app.server.module#AppModule'),
exclude: ['./**/*.browser.ts']
})
]),
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, './ClientApp/dist')
},
target: 'node',
externals: [nodeExternals()], // in order to ignore all modules in node_modules folder,
devtool: 'inline-source-map'
});
return [clientBundleConfig, serverBundleConfig];
};
GOT IT!
Before posting my solution, I'd like to thanks Aluan Haddad for his useful comment in my question above.
As suggested by Aluan, in fact, the problem was related to the need to use also a module loader, more than a module bundler.
So, the steps that I followed are these:
Installing requireJS ==> http://requirejs.org/docs/node.html
Removing externals: [nodeExternals()], // in order to ignore all modules in node_modules folder from my common webpack configuration and adding it under my server configuration (done before my question, but it's a really important step) [see webpack.config.js content in the question]
Adding target: 'node', before my externals point above, under my server side section (done before my question, but it's a really important step) [see webpack.config.js content in the question]
This makes sure that browser side keeps target:'web' (default target), and target becomes node just for the server.
launched webpack config vendor command manually from powershell webpack --config webpack.config.vendor.js
launched webpack config command manually from powershell webpack --config webpack.config.js
That worked for me! Hope It will works also for anyone else reading this question and encountering this issue!

babel-plugin-react-css-modules aren't outputting styles

I'm using babel-plugin-react-css-modules and it seems to be creating the className/styleName, but for some reason it isn't outputting the CSS so that it can be read by the module.
Here's my application: launchpad
I'm using webpack 4.2.0/React 16 and use a babel configuration in the webpack.config imported from a separate config file as you can't set context in .babelrc.
here's the babel config:
{
loader: 'babel-loader',
options: {
'plugins': [
'react-hot-loader/babel',
'transform-runtime',
'transform-object-rest-spread',
'transform-class-properties',
['react-css-modules',
{
context: paths.javascript,
'generateScopedName': '[name]__[local]___[hash:base64:5]',
'filetypes': {
'.scss': {
'syntax': 'postcss-scss',
'plugins': ['postcss-nested']
}
},
'exclude': 'node_modules',
'webpackHotModuleReloading': true
}
]
],
'presets': [
'env',
'react',
'flow'
],
'env': {
'production': {
'presets': ['react-optimize']
}
}
}
}
And here is my webpack:
const webpack = require('webpack')
const path = require('path')
const paths = require('./webpack/config').paths
const outputFiles = require('./webpack/config').outputFiles
const rules = require('./webpack/config').rules
const plugins = require('./webpack/config').plugins
const resolve = require('./webpack/config').resolve
const IS_PRODUCTION = require('./webpack/config').IS_PRODUCTION
const IS_DEVELOPMENT = require('./webpack/config').IS_DEVELOPMENT
const devServer = require('./webpack/dev-server').devServer
const DashboardPlugin = require('webpack-dashboard/plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// Default client app entry file
const entry = [
'bootstrap-loader/extractStyles',
path.join(paths.javascript, 'index.js')
]
plugins.push(
// Creates vendor chunk from modules coming from node_modules folder
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: outputFiles.vendor,
minChunks (module) {
const context = module.context
return context && context.indexOf('node_modules') >= 0
}
}),
// Builds index.html from template
new HtmlWebpackPlugin({
template: path.join(paths.source, 'index.html'),
path: paths.build,
filename: 'index.html',
minify: {
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true,
removeComments: true,
useShortDoctype: true
}
})
)
if (IS_DEVELOPMENT) {
// Development plugins
plugins.push(
// Enables HMR
new webpack.HotModuleReplacementPlugin(),
// Don't emmit build when there was an error while compiling
// No assets are emitted that include errors
new webpack.NoEmitOnErrorsPlugin(),
// Webpack dashboard plugin
new DashboardPlugin()
)
// In development we add 'react-hot-loader' for .js/.jsx files
// Check rules in config.js
rules[0].use.unshift('react-hot-loader/webpack')
entry.unshift('react-hot-loader/patch')
}
// Webpack config
module.exports = {
devtool: IS_PRODUCTION ? false : 'eval-source-map',
context: paths.javascript,
watch: IS_DEVELOPMENT,
entry,
output: {
path: paths.build,
publicPath: '/',
filename: outputFiles.client
},
module: {
rules
},
resolve,
plugins,
devServer
}
If you run npm start or yarn start the code will compile and run, and you can see a basic application layout. If you inspect the react code you will see that the style name is reflected in the component. What doesn't happen is that the style isn't being output. I can't seem to figure out why.
Any help is appreciated.

webpack-bundle-analyzer shows webpack -p does not remove development dependency react-dom.development.js

This is my webpack setup
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const SOURCE_DIR = './src';
const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
template: SOURCE_DIR + '/index.html',
filename: 'index.html',
inject: 'body'
});
module.exports = env => {
console.log(`Environment Configs: ${JSON.stringify(env) || 'Default'}`);
console.log(`
Available Configs:
--env.watch = true / false //for allow webpack to watch build
`)
let environment = env || {};
const {
watch,
analyze,
} = environment;
const configedAnalyzer = new BundleAnalyzerPlugin({
// Can be `server`, `static` or `disabled`.
// In `server` mode analyzer will start HTTP server to show bundle report.
// In `static` mode single HTML file with bundle report will be generated.
// In `disabled` mode you can use this plugin to just generate Webpack Stats JSON file by setting `generateStatsFile` to `true`.
analyzerMode: 'static',//was server
// Host that will be used in `server` mode to start HTTP server.
analyzerHost: '127.0.0.1',
// Port that will be used in `server` mode to start HTTP server.
analyzerPort: 9124,
// Path to bundle report file that will be generated in `static` mode.
// Relative to bundles output directory.
reportFilename: './../report/bundle_anlaysis.html',
// Module sizes to show in report by default.
// Should be one of `stat`, `parsed` or `gzip`.
// See "Definitions" section for more information.
defaultSizes: 'stat',
// Automatically open report in default browser
openAnalyzer: Boolean(analyze),
// If `true`, Webpack Stats JSON file will be generated in bundles output directory
generateStatsFile: Boolean(analyze),
// Name of Webpack Stats JSON file that will be generated if `generateStatsFile` is `true`.
// Relative to bundles output directory.
statsFilename: 'stats.json',
// Options for `stats.toJson()` method.
// For example you can exclude sources of your modules from stats file with `source: false` option.
// See more options here: https://github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21
statsOptions: null,
// Log level. Can be 'info', 'warn', 'error' or 'silent'.
logLevel: 'info'
});
return {
entry: SOURCE_DIR + '/index.js',
output: {
path: path.resolve(__dirname, "dist"),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'react']
}
}
}
]
},
watchOptions: {
aggregateTimeout: 300,
poll: 1000
},
watch: Boolean(watch),
plugins: [HtmlWebpackPluginConfig, configedAnalyzer], //
devServer: {
contentBase: path.join(__dirname, "dist"),
compress: false,
port: 9123,
}
};
}
When I do webpack -p file size is a lot smaller but this react-dom.development.js take over almost 50% of the size, in my case 500ish KB out of 1.1ish MB.
Report here:
To see a demo of the report and how it got run you can check this repository.
NOTE: even I add NODE_ENV=production, size is smaller but the development JavaScript file is still there!
Your application's process.env.NODE_ENV variable needs to be set to production within webpack's build script. React's Optimizing Performance documentation instructs webpack users to do this using webpack's DefinePlugin.
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
While it seems that the -p option should set process.env.NODE_ENV to production, there is a caveat explained in Webpack's Specifying the Environment documentation that this is not set withtin webpack's build script (for what its worth, many developers have reported this as an issue).

Categories