Conditional webpack require?



// Required Components
// ==========================================================================
const ev = require('../export-vars.js');
const path = require('path');

const merge = require('webpack-merge');
const common = require('../webpack/webpack.common.js');
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');

const dv = require('./district-vars.js');

const webpackDistrictDeploy = merge(common, {

  entry: dv.entryPROD,

  devtool: 'none',

  watch: true,
  watchOptions: {
    ignored: [
      /node_modules/,
      "../src/**/**/**/**",
    ]
  },

  performance: {
    hints: "warning",
    maxEntrypointSize: 500000,
    maxAssetSize: 300000
  },

  plugins: [

    new ev.UglifyJsPlugin({
      extractComments: true
    }),

    new ev.wp.optimize.CommonsChunkPlugin({
      children: true,
    }),

    new BrowserSyncPlugin({
      host: 'localhost',
      port: 5000,
      proxy: ev.pkg.config.siteurl + '/' + ev.pkg.config.startpage,
      notify: false,
      // files: [ 
      //   ev.directory + 'main.css', 
      // ],
    }, {
        reload: true,
    }),
  ],

  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ev.ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [
            { loader: 'css-loader'},
            { loader: 'postcss-loader',
              options: {
                plugins: [
                  require('cssnano')({
                    preset: [
                      'default',
                      { discardComments: { removeAll: true } }
                    ],
                  }),
                ],
                minimize: true
              },
            },
            { loader: 'resolve-url-loader'},
            { loader: 'sass-loader',
              options: {
                sourceMap: true,
                outputStyle: 'compact',
                data: '@import "include";',
                includePaths: [
                  path.resolve(__dirname, '../src/sass/')
                ]
              }
            }
          ]
        })
      }
    ],
  },

  output: {
    path: ev.directory,
    filename: '[name].js'
  }

});

module.exports = webpackDistrictDeploy;

This is school1.js which is the basis of what loads in for “sub sites”

require("../sass/school1.scss");
require("../../build.js");
require("./all.js");

And this is build.js

// require main build files
require('./sass/main.scss'); 
require('./js/global.js');

//components
require('./components/test/index.js');

The problem I have is Sass related; school1.scss and build.js both import something called main.scss. school1.scss imports main.scss and then I update color variables, and that’s the purpose of that.

The problem is that build.js also imports main.scss. I know conditional requires aren’t really a thing in webpack. I tried to conditionally require main.scss only if “sub sites” are turned on. But it was bundling it anyway.

Is there anything I can do here? Some way to only load in main.scss once? The problem is that I’m importing it twice so my file sizes are twice as big.

I tried changing this up to add in build.js (first value in the array) but I ended up removing it because the color Sass variables weren’t updating. I think I have to keep this as just [PATHS[name]];

exports.entryPROD[name] = [path.join(__dirname, "../src/build.js"), PATHS[name]];

You mean you’re loading main.scss a second time but with certain variables set differently? Then there’s no way around including it twice I think – the SASS variables being resolved at built time, it will actually result in different CSS. What you might do though is only load it once in your main entry point, and use actual CSS variables that you can overwrite per component… e.g.

/* In your main CSS */
:root {
  --color: red;
}

p {
  color: var(--color);
}

/* In your component CSS */
.my-component {
  --color: blue;
}

This won’t work in IE though and is hard to polyfill – the solutions doing that are using JS to re-parse the CSS. I have not tried supporting CSS variables in IE myself, so I’m not sure how well that works.

Another possibility to reduce the bundle size would be refactoring your SASS to strictly separate variables (plus related mixins / placeholders) from the parts actually producing output, so you’ll only get the relevant parts generated again.

It’s only importing it twice right now because I can’t find a way to NOT have it import once, and also set color variables.

The last line of code is what I mean. I could update it to

exports.entryPROD[name] = [path.join(__dirname, "../src/build.js"), PATHS[name]];

…instead of only [PATHS[name]] BUT then the variables don’t update (presumably because they are being resolved separately). I am looking to avoid major rewrites (aka your suggestion, sorry :frowning: ) . Thank you :slight_smile: .

Hm okay, so just that I properly understand the issue: you have SASS files looking something like this:

main.scss

$color: red !default;

p {
  color: $color;
}

school.scss

$color: blue;
@import './main.scss';

And the JS files:

index.js

require('./main.scss')
require('./school')

school.js

require('./school.scss')

So you have to

  • @import './main.scss' inside scool.scss because otherwise the variables inside main.scss won’t get set, and
  • also require('./main.scss') inside index.js because school.js may or may not be part of the build.

If this is the case then you could specify the entry array accordingly:

module.exports = (env = {}) => ({
  entry: env.subsites
    ? ['./src/index.js', './src/school.js']
    : ['./src/index.js', './src/main.scss'],
  // ...
})

Then you can get the desired output with the --env flag like so:

$ yarn webpack
# main.css: p{color:red}

$ yarn webpack --env subsites
# main.css: p{color:blue}

Here’s a most minimal setup, feel free to fork if this doesn’t describe the problem accurately… it’s a bit hard to tell / reproduce with just fragments of (possibly even unrelated) code y’know. :-)

index.js calls all of the Javascript for the build. Not main.scss and school.

Your first assumption is correct. Your second is incorrect. School1.js gets called (as of right now) because that’s how main.scss ultimatey gets imported (through school1.scss).

I don’t think we are using yarn, and also I think we need to cement your understanding of how my webpack works. Do you think you understand now? This below is a message I sent to a colleague but it might plug some gaps in the knowledge of how my webpack works.

fir> st let me explain how i understand the webpack build to work

if districts arent on, then it runs webpack.production.js (this whole scenario is assuming localhost:5000 / publishing to the server). there is something in there called PATHS.main and that file determines what ultimately is run, in terms of sass/js. that resolves to build.js ultimately, which runs global.js (and all of our JS) and then runs main.scss, which obviously handles all our sass.
in district mode, each individual school has a PATHS[school1] that is NOT set to run build.js, but is set to run their respective school file. e.g. school1.js. THAT file (as it is now) is set to call school1.scss (which then imports main.scss and we override that with color variables and whatever else). this works great. However, that school1.js only called school1.scss (main). this is why school1.js files remain empty and are additive to themes, instead of a replacement (like CSS). e.g. schoo1.css gets used in PLACE of main.css, whereas school1.js is added AFTER main.js in the theme (main.js stays on)
so the issue right now, is that the components has no chance of ever going in because we call components in build.js. ultimately, if i include build.js though, then main.scss gets imported twice. it fixes EVERYTHING but it basically results in the school CSS files being double file size.
ok so what if i comment out the main.scss import in /district/sass/all.scss? well, while that fixes the issue, color variables don’t update. that’s because of build.js is using nodejs to “require” the main.scss . thats one bundle. that resolves the sass without chance to change the variables (from what i gather).
so then i tried conditionally requiring main.scss only if PATHS.main = main.scss (aka isnt a school SCSS file) but conditional requires arent really a thing in webpack for a few technical reasons. there is a webpack plugin for conditional requires but i need to check for 2 thingsthe file name, and if districts is set to true in package.json…but importing that data in is gonna be messy and i had to take a step back and think about how far i want to be changing the build.

It would work with npm just as well of course.

Well I’m a bit at a loss with those descriptions alone TBH… don’t you think you can provide a minimal setup to reproduce the issue? As for instance:

Where again is that all.scss getting included – from within school.scss? It may be clear if you know the project, but I find it quite hard to comprehend otherwise. :-/

If you can’t / don’t want to change the build, would it be an option to dynamically import the files at runtime?

If this last attempt at describing it doesn’t work, I’ll look into making something. Assume this file structure:
Two dashes = folder.
One dash = file

project folder
  --district
    -district production webpack.js
    -district config.js
  --node_modules
  --src
    -build.js
    --components
      -component1..
      -component2... etc
    --district
      --district sass
        -all.scss and other school files
      --district js
        -all.jsand other school files
    --sass
      -sass partials and main.scss which imports everything
    --js
      -js files and global.js which compiles everything in here.
  --webpack
    -webpack production
  -package.json

There are some files in the folder I didn’t mention but they are irrelevant for now.

The work flow right now is that package.json determines if districts are true or false. Let’s assume its true. That then causes district production webpack.js to run. That creates all the school sass/js files. Remember, the school JS file require()'s the SASS.

Those newly created school SASS files get compiles on its own, separate of the main.scss file. These school sass files are meant to run after main.scss so color variables can change. Then that spits out a CSS file we can then use with new color changes.

The main “site” (not the school “sites”) uses build.js, which has the global.js (all the JS) and main.scss (all the scss). It also calls in components. This is important. Notice that earlier I mentioned what school1.js imports; it’s only the school sass. So I need to get these components imported but if I require build.js in the school1.js file…then main.scss is then called TWICE (once in build.js and one in school1.scss which imports main.scss).

Does that help? Ultimately build.js resolves separately so I seemingly can’t change variables if I only have school1.js require build.js and not have school1.scss call main.scss (that way it only calls in once, follow me?)

Thanks for the clarification! Now whenever you import a SASS file from your JS, it’s going to be its own isolated SASS build, so you can’t update the variables from main.scss as require()d elsewhere. But you know that already. Also, as mentioned earlier, the generated output will not exactly be a duplicated CSS but a somewhat different one (depending on the variables being set).

So just don’t include the main.scss from your global.js; and if you’re not going to include it somewhere else, add it to the entry point array instead. Not sure what you mean with being determined by the package.json, but aforementioned environment variables would be an option – did you give that a try? Other than that, you probably have to bite the bullet and refactor your SASS so that there’s not a single entry point containing everything, but separate variables and the like from output generating modules. This way, you could at least keep the overrides inside the CSS to a minimum.

Global.js handles only Javascript. To clarify, regular Javascript. Like, it calls in treatments, accessibility code, and styled treatment Javascript code. It’s the Javascript version of main.scss (which imports all the Sass partials).

But yeah, the only other thing I could think of is to have build.js not call components via require there…but rather call a file called components.js which then calls the components in there.

That way we can still go to school1.js and require components file and we should be good. This is an idea I floated to my boss but I don’t want to act unilaterally since it’s a change to how the webpack stuff works, and we have a few projects out here that use it, so we’d need to update a few projects.

(Bit over the place here…) Package.json has an object in it which sets districts to false or true. It’s a simple variable setup the developer has to look at. What do you mean by environment variables? How would that help?