Splitting out react components as widgets - javascript

I'm trying to create JS widgets from my existing react app. So currently I have an app that looks something like this
-src
- config
- components
- containers
- lib
- assets
- widgets
- widgetOne
- widgetTwo
- components
- widget.js
- index.js
- index.js
- index.html
So, I want directories in the widgets directories to be self contained apps that I can break out into a separate js file and a client can just add the js script into their page in a script tag.
I've come close but still facing a few issues. Also, I wanted to see if someone had experience doing this following a better pattern.
Right now I'm using webpack to do this splitting. I'm just defining /src/widgets/widgetOne/index.js as an entry point and perfectly creates a separate file.
Here is my webpack:
const appConstants = function() {
switch (process.env.NODE_ENV) {
case 'local':
const localConfig = require('./config/local');
return localConfig.config();
case 'development':
const devConfig = require('./config/development');
return devConfig.config();
case 'production':
default:
const prodConfig = require('./config/production');
return prodConfig.config();
}
};
const HtmlWebPackPlugin = require("html-webpack-plugin");
const webpack = require('webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const htmlWebpackPlugin = new HtmlWebPackPlugin({
template: "./src/index.html",
filename: "./index.html",
hash: true
});
let webpackConfig = {
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.css$/,
exclude: [ /assets/, /node_modules/ ],
use: [
{
loader: "style-loader"
},
{
loader: "css-loader",
options: {
modules: true,
importLoaders: 1,
localIdentName: "[name]_[local]_[hash:base64]",
sourceMap: true,
minimize: true
}
}
]
},
{
test: /\.(pdf|jpg|png|gif|svg|ico)$/,
exclude: [/node_modules/],
use: [
{
loader: 'file-loader'
},
]
},
{
test: /\.(woff|woff2|eot|ttf|svg)$/,
exclude: [/node_modules/],
use: {
loader: 'url-loader?limit100000'
}
}
]
},
entry: {
main: [ "#babel/polyfill", "./src/index.js"],
widgetOne: ["./src/widgets/widgetOne/index.js"]
},
output: {
publicPath: appConstants().BASENAME ? JSON.parse(appConstants().BASENAME) : '/'
},
optimization: {
splitChunks: {
chunks: 'all'
}
},
plugins: [
htmlWebpackPlugin,
new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /en/),
new BundleAnalyzerPlugin({
analyzerMode: 'disabled',
generateStatsFile: true,
statsOptions: { source: false }
}),
new webpack.DefinePlugin({
'process.env': appConstants()
}),
new webpack.EnvironmentPlugin(['NODE_ENV'])
],
devServer: {
historyApiFallback: true,
port: 9090
}
};
module.exports = webpackConfig;
The problem I have now is while I get the widgetOne.js:
1) I end up with a vendor~widgetOne.js file that I also need to include to make the widgetOne app to work.
2) The widgetOne.js also gets added to my index.html file for my main app which I do not want.
Is there a way to configure webpack properly to make this work?

So, this is the solution I came up with that seems to work. I still don't know if this is the best approach but it' the only one I was able to get to work for me.
I decided to build the widgets as a different environment process and and modify the webpack configs based on that environment.
So the package.json I add this line under scritps:
"build-widgets": "cross-env NODE_ENV=plugins webpack --mode development",
And I added this section to the end of my webpack.config.js file:
// Override webpack configs when building plugins
if ( process.env.NODE_ENV === 'plugins') {
webpackConfig.entry = {
widgetOne: [ "#babel/polyfill", "./src/plugins/widgetOne/index.js"]
}
webpackConfig.output = {
publicPath: appConstants().DS_BASENAME ? JSON.parse(appConstants().DS_BASENAME) : '/',
path: __dirname + '/dist/widgets',
library: 'MyApp',
libraryTarget: 'umd',
umdNamedDefine: true
}
}
Alternatively, I could have just added a second webpack.config.js exclusively to deal with my widgets build. In my case I didn't feel the need for it just yet but it's something to be considered for sure, just for the sake of keeping configs separate.

Related

Independent chunk files with webpack

I am building a component library and I am using Webpack to bundle it. Some components only rely on html templates, css and JavaScript that I've written, but some components require external libraries.
What I'd like to achieve is a vendor.js that is optional to include if the component you want to use needs it.
For instance, If a user only needs a component without vendor dependencies, it would suffice that they use main.bundle.js which only contains my own code.
In my index.js, I have the following imports:
import { Header } from './components/header/header.component';
import { Logotype } from './components/logotype/logotype.component';
import { Card } from './components/card/card.component';
import { NavigationCard } from './components/navigation-card/navigation-card.component';
import { AbstractComponent } from './components/base/component.abstract';
import { Configuration } from './system.config';
import 'bootstrap-table';
import './scss/base.scss';
All of these imports are my own, expect for bootstrap-table.
I have configured Webpack like this:
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractScss = new ExtractTextPlugin({
filename: "[name].bundle.css"
});
module.exports = {
entry: {
main: './src/index.ts'
},
output: {
path: path.resolve(__dirname, 'dist/release'),
filename: "[name].bundle.js",
chunkFilename: "[name].bundle.js"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor', // Specify the common bundle's name.
minChunks: function (module) {
// Here I would like to tell Webpack to give
// each bundle the ability to run independently
return module.context && module.context.indexOf('node_modules') >= 0;
}
}),
extractScss
],
devtool: "source-map",
resolve: {
// Add `.ts` as a resolvable extension.
extensions: ['.webpack.js', '.web.js', '.ts', '.js', '.ejs']
},
module: {
rules: [
// All files with a '.ts' extension will be handled by 'awesome-typescript-loader'.
{ test: /\.ts?$/, exclude: /node_modules/, loader: "awesome-typescript-loader" },
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" },
// Allows for templates in separate ejs files
{test: /\.ejs$/, loader: 'ejs-compiled-loader'},
{
test: /\.scss$/,
use: extractScss.extract({
use: [{
loader: 'css-loader', options: {
sourceMap: true
}
}, {
loader: 'sass-loader', options: {
soureMap: true
}
}]
})}
]
}
}
This results in two .js files and one .css. However, webpacks common module loading functionality resides in vendor.js, and that renders my main unusable if I don't include vendor first, and it isn't always needed.
To sum it up, if a user only needs the footer (no external dependencies), this would suffice:
<script src="main.bundle.js"></script>
If the user wants to use the table, which has an external dependency, they would need to include both:
<script src="vendor.js"></script>
<script src="main.bundle.js"></script>
Right now, including only main.bundle.js gives me this error:
Uncaught ReferenceError: webpackJsonp is not defined.
I am aware that I can extract all common functionality by adding this after my vendor chunk is created in the Webpack config:
new webpack.optimize.CommonsChunkPlugin({
name: 'common'
})
But this approach still requires the user to include two .js files.
How can I go about achieving this? It seems that it only differs 2 kb when I don't extract the common modules like I do above, and that is fine with me.
Turns out this is very easy to do if you can stand some manual work and actually understand what Webpack does (which I didn't). I solved it like this:
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractScss = new ExtractTextPlugin({
filename: "[name].bundle.css"
});
module.exports = {
entry: {
main: './src/index.ts',
vendor: './src/vendor/vendor.ts'
},
output: {
path: path.resolve(__dirname, 'dist/release'),
filename: "[name].bundle.js",
chunkFilename: "[name].bundle.js"
},
plugins: [
extractScss
],
devtool: "source-map",
resolve: {
// Add `.ts` as a resolvable extension.
extensions: ['.webpack.js', '.web.js', '.ts', '.js', '.ejs']
},
module: {
rules: [
// All files with a '.ts' extension will be handled by 'awesome-typescript-loader'.
{ test: /\.ts?$/, exclude: /node_modules/, loader: "awesome-typescript-loader" },
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" },
// Allows for templates in separate ejs files
{test: /\.ejs$/, loader: 'ejs-compiled-loader'},
{
test: /\.scss$/,
use: extractScss.extract({
use: [{
loader: 'css-loader', options: {
sourceMap: true
}
}, {
loader: 'sass-loader', options: {
soureMap: true
}
}]
})}
]
}
}
In vendor.ts, I then simply import any vendor dependencies I have:
import 'jquery';
import 'bootstrap-table';
This results in two different files, both have Webpacks bootstrapping logic.
Hope this helps someone.

How to increase the execution speed of webpack?

Actually, I wrote webpack for my website which was using the reactjs. In that I have lot of images and scss files so, that it is taking more amount of time to create bundle.js file upto that the website was in loading state. So, how I can increase the execution speed.
webpack.config.js
const path = require("path");
const webpack = require("webpack");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
let config = {
entry: ["babel-polyfill", "./app/js/index.js"],
output: {
path: path.resolve(__dirname, "public"),
filename: "./build.js",
//publicPath: "/"
},
resolve: {
alias: {
"js": path.resolve(__dirname, "app/js"),
"css": path.resolve(__dirname, "app/css")
},
extensions: [".js", ".jsx"]
},
module: {
rules: [
{test: /\.(js|jsx)$/, loader: "babel-loader"},
{
test: /\.(eot|svg|ttf|woff|woff2)$/,
loader: 'url-loader'
},
{test: /\.scss$/, loader: ExtractTextPlugin.extract(["css-loader", "sass-loader"])},
{test: /\.(jpg|gif|png)$/, loader: "file-loader", options: {name: "./images/[name].[ext]"}},
]
},
devServer: {
historyApiFallback: true
},
plugins: [
new HtmlWebpackPlugin({
template: "./app/index.html"
}),
new webpack.LoaderOptionsPlugin({
options: {
sassLoader: {
includePaths: ["app/css"]
}
}
}),
new ExtractTextPlugin({
filename: "style.css",
allChunks: true
})
]
};
if (process.env.NODE_ENV === "production") {
config.plugins.push(
new webpack.DefinePlugin({
"process.env": {
"NODE_ENV": JSON.stringify(process.env.NODE_ENV)
}
}),
new webpack.optimize.UglifyJsPlugin()
);
}
module.exports = config;
Besides tweaking your configuration, you could use webpack --watch to have webpack watch your source files and recompile whenever you change something. The first compilation will still be a full one, but after that only the necessary parts will be recompiled each time, which can cut down compilation time from 30 seconds to 1 or 2 seconds.
Even better is to set up webpack's devserver with hot reloading, which also watches and incrementally compiles, but in addition will update your app in the browser, so you won't need to reload the page and lose react state. It can be a bit finicky to set up, but it's well worth the trouble.

angular2 bundle node_modules only and not application code

Angular v.4 and webpack
i am looking for answer of very simple question.
How can i bundle node_module folder as vendor.js but not your application code. on page load seems like there are more then 300 requests from angular code to load internal files.
if i am able to merge all node_modules libraries i will save lot of browser requests.
I followed this example was able to get it run but it bundles libraries and your application code as well. i was able to successfully generate the vendor.js only but i am not sure after i add vendor.js in index.html how my application code will recognize #angular lib references. currently it is being resolved using systemjs.config.js some thing like this
#angular/code : 'npm:#angular/code/bundle/core.umd.js'
..
..
and exactly thats what happen as i thought application crashed it can't find references to #angular from my application code.
is it possible to just bundle node_module folder into vendor.js and still able to use in my application code without bundling app code ?
adding webpack config
webpack.config.js
module.exports = require('./config/webpack.prod.js');
webpack.common.js
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var helpers = require('./helpers');
module.exports = {
entry: {
'polyfills': './src/polyfills.ts',
'vendor': './src/vendor.ts',
'app': './src/main.ts'
},
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
loaders: [
{
loader: 'awesome-typescript-loader',
options: { configFileName: helpers.root('src', 'tsconfig.json') }
} , 'angular2-template-loader'
]
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file-loader?name=assets/[name].[hash].[ext]'
},
{
test: /\.css$/,
exclude: helpers.root('src', 'app'),
loader: ExtractTextPlugin.extract({ fallbackLoader: 'style-loader', loader: 'css-loader?sourceMap' })
},
{
test: /\.css$/,
include: helpers.root('src', 'app'),
loader: 'raw-loader'
}
]
},
plugins: [
// Workaround for angular/angular#11580
new webpack.ContextReplacementPlugin(
// The (\\|\/) piece accounts for path separators in *nix and Windows
/angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
helpers.root('./src'), // location of your src
{} // a map of your routes
),
new webpack.optimize.CommonsChunkPlugin({
name: ['app', 'vendor', 'polyfills']
}),
new HtmlWebpackPlugin({
template: 'src/index.html'
})
]
};
webpack.prod.js
var webpack = require('webpack');
var webpackMerge = require('webpack-merge');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');
const ENV = process.env.NODE_ENV = process.env.ENV = 'production';
module.exports = webpackMerge(commonConfig, {
devtool: 'source-map',
output: {
path: helpers.root('dist'),
publicPath: '/',
filename: '[name].[hash].js',
chunkFilename: '[id].[hash].chunk.js'
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
new webpack.optimize.UglifyJsPlugin({ // https://github.com/angular/angular/issues/10618
mangle: {
keep_fnames: true
}
}),
new ExtractTextPlugin('[name].[hash].css'),
new webpack.DefinePlugin({
'process.env': {
'ENV': JSON.stringify(ENV)
}
}),
new webpack.LoaderOptionsPlugin({
htmlLoader: {
minimize: false // workaround for ng2
}
})
]
});

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.

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