I have a few SCSS theme files I want to extract to CSS files and later load them into the page. I want to be able to use contenthash for long term caching.
Since I'm using Webpack 4, I am also using mini-css-extract-plugin. I started down the path of creating a splitChunks in my webpack config.
// webpack.config.js
module.exports = {
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: "[name].[contenthash].css",
chunkFilename: "[id].[contenthash].css"
})
],
optimization: {
splitChunks: {
cacheGroups: {
'vendor': {
// custom commons chunk for js
},
'theme-a': {
test: /theme-a.\scss/,
},
'theme-b': {
test: /theme-b.\scss/,
},
// more themes
}
}
}
module: {
rules: [
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader"
]
}
]
}
}
I've then tried dynamically importing the css in my app:
// app.js
class App extends React.Component {
// constructor
login(themeName) {
import(/* webpackChunkName: "`${themeName}`" */ `./path/to/${themeName}.scss`).then(theme => {
// do something with `theme`
}
}
// other stuff
}
I need to be able to load that css file dynamically in login() and I'm just not sure how to reference it when it has a generated [contenthash].
TLDR: Is there a good way to both extract css and import the referenced CSS bundle to lazy load later? I'm not tied to mini-css-extract-plugin.
Edit: Created a mini-css-extract-plugin issue here.
My solution ended up using extract-text-webpack-plugin. My config now looks like this:
// webpack.config.js
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ExtractThemeA = new ExtractTextPlugin({ filename: 'themeA.[hash].css', allChunks: true});
module.exports = {
plugins: [
ExtractThemeA,
// more plugins for other css files
],
optimization: {
splitChunks: {
cacheGroups: {
// Note: No changes to splitChunks
'vendor': {
// custom commons chunk for js
}
}
}
module: {
rules: [
{
test: /theme-a\.scss$/,
use: ExtractThemeA.extract([ 'css-loader', 'sass-loader' ])
},
// more module rules for each css file needed
]
}
}
Then, these chunks are available by file name in my HtmlWebpackPlugin:
<!-- HtmlWebpackPlugin Template -->
<script>
// provides me with an array of file name strings
var themesManifest = <%= htmlWebpackPlugin.files.css %>
</script>
Sorry for my miss understanding,
You could probably just make two different scss-files and import them as needed. theme.scss admin.scss or like so
This is how I am doing scss in React right now
In App.js
import styles from '../../stylesheet/main.scss'
// could be as well
import styles1 from '../../stylesheet/theme.scss' // some file
import styles2 from '../../stylesheet/admin.scss' // some other file
const App = () => {
<div className={styles.action_feed} >{ content }</div>
}
In main.scss
.action_feed {
position: fixed;
width: 350px;
height: auto;
max-height: 100%;
max-width: 100%;
top: 0;
left: 0;
z-index: 9999;
}
I think you could just as well do it like so
const themeName = 'main'
import(`./stylesheet/${themeName}.scss`, (css) => {
// meaby set this to state? not quite sure how should properly handle
// dynamically imported css
this.setState({ css: css.action_feed })
// or possible this way. I have done some non-React dom manipulation
// this way before
document.querySelector('body').classList.add(css.action_feed)
})
<div className={this.state.css}>{ content }</div>
You should probably check out React's new Refs API as well. It might give you some nice flexibility for giving className-attr to required element.
Having set to splitChunks.chunks to all works though i think in this case anyway
Related
I have an MPA app, where vue.js is used as a part of the application. I have a very simple test set up, here:
relevant parts of my template
....
<div id='app-basket-checkout'>
<h1>Better Be Here</h1>
</div>
....
pageBasketCheckout.js (essentially my app.js)
import Vue from 'vue'
import AppBasketCheckout from './BasketCheckout.vue'
import './dummyScss.css'
Vue.config.productionTip = false
new Vue({
render: h => h(AppBasketCheckout)
}).$mount('#app-basket-checkout')
component
<template>
<div id="app-basket-checkout">
{{msg}}
</div>
</template>
<script>
export default {
name: 'AppBasketCheckout',
components: {
},
data() {
return {
msg: 'Hello'
}
}
}
</script>
<style scoped>
</style>
So the above code renders just fine in my front end. I end up with an extra div that has hello printed inside, well done.
However when I add css to the style tag:
<template>
<div id="app-basket-checkout">
{{msg}}
</div>
</template>
<script>
export default {
name: 'AppBasketCheckout',
components: {
},
data() {
return {
msg: 'Hello'
}
}
}
</script>
<style scoped>
body {
font-family: Arial, Helvetica, sans-serif;
line-height: 1.4;
}
</style>
This produces this error in chrome:
Uncaught Error: Cannot find module './BasketCheckout.vue?vue&type=style&index=0&id=2711cf65&scoped=true&lang=css&'
at webpackMissingModule (VM45512 BasketCheckout.vue:4)
at eval (VM45512 BasketCheckout.vue:4)
at Module../src/BasketCheckout.vue (pageBasketCheckout.bundle.js:40)
at __webpack_require__ (index.bundle.js:4312)
at eval (pageBasketCheckout.js:3)
at Module../src/pageBasketCheckout.js (pageBasketCheckout.bundle.js:29)
at __webpack_require__ (index.bundle.js:4312)
at checkDeferredModulesImpl (index.bundle.js:4453)
at webpackJsonpCallback (index.bundle.js:4435)
at pageBasketCheckout.bundle.js:9
Again this error only happens when adding css to the component. Here is my webpack.config.js:
const path = require('path');
const webpack = require('webpack')
const glob = require('glob')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
watch: true,
context: path.resolve(__dirname, 'uniquesite/uniquesite'),
mode: 'development',
entry: {
index: {
import: ['#babel/polyfill', './src/index.js'],
// dependOn: ['babel'],
},
pageProductDetails: {
import: ['#babel/polyfill', './src/pageProductDetails.js'],
dependOn: ['index'],
},
pageBasketCheckout: {
import: ['#babel/polyfill', './src/dummyScss.scss', './src/pageBasketCheckout.js'],
dependOn: ['index']
}
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'uniquesite/uniquesite/static/uniquesite/js/'),
},
plugins: [
new VueLoaderPlugin()
],
resolve: {
alias: {
jquery: "jquery/src/jquery",
'jquery-ui': "jquery-ui-dist/jquery-ui.js",
boostrap: "bootstrap/dist/js/bootstrap.bundle.js"
}
},
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader'
},{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
},
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
}
]
}
};
You'll note I've also tried importing a dummy .css file to ensure the style loader works, as I've seen one more SO question with a similar problem that solved it that way. That didn't work for me however.
Update 1
My current thinking is that the problem has to be happening in the VueLoaderPlugin. That plugin is reponsible for splitting the script into distinct parts for template, logic, and style. It looks like the style is not actually making it into the bundle. See below.
"use strict";
eval(
"__webpack_require__.r(__webpack_exports__);
/* harmony import */
var _BasketCheckout_vue_vue_type_template_id_2711cf65___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
/*! ./BasketCheckout.vue?vue&type=template&id=2711cf65& */
\"./src/BasketCheckout.vue?vue&type=template&id=2711cf65&\"
);
/* harmony import */
var _BasketCheckout_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(
/*! ./BasketCheckout.vue?vue&type=script&lang=js& */
\"./src/BasketCheckout.vue?vue&type=script&lang=js&\"
);
Object(function webpackMissingModule() {
var e = new Error(
\"Cannot find module './BasketCheckout.vue?vue&type=style&index=0&lang=css&'\"
); e.code = 'MODULE_NOT_FOUND';
throw e;
}());
/* harmony import */
var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(
/*! !../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */
\"../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"
);
/* normalize component */
var component = (
0,_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__.default
)(
_BasketCheckout_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__.default,
_BasketCheckout_vue_vue_type_template_id_2711cf65___WEBPACK_IMPORTED_MODULE_0__.render,
_BasketCheckout_vue_vue_type_template_id_2711cf65___WEBPACK_IMPORTED_MODULE_0__.staticRenderFns,
false,
null,
null,
null)
/* hot reload */
if (false) { var api; }
component.options.__file = \"src/BasketCheckout.vue\"
/* harmony default export */
__webpack_exports__[\"default\"] = (component.exports);
//# sourceURL=webpack:///./src/BasketCheckout.vue?"
);
Scoped CSS rules only apply to the current component (and its child components' root nodes).
You are mounting your Vue instance at #app-basket-checkout, which is already inside a <body> element.
You can style <body>, but do it using a global stylesheet that is imported in your app.js, not a subcomponent.
Alternatively, you can apply a class-based style at a low level node within your Vue instance and likely deliver your desired styles.
I am creating an app in Svelte and I have a problem with styles.
I preprocess imports in style tags using the rollup.config.js file, not with svelte-preprocess, but with rollup-plugin-svelte preprocess.
I do it following the example of the official docs. https://svelte.dev/docs#svelte_preprocess
Everything works fine, in the sass return code: css.toString(); I get the css code from my imports, but the result is not added to my bundle.css, it just disappears.
What am I missing?
My rolling.config.js is:
...
...
plugins: [
svelte({
dev: !production,
css: css => {
css.write('public/build/bundle.css');
},
preprocess: {
style: async ({ content, attributes, filename }) => {
// only process <style lang="sass">
if (attributes.lang !== 'sass') return;
const { css, stats } = await new Promise((resolve, reject) => sass.render({
file: filename,
data: content,
includePaths: [
path.dirname(filename),
'./node_modules',
],
}, (err, result) => {
if (err) reject(err);
else resolve(result);
}));
return {
code: css.toString(), // this works
dependencies: stats.includedFiles
};
}
},
}),
resolve({
browser: true,
dedupe: ['svelte']
}),
...
...
In one of my .svelte files
<style lang="sass">
#import './styles/App.scss';
</style>
All other styles without the lang = "sass" attribute are not preprocessed and are added to the bundle.css file.
I'm blocked, does anyone help me?
Add emitCss: true in svelte(..) like this :
svelte({
dev: !production,
emitCss: true, // without this, <style> in components are not included in bundle
css: css => {
css.write('public/build/bundle.css')
}
}),
This will emit CSS as "files" for other plugins to process.
Use the default svelte template that renders css to add css support appropriately... For further details check https://github.com/sveltejs/template/blob/master/rollup.config.js
set bundle.css as indicated
I am using Symfony + Vue.js, and I would like to import my _variables.sccs file in all the vue components. I want to use the scss variables in the style blocks of all of my components
I know how to do it in a vue app created with vue-cli (using vue.config.js config file), but i've tried to replicate this in different ways in the webpack.config.js used by Encore... and there is no way to make it work.
This is my webpack.config.js file:
var Encore = require('#symfony/webpack-encore');
Encore
.setOutputPath('public/build/')
.setPublicPath('/build')
.addEntry('app', './assets/js/app/app.js')
.enableSingleRuntimeChunk()
.cleanupOutputBeforeBuild()
.enableBuildNotifications()
.enableSourceMaps(!Encore.isProduction())
.enableVersioning(Encore.isProduction())
// enables #babel/preset-env polyfills
.configureBabelPresetEnv((config) => {
config.useBuiltIns = 'usage';
config.corejs = 3;
})
// enables Sass/SCSS support
.enableSassLoader()
// enable Vue.js
.enableVueLoader()
.configureWatchOptions(function (watchOptions) {
watchOptions.poll = 250;
})
;
module.exports = Encore.getWebpackConfig();
Any idea?
This is how i get it working with Vue 3 and AdonisJS 5
// webpack.config.js
Encore.enableVueLoader(() => { }, {
version: 3,
runtimeCompilerBuild: false,
useJsx: false,
})
Encore.configureLoaderRule('scss', loaderRule => {
loaderRule.oneOf.forEach(rule => {
rule.use.forEach(loader => {
if (loader.loader.indexOf('sass-loader') > -1) {
loader.options.additionalData = '#import "./resources/css/_import-everywhere.scss";'
}
})
});
});
fuxinko's answers works fine, but it'll give a warning:
WARNING Be careful when using Encore.configureLoaderRule(), this is a low-level method that can potentially break Encore and Webpack when not used carefully.
You should get the same result (without warning) by using using a callback in enableSassLoader:
.enableSassLoader(options => {
options.additionalData = '#import "./resources/css/_import-everywhere.scss";'
})
Note that the semicolon is not allowed in sass files, so if you need both scss and sass compiled, things could get more difficult: I've added a possible solution for this to a similar question: https://stackoverflow.com/a/68914128/1370000
I'm also curious about this one. I found on the web the following piece of code that should be added into the Symfony Webpack's file :
.enableVueLoader(function(options) {
options.loaders.scss.forEach(loader => {
if(loader.loader === 'sass-loader') {
loader.options = {
sourceMap: true,
data: `
#import "./assets/css/common/variables";
`
}
}
});
})
But in my case this doesn't work as options is empty in the callback function.
You can make this work by using sass-resources-loader. Install this loader first by using npm for example.
npm install sass-resources-loader
And than use the .configureLoaderRule method Webpack Encore ships with.
.configureLoaderRule('scss', (loaderRule) => {
loaderRule.oneOf.forEach((rule) => {
rule.use.push({
loader: 'sass-resources-loader',
options: {
resources: [
// Change this to your _variables.scss path
path.resolve(__dirname, './assets/css/_variables.scss'),
]
},
})
})
})
I've tried to make this work myself with LESS and it took me too much time to find out how it worked. Anyway for the people using LESS you can use a different loader like the: style-resources-loader
I have used this a couple of times. To get it to work I enable the vueLoader and add the vue-style-loader :
Encore
//...
.enableVueLoader()
.addLoader({
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
data: `
#import "./assets/scss/_variables.scss";
#import "./assets/scss/_mixins.scss";
`,
includePaths: [__dirname]
},
},
]
})
The images not loading from CSS file which is used for the background but in jsx it's loading
In the CSS file, I have used like below
.main-banner {
position: relative;
height: 910px;
z-index: 1;
background: transparent url(../../static/images/banner-bg1.jpg) right top no-repeat;
}
the image URL is absolutely fine
And configuration file like this
//next.config.js
const withImages = require('next-images');
const withCSS = require('#zeit/next-css')
module.exports = withImages(withCSS({
cssLoaderOptions: {
url: false
}
}))
what is the issue actually?
Thanks
Your static files are being deployed to the web's root. Same as your compiled js and css files.
So you can access them with the following url: url(/static/images/banner-bg1.jpg)
More information about that on this github issue.
I recieved [object Module] in css prop background-image. flag esModule: false in url-loader options fixed that problem.
const withCSS = require('#zeit/next-css');
const nextConfig = withCSS({
webpack(config, options) {
config.module.rules.push({
test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
use: {
loader: 'url-loader',
options: {
limit: 100000,
esModule: false,
name: '[name].[ext]'
}
}
});
return config;
}
});
I have a minimal React component which consists of two files: button.jsx and button.less. The styles are imported and the class names are appended with a hash to make all styles local to the component.
This is great, but i'd like to have all component code in one file. Is it possible to inline the styles in jsx file without losing css modularity?
Current Code
button.jsx
import React from 'react';
import styles from './button.less'
export default class Button extends React.Component {
render() {
return <button className={styles.primary}>{this.props.text}</button>;
}
}
button.less
#import '~semantic-ui/src/definitions/elements/button.less';
.common {
composes: ui button;
}
.primary {
composes: common primary;
}
webpack.config.js (relevant bits)
module: {
loaders: [
{
test: /\.jsx$/,
loader: 'babel'
},
{
test: /\.less$/,
loader: "style!css?modules&importLoaders=1!less"
}
]
},
What i'd like to write instead
button.jsx
<style lang="less" modules>
#import '~semantic-ui/src/definitions/elements/button.less';
.common {
composes: ui button;
}
.primary {
composes: common primary;
}
</style>
import React from 'react';
export default class Button extends React.Component {
render() {
return <button className={styles.primary}>{this.props.text}</button>;
}
}
Inspired by vue.js and vue-loader.
I believe this is a duplicate of this unanswered question:
Using css-loader inline with Webpack + React
I wrote a Webpack loader for this very purpose:
https://github.com/chrisdavies/stylextract-loader
It allows you to write a single style tag per JSX file and it supports webpack CSS modules, too, if you want.
At build time, it extracts the rules from your style tag and moves them out to an external CSS file.
I should note that because it simply extracts your rules out to an external CSS file, it plays nice with SASS, autoprefixer, etc
You can use callback-loader for this. This is actualy a workaround, but it do the trick. Just implement a callback which will extract your css-code and replace it with appropriate import. For example:
webpack.config.js
var fs = require('fs');
var cssIndex = 0;
// Do not forget to create and clean temporary folder "cssTemp" before
var webpackConfig = {
...
resolve: {
alias: {
cssTemp: path.resolve('./cssTemp')
}
},
module: {
loaders: [
{ test: /\.jsx$/, loader: "callback!babel" }
]
},
callbackLoader: {
cssCallback: function(code) {
var filename = cssIndex + '.less';
cssIndex++;
// Save the css code from the callback argument
fs.writeFileSync('./cssTemp/' + filename, code);
// Return the import statement which will replace the callback statement
return 'import styles from "cssTemp/' + filename + '";';
}
}
...
};
button.jsx
import React from 'react';
cssCallback(`
#import '~semantic-ui/src/definitions/elements/button.less';
.common {
composes: ui button;
}
.primary {
composes: common primary;
}
`);
export default class Button extends React.Component {
render() {
return <button className={styles.primary}>{this.props.text}</button>;
}
}