Cypress for Components Tests React configuration problem absolute path - javascript

the goal is to use Cypress for component testing and e2e testing mainly.
Install and configure all Cypress correctly, configure Cypress in the 'cypress open' script to open E2E related tests and configure the 'cypress open-ct' script for component tests, all settings work very well.
Problem
The project is already ready and I have absolute folders configuration, when I import a component from '~/components' I am accessing 'src/components', I am not able to make the proper settings to be able to access the absolute folders because when I run the cypress open script -ct doesn't seem to run 'file:preprocessor', where you need to run it to run the webpack where you have the absolute folder settings.
I tried several alternatives and this was the one that worked the best because when I run the 'cypress open' script cypress executes 'file:preprocessor' it applies the webpack settings and if I try to import a component using '~/component/InputDate' it works , however 'cypress open' is for E2E tests, I can't run unit tests through this script.
I can be wrong about the cypress open script not running component tests correctly but I'm looking for a solution to the big problem that is these absolute folders.
Attempts
I tried the tsconfig.json files configuration solution but I couldn't make it work because I use it in the jsconfig.json project, I tried to use tsconfig.json I used the 'tsconfig-paths' package and got no result.
cypress/plugins/index.js
const webpack = require('#cypress/webpack-preprocessor')
const injectDevServer = require('#cypress/react/plugins/react-scripts')
module.exports = (on, config) => {
const options = {
webpackOptions: require('../../webpack.config.js'),
watchOptions: {},
}
on('file:preprocessor', webpack(options))
injectDevServer(on, config)
return config
}
cypress.json
{
"component": {
"componentFolder": "src/tests/components"
},
"integrationFolder": "src/tests/integration"
}
webpackconfig.js
var path = require('path')
module.exports = {
mode: 'development',
module: {
rules: [
{
test: /\.js$|jsx/,
exclude: [/node_modules/],
use: [
{
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env', '#babel/preset-react'],
},
},
],
},
],
},
resolve: {
alias: {
'~': path.resolve(__dirname, 'src'),
},
},
}
packjage.json
"devDependencies": {
"#cypress/react": "5.12.4",
"#cypress/webpack-dev-server": "1.8.3",
"#cypress/webpack-preprocessor": "^5.11.1",
"#types/jwt-decode": "^3.1.0",
"babel-loader": "^8.2.3",
"cypress": "9.5.2",
"webpack-dev-server": "3.2.1"
}

I managed to do the following solution:
plugins/index.js
const { startDevServer } = require('#cypress/webpack-dev-server')
const webpackConfig = require('../../webpack.config.js')
module.exports = (on, config) => {
on('dev-server:start', async (options) =>
startDevServer({ options, webpackConfig }),
)
return config
}
cypress.json
{
"component": {
"componentFolder": "src/tests/unity"
},
"integrationFolder": "src/tests/integration"
}
webpack.config.js
var path = require('path')
module.exports = {
mode: 'development',
module: {
rules: [
{
test: /\.js$|jsx/,
exclude: [/node_modules/],
use: [
{
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env', '#babel/preset-react'],
},
},
],
},
],
},
resolve: {
alias: {
'~': path.resolve(__dirname, 'src'),
},
},
}

Related

How do I fingerprint images and other static assets in Ionic for cache busting?

I have extended default web pack config in Ionic v3 for forcing cache busting.
I am able to fingerprint generated JavaScript artifacts, but I am unable to fingerprint images and JSON files under the assets folder. I took Help from Bundled files and cache-busting.
An excerpt of webpack config.js
module.exports = {
// ...
output: {
filename: '[name].[chunkhash].js',
chunkFilename: '[name].[chunkhash].js',
},
plugins: [
new WebpackChunkHash({algorithm: 'md5'}) // 'md5' is default value
]
}
The above is the approach for fingerprinting JavaScript bundles, and it's working fine. I want to add hashes/fingerprint images and JSON files inside the assets folder. I used the same approach for images also, but it did not work.
I have extended webpack config.js and added a new rule for images. By default webpack directly copies the images and assets to the output folder.
Copy Config.js
module.exports = {
copyAssets: {
src: ['{{SRC}}/assets/**/*'],
dest: '{{WWW}}/assets'
},
copyIndexContent: {
src: ['{{SRC}}/index.html', '{{SRC}}/manifest.json', '{{SRC}}/service-worker.js'],
dest: '{{WWW}}'
},
copyFonts: {
src: ['{{ROOT}}/node_modules/ionicons/dist/fonts/**/*', '{{ROOT}}/node_modules/ionic-angular/fonts/**/*'],
dest: '{{WWW}}/assets/fonts'
},
Here images and other assets are directly copied.
I have added a new rule in extended webpack.config.js, but the build process is ignoring it. How do I fix this issue?
Excerpt of webpack config.js
{
test: /\.(png|jpg|gif)$/,
loader: 'file-loader',
options: {
name:'[name].[hash].[ext]',//adding hash for cache busting
outputPath:'assets/imgs',
publicPath:'assets/imgs'
},
entire Webpack.config.js
/*
* The webpack config exports an object that has a valid webpack configuration
* For each environment name. By default, there are two Ionic environments:
* "dev" and "prod". As such, the webpack.config.js exports a dictionary object
* with "keys" for "dev" and "prod", where the value is a valid webpack configuration
* For details on configuring webpack, see their documentation here
* https://webpack.js.org/configuration/
*/
var path = require('path');
var webpack = require('webpack');
var ionicWebpackFactory = require(process.env.IONIC_WEBPACK_FACTORY);
var ModuleConcatPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
var PurifyPlugin = require('#angular-devkit/build-optimizer').PurifyPlugin;
var optimizedProdLoaders = [
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.js$/,
loader: [
{
loader: process.env.IONIC_CACHE_LOADER
},
{
loader: '#angular-devkit/build-optimizer/webpack-loader',
options: {
sourceMap: true
}
},
]
},
{
test: /\.ts$/,
loader: [
{
loader: process.env.IONIC_CACHE_LOADER
},
{
loader: '#angular-devkit/build-optimizer/webpack-loader',
options: {
sourceMap: true
}
},
{
test: /\.(png|jpg|gif)$/,
loader: 'file-loader',
options: {
name:'[name].[hash].[ext]',
outputPath:'assets/imgs',
publicPath:'assets/imgs'
},
},
{
loader: process.env.IONIC_WEBPACK_LOADER
}
]
}
];
function getProdLoaders() {
if (process.env.IONIC_OPTIMIZE_JS === 'true') {
return optimizedProdLoaders;
}
return devConfig.module.loaders;
}
var devConfig = {
entry: process.env.IONIC_APP_ENTRY_POINT,
output: {
path: '{{BUILD}}',
publicPath: 'build/',
filename: '[name].js',
devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),
},
devtool: process.env.IONIC_SOURCE_MAP_TYPE,
resolve: {
extensions: ['.ts', '.js', '.json'],
modules: [path.resolve('node_modules')]
},
module: {
loaders: [
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.ts$/,
loader: process.env.IONIC_WEBPACK_LOADER
},
{
test: /\.(jpg|png)$/,
use: {
loader: "file-loader",
options: {
name: "[name].[hash].[ext]",
outputPath:'assets/imgs',
publicPath:'assets/imgs'
},
}},
]
},
plugins: [
ionicWebpackFactory.getIonicEnvironmentPlugin(),
ionicWebpackFactory.getCommonChunksPlugin()
],
// Some libraries import Node.js modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
fs: 'empty',
net: 'empty',
tls: 'empty'
}
};
var prodConfig = {
entry: process.env.IONIC_APP_ENTRY_POINT,
output: {
path: '{{BUILD}}',
publicPath: 'build/',
filename: '[name].js',
devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),
},
devtool: process.env.IONIC_SOURCE_MAP_TYPE,
resolve: {
extensions: ['.ts', '.js', '.json'],
modules: [path.resolve('node_modules')]
},
module: {
loaders: getProdLoaders()
},
plugins: [
ionicWebpackFactory.getIonicEnvironmentPlugin(),
ionicWebpackFactory.getCommonChunksPlugin(),
new ModuleConcatPlugin(),
new PurifyPlugin()
],
// Some libraries import Node.js modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
fs: 'empty',
net: 'empty',
tls: 'empty'
}
};
module.exports = {
dev: devConfig,
prod: prodConfig
}
Using Webpack 4 you should not need any additional plugins or loaders.
It will give you the naming option [contenthash].
Also, it looks like you have this block nested under the test: .ts block.
{
test: /\.(png|jpg|gif)$/,
loader: 'file-loader',
options: {
name:'[name].[hash].[ext]', // Adding hash for cache busting
outputPath:'assets/imgs',
publicPath:'assets/imgs'
}
}
Ultimately, you can do something like this:
// Copy static assets over with file-loader
{
test: /\.(ico)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader', options: {name: '[name].[contenthash].[ext]'},
},
{
test: /\.(woff|woff2|eot|ttf|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader', options: {name: 'fonts/[name].[contenthash].[ext]'},
},
{
test: /\.(jpg|gif|png|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader', options: {name: 'images/[name].[contenthash].[ext]'},
}
]
Using [chunkhash] instead of content should still work, and if you're not using webpack4 do that, but otherwise for more information see this issue for an explanation.
For more help, read the long-term caching performance guide from Google and the latest caching documentation from Webpack.
the files copied via CopyPlugin will not pass to loaders.
So even you have correct loader setting with hashname for images, it doesn't work.
but you can see https://github.com/webpack-contrib/copy-webpack-plugin#template
the CopyPlugin provide you a way to specify output name which can be set with hash:
module.exports = {
plugins: [
new CopyPlugin([
{
from: 'src/',
to: 'dest/[name].[hash].[ext]',
toType: 'template',
},
]),
],
};
Eventually, I used gulp for fingerprinting static assets.
Drop Angular Output hashing and build the application.ng build --prod --aot --output-hashing none .
post-build execute a gulp script which would fingerprint all the assets and update the references.
npm i gulp gulp-rev gulp-rev-delete-original gulp-rev-collector
gulpfile.js
const gulp = require('gulp');
const rev = require('gulp-rev');
const revdel = require('gulp-rev-delete-original');
const collect = require('gulp-rev-collector');
// finger priniting static assets
gulp.task('revision:fingerprint', () => {
return gulp
.src([
'dist/welcome/**/*.css',
'dist/welcome/**/*.js',
'dist/welcome/**/*.{jpg,png,jpeg,gif,svg,json,xml,ico,eot,ttf,woff,woff2}'
])
.pipe(rev())
.pipe(revdel())
.pipe(gulp.dest('dist/welcome'))
.pipe(rev.manifest({ path: 'manifest-hash.json' }))
.pipe(gulp.dest('dist'));
});
gulp.task('revision:update-fingerprinted-references', () => {
return gulp
.src(['dist/manifest-hash.json', 'dist/**/*.{html,json,css,js}'])
.pipe(collect())
.pipe(gulp.dest('dist'));
});
gulp.task(
'revision',
gulp.series(
'revision:fingerprint',
'revision:update-fingerprinted-references'));
Add a new script in package.json
"gulp-revision": "gulp revision"
Execute npm run gulp-revision Post-build.
Solving Browser Cache Hell With Gulp-Rev
Using webpack-assets-manifest you can generate a map of asset names to fingerprinted names like so:
{
"images/logo.svg": "images/logo-b111da4f34cefce092b965ebc1078ee3.svg"
}
Using this manifest you can then rename the assets in destination folder, and use the "correct", hash-inclusive src or href in your project.
The fix isn't framework-specific.

Bundling node.js web API with webpack

I'm implementing a web API based on node.js. API can interact with a database. So I use node-postgres library for a data access layer.
Now I need to configure webpack in the right manner in order to bundle api in a sigle file. However I can't do that because of node-postgres dependency "pg-native". I can only build api with this code in webpack.config.js:
externals: {
'pg': 'commonjs pg'
}
But this solution force me to keep node_modules folder when I going to deploy the API.
Here is my webpack.config.js:
var path = require('path');
var nodeNativeModules = {};
module.exports = function(environment) {
var entryCfg = '';
switch(environment){
case 'development':
entryCfg = { 'main_ts': './src/api/main.ts'};
break;
}
var CONFIG = {
entry: entryCfg,
target: 'node',
output: {
path: path.join(__dirname, 'dist/'),
filename: '[name].js'
},
resolve: {
extensions: ['.ts', '.js', '.json'],
},
externals: {
'pg': 'commonjs pg'
},
module: {
loaders: [{
test: /\.json$/,
loader: 'json-loader'
}, {
test: /\.ts$/,
loaders: [
'awesome-typescript-loader',
],
exclude: [/\.(spec|e2e)\.ts$/]
},]
},
devtool: 'source-map'
}
return CONFIG;
}
Is there a possible way to bundle node-postgres?
How to configure webpack to bundle native module dependencies?
Since I don't use 'pg-native' my issue becomes a webpack configuration issue.
Here is the answer that helps me https://github.com/serverless-heaven/serverless-webpack/issues/78

Prevent display of raw HTML in react

Is there a way to prevent in React.js, raw HTML display before the CSS stylesheets are completely loaded. I'm using Webpack, Semantic-UI (react version) and React.js.
Is there an equivalent of ng-cloak (angular) in React ?
Here's the content of my webpack config file :
const webpack = require('webpack')
const ManifestPlugin = require('webpack-manifest-plugin')
const path = require("path");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const DEBUG = process.env.NODE_ENV !== 'production'
const plugins = [
new webpack.DefinePlugin({
'process.env.NODE_ENV': `"${process.env.NODE_ENV}"`
})
]
const assetsDir = process.env.ASSETS_DIR
const assetMapFile = process.env.ASSETS_MAP_FILE
const outputFile = DEBUG ? '[name].js' : '[name].[chunkhash].js'
if (!DEBUG) {
plugins.push(new ManifestPlugin({
fileName: assetMapFile
}))
plugins.push(new webpack.optimize.UglifyJsPlugin({ minimize: true }))
}
const config = {
entry: {
bundle: ['babel-polyfill', './src/client/index.jsx']
},
module: {
noParse: [],
loaders: [
{ test: /\.json$/, loader: 'json' },
{ test: /\.css$/, loader: 'style!css' },
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /(node_modules|bower_components)/,
query: {
cacheDirectory: DEBUG
}
},
{test:/.svg$/,loader:'url-loader',query:{mimetype:'image/svg+xml',
name:'/semantic/themes/default/assets/fonts/icons.svg'}},
{test:/.woff$/,loader:'url-loader',query:{mimetype:'application/font-woff',
name:'/semantic/themes/default/assets/fonts/icons.woff'}},
{test:/.woff2$/,loader:'url-loader',query:{mimetype:'application/font-woff2',
name:'/semantic/themes/default/assets/fonts/icons.woff2'}},
{test:/.[ot]tf$/,loader:'url-loader',query:{mimetype:'application/octet-stream',
name:'/semantic/themes/default/assets/fonts/icons.ttf'}},
{test:/.eot$/,loader:'url-loader',query:{mimetype:'application/vnd.ms-fontobject',
name:'/semantic/themes/default/assets/fonts/icons.eot'}},
{ test: /\.png/, loader: "url-loader?limit=100000&minetype=image/png" },
{ test: /\.jpg/, loader: "file-loader" }
]
},
node: {
fs: "empty"
},
resolve: {
alias: {
"semantic-ui" : path.resolve( __dirname, "../semantic/dist/semantic.min.css")
},
extensions: ['', '.js', '.jsx', ".css"]
},
plugins,
output: {
filename: outputFile,
path: DEBUG ? '/' : assetsDir,
publicPath: '/assets/'
}
}
if (DEBUG) {
config.devtool = '#inline-source-map'
} else if (process.env.NODE_ENV === 'production') {
config.devtool = 'source-map'
}
module.exports = config
I got this error when I try to load my css from my component :
Cannot find module '/semantic/dist/semantic.min.css'
and the module exists.
I tried the exact same configuration without Webpack and import from component worked.
This is not the correct setup for a production deploy.
By default, Webpack turns your CSS into Javascript code that injects CSS tags in the page. This allows for hot CSS reloading. It's only appropriate for the development environment, obviously. You should be using this default behavior in dev, and should not be using it in production.
In production, you need to build a separate CSS file and load it normally with a <style> tag in your production HTML code. To tell Webpack to pull that out into a file, use the ExtractTextPlugin, which your code requires but never uses.
You should maintain two Webpack config files, one for development which doesn't extract text (and doesn't minify/uglify, etc), and one for production, which correctly minifies, hashes names, extracts text, etc.

Setup webpack with server side rendering to load Sass files in asp.net core project

I'm using a Yeoman project template called "aspnetcore-spa", which is an ASP.net core 1 template working in conjunction with major SPA frameworks (Angular2 and React).
I created a project with Angular2.The biolerplate's code works fine and there is no problem. Once I add Sass loader to webpack.config.js and make a reference to the Sass file from any angular file.
In webpack.config.js :
var isDevBuild = process.argv.indexOf('--env.prod') < 0;
var path = require('path');
var webpack = require('webpack');
var nodeExternals = require('webpack-node-externals');
var merge = require('webpack-merge');
var allFilenamesExceptJavaScript = /\.(?!js(\?|$))([^.]+(\?|$))/;
// Configuration in common to both client-side and server-side bundles
var sharedConfig = {
resolve: { extensions: [ '', '.js', '.ts' ] },
output: {
filename: '[name].js',
publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
module: {
loaders: [
{ test: /\.ts$/, include: /ClientApp/, loader: 'ts', query: { silent: true } },
{ test: /\.scss$/,include:/ClientApp/, loaders: ["style", "css", "sass"] },
{ test: /\.html$/,include: /ClientApp/, loader: 'raw' },
{ test: /\.css$/, loader: 'to-string!css' },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, loader: 'url', query: { limit: 25000 } }
]
}
};
// Configuration for client-side bundle suitable for running in browsers
var clientBundleOutputDir = './wwwroot/dist';
var clientBundleConfig = merge(sharedConfig, {
entry: { 'main-client': './ClientApp/boot-client.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.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin()
])
});
// Configuration for server-side (prerendering) bundle suitable for running in Node
var serverBundleConfig = merge(sharedConfig, {
entry: { 'main-server': './ClientApp/boot-server.ts' },
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, './ClientApp/dist')
},
target: 'node',
devtool: 'inline-source-map',
externals: [nodeExternals({ whitelist: [allFilenamesExceptJavaScript] })] // Don't bundle .js files from node_modules
});
module.exports = [clientBundleConfig, serverBundleConfig];
In my component :
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-wine',
template: require('./wine.component.html'),
styles: require('./wine.component.scss')
})
export class WineComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
I have already installed npm packages pertinent to sass loader :
npm install node-sass sass-loader --save-dev
I have checked the main-server.js file in wwwroot/dist folder which is the result of webpack bundling, I saw that the .scss file is loaded and they styles are processed correctly. Once I run the app though, shows this exception which is coming from the server side rendering side:
An unhandled exception occurred while processing the request.
Exception: Call to Node module failed with error: ReferenceError: window is not defined at E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:573:31 at E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:568:48 at module.exports (E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:590:69) at Object. (E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:526:38) at webpack_require (E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:20:30) at E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:501:22 at Object.module.exports (E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:506:3) at webpack_require (E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:20:30) at Object. (E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:129:25) at webpack_require (E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:20:30)
It's obviously because of the webpack's server-side rendering, as it's running the code on Node.js side (through ASP.net Core's Javascript Services) and there is a code that is coupled with the DOM window object which is not valid on node.
Any clues?
I managed to fix the problem, here's the web.config.js bit:
(Notice the loaders for .scss files)
module: {
loaders: [
{ test: /\.ts$/, include: /ClientApp/, loader: 'ts', query: { silent: true } },
{ test: /\.scss$/,include:/ClientApp/, loaders: ["to-string", "css", "sass"] },
{ test: /\.html$/,include: /ClientApp/, loader: 'raw' },
{ test: /\.css$/, loader: 'to-string!css' },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, loader: 'url', query: { limit: 25000 } }
]
}
And in the Angular component I changed the styles to this :
(Passed an array of required css files rather than a single css file)
#Component({
selector: 'app-wine',
template: require('./wine.component.html'),
styles: [require('./wine.component.scss')]
})

Can I use webpack to generate CSS and JS separately?

I have:
JS files that I want to bundle.
LESS files that I want to compile down to CSS (resolving #imports into a single bundle).
I was hoping to specify these as two separate inputs and have two separate outputs (likely via extract-text-webpack-plugin). Webpack has all the proper plugins/loaders to do compilation, but it doesn't seem to like the separation.
I've seen examples of people requiring their LESS files directly from JS, such as require('./app.less');, for no other reason than to tell webpack to include those files into the bundle. This allows you to only have a single entry point, but it seems really wrong to me -- why would I require LESS in my JS when it has nothing to do with my JS code?
I tried using multiple entry points, handing both the entry JS and main LESS file in, but when using multiple entry points, webpack generates a bundle that doesn't execute the JS on load -- it bundles it all, but doesn't know what should be executed on startup.
Am I just using webpack wrong? Should I run separate instances of webpack for these separate modules? Should I even be using webpack for non-JS assets if I'm not going to mix them into my JS?
Should I even be using webpack for non-JS assets if I'm not going to mix them into my JS?
Maybe not. Webpack is definitely js-centric, with the implicit assumption that what you're building is a js application. Its implementation of require() allows you to treat everything as a module (including Sass/LESS partials, JSON, pretty much anything), and automatically does your dependency management for you (everything that you require is bundled, and nothing else).
why would I require LESS in my JS when it has nothing to do with my JS code?
People do this because they're defining a piece of their application (e.g. a React component, a Backbone View) with js. That piece of the application has CSS that goes with it. Depending on some external CSS resource that's built separately and not directly referenced from the js module is fragile, harder to work with, and can lead to styles getting out of date, etc. Webpack encourages you to keep everything modular, so you have a CSS (Sass, whatever) partial that goes with that js component, and the js component require()s it to make the dependency clear (to you and to the build tool, which never builds styles you don't need).
I don't know if you could use webpack to bundle CSS on its own (when the CSS files aren't referenced from any js). I'm sure you could wire something up with plugins, etc., but not sure it's possible out of the box. If you do reference the CSS files from your js, you can easily bundle the CSS into a separate file with the Extract Text plugin, as you say.
webpack 4 solution with mini-css-extract plugin
the webpack team recommends using mini-css-extract over the extract text plugin
this solution allows you to create a separate chunk containing only your css entries:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
function recursiveIssuer(m) {
if (m.issuer) {
return recursiveIssuer(m.issuer);
} else if (m.name) {
return m.name;
} else {
return false;
}
}
module.exports = {
entry: {
foo: path.resolve(__dirname, 'src/foo'),
bar: path.resolve(__dirname, 'src/bar'),
},
optimization: {
splitChunks: {
cacheGroups: {
fooStyles: {
name: 'foo',
test: (m, c, entry = 'foo') =>
m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
chunks: 'all',
enforce: true,
},
barStyles: {
name: 'bar',
test: (m, c, entry = 'bar') =>
m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
chunks: 'all',
enforce: true,
},
},
},
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
};
Here is a more contrived example using mutliple entries from one of my personal projects:
const ManifestPlugin = require('webpack-manifest-plugin')
const webpack = require('webpack')
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const VENDOR = path.join(__dirname, 'node_modules')
const LOCAL_JS = path.join(__dirname, 'app/assets/js')
const LOCAL_SCSS = path.join(__dirname, 'app/assets/scss')
const BUILD_DIR = path.join(__dirname, 'public/dist')
const EXTERNAL = path.join(__dirname, 'public/external')
function recursiveIssuer(m) {
if (m.issuer) {
return recursiveIssuer(m.issuer);
} else if (m.name) {
return m.name;
} else {
return false;
}
}
module.exports = {
entry: {
vendor: [
`${VENDOR}/jquery/dist/jquery.js`,
`${VENDOR}/codemirror/lib/codemirror.js`,
`${VENDOR}/codemirror/mode/javascript/javascript.js`,
`${VENDOR}/codemirror/mode/yaml/yaml.js`,
`${VENDOR}/zeroclipboard/dist/ZeroClipboard.js`,
],
app: [
`${LOCAL_JS}/utils.js`,
`${LOCAL_JS}/editor.js`,
`${LOCAL_JS}/clipboard.js`,
`${LOCAL_JS}/fixtures.js`,
`${LOCAL_JS}/ui.js`,
`${LOCAL_JS}/data.js`,
`${LOCAL_JS}/application.js`,
`${LOCAL_JS}/google.js`
],
'appStyles': [
`${EXTERNAL}/montserrat.css`,
`${EXTERNAL}/icons.css`,
`${VENDOR}/purecss/pure-min.css`,
`${VENDOR}/purecss/grids-core-min.css`,
`${VENDOR}/purecss/grids-responsive-min.css`,
`${VENDOR}/codemirror/lib/codemirror.css`,
`${VENDOR}/codemirror/theme/monokai.css`,
]
},
optimization: {
splitChunks: {
cacheGroups: {
appStyles: {
name: 'appStyles',
test: (m, c, entry = 'appStyles') =>
m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
chunks: 'all',
enforce: true,
},
},
},
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [ 'script-loader'],
},
{
test: /\.(scss|css)$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
],
},
],
},
mode: 'development',
resolve: {
extensions: ['.js', '.css', '.scss']
},
output: {
path: BUILD_DIR,
filename: "[name].[chunkhash].js",
},
plugins: [
new ManifestPlugin(),
new MiniCssExtractPlugin({
filename: '[name].css'
}),
]
};
I realize this approach is not very modular, but it should give you a foundation to build from and is an excellent strategy for adopting webpack in projects where you do not wish to inter-mix javascript and css.
The downside to this approach is that css-loader still generates an additional javascript file (whether you choose to use it or not), this will supposedly be fixed in webpack 5.
Should I even be using webpack for non-JS assets if I'm not going to mix them into my JS?
I don't see anything wrong with this but ultimately it depends on your tolerance for managing multiple build systems. To me this feels like overkill, so my preference is to remain in the webpack ecosystem.
For more information on the strategies outlined above, please see https://github.com/webpack-contrib/mini-css-extract-plugin#extracting-css-based-on-entry
A separate CSS bundle can be generated without using require('main/less) in any of your JS, but as Brendan pointed out in the first part of his answer Webpack isn't designed for a global CSS bundle to go alongside modular JS, however there are a couple of options.
The first is to add an extra entry point for main.less, then use the Extract Text plugin to create the CSS bundle:
var webpack = require('webpack'),
ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: {
home: [
'js/common',
'js/homepage'
],
style: [
'styles/main.less'
]
},
output: {
path: 'dist',
filename: "[name].min.js"
},
resolve: {
extensions: ["", ".js"]
},
module: {
loaders: [{
test: /\.less$/,
loader: ExtractTextPlugin.extract("style", "css", "less")
}]
},
plugins: [
new ExtractTextPlugin("[name].min.css", {
allChunks: true
})
]
};
The problem with this method is you also generate an unwanted JS file as well as the bundle, in this example: style.js which is just an empty Webpack module.
Another option is to add the main less file to an existing Webpack entry point:
var webpack = require('webpack'),
ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: {
home: [
'js/common',
'js/homepage',
'styles/main.less'
],
},
output: {
path: 'dist',
filename: "[name].min.js"
},
resolve: {
extensions: ["", ".js"]
},
module: {
loaders: [{
test: /\.less$/,
loader: ExtractTextPlugin.extract("style", "css", "less")
}]
},
plugins: [
new ExtractTextPlugin("[name].min.css", {
allChunks: true
})
]
};
This is ideal if you have only 1 entry point, but if you have more, then your Webpack config will look a bit odd as you'll have to arbitrarily choose which entry point to add the main less file to.
To further clarify bdmason's former answer - it seems the desirable configuration would be to create a JS and CSS bundle for each page, like so:
entry: {
Home: ["./path/to/home.js", "./path/to/home.less"],
About: ["./path/to/about.js", "./path/to/about.less"]
}
And then use the [name] switch:
output: {
path: "path/to/generated/bundles",
filename: "[name].js"
},
plugins: new ExtractTextPlugin("[name].css")
Full configuration - with some additions not connected to the question (we're actually using SASS instead of LESS):
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var debug = process.env.NODE_ENV !== "production";
var webpack = require('webpack');
require('babel-polyfill');
module.exports = [{
devtool: debug ? "inline-sourcemap" : null,
entry: {
Home: ['babel-polyfill', "./home.js","path/to/HomeRootStyle.scss"],
SearchResults: ['babel-polyfill', "./searchResults.js","path/to/SearchResultsRootStyle.scss"]
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader',
query: {
presets: ['react', 'es2015'],
plugins: ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy']
}
},
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract("style-loader","css-raw-loader!sass-loader")
}
]
},
output: {
path: "./res/generated",
filename: "[name].js"
},
plugins: debug ? [new ExtractTextPlugin("[name].css")] : [
new ExtractTextPlugin("[name].css"),
new webpack.DefinePlugin({
'process.env':{
'NODE_ENV': JSON.stringify('production')
}
}),
new webpack.optimize.UglifyJsPlugin({
compress:{
warnings: true
}
})
]
}
];
Yes, this is possible but like others said you will need additional packages to do so (see devDependencies under package.json). here is the sample code that I used to compile my bootstrap SCSS --> CSS and Bootstrap JS --> JS.
webpack.config.js:
module.exports = {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
entry: ['./src/app.js', './src/scss/app.scss'],
output: {
path: path.resolve(__dirname, 'lib/modules/theme/public'),
filename: 'js/bootstrap.js'
},
module: {
rules: [
{
test: /\.scss$/,
use: [
{
loader: 'file-loader',
options: {
name: 'css/bootstrap.css',
}
},
{
loader: 'extract-loader'
},
{
loader: 'css-loader?-url'
},
{
loader: 'postcss-loader'
},
{
loader: 'sass-loader'
}
]
}
]
}
};
additional postcss.config.js file:
module.exports = {
plugins: {
'autoprefixer': {}
}
}
package.json:
{
"main": "app.js",
"scripts": {
"build": "webpack",
"start": "node app.js"
},
"author": "P'unk Avenue",
"license": "MIT",
"dependencies": {
"bootstrap": "^4.1.3",
},
"devDependencies": {
"autoprefixer": "^9.3.1",
"css-loader": "^1.0.1",
"exports-loader": "^0.7.0",
"extract-loader": "^3.1.0",
"file-loader": "^2.0.0",
"node-sass": "^4.10.0",
"popper.js": "^1.14.6",
"postcss-cli": "^6.0.1",
"postcss-loader": "^3.0.0",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"webpack": "^4.26.1",
"webpack-cli": "^3.1.2"
}
}
See the tutorial here: https://florianbrinkmann.com/en/4240/sass-webpack
Like others mentioned you can use a plugin.
ExtractTextPlugin is deprecated.
You can use the currently recommended MiniCssExtractPlugin in your webpack configuration:
module.exports = {
entry: {
home: ['index.js', 'index.less']
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
}),
]
}
You can also put your Less require statements in your entry JS file also:
in body.js
// CSS
require('css/_variable.scss')
require('css/_npm.scss')
require('css/_library.scss')
require('css/_lib.scss')
Then in webpack
entry: {
body: [
Path.join(__dirname, '/source/assets/javascripts/_body.js')
]
},
const extractSass = new ExtractTextPlugin({
filename: 'assets/stylesheets/all.bundle.css',
disable: process.env.NODE_ENV === 'development',
allChunks: true
})

Categories

Resources