Skip to content

Commit 18118ae

Browse files
michalkvasnicakthemouette
authored andcommitted
accumulate generated css styles for imported/required css files
Fixes michalkvasnicak#5 Replaces michalkvasnicak#6 It is helpful to release a library that uses css-modules. 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" } } ] ] } ```
1 parent c2b7b3d commit 18118ae

11 files changed

+383
-43
lines changed

README.md

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ npm install --save-dev babel-plugin-css-modules-transform
7474
"npm-module-name",
7575
"./path/to/module-exporting-a-function.js"
7676
],
77+
"extractCss": "./dist/stylesheets/combined.css"
7778
}
7879
]
7980
]
@@ -116,6 +117,54 @@ and then add any relevant extensions to your plugin config:
116117
117118
```
118119

120+
## Extract CSS Files
121+
122+
When you publish a library, you might want to ship compiled css files as well to
123+
help integration in other projects.
124+
125+
An more complete alternative is to use
126+
[babel-plugin-webpack-loaders](https://github.com/istarkov/babel-plugin-webpack-loaders)
127+
but be aware that a new webpack instance is run for each css file, this has a
128+
huge overhead. If you do not use fancy stuff, you might consider using
129+
[babel-plugin-css-modules-transform](https://github.com/michalkvasnicak/babel-plugin-css-modules-transform)
130+
instead.
131+
132+
133+
To combine all css files in a single file, give its name:
134+
135+
```
136+
{
137+
"plugins": [
138+
[
139+
"css-modules-transform", {
140+
"extractCss": "./dist/stylesheets/combined.css"
141+
}
142+
]
143+
]
144+
}
145+
```
146+
147+
To extract all files in a single directory, give an object:
148+
149+
```
150+
{
151+
"plugins": [
152+
[
153+
"css-modules-transform", {
154+
"extractCss": {
155+
dir: "./dist/stylesheets/",
156+
relativeRoot: "./src/",
157+
filename: "[path]/[name].css"
158+
}
159+
}
160+
]
161+
]
162+
}
163+
```
164+
165+
Note that `relativeRoot` is used to resolve relative directory names, available
166+
as `[path]` in `filename` pattern.
167+
119168
## Using a `babel-register`
120169

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

125174
```js
126175
require('babel-register')({
127-
ignore: /processCss\.js$/ // regex matching all files used by css-modules-require-hook to process your css files
176+
ignore: /processCss\.js$/ // regex matching all files used by css-modules-require-hook to process your css files
128177
})
129178
```
130179

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

135184
```js
136185
require('babel-register')({
137-
ignore: /processCss\.js$/ // regex matching all files used by css-modules-require-hook to process your css files
186+
ignore: /processCss\.js$/ // regex matching all files used by css-modules-require-hook to process your css files
138187
})
139188
```
140189

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
},
2626
"homepage": "https://github.com/michalkvasnicak/babel-plugin-css-modules-transform#readme",
2727
"dependencies": {
28-
"css-modules-require-hook": "^3.0.0"
28+
"css-modules-require-hook": "^3.0.0",
29+
"mkdirp": "^0.5.1"
2930
},
3031
"devDependencies": {
3132
"babel-cli": "^6.1.18",
@@ -49,7 +50,8 @@
4950
"postcss-modules-extract-imports": "^1.x",
5051
"postcss-modules-local-by-default": "^1.x",
5152
"postcss-modules-scope": "^1.x",
52-
"postcss-modules-values": "^1.x"
53+
"postcss-modules-values": "^1.x",
54+
"rimraf": "^2.5.4"
5355
},
5456
"engines": {
5557
"node": ">=4.0.0"

src/index.js

Lines changed: 109 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
1-
import { resolve, dirname, isAbsolute } from 'path';
1+
import { resolve, dirname, basename, extname, isAbsolute, join, relative } from 'path';
2+
3+
import mkdirp from 'mkdirp';
4+
// *
5+
import { writeFileSync, appendFileSync } from 'fs';
6+
/* /
7+
const writeFileSync = (file, content) => {
8+
console.log(`Will save ${file}\n${content.replace(/^/gm, ' ')}`);
9+
};
10+
// */
11+
12+
const writeCssFile = (filename, content) => {
13+
mkdirp.sync(dirname(filename));
14+
writeFileSync(filename, content);
15+
};
16+
const appendCssFile = (filename, content) => {
17+
mkdirp.sync(dirname(filename));
18+
appendFileSync(filename, content);
19+
};
220

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

5371
return {
5472
visitor: {
55-
Program(path, { opts }) {
73+
Program(path, state) {
5674
if (initialized) {
5775
return;
5876
}
5977

60-
const currentConfig = { ...defaultOptions, ...opts };
78+
const currentConfig = { ...defaultOptions, ...state.opts };
79+
// this is not a css-require-ook config
80+
delete currentConfig.extractCss;
6181

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

86+
// Add a space in current state for css filenames
87+
state.$$css = {
88+
styles: new Map()
89+
};
90+
91+
const extractCssFile = (filepath, css) => {
92+
const { extractCss = null } = state.opts;
93+
if (!extractCss) return null;
94+
95+
// this is the case where a single extractCss is requested
96+
if (typeof(extractCss) === 'string') {
97+
// If this is the first file, then we should replace
98+
// old content
99+
if (state.$$css.styles.size === 1) {
100+
return writeCssFile(extractCss, css);
101+
}
102+
// this should output in a single file.
103+
// Let's append the new file content.
104+
return appendCssFile(extractCss, css);
105+
}
106+
107+
// This is the case where each css file is written in
108+
// its own file.
109+
const {
110+
dir = 'dist',
111+
filename = '[name].css',
112+
relativeRoot = ''
113+
} = extractCss;
114+
115+
// Make css file narmpe relative to relativeRoot
116+
const relativePath = relative(
117+
resolve(process.cwd(), relativeRoot),
118+
filepath
119+
);
120+
const destination = join(
121+
resolve(process.cwd(), dir),
122+
filename
123+
)
124+
.replace(/\[name]/, basename(filepath, extname(filepath)))
125+
.replace(/\[path]/, relativePath);
126+
127+
writeCssFile(destination, css);
128+
};
129+
130+
const pushStylesCreator = (toWrap) => (css, filepath) => {
131+
let processed;
132+
if (typeof toWrap === 'function') {
133+
processed = toWrap(css, filepath);
134+
}
135+
if (typeof processed !== 'string') processed = css;
136+
137+
if (!state.$$css.styles.has(filepath)) {
138+
state.$$css.styles.set(filepath, processed);
139+
extractCssFile(filepath, processed);
140+
}
141+
142+
return processed;
143+
};
144+
66145
// check if there are simple requires and if they are functions
67146
simpleRequires.forEach(key => {
68147
if (typeof currentConfig[key] !== 'string') {
@@ -108,6 +187,9 @@ export default function transformCssModules({ types: t }) {
108187
}
109188
});
110189

190+
// wrap or define processCss function that collect generated css
191+
currentConfig.processCss = pushStylesCreator(currentConfig.processCss);
192+
111193
complexRequires.forEach(key => {
112194
if (!currentConfig.hasOwnProperty(key)) {
113195
return;
@@ -142,6 +224,18 @@ export default function transformCssModules({ types: t }) {
142224
initialized = true;
143225
},
144226

227+
ImportDeclaration(path, { file }) {
228+
// this method is called between enter and exit, so we can map css to our state
229+
// it is then replaced with require call which will be handled in seconds pass by CallExpression
230+
// CallExpression will then replace it or remove depending on parent node (if is Program or not)
231+
const { value } = path.node.source;
232+
233+
if (matchExtensions.test(value)) {
234+
const requiringFile = file.opts.filename;
235+
requireCssFile(requiringFile, value);
236+
}
237+
},
238+
145239
CallExpression(path, { file }) {
146240
const { callee: { name: calleeName }, arguments: args } = path.node;
147241

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

154248
if (matchExtensions.test(stylesheetPath)) {
155-
// if parent expression is variable declarator, replace right side with tokens
156-
if (!t.isVariableDeclarator(path.parent)) {
157-
throw new Error(
158-
`You can't import css file ${stylesheetPath} to a module scope.`
159-
);
160-
}
161-
162249
const requiringFile = file.opts.filename;
163250
const tokens = requireCssFile(requiringFile, stylesheetPath);
164251

165-
/* eslint-disable new-cap */
166-
path.replaceWith(t.ObjectExpression(
252+
// if parent expression is not a Program, replace expression with tokens
253+
// Otherwise remove require from file, we just want to get generated css for our output
254+
if (!t.isExpressionStatement(path.parent)) {
255+
/* eslint-disable new-cap */
256+
path.replaceWith(t.ObjectExpression(
167257
Object.keys(tokens).map(
168258
token => t.ObjectProperty(
169-
t.StringLiteral(token),
170-
t.StringLiteral(tokens[token])
259+
t.StringLiteral(token),
260+
t.StringLiteral(tokens[token])
261+
)
171262
)
172-
)
173-
));
263+
));
264+
} else {
265+
path.remove();
266+
}
174267
}
175268
}
176269
}

test/fixtures/exctractcss.include.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require('../styles.css');
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
'use strict';
2+
3+
require('./exctractcss.include.js');

test/fixtures/exctractcss.main.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require('./exctractcss.include.js');
2+
require('../parent.css');
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.parent__block___33Sxl {
2+
display: block;
3+
}
4+
.styles__className___385m0 {
5+
color: red;
6+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.parent__block___33Sxl {
2+
display: block;
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.styles__className___385m0 {
2+
color: red;
3+
}

test/fixtures/import.expected.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ var _styles = {
66

77
var _styles2 = _interopRequireDefault(_styles);
88

9-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
9+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

0 commit comments

Comments
 (0)