I've integrated PostCSS into Webpack, using a separate postcss.config.js file.
I want to enable cssnano when doing production builds, and disable it for development builds. How can I do this?
Here is my webpack.config.js
const webpack = require('webpack');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const path = require('path');
module.exports = (env, argv) =>
{
//const isProduction = (process.env.WEBPACK_MODE === 'production')
const isProduction = argv.mode === 'production' || process.env.NODE_ENV === 'production';
const config = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js'
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
// fallback to style-loader in development
//process.env.NODE_ENV !== 'production' ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: MiniCssExtractPlugin.loader,
options: {
sourceMap: true
}
},
//'css-loader?-url',
{
loader: 'css-loader',
options: {
minimize: true,
sourceMap: true,
url: false
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
},
{
loader: 'resolve-url-loader',
options: {
sourceMap: true,
debug: false
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
outputStyle: 'compressed',
sourceMapContents: false
}
}
]
}
]
},
plugins: [
new CleanWebpackPlugin('dist', {
watch: true
}),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: isProduction ? "live.[contenthash].css" : "live.css",
chunkFilename: "[name].[contenthash].css"
}),
new ManifestPlugin()
]
}
return config;
}
Here is my postcss.config.js
module.exports = {
plugins: [
require('postcss-import'),
require('postcss-url'),
require('autoprefixer'),
require('cssnano')({
preset: 'default',
})
]
}
Secondly, is a separate postcss.config.js recommended? I see some examples where the PostCSS plugins are defined in webpack.config.js, and others where it's all done in the separate file.
Option 1. Use webpack merge
With webpack-merge, you can create conditional configurations based on the NODE_ENV and merge them in one single configuration at execution time, the advantage is that you don't create duplication of code and everything can be done in one file, the only disadvantage is using a new package.
const ENV = process.env.NODE_ENV
let Config = {
//Other shared configurations by production and development
plugins: [
new webpack.ProgressPlugin(),
new CopyWebpackPlugin([
{ from: 'public', to: 'public' },
])
]
}
if (ENV === 'production') {
Config = merge(Config, {
plugins: [
new MiniCssExtractPlugin({
filename: "public/styles.css"
}),
new OptimizeCSSAssetsPlugin({
cssProcessor: cssnano,
cssProcessorOptions: {
parser: safe,
discardComments: { removeAll: true }
}
})
],
mode: 'production'
})
}
if (ENV === 'development') {
Config = merge(Config, {
devtool: 'source-map',
mode: 'development',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new StyleLintPlugin(
{
emitErrors: false,
configFile: '.stylelintrc',
context: 'src',
files: '**/*.pcss',
},
),
]
})
}
const WebpackConfig = Config
Option 2. Use different configurations
Two separated files webpack.config.prod.js and webpack.config.dev.js can be created and call them with different npm scripts. The problem with this solution is that there is a duplication of code.
In your postcss.config.js you can do checking for the environment property:
module.exports = ({ env }) => ({
plugins: [
require('postcss-import')(),
require('postcss-url')(),
require('autoprefixer')(),
env === 'production' ? require('cssnano')({preset: 'default' })() : false,
]
})
Related
I've never used Webpack before and I'm working on a project that's just vanilla JS and HTML. I'm having an issue accessing the values I set in .env. Here's my config.
const path = require("path");
const dotenv = require('dotenv');
var webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const Dotenv = require('dotenv-webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = () => {
env = dotenv.config().parsed;
const envKeys = Object.keys(env).reduce((prev, next) => {
prev[`process.env.${next}`] = JSON.stringify(env[next]);
return prev;
}, {});
return {
entry: {
main: './src/index.js'
},
output: {
path: path.join(__dirname, '../build'),
filename: '[name].bundle.js'
},
mode: 'development',
devServer: {
contentBase: "./src/",
publicPath: "./src/",
compress: true,
port: 9000,
overlay: true,
disableHostCheck: true
},
devtool: 'inline-source-map',
resolve: {
alias: {
process: "process/browser"
}},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|svg|jpe?g|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'assets/'
}
}
]
},
{
test: /\.html$/,
use: {
loader: 'html-loader',
options: {
//attributes: ['img:src', ':data-src'],
minimize: true
}
}
}
]
},
plugins: [
new webpack.ProvidePlugin({
process: 'process/browser',
}),
new Dotenv(),
new webpack.DefinePlugin(envKeys),
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
}),
]
}
};
As you can see, I'm using dotEnv, definePlugin, and even the dotEnv-webpack plugin. Unfortunately, none of these solutions seem to allow me to access process.env.originURL.
originURL=https://localhost:3000
I'm not importing or requiring anything in my javascript file, index.js. I'm assuming this should work, but the console tells me that process is undefined.
console.log(process.env.originURL);
How can I access process.env.originURL in my index.js?
It should work. Maybe you could try this version:
new Dotenv({ systemvars: true })
If it still doesn't work, please show your package.json, as well as you .env file (randomize the values of course). Is .env at the root of you app?
I am using webpack and typescript in my SPA along with the oidc-client npm package.
which has a structure like this:
oidc-client.d.ts
oidc-client.js
oidc-client.rsa256.js
When I import the oidc-client in my typescript file as below:
import oidc from 'oidc-client';
I want to import the rsa256 version and not the standard version, but I am unsure how to do this.
in my webpack config I have tried to use the resolve function but not I am not sure how to use this properly:
const path = require('path');
const ForkTsChecker = require('fork-ts-checker-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const FileManagerPlugin = require('filemanager-webpack-plugin');
const shell = require('shelljs');
const outputFolder = 'dist';
const destinationFolder = '../SPA/content';
if (shell.test('-d', outputFolder)) {
shell.rm('-rf', `${outputFolder}`);
}
if (shell.test('-d', destinationFolder)) {
shell.rm('-rf', `${destinationFolder}`);
}
module.exports = (env, options) => {
//////////////////////////////////////
/////////// HELP /////////////////////
/////////////////////////////////////
resolve: {
oidc = path.resolve(__dirname, 'node_modules\\oidc-client\\dist\\oidc- client.rsa256.slim.min.js')
};
const legacy = GenerateConfig(options.mode, "legacy", { "browsers": "> 0.5%, IE 11" });
return [legacy];
}
function GenerateConfig(mode, name, targets) {
return {
entry: `./src/typescript/main.${name}.ts`,
output: {
filename: `main.${name}.js`,
path: path.resolve(__dirname, `${outputFolder}`),
publicPath: '/'
},
resolve: {
extensions: ['.js', '.ts']
},
stats: {
children: false
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.ts\.html?$/i,
loader: 'vue-template-loader',
},
{
test: /\.vue$/i,
loader: 'vue-loader'
},
{
test: /\.ts$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'#babel/env',
{
"useBuiltIns": "usage",
"corejs": { version: 3, proposals: true },
"targets": targets
}
],
"#babel/typescript"
],
plugins: [
"#babel/transform-typescript",
[
"#babel/proposal-decorators",
{
"legacy": true
}
],
"#babel/plugin-proposal-optional-chaining",
"#babel/plugin-proposal-nullish-coalescing-operator",
"#babel/proposal-class-properties",
"#babel/proposal-numeric-separator",
"babel-plugin-lodash",
]
}
}
}
]
},
devServer: {
contentBase: path.join(__dirname, outputFolder),
compress: true,
hot: true,
index: `index.${name}.html`,
open: 'chrome'
},
plugins: [
new ForkTsChecker({
vue: true
}),
new HtmlWebpackPlugin({
filename: `index.${name}.html`,
template: 'src/index.html',
title: 'Project Name'
}),
new MiniCssExtractPlugin({ filename: 'app.css', hot: true }),
new VueLoaderPlugin()
]
};
}
Please add an alias to specify the exact .js module you'd like to import, like this for example:
resolve: {
extensions: ['.ts','.js'],
modules: ['node_modules'],
alias: {
'oidc-client': 'oidc-client-js/dist/oidc-client.rsa256.slim'
}
}
If you don't specify an alias, webpack/ will follow node module resolution and we'll eventually find your module by going to
library oidc-client-js in node_modules and follow package.json main property which is "lib/oidc-client.min.js" and that's not what you want.
I've got webpack up and running and can't quite figure our how to get .scss file compiled to be used with custom styling such as colors etc. Bootstrap is otherwise loaded fine.
Here's my webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = (env = {}, argv = {}) => {
const isProd = argv.mode === 'production';
const config = {
mode: argv.mode || 'development', // we default to development when no 'mode' arg is passed
optimization: {
minimize: true
},
entry: {
main: './src/main.js'
},
output: {
filename: isProd ? 'bundle-[chunkHash].js' : '[name].js',
path: path.resolve(__dirname, '../wwwroot/dist'),
publicPath: "/dist/"
},
plugins: [
new MiniCssExtractPlugin({
filename: isProd ? 'style-[contenthash].css' : 'style.css'
}),
new CompressionPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: /\.js$|\.css$|\.html$|\.eot?.+$|\.ttf?.+$|\.woff?.+$|\.svg?.+$/,
threshold: 10240,
minRatio: 0.8
}),
new HtmlWebpackPlugin({
template: '_LayoutTemplate.cshtml',
filename: '../../Views/Shared/_Layout.cshtml', //the output root here is /wwwroot/dist so we ../../
inject: false
})
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader',
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
},
{
test: /\.(png|jpg|gif|woff|woff2|eot|ttf|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[hash].[ext]',
outputPath: 'assets/'
}
}
]
}
};
return config;
};
I have node-sass installed which I think compiles it? Just not sure how to get it going.
Thanks,
You need to import your CSS into your entrypoint. So in main.js, import your scss file like this...
import './path-to/file.scss
My project is using 3 webpack files (one common, and two for dev/prod env) and .babelrc file for babel config
.babelrc
{
"presets": ["#babel/preset-env"],
"plugins": ["angularjs-annotate"]
}
webpack.common.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const webpack = require("webpack");
const WebpackBar = require('webpackbar');
const pathPublic = "/public/path/";
const pathFonts = "/fonts/";
module.exports = {
devtool: "eval",
entry: {
app: "./src/js/app.index.js",
},
module: {
rules: [{
test: /\.(gif|png|jpe?g)$/i,
use: ["file-loader"],
}, {
test: /\.(woff|woff2|ttf|eot|svg|otf)(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: 'file-loader',
options: {
publicPath: pathPublic + pathFonts,
name: "[name].[ext]",
outputPath: pathFonts,
chunks: true
}
}]
}, {
test: /\.css$/,
use: [
{loader: "style-loader"},
{loader: MiniCssExtractPlugin.loader},
{loader: "css-loader"}
]
}, {
test: /\.scss$/,
use: [
{loader: "style-loader"},
{loader: MiniCssExtractPlugin.loader},
{loader: "css-loader"},
{loader: "sass-loader"}
]
}, {
test: /\.less$/,
use: [
{loader: "style-loader"},
{loader: MiniCssExtractPlugin.loader, options: {publicPath: pathPublic}},
{loader: "css-loader"},
{loader: "less-loader"}
]
}]
},
plugins: [
new WebpackBar(),
new CleanWebpackPlugin(["frontend/dist", "static/"], {root: __dirname + "/.."}),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
collapseWhitespace: false,
template: path.join(__dirname, "src/assets/html/index.proto.html"),
title: 'SC app front page',
inject: "html",
hash: true
}),
new ScriptExtHtmlWebpackPlugin({defaultAttribute: "defer"}),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
}),
new MiniCssExtractPlugin({
filename: "styles.min.css",
})
],
stats: {
children: false,
colors: true
},
};
webpack.prod.js
const merge = require('webpack-merge');
const common = require('./webpack.common');
const path = require('path');
const pathDist = path.resolve(__dirname, "dist");
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = merge(common, {
mode: "production",
output: {
filename: "[name].min.js",
publicPath: "/public/path/",
chunkFilename: "npm.[id].[chunkhash].min.js",
path: pathDist
},
optimization: {
runtimeChunk: "single",
minimizer: [
new UglifyJsPlugin({
sourceMap: false,
uglifyOptions: {
compress: true,
output: {comments: false},
}
}),
new OptimizeCssAssetsPlugin({
assetNameRegExp: /\.(css|sass|scss|less)$/g,
cssProcessor: require("cssnano")({
safe: true,
normalizeWhitespaces: true,
discardDuplicates: true,
discardComments: {
removeAll: true,
}
})
})
],
splitChunks: {
chunks: "all",
maxInitialRequests: Infinity,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
maxSize: 249856, // 244 KiB Webpack recommended size
},
}
},
}
});
webpack.dev.js
const merge = require('webpack-merge');
const common = require('./webpack.common');
const path = require('path');
const pathDist = path.resolve(__dirname, "dist");
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = merge(common, {
mode: "development",
devtool: "inline-source-map",
output: {
filename: "[name].min.js",
publicPath: "/public/path/",
chunkFilename: "npm.[id].[chunkhash].min.js",
path: pathDist
},
optimization: {
minimizer: [
new UglifyJsPlugin({
sourceMap: true,
uglifyOptions: {
compress: true,
output: {comments: false}
}
}),
new OptimizeCssAssetsPlugin({
assetNameRegExp: /\.(css|sass|scss|less)$/g,
cssProcessor: require("cssnano")({
safe: true,
normalizeWhitespaces: true,
discardDuplicates: true,
discardComments: {
removeAll: true,
},
})
})
]
},
devServer: {
contentBase: pathDist
}
});
the problem is that when i do dev build, es6 class names remains unchanged, but when i do prod build, es6 class names changes and this will crush angularjs DI process.
EXAMPLE:
Let's say there is 2 classes (one service and one controller)
/**
* build-dev: MyService.prototype.constructor.name = "MyService"
* build-prod: MyService.prototype.constructor.name = "MyService__MyService"
*/
class MyService {
serviceMethod() {
// DO STUFF
}
}
/**
* build-dev: MyCtrl.prototype.constructor.name = "MyCtrl"
* build-prod: MyCtrl.prototype.constructor.name = "MyCtrl__MyCtrl"
*/
class MyCtrl {
constructor(MyService) {
this.MyService = MyService;
}
ctrlMethod() {
this.MyService.serviceMethod()
}
}
and those classes are registered with angularjs DI this way
angular.module("MyApp")
.controller(MyCtrl.prototype.constructor.name, MyCtrl)
.service(MyService.prototype.constructor.name, MyService)
now pay attention at class annotations (build-dev, build-prod)
I can't build a project with webpack because I'm getting a syntax error all the time.
Moreover when my friend runs the same code on Linux (I work on Windows 10) he doesn't get any errors and everything works fine.
Here is my webpack config
const path = require('path');
const autoprefixer = require('autoprefixer');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WebpackMd5Hash = require('webpack-md5-hash');
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const HMRPlugin = require('webpack/lib/HotModuleReplacementPlugin');
const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin');
const NamedModulesPlugin = require('webpack/lib/NamedModulesPlugin');
const OccurrenceOrderPlugin = require('webpack/lib/optimize/OccurrenceOrderPlugin');
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
//=========================================================
// VARS
//---------------------------------------------------------
const NODE_ENV = process.env.NODE_ENV;
const DEVELOPMENT = NODE_ENV === 'development';
const PRODUCTION = NODE_ENV === 'production';
const HOST = process.env.HOST || '127.0.0.1';
const PORT = process.env.PORT || '3000';
//=========================================================
// LOADERS
//---------------------------------------------------------
const rules = {
js: {
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
},
json: {
test: /\.json$/,
loader: 'json-loader',
},
css: {
test: /\.css$/,
loader: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'resolve-url-loader', 'postcss-loader'],
}),
},
scss: {
test: /\.scss$/,
loader: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'resolve-url-loader', 'postcss-loader', 'sass-loader'],
}),
},
fonts: {
test: /\.(eot|svg|ttf|woff|woff2)$/,
loader: 'file-loader',
query: {
name: `[name].[ext]`,
publicPath: '/assets/fonts/',
}
},
images: {
test: /\.(svg|png|jpg|jpeg|gif)$/,
loader: 'file-loader',
query: {
limit: 10000,
name: `[name].[ext]`,
publicPath: '/assets/images/',
}
}
};
//=========================================================
// CONFIG
//---------------------------------------------------------
const config = {};
config.entry = {
polyfills: './src/application/polyfills.js',
main: ['./src/application/index.js'],
};
config.output = {
filename: 'assets/js/[name].js',
path: path.resolve('./dist'),
// publicPath: '/',
};
config.resolve = {
extensions: ['.js', '.json'],
modules: [
path.resolve('./src'),
'node_modules',
]
};
config.module = {
rules: [
rules.js,
rules.css,
rules.json,
rules.scss,
rules.fonts,
rules.images,
]
};
config.plugins = [
new LoaderOptionsPlugin({
debug: !PRODUCTION,
cache: !PRODUCTION,
minimize: PRODUCTION,
options: {
postcss: [
autoprefixer({
browsers: ['last 3 versions'],
})
],
sassLoader: {
outputStyle: PRODUCTION ? 'compressed' : 'expanded',
precision: 10,
sourceComments: false,
sourceMap: PRODUCTION,
}
}
}),
new DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(NODE_ENV),
}),
new OccurrenceOrderPlugin(true),
new NamedModulesPlugin(),
new CommonsChunkPlugin({
name: 'polyfills',
chunks: ['polyfills'],
}),
// This enables tree shaking of the vendors modules
new CommonsChunkPlugin({
name: 'vendor',
chunks: ['main'],
minChunks: module => /node_modules/.test(module.resource),
}),
new CommonsChunkPlugin({
name: ['polyfills', 'vendor'].reverse(),
}),
new CopyWebpackPlugin([
{
from: './src/assets',
to: 'assets',
ignore: ['**/*.scss'],
}
]),
new ExtractTextPlugin({
filename: 'assets/css/[name].css',
disable: !PRODUCTION,
allChunks: true,
}),
new HtmlWebpackPlugin({
filename: 'index.html',
hash: false,
inject: 'body',
chunksSortMode: 'dependency',
template: './src/index.html',
})
];
//=====================================
// DEVELOPMENT
//-------------------------------------
if (DEVELOPMENT) {
config.devtool = 'cheap-module-source-map';
config.entry.main.unshift(
'react-hot-loader/patch',
`webpack-dev-server/client?http://${HOST}:${PORT}`,
'webpack/hot/only-dev-server',
);
config.plugins.push(
new HMRPlugin(),
new ProgressPlugin(),
);
config.devServer = {
contentBase: path.resolve(__dirname, 'dist'),
historyApiFallback: true,
host: HOST,
hot: true,
port: PORT,
stats: {
cached: true,
cachedAssets: true,
children: false,
chunks: false,
chunkModules: false,
colors: true,
modules: false,
hash: false,
reasons: true,
timings: true,
version: false,
}
};
}
//=====================================
// PRODUCTION
//-------------------------------------
if (PRODUCTION) {
config.devtool = 'hidden-source-map';
config.plugins.push(
new WebpackMd5Hash(),
new UglifyJsPlugin({
comments: false,
compress: {
unused: true,
dead_code: true,
screw_ie8: true,
warnings: false,
},
mangle: {
screw_ie8: true,
}
})
);
}
module.exports = config;
And this is my package.json scripts
"scripts": {
"test": "jest",
"server:dev": "set NODE_ENV='development' && webpack-dev-server --color",
"start": "npm run server:dev",
"build": "set NODE_ENV='production' && webpack --color"
},
When I try to run "npm start", I have this error
C:\Users\vellgreen\Desktop\my_webpack_react\webpack.config.js:184
);
^
SyntaxError: Unexpected token )
when I put this into comment everything is working normal.
config.entry.main.unshift(
'react-hot-loader/patch',
`webpack-dev-server/client?http://${HOST}:${PORT}`,
'webpack/hot/only-dev-server',
);
config.plugins.push(
new HMRPlugin(),
new ProgressPlugin(),
);
When I run code for production everything is also fine.
My OS is Windows 10, webpack v3.1.0, npm v5.3.0, node v6.10.3.
Does anyone know what may cause such an error?
Trailing commas in functions are a relatively recent feature and Node started supporting it in version 8. Your friend is simply using a version that supports it (it's unrelated to the OS).
config.entry.main.unshift(
'react-hot-loader/patch',
`webpack-dev-server/client?http://${HOST}:${PORT}`,
'webpack/hot/only-dev-server',
// ^ --- remove this comma
);
config.plugins.push(
new HMRPlugin(),
new ProgressPlugin(),
// ^ --- and this comma
);