Skip to content

accumulate generated css styles for imported/required css files #16

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ npm install --save-dev babel-plugin-css-modules-transform
"npm-module-name",
"./path/to/module-exporting-a-function.js"
],
"extractCss": "./dist/stylesheets/combined.css"
}
]
]
Expand Down Expand Up @@ -116,6 +117,54 @@ and then add any relevant extensions to your plugin config:

```

## Extract CSS Files

When you publish a library, you might want to ship compiled css files as well to
help integration in other projects.

An more complete alternative is to use
[babel-plugin-webpack-loaders](https://github.com/istarkov/babel-plugin-webpack-loaders)
but be aware that a new webpack instance is run for each css file, this has a
huge overhead. If you do not use fancy stuff, you might consider using
[babel-plugin-css-modules-transform](https://github.com/michalkvasnicak/babel-plugin-css-modules-transform)
instead.


To combine all css files in a single file, give its name:

```
{
"plugins": [
[
"css-modules-transform", {
"extractCss": "./dist/stylesheets/combined.css"
}
]
]
}
```

To extract all files in a single directory, give an object:

```
{
"plugins": [
[
"css-modules-transform", {
"extractCss": {
dir: "./dist/stylesheets/",
relativeRoot: "./src/",
filename: "[path]/[name].css"
}
}
]
]
}
```

Note that `relativeRoot` is used to resolve relative directory names, available
as `[path]` in `filename` pattern.

## Using a `babel-register`

Make sure you set `ignore` option of `babel-register` to ignore all files used by css-modules-require-hook to process your css files.
Expand All @@ -124,7 +173,7 @@ Make sure you set `ignore` option of `babel-register` to ignore all files used b

```js
require('babel-register')({
ignore: /processCss\.js$/ // regex matching all files used by css-modules-require-hook to process your css files
ignore: /processCss\.js$/ // regex matching all files used by css-modules-require-hook to process your css files
})
```

Expand All @@ -134,7 +183,7 @@ Create a js file with content

```js
require('babel-register')({
ignore: /processCss\.js$/ // regex matching all files used by css-modules-require-hook to process your css files
ignore: /processCss\.js$/ // regex matching all files used by css-modules-require-hook to process your css files
})
```

Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
},
"homepage": "https://github.com/michalkvasnicak/babel-plugin-css-modules-transform#readme",
"dependencies": {
"css-modules-require-hook": "^3.0.0"
"css-modules-require-hook": "^3.0.0",
"mkdirp": "^0.5.1"
},
"devDependencies": {
"babel-cli": "^6.1.18",
Expand All @@ -49,7 +50,8 @@
"postcss-modules-extract-imports": "^1.x",
"postcss-modules-local-by-default": "^1.x",
"postcss-modules-scope": "^1.x",
"postcss-modules-values": "^1.x"
"postcss-modules-values": "^1.x",
"rimraf": "^2.5.4"
},
"engines": {
"node": ">=4.0.0"
Expand Down
125 changes: 109 additions & 16 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
import { resolve, dirname, isAbsolute } from 'path';
import { resolve, dirname, basename, extname, isAbsolute, join, relative } from 'path';

import mkdirp from 'mkdirp';
// *
import { writeFileSync, appendFileSync } from 'fs';
/* /
const writeFileSync = (file, content) => {
console.log(`Will save ${file}\n${content.replace(/^/gm, ' ')}`);
};
// */

const writeCssFile = (filename, content) => {
mkdirp.sync(dirname(filename));
writeFileSync(filename, content);
};
const appendCssFile = (filename, content) => {
mkdirp.sync(dirname(filename));
appendFileSync(filename, content);
};

const simpleRequires = [
'createImportedName',
Expand Down Expand Up @@ -52,17 +70,78 @@ export default function transformCssModules({ types: t }) {

return {
visitor: {
Program(path, { opts }) {
Program(path, state) {
if (initialized) {
return;
}

const currentConfig = { ...defaultOptions, ...opts };
const currentConfig = { ...defaultOptions, ...state.opts };
// this is not a css-require-ook config
delete currentConfig.extractCss;

// match file extensions, speeds up transform by creating one
// RegExp ahead of execution time
matchExtensions = matcher(currentConfig.extensions);

// Add a space in current state for css filenames
state.$$css = {
styles: new Map()
};

const extractCssFile = (filepath, css) => {
const { extractCss = null } = state.opts;
if (!extractCss) return null;

// this is the case where a single extractCss is requested
if (typeof(extractCss) === 'string') {
// If this is the first file, then we should replace
// old content
if (state.$$css.styles.size === 1) {
return writeCssFile(extractCss, css);
}
// this should output in a single file.
// Let's append the new file content.
return appendCssFile(extractCss, css);
}

// This is the case where each css file is written in
// its own file.
const {
dir = 'dist',
filename = '[name].css',
relativeRoot = ''
} = extractCss;

// Make css file narmpe relative to relativeRoot
const relativePath = relative(
resolve(process.cwd(), relativeRoot),
filepath
);
const destination = join(
resolve(process.cwd(), dir),
filename
)
.replace(/\[name]/, basename(filepath, extname(filepath)))
.replace(/\[path]/, relativePath);

writeCssFile(destination, css);
};

const pushStylesCreator = (toWrap) => (css, filepath) => {
let processed;
if (typeof toWrap === 'function') {
processed = toWrap(css, filepath);
}
if (typeof processed !== 'string') processed = css;

if (!state.$$css.styles.has(filepath)) {
state.$$css.styles.set(filepath, processed);
extractCssFile(filepath, processed);
}

return processed;
};

// check if there are simple requires and if they are functions
simpleRequires.forEach(key => {
if (typeof currentConfig[key] !== 'string') {
Expand Down Expand Up @@ -108,6 +187,9 @@ export default function transformCssModules({ types: t }) {
}
});

// wrap or define processCss function that collect generated css
currentConfig.processCss = pushStylesCreator(currentConfig.processCss);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure that processCss is not transforming css in some way and returns it? Because if so then we need to update pushStylesCreator to reflect those changes before css is passed to extractCssFile.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed.

I updated pushStylesCreator to use processCss return value if it is a string.


complexRequires.forEach(key => {
if (!currentConfig.hasOwnProperty(key)) {
return;
Expand Down Expand Up @@ -142,6 +224,18 @@ export default function transformCssModules({ types: t }) {
initialized = true;
},

ImportDeclaration(path, { file }) {
// this method is called between enter and exit, so we can map css to our state
// it is then replaced with require call which will be handled in seconds pass by CallExpression
// CallExpression will then replace it or remove depending on parent node (if is Program or not)
const { value } = path.node.source;

if (matchExtensions.test(value)) {
const requiringFile = file.opts.filename;
requireCssFile(requiringFile, value);
}
},

CallExpression(path, { file }) {
const { callee: { name: calleeName }, arguments: args } = path.node;

Expand All @@ -152,25 +246,24 @@ export default function transformCssModules({ types: t }) {
const [{ value: stylesheetPath }] = args;

if (matchExtensions.test(stylesheetPath)) {
// if parent expression is variable declarator, replace right side with tokens
if (!t.isVariableDeclarator(path.parent)) {
throw new Error(
`You can't import css file ${stylesheetPath} to a module scope.`
);
}

const requiringFile = file.opts.filename;
const tokens = requireCssFile(requiringFile, stylesheetPath);

/* eslint-disable new-cap */
path.replaceWith(t.ObjectExpression(
// if parent expression is not a Program, replace expression with tokens
// Otherwise remove require from file, we just want to get generated css for our output
if (!t.isExpressionStatement(path.parent)) {
/* eslint-disable new-cap */
path.replaceWith(t.ObjectExpression(
Object.keys(tokens).map(
token => t.ObjectProperty(
t.StringLiteral(token),
t.StringLiteral(tokens[token])
t.StringLiteral(token),
t.StringLiteral(tokens[token])
)
)
)
));
));
} else {
path.remove();
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/exctractcss.include.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('../styles.css');
3 changes: 3 additions & 0 deletions test/fixtures/exctractcss.main.expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

require('./exctractcss.include.js');
2 changes: 2 additions & 0 deletions test/fixtures/exctractcss.main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require('./exctractcss.include.js');
require('../parent.css');
6 changes: 6 additions & 0 deletions test/fixtures/extractcss.combined.expected.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.parent__block___33Sxl {
display: block;
}
.styles__className___385m0 {
color: red;
}
3 changes: 3 additions & 0 deletions test/fixtures/extractcss.parent.expected.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.parent__block___33Sxl {
display: block;
}
3 changes: 3 additions & 0 deletions test/fixtures/extractcss.styles.expected.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.styles__className___385m0 {
color: red;
}
2 changes: 1 addition & 1 deletion test/fixtures/import.expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ var _styles = {

var _styles2 = _interopRequireDefault(_styles);

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