Skip to content

Instantly share code, notes, and snippets.

@developit
Last active February 4, 2022 17:15
Show Gist options
  • Star 49 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save developit/3436591071d6b8f12dc87ca53c20dfa6 to your computer and use it in GitHub Desktop.
Save developit/3436591071d6b8f12dc87ca53c20dfa6 to your computer and use it in GitHub Desktop.
Inline Webpack CSS Modules classNames, reducing bundle size. https://npm.im/constant-locals-loader

constant-locals-loader for Webpack

This loader optimizes the output of mini-css-extract-plugin and/or css-loader, entirely removing the potentially large CSS classname mappings normally inlined into your bundle when using CSS Modules.

Run npm install constant-locals-loader, then make these changes in your Webpack config:

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
+         'constant-locals-loader',
          {
            loader: MiniCSSExtractPlugin.loader,
            options: {
+             esModule: true,
            },
          },
          {
            loader: 'css-loader',
            options: {
              modules: true,
+             localsConvention: 'camelCaseOnly',
+             esModule: true,
            },
          },
        ],
      },
    ],
  },
  plugins: [new MiniCSSExtractPlugin({})],
};
/**
* Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// This tweak allows Webpack to completely dissolve intermediary className mappings.
// The hashed/prefixed versions of imported classNames (style.foo) get inlined where they are used.
// Example output improvement: https://gist.github.com/64b04c7d8fff564e3047226493597cc8
module.exports = function(code) {
return code.replace(/export\s+default\s*(\{[^}]+\})\s*;?/, (s, json) => {
try {
const obj = JSON.parse(json);
let out = '';
for (let key in obj) {
// generated CSS classes don't contain characters that require escapement
let val = obj[key];
out += `export const ${key} = "${val}";`;
}
return out;
} catch (e) {
return s;
}
});
};
{
"name": "constant-locals-loader",
"version": "0.1.0",
"main": "constant-locals-loader.js",
"repo": "gist:3436591071d",
"homepage": "https://gist.github.com/developit/3436591071d6b8f12dc87ca53c20dfa6",
"scripts": { "prepack": "mv *constant-locals-loader.md README.md", "postpack": "mv README.md *constant-locals-loader.md" },
"author": "Jason Miller <jason@developit.ca>",
"license": "Apache-2.0"
}
@shakyShane
Copy link

this looks great, but fails when an exported classname uses a js keyword, like .switch

@DavidWells
Copy link

This is super cool. Thanks for creating!

Perhaps the const creation could check if reserved JS keywords are being used like switch, function etc. and throw if a classname clashes with this technique. Granted class names like .switch, .var, .let are likely quite rare

@SinimaWath
Copy link

SinimaWath commented Mar 14, 2020

Hi, again ;) Your code have little bug. Which produce extra ;. I have fixed it in my forked version https://gist.github.com/SinimaWath/8000f919bd53621b6ecf6706ffb9076e#file-constant-locals-loader-js-L18

by adding a ; to regexp

@developit
Copy link
Author

Good catch @SinimaWath! I've updated the code and will publish a new version shortly.

@danieljuhl
Copy link

danieljuhl commented Mar 23, 2020

@developit this is really nice. I did face an issue though. I have a project, in which we combine all of our CSS using cacheGroups, to avoid splitting it in lots of small chunks, as most of our CSS is the same for all chunks. But when using with this loader, it seems like the loader does nothing - if I remove the cacheGroup, everything works as expected, but we end up with multiple .css files. Is this working by design, or something that could/should be addressed?

        cacheGroups: {
          styles: {
            priority: 0,
            test: /\.(sc|c|le)ss$/,
            chunks: 'all',
            name: 'common'
          }
        }

@developit
Copy link
Author

@danieljuhl - interesting. That seems like a bug, though I am not sure it is a bug in this loader but maybe ModuleConcatenationPlugin?

@danieljuhl
Copy link

@developit I found another possible issue. As the loader outputs const it breaks bundles for legacy browsers, as the const is not translated to var. It happens if the application code does not consume all classes in a way so that the definition can be completely removed. By using export var ... the bundle works well in legacy browsers, and the loader still does it magic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment