Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possibility to specify custom pattern for the classname #6

Open
mightyaleksey opened this issue Aug 5, 2015 · 30 comments
Open

Possibility to specify custom pattern for the classname #6

mightyaleksey opened this issue Aug 5, 2015 · 30 comments

Comments

@mightyaleksey
Copy link
Member

Nice that already works for me. Does this already has an option to define how class names are generated? Basically I’d need to get the same class names as with webpack using ?localIdentName='[local]--[hash:base64:5]' or a different variation of the setting.
by @maxhoffmann

I'm not sure whether this fits in here, but for those who use webpack with css-loader/locals: I forked css-loader and added a new parameter for localIdentName: sourceHash. This generate a hash from the localName and the source code, so the class names are stable.
https://github.com/Aluxian/css-loader
by @aluxian

@mightyaleksey mightyaleksey changed the title Possibility to specify custom classname pattern Possibility to specify custom pattern for the classname Aug 5, 2015
@maxhoffmann
Copy link

Thanks for reopening this issue here. How does the css modulde require hook currently generate the class names? Maybe I can configure webpack to generate the class names differently so that universal usage is possible.

I wonder if you can provide any information about modules which you use?

We currently use React and webpack to compile JS, but Stylus for CSS. We have very strict naming conventions so that all styles are scoped by manually prefixing each class name with its unique component name. A gradual refactoring to use css-modules would therefore be possible without big rewrites. There is more to read about the setup of the current project I’m working on here if you are interested. Hope this answers your question.

@mightyaleksey
Copy link
Member Author

Sorry for the delay. Require-hook is pretty simple. It is a small wrapper for the PostCSS package and its plugins.

PostCSS parses the source CSS file, then runs some transformations, implemented with the plugins. They are - postcss-modules-extract-imports, postcss-modules-local-by-default and postcss-modules-scope. After that it runs the private plugin to extract tokens from the CSS node tree, which you usually get from require function.
Back to your question I think postcss-modules-scope plugin generates resulting class names. It uses relative paths from the root directory (current specified with the root option or process.cwd() by default) to the source CSS file. So in order to implement this feature we have to start from updating that plugin.

By the way, I haven't checked it out, but looks like it is possible to specify the custom function for generating class names: https://github.com/css-modules/postcss-modules-scope/blob/master/src/index.js#L33
The default implementation is here: https://github.com/css-modules/postcss-modules-scope/blob/master/src/index.js#L159-L162

I still haven't checked how webpack generates class names, but in my opinion your request doesn't require much work.

@mightyaleksey
Copy link
Member Author

As a quick solution you can try to specify custom plugins for the require-hook with use option.
You can try something like this:

var ExtractImports = require('postcss-modules-extract-imports');
var LocalByDefault = require('postcss-modules-local-by-default');
var Scope = require('postcss-modules-scope');
var cssHook = require('css-modules-require-hook');

function generateScopedName(exportedName, path) {/* your code here */}

var scopeInstance = new Scope({generateScopedName: generateScopedName});

cssHook({
  use: [
    ExtractImports,
    LocalByDefault,
    scopeInstance
  ]
});

@mightyaleksey
Copy link
Member Author

I'm making a small demo project to show a possible way of css-modules usage in the universal apps. So, if you are interested, you can try css-modules on practice with https://github.com/sullenor/todos

@pocketjoso
Copy link

@sullenor I'm trying to figure out a good way to make use of this for a universal render with webpack. I tried your quick example above, but it doesn't work out of the box, and I'm not familiar with postcss plugins..

When Scope is required/imported it seems to self execute (without options), whereas the scopeInstance you create somehow doesn't seem to get used (just based on simple log debugging)? Do you know how to make it work in this context? Maybe @sokra knows better?

Cheers

@mightyaleksey
Copy link
Member Author

@pocketjoso I'm not good with webpack itself :) In the latest version, I added another option generateScopedName, so you don't need to work with plugins manually.

I assume you should set up the same root directories for webpack and require hook. For the hook, you can use rootDir. So, your declaration will be smthing like that:

var cssHook = require('css-modules-require-hook');

cssHook({
  generateScopedName: function (exportedName, path) { ... },
  rootDir: process.cwd() // by default or you can change it manually
});

May be, if you are familiar to the css-loader, which is used by webpack (I assume it generates names), you should use it to set up function generateScopedName.

Unfortunately, I don't know anymore. Hope that helps.

@mightyaleksey
Copy link
Member Author

Here is an example of generateScopedName function, which is used by default in the plugin: https://github.com/css-modules/postcss-modules-scope/blob/master/src/index.js#L159-L162

@mightyaleksey
Copy link
Member Author

@joeybaker Hey, Joey! Can you tell me if you have tried to use require hook with webpack? Please, share your experience :)

@pocketjoso
Copy link

Thanks for quick reply, but I'm still having exactly the same issue:

import cssModuleRequireHook from 'css-modules-require-hook'

function generateScopedName (exportedName, path) {
  // this is never called... the default generateScopedName continues to be used..
  console.log('generateScopedName called..')
  let [, fileName] = path.match(/(\w*)\.css$/)
  return fileName + '__' + exportedName
}
cssModuleRequireHook({
  generateScopedName
})

@mightyaleksey
Copy link
Member Author

@pocketjoso What version do you use? I assume it should be 1.0.4.

@pocketjoso
Copy link

@sullenor Yes.

@mightyaleksey
Copy link
Member Author

@pocketjoso okay, I think you can it in another way:

// hook itself
var cssHook = require('css-modules-require-hook');
// basic plugins
var ExtractImports = require('postcss-modules-extract-imports');
var LocalByDefault = require('postcss-modules-local-by-default');
var Scope = require('postcss-modules-scope');

// specify your custom function
function generateScopedName(exportedName, path) {/* your code here */}

// create an instance of the "postcss-modules-scope" plugin,
// so you can manually pass the options to it
var scopeInstance = new Scope({generateScopedName: generateScopedName});

cssHook({
  // setting custom plugins list
  use: [
    ExtractImports,
    LocalByDefault,
    scopeInstance // using plugin instance
  ]
});

I added comments to the code, hope that helps.

@pocketjoso
Copy link

@sullenor I already tried that code, as I wrote in my earlier post.

In dist/index.js you're self executing buildOptions(); before I've had a chance to pass them in.. this is what I commented on earlier - your module is setting up when I require it , not when I call it... as well as when I call it.

@mightyaleksey
Copy link
Member Author

@pocketjoso Have you tried to import any CSS file in runtime? I think, that should work:

var hook = require('css-modules-require-hook');
hook({generateScopedName: function (c, p) { console.log(c, p); return c; }});

console.log(require('./test.css'));

@pocketjoso
Copy link

Nope, doesn't use the generatedScopeName func I pass in; uses the default one. It seems to me that only the first time when you set the plugins on postscss in the load function in dist/index.js something actually happens, and the other times it's ignored.

@mightyaleksey
Copy link
Member Author

Can you share your example? Sounds odd to me

@pocketjoso
Copy link

Hmm so it breaks when I import css module files in the same file as this hook... Below is my isolated test case

import cssModuleRequireHook from 'css-modules-require-hook'
function generateScopedName (exportedName, path) { /* code here /* }

cssModuleRequireHook({ generateScopedName })
console.log(require('./client/components/AccountModule.css'))

// ------ if I uncomment the next line it stops working,
// i.e. my custom `generateScopedName` is not used
// import AccCss from './client/components/AccountModule.css'

So it breaks with babel and import statements somehow? Discovered that it works if I use require instead... (for final line in code above)

@pocketjoso
Copy link

I should say that in the file that requires the code above I use require('babel/register').. in case that some how messes things up here.

@mightyaleksey
Copy link
Member Author

@pocketjoso ah, i got it. Looks like you used import to fetch the CSS file earlier right after the hook function call. So something like this:

import hook from 'css-modules-require-hook';
hook({generateScopedName: function (c, p) { console.log(c, p); return c; }});

import css from './test.css';
console.log(css);

will compile to

'use strict';

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _cssModulesRequireHook = require('css-modules-require-hook');

var _cssModulesRequireHook2 = _interopRequireDefault(_cssModulesRequireHook);

var _testCss = require('./test.css');

var _testCss2 = _interopRequireDefault(_testCss);

(0, _cssModulesRequireHook2['default'])({ generateScopedName: function generateScopedName(c, p) {
    console.log(c, p);return c;
  } });

console.log(_testCss2['default']);

So, all your imports will be called first and you'll setup hook after that.

@mightyaleksey
Copy link
Member Author

@pocketjoso from http://calculist.org/blog/2012/06/29/static-module-resolution/

This import is resolved at compile time — that is, before the script starts executing. All the imports and exports of the declarative module dependency graph are resolved before execution.

Damn, I haven't thought about it. Looks like it ruins it totally :(

@mightyaleksey
Copy link
Member Author

@pocketjoso I guess, you have to write your entry point for the app in the old syntax (set up hooks and etc). Then, run the other code with modern syntax.

Similar to babel: https://babeljs.io/docs/usage/require/

@geekyme
Copy link

geekyme commented Sep 14, 2015

+1 I'm having the same issue. Would like to customize the classnames created by this hook to match those created by webpack.

@yacodes
Copy link

yacodes commented Sep 30, 2015

First of all thanks for this awesome hook, everything works fine, using babel/register and css-modules-require-hook together, but i've got a dumb question: how can i generate scoped name, like webpack generates it with [name]-[local]-[hash:base64:5] for example?

@drinchev
Copy link

drinchev commented Oct 1, 2015

By the way if I combine use: [ require('cssnext')() ], with generateScopedName: function (c, p) { console.log(c, p); return c; } I don't have any output.

On the other hand if the only option is generateScopedName works good.

@mightyaleksey
Copy link
Member Author

@canvaskisa Hi, I haven't worked with webpack a lot, but I managed to make a small demo, which can generate class names in the same way as webpack does. It looks a bit aweful, but it works :)
https://github.com/sullenor/universal-demo/blob/master/server.js#L10-L22

Currently it works with [name]__[local]___[hash:base64:5], but I think you can adjust it.

@mightyaleksey
Copy link
Member Author

@drinchev Hi, yeah. I think it looks a bit odd, but generateScopedName is a short alias for the option from the scope plugin. So, when you use the use option, you should provide the PostCSS pipeline with all necessary plugins and you need to pass it manually to the scope plugin. Like in the example above.

So, your set up should look like similar to this:

var hook = require('css-modules-require-hook');
var Scope = require('postcss-modules-scope');

// specify your custom function
function generateScopedName(exportedName, path) {/* your code here */}

// create an instance of the "postcss-modules-scope" plugin,
// so you can manually pass the options to it
var scopeInstance = new Scope({generateScopedName: generateScopedName});

hook({
  use: [
    // custom
    require('postcss-custom-selectors'),
    // css-modules
    require('postcss-modules-extract-imports'),
    require('postcss-modules-local-by-default'),
    scopeInstance
  ]
});

@mightyaleksey
Copy link
Member Author

Good news, it is possible now to specify string pattern for the generateScopedName option and it is similar to webpack's https://github.com/webpack/css-loader#local-scope

So, you can write now:

var hook = require('css-modules-require-hook');

hook({
  generateScopedName: '[name]__[local]___[hash:base64:5]'
});

Works with the latest version of the loader and 2.x version of the require hook. Hope that helps.

@geekyme
Copy link

geekyme commented Nov 5, 2015

Woot !!

On 5 Nov 2015, at 4:30 AM, Alexey Litvinov [email protected] wrote:

Good news, it is possible now to specify string pattern for the generateScopedName option and they similar to webpack's https://github.com/webpack/css-loader#local-scope

So, you can write now:

var hook = require('css-modules-require-hook');

hook({
generateScopedName: '[name][local]_[hash:base64:5]'
});
Works with the latest version of the loader and 2.x version of the require hook. Hope that helps.


Reply to this email directly or view it on GitHub.

@maxhoffmann
Copy link

Awesome! Thanks for working on this!

@bstst
Copy link

bstst commented Mar 14, 2016

If anyone stumbles here with a problem of hook generated classnames not matching the webpack css modules generated classnames -- use the 'rootDir' option. Works like a charm, my classnames are now matching.

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

No branches or pull requests

7 participants