Webpack-compiled CSS file is including Javascript variables and functions - javascript

We use a simple application of webpack/mix:
mix.js('resources/js/app.js', 'public/js')
.js('resources/js/cpg.js', 'public/js')
.js('resources/js/editor.js', 'public/js')
.copy('resources/js/ckeditor/dist', 'public/js/editor')
.sass('resources/sass/app.scss', 'public/css')
.sass('resources/sass/cpg.scss', 'public/css')
With webpack.config.js:
module.exports = {
resolve: {
alias: {
'#': path.resolve('resources/js'),
},
},
// https://webpack.js.org/configuration/entry-context/
entry: './resources/js/editor.js',
// https://webpack.js.org/configuration/output/
output: {
path: path.resolve(__dirname, 'public/js/editor'),
filename: 'editor.js'
},
module: {
rules: [
{
test: /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/,
use: ['raw-loader']
},
{
test: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
use: [
{
loader: 'style-loader',
options: {
injectType: 'singletonStyleTag',
attributes: {
'data-cke': true
}
}
},
{
loader: 'postcss-loader',
options: styles.getPostCssConfig({
themeImporter: {
themePath: require.resolve('#ckeditor/ckeditor5-theme-lark')
},
minify: true
})
}
]
}
]
},
// Useful for debugging.
devtool: 'source-map',
// By default webpack logs warnings if the bundle is bigger than 200kb.
performance: { hints: false }
}
Prior to the addition of ckeditor, we had no troubles. But now that ckeditor has been added, the following JS now appears in our compiled cpg.css file:
function webpackContext(req) {
var id = webpackContextResolve(req);
return __webpack_require__(id);
}
function webpackContextResolve(req) {
if(!__webpack_require__.o(map, req)) {
var e = new Error("Cannot find module '" + req + "'");
e.code = 'MODULE_NOT_FOUND';
throw e;
}
return map[req];
}
webpackContext.keys = function webpackContextKeys() {
return Object.keys(map);
};
webpackContext.resolve = webpackContextResolve;
module.exports = webpackContext;
webpackContext.id = "./node_modules/moment/locale sync recursive ^\\.\\/.*$";
Obviously, this is a problem. JS code does not belong in CSS files, and it trips up our SonarCloud quality gate (for good reason) so we can't deploy anything that's been compiled unless we manually edit the compiled files. Which mostly defeats the purpose of having them.
Further backstory: the section of our project that uses CKEditor was completed by a contractor. So, all of this was merged into our project before we saw that compiled files were improper. The contractor is no longer with the company, so I'm trying to debug on my own and getting nowhere. It seems to be an exceedingly rare bug for Webpack to place JS code in a CSS file.
Progress update: Removing the ckeditor references has no impact. The Webpack just seems to be broken now. Comprehensive node_modules re-install had no effect. It's just broken.
Issue appears to be a copy of https://github.com/laravel-mix/laravel-mix/issues/1976. Upgrading to Mix 6 creates an absolutely absurd amount of problems for my project, so this will just go unresolved.
Followed the instructions here: https://github.com/laravel-mix/laravel-mix/issues/2633#issuecomment-802023077 I was able to resolve the problem.

Might be related to this https://github.com/ckeditor/ckeditor5/issues/8112
A solution there suggests this change
use: [
{
loader: 'style-loader',
options: {
injectType: 'singletonStyleTag',
attributes: {
'data-cke': true
}
}
},
'css-loader', // added this line
{
loader: 'postcss-loader',
options: styles.getPostCssConfig({
themeImporter: {
themePath: require.resolve('#ckeditor/ckeditor5-theme-lark')
},
minify: true
})
}
]

Related

Webpack-dev-server HMR not working with multiple entry points

today I've noticed a strange bug (or I am to dumb?) with my webpack-dev-server.
I've got a Spring Boot App with thymleaf templates. Some pages may only load one others may have more than one js-file:
// main.js
import "../style.scss";
single.html:
<body>
<script th:src="#{/myapp/js/main.js}"></script>
</body>
multiple.html
<body>
<script th:src="#{/myapp/js/main.js}"></script>
<script th:src="#{/myapp/js/other.js}"></script>
</body>
I've splitted my config into a dev, production and common part:
webpack.common.js:
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: {
main: path.resolve(__dirname + "/src/main/js/main.js"),
other: path.resolve(__dirname + "/src/main/js/other.js"),
},
output: {
path: path.resolve(__dirname, "./src/main/resources/static/myapp"),
filename: "js/[name].js",
},
module: {
rules: [
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
"sass-loader",
],
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
// prettier-ignore
["#babel/preset-env", {
corejs: "3.6.4",
// debug: true,
useBuiltIns: "usage"
}
],
],
},
},
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name].css",
chunkFilename: "css/[name].css",
}),
],
};
webpack.dev.js
const common = require("./webpack.common");
const { merge } = require("webpack-merge");
module.exports = merge(common, {
mode: "development",
devtool: "inline-source-map",
devServer: {
proxy: {
"/": "http://localhost:8081",
},
port: 8083,
devMiddleware: {
publicPath: "/myapp",
},
},
});
The strange behaviour: If I'm editing files for the page which only a single script has been loaded (single.html), changes are applied immediately. For example changing the background color in the css file is displayed without pagereload. If I'm editing a page where multiple scripts (entry points) are used this is not working anymore. My dev-console logs the following:
[HMR] Update failed: Loading hot update chunk global failed.
(missing: http://localhost:8083/myapp/main.618757b0411fc5552e94.hot-update.js)
The first entry point / chunk (main.js) cannot be loaded, caused by the hash? I need to manually refresh the whole page, to apply changes. I've already searched for solutions and tried to apply this tip
optimization: {
runtimeChunk: {
name: 'single',
},
}
However my dev console does not log any HMR output anymore and nothing happens. It seems like HMR has stopped working in my browser. Webpack is running and bundling it correctly!
Any ideas? Thanks so far and apologizes for this wall of text.
The optimization.runtimeChunk option should be true or 'single' and not an object with name:
optimization: {
runtimeChunk: 'single',
},
As the documentation explains this is an alias for:
optimization: {
runtimeChunk: {
name: 'runtime',
},
},
Also, you'll want to include the runtime.js file on the page, however you need to do that for your set up. It should only be served in the dev environment (so for example, if NODE_ENV is "development").
In my case I have production, server-side script files alongside webpack-dev-server, and needed this option enabled so it would correctly serve runtime.js from the manifest.json:
devServer: {
devMiddleware: {
writeToDisk: true,
},
}

Webpack. Different rules for different entry points with similar test-mask

We have a project with old-good-smarty-part and react-part.
We bundle react-part files with webpack.
We want to bundle non-react-part files with webpack too, but this files are a bit upper than webpack in project-tree. And here is a problem: we need to bundle them and place result to grand-grand-parent directory... So, how to split rules for this entry-point?
const ExtractJS = new ExtractTextPlugin({
filename: "../../[name].min.js"
});
module.exports = {
entry: {
reactApp: ["./src/entries/reactApp/index.tsx"],
// JS -- we need to place it "upper" than webpack directory in project
"scripts/oldStuff": ["./src/entries/oldStuff/index.js"]
},
module: {
rules: [
{
test: /\.js?$/,
loader: "babel-loader",
exclude: /node_modules/,
options: {
cacheDirectory: true,
presets: [["es2015", { modules: false }], "stage-2", "react"],
plugins: ["transform-node-env-inline"],
env: {
development: {
plugins: ["react-hot-loader/babel"]
},
targets: {
browsers: ["last 2 versions", "ie >= 10"]
}
}
}
},
{
test: /\.js?$/,
include: [/(.*?)scripts(.*?)/, /(.*?)lib(.*?)/],
use: ExtractJS.extract({
use: {
loader: "raw-loader"
}
})
}
}
}
}
was tryed to solve this with include and exclude options, but this looks not good -- we got "cross-entry" files (something like /lib/foo/bar.js for react part and /lib/foo/boar.js for non-react part) and its about 15-20 files...
And here is a main question: is it possible to exclude entry-point for one rule and include it for another one (with exception of other entry-points)?

Webpack 2: shimming like RequireJS for jQWidgets?

I’m migrating from a RequireJS project to Webpack.
The latter is new to me, I’m using this as a learning exercise.
In RequireJS I could register stuff like this:
shim: {
'jqxcore': {
exports: "$",
deps: ["jquery"]
},
'jqxtree': {
exports: "$",
deps: ["jquery", "jqxcore"]
},
'jqxbutton': {
exports: "$",
deps: ["jquery", "jqxcore"]
},
'jqxsplitter': {
exports: "$",
deps: ["jquery", "jqxcore"]
},
'jqxmenu': {
exports: "$",
deps: ["jquery", "jqxcore"]
}
}
and then just require “jqxsplitter” for example like so:
import "jqxsplitter"
and stuff would be correctly registered and loaded.
Now I was looking at a couple of guides/tutorials/takes I found on migrating from RequireJS to Webpack, such as this one and this one.
So following those insights I’m trying something like this in my webpack.config.js:
"use strict";
// Required to form a complete output path
const path = require("path");
// Plagin for cleaning up the output folder (bundle) before creating a new one
const CleanWebpackPlugin = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require("webpack");
// Path to the output folder
const bundleFolder = "./wwwroot/";
// Path to the app source code
const appFolder = "./app/";
module.exports = {
// Application entry point
entry: {
main: appFolder + "index.ts",
vendor: [
"knockout",
"jquery",
"jqxcore"
],
jqxsplitter: "jqxsplitter"
},
// Output file
output: {
filename: "[name].js",
chunkFilename: "[name].js",
path: path.resolve(bundleFolder)
},
module: {
rules: [{
test: /\.tsx?$/,
loader: "ts-loader",
exclude: /node_modules/
}, {
test: /\.html?$/,
loader: "html-loader" //TODO: file-loader?
}],
loaders: [{
test: /jqxcore/,
loader: "imports?jquery!exports?$"
}, {
test: /jqxsplitter/,
loader: "imports?jquery,jqxcore!exports?$"
}]
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
alias: {
"jqxcore": "jqwidgets-framework/jqwidgets/jqxcore",
"jqxsplitter": "jqwidgets-framework/jqwidgets/jqxsplitter"
}
},
plugins: [
new CleanWebpackPlugin([bundleFolder]),
new HtmlWebpackPlugin({
filename: "index.html",
template: appFolder + "index.html",
chunks: ["main", "vendor"]
}),
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
filename: "vendors.js",
minChunks: Infinity
})
],
devtool: "source-map"
};
the relevant part (I assume) being
module: {
loaders: [{
test: /jqxcore/,
loader: "imports?jquery!exports?$"
}, {
test: /jqxsplitter/,
loader: "imports?jquery,jqxcore!exports?$"
}]
},
It’s pretty clear how the syntax of “imports/exports” is supposed to be the equivalent of RequireJS’ “deps” and “exports”.
However when I do this in my index.ts file (app root):
import "jqwidgets-framework/jqwidgets/jqxsplitter";
I get the “jqxBaseFramework is undefined” error when running my app.
I’ve found references to this error on the forums of jQWidgets, but none of the answers seem to REALLY tackle the issue or include things like the AOT compilation, which doesn’t apply to my situation because I’m not using Angular.
I've posted this same question on the jQWidges forums, but so far no actual answer (going on two weeks now), only a single generic answer saying I should load jqxcore.js before jqxwhateverplugin.js.
Well yes, obviously, that's what I'm trying to accomplish using the shimming after all.
Any ideas?
Well I ended up deep diving and figuring it out for myself.
Here's the solution should anyone find themselves in the same or a similar boat.
If you beautify the jQWidgets script files jqxcore.js, you'll see it creates a what would normally be a global variable called "jqxBaseFramework", which will of course never be exposed globally, only within its own module. And there lies the problem.
The solution is to use this configuration:
module: {
rules: [{
test: /jqxcore/,
use: "exports-loader?jqxBaseFramework"
}, {
test: /jqxknockout/,
use: ["imports-loader?jqxBaseFramework=jqxcore,ko=knockout", "exports-loader?jqxBaseFramework"]
}, {
test: /jqxsplitter/,
use: "imports-loader?jqxBaseFramework=jqxknockout"
}]
},
resolve: {
...
alias: {
"knockout": "knockout/build/output/knockout-latest",
"jqxcore": "jqwidgets-framework/jqwidgets/jqxcore",
"jqxknockout": "jqwidgets-framework/jqwidgets/jqxknockout",
"jqxsplitter": "jqwidgets-framework/jqwidgets/jqxsplitter"
}
},
I guess once it clicks, this all makes sense.
The jqxcore module will now export its jqxBaseFramework variable with the same name.
I added in knockout support while at it.
jqxknockout expects two global variables to work normally: ko (knockout) and jqxBaseFramework.
So now we tell webpack that whenever jqxknockout is loaded, it should load the jqxcore module and assign its export to a module-local variable called "jqxBaseFramework" and load the knockout module and assign its export to a module-local variable called "ko".
This effectively equates to prepending the following code to the jqxknockout.js script:
var jqxBaseFramework = require("jqxcore");
var ko = require("knockout");
The script can now execute again because those two variables are found.
I added the export loader to export the same, but now processed/augmented jqxBaseFramework variable from jqxknockout.
jqxSplitter normally only needs jqxCore to work, but I want to use it with knockout, always. So instead of importing jqxBaseFramework from jqxCore for jqxSplitter, I'm getting it from jqxKnockout, so all the pieces are in place.
So now when I add this code to whatever file I'm in:
import "jqwidgets-framework/jqwidgets/jqxsplitter";
Webpack will require jqxknockout and its export for it, being jqxBaseFramework, which in turn will require jqxcore and knockout et voilà, the whole thing is wired up beautifully.
Hope this helps someone!

ERROR from UglifyJs: SyntaxError: Unexpected token: operator (>)

I'm getting an error when trying to run my webpack for production.
ERROR in js/main.21dbce548a76ffc14cfb.js from UglifyJs
SyntaxError: Unexpected token: operator (>) [./~/tmi.js/lib/utils.js:3,0][js/main.21dbce548a76ffc14cfb.js:3529,20]
utils.js:3,0 (which is the same as in my minified js) is:
// Return the second value if the first value is undefined..
get: (obj1, obj2) => { return typeof obj1 === "undefined" ? obj2 : obj1; },
So I assume from that the error is thrown because it's reading ES6 but it doesn't understand ES6? (The arrow function)
I don't see what's going wrong here, this is my webpack.config.js
// changed some loader syntax after reading
// https://webpack.js.org/how-to/upgrade-from-webpack-1/
const path = require(`path`);
const webpack = require(`webpack`);
const {UglifyJsPlugin} = webpack.optimize;
const CopyWebpackPlugin = require(`copy-webpack-plugin`);
const ExtractTextWebpackPlugin = require(`extract-text-webpack-plugin`);
const configHtmls = require(`webpack-config-htmls`)();
const extractCSS = new ExtractTextWebpackPlugin(`css/style.css`);
// change for production build on different server path
const publicPath = `/`;
// hard copy assets folder for:
// - srcset images (not loaded through html-loader )
// - json files (through fetch)
// - fonts via WebFontLoader
const copy = new CopyWebpackPlugin([{
from: `./src/assets`,
to: `assets`
}], {
ignore: [ `.DS_Store` ]
});
const config = {
entry: [
`./src/css/style.css`,
`./src/js/script.js`
],
resolve: {
// import files without extension import ... from './Test'
extensions: [`.js`, `.jsx`, `.css`]
},
output: {
path: path.join(__dirname, `server`, `public`),
filename: `js/[name].[hash].js`,
publicPath
},
devtool: `sourcemap`,
module: {
rules: [
{
test: /\.css$/,
loader: extractCSS.extract([
{
loader: `css`,
options: {
importLoaders: 1
}
},
{
loader: `postcss`
}
])
},
{
test: /\.html$/,
loader: `html`,
options: {
attrs: [
`audio:src`,
`img:src`,
`video:src`,
`source:srcset`
] // read src from video, img & audio tag
}
},
{
test: /\.(jsx?)$/,
exclude: /node_modules/,
use: [
{
loader: `babel`
},
{
loader: `eslint`,
options: {
fix: true
}
}
]
},
{
test: /\.(svg|png|jpe?g|gif|webp)$/,
loader: `url`,
options: {
limit: 1000, // inline if < 1 kb
context: `./src`,
name: `[path][name].[ext]`
}
},
{
test: /\.(mp3|mp4)$/,
loader: `file`,
options: {
context: `./src`,
name: `[path][name].[ext]`
}
}
]
},
plugins: [
extractCSS,
copy
]
};
if(process.env.NODE_ENV === `production`){
//image optimizing
config.module.rules.push({
test: /\.(svg|png|jpe?g|gif)$/,
loader: `image-webpack`,
enforce: `pre`
});
config.plugins = [
...config.plugins,
new UglifyJsPlugin({
sourceMap: true, // false returns errors.. -p + plugin conflict
comments: false
})
];
}
config.plugins = [...config.plugins, ...configHtmls.plugins];
module.exports = config;
OP's error is from UglifyJs, as is solved in the accepted answer, some people to this page may get the error from babel, in which case, fix it with: add "presets": ["es2015"] either to the options.presets section of babel-loader, or to .babelrc.
UglifyJs2 has a Harmony branch which accepts ES6 syntax to be minified. At this moment, you need to create a fork of webpack and point webpack to that fork.
I recently answered a couple of similar questions. Please have a look at #38387544 or #39064441 for detailed instructions.
In my case I was using webpack version 1.14
I got help from git ref
steps:
install yarn add uglifyes-webpack-plugin (and removed yarn remove uglifyjs-webpack-plugin)
then install yarn add uglify-js-es6
In webpack.config.js file change new webpack.optimize.UglifyJsPlugin to
new UglifyJsPlugin
then I was able to build. Thanks

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