Skip to content

Commit fedab0d

Browse files
committed
Merge pull request #3 from sullenor/postcss-as-a-loader
Postcss as a loader
2 parents 19aef7b + 1c2f082 commit fedab0d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+479
-113
lines changed

index.js

Lines changed: 12 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,19 @@
11
'use strict';
22

3-
if (global._cssModulesPolyfill) {
4-
throw new Error('only one instance of css-modules/polyfill is allowed');
5-
}
6-
7-
global._cssModulesPolyfill = true;
8-
9-
var assign = require('object-assign');
10-
var options = {};
3+
var path = require('path');
114

12-
/**
13-
* Posibility to pass custom options to the module
14-
* @param {object} opts
15-
*/
16-
module.exports = function (opts) {
17-
assign(options, opts);
5+
var escape = function (str) {
6+
return str.replace(/[\[\]\/{}()*+?.\\^$|-]/g, '\\$&');
187
};
198

20-
var Core = require('css-modules-loader-core');
21-
var pluginsCache;
22-
23-
/**
24-
* Caching plugins for the future calls
25-
* @return {array}
26-
*/
27-
function loadPlugins() {
28-
// retrieving from cache if they are already loaded
29-
if (pluginsCache) {
30-
return pluginsCache;
31-
}
32-
33-
// PostCSS plugins passed to FileSystemLoader
34-
var plugins = options.use || options.u;
35-
if (!plugins) {
36-
plugins = Core.defaultPlugins;
37-
} else {
38-
if (typeof plugins === 'string') {
39-
plugins = [ plugins ];
40-
}
41-
42-
plugins = plugins.map(function requirePlugin (name) {
43-
// assume functions are already required plugins
44-
if (typeof name === 'function') {
45-
return name;
46-
}
47-
48-
var plugin = require(require.resolve(name));
9+
var regexp = ['src', 'test'].map(function (i) {
10+
return '^' + escape(path.join(__dirname, i) + path.sep);
11+
}).join('|');
4912

50-
// custom scoped name generation
51-
if (name === 'postcss-modules-scope') {
52-
options[name] = options[name] || {};
53-
options[name].generateScopedName = createScopedNameFunc(plugin);
54-
}
13+
require('babel/register')({
14+
only: new RegExp('(' + regexp + ')'),
15+
ignore: false,
16+
loose: 'all'
17+
});
5518

56-
if (name in options) {
57-
plugin = plugin(options[name]);
58-
} else {
59-
plugin = plugin.postcss || plugin();
60-
}
61-
62-
return plugin;
63-
});
64-
}
65-
66-
return pluginsCache = plugins;
67-
}
68-
69-
var FileSystemLoader = require('css-modules-loader-core/lib/file-system-loader');
70-
var path = require('path');
71-
72-
require.extensions['.css'] = function (m, filename) {
73-
var plugins = loadPlugins();
74-
var loader = new FileSystemLoader(path.dirname(filename), plugins);
75-
var tokens = loader.fetchSync(path.basename(filename), '/');
76-
77-
return m._compile('module.exports = ' + JSON.stringify(tokens), filename);
78-
};
19+
module.exports = require('./src');

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@
44
"description": "A require hook to compile CSS Modules on the fly",
55
"main": "index.js",
66
"dependencies": {
7-
"babel": "^5.6.14",
8-
"object-assign": "^3.0.0"
7+
"babel": "^5.8.20",
8+
"css-modules-loader-core": "0.0.11",
9+
"postcss": "^4.1.16",
10+
"postcss-modules-extract-imports": "0.0.5",
11+
"postcss-modules-local-by-default": "0.0.9",
12+
"postcss-modules-scope": "0.0.8"
913
},
1014
"devDependencies": {
1115
"mocha": "^2.2.5"
1216
},
1317
"scripts": {
14-
"clone": "git clone https://github.com/sullenor/css-modules-loader-core.git node_modules/css-modules-loader-core",
15-
"deps": "cd node_modules/css-modules-loader-core && npm i",
16-
"postinstall": "npm run clone && npm run deps",
17-
"preinstall": "rm -rf node_modules/css-modules-loader-core",
18-
"test": "mocha test"
18+
"test": "mocha --compilers js:babel/register"
1919
},
2020
"repository": {
2121
"type": "git",

src/guard.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
if (global._cssModulesPolyfill) {
4+
throw new Error('only one instance of css-modules/polyfill is allowed');
5+
}
6+
7+
global._cssModulesPolyfill = true;

src/hook.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use strict';
2+
3+
/**
4+
* @param {function} compile
5+
*/
6+
module.exports = function (compile) {
7+
require.extensions['.css'] = function (m, filename) {
8+
var tokens = compile(filename);
9+
return m._compile('module.exports = ' + JSON.stringify(tokens), filename);
10+
};
11+
};

src/index.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
'use strict';
2+
3+
import './guard';
4+
import hook from './hook';
5+
import postcss from 'postcss';
6+
import { basename, dirname, join, resolve } from 'path';
7+
import { readFileSync } from 'fs';
8+
9+
import extractImports from 'postcss-modules-extract-imports';
10+
import localByDefault from 'postcss-modules-local-by-default';
11+
import scope from 'postcss-modules-scope';
12+
import parser from './parser';
13+
14+
let plugins = [localByDefault, extractImports, scope];
15+
16+
const load = (sourceString, sourcePath, trace, pathFetcher) => {
17+
let exportTokens = {};
18+
let result = postcss(plugins.concat(new parser({ exportTokens, pathFetcher, trace })))
19+
.process(sourceString, {from: '/' + sourcePath})
20+
.stringify();
21+
22+
return { injectableSource: result.css, exportTokens: exportTokens };
23+
}
24+
25+
hook(filename => {
26+
const root = dirname(filename);
27+
const sources = {};
28+
const tokensByFile = {};
29+
let importNr = 0;
30+
31+
const fetch = (_newPath, _relativeTo, _trace) => {
32+
let newPath = _newPath.replace(/^["']|["']$/g, '');
33+
let trace = _trace || String.fromCharCode(importNr++);
34+
35+
let relativeDir = dirname(_relativeTo);
36+
let rootRelativePath = resolve(relativeDir, newPath);
37+
let fileRelativePath = resolve(join(root, relativeDir), newPath);
38+
39+
const tokens = tokensByFile[fileRelativePath];
40+
if (tokens) {
41+
return tokens;
42+
}
43+
44+
let source = readFileSync(fileRelativePath, 'utf-8');
45+
let { injectableSource, exportTokens } = load(source, rootRelativePath, trace, fetch);
46+
47+
sources[trace] = injectableSource;
48+
tokensByFile[fileRelativePath] = exportTokens;
49+
50+
return exportTokens;
51+
}
52+
53+
return fetch(basename(filename), '/');
54+
});
55+
56+
/**
57+
* @param {object} opts
58+
* @param {array} opts.u
59+
* @param {array} opts.use
60+
*/
61+
export default function configure(opts) {
62+
opts = opts || {};
63+
64+
let customPlugins = opts.u || opts.use;
65+
plugins = Array.isArray(customPlugins)
66+
? customPlugins
67+
: [localByDefault, extractImports, scope];
68+
}

src/parser.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
'use strict';
2+
3+
import { plugin } from 'postcss';
4+
5+
const importRegexp = /^:import\((.+)\)$/
6+
7+
export default plugin('parser', function (opts) {
8+
opts = opts || {};
9+
10+
let exportTokens = opts.exportTokens;
11+
let translations = {};
12+
13+
const fetchImport = (importNode, relativeTo, depNr) => {
14+
let file = importNode.selector.match( importRegexp )[1];
15+
let depTrace = opts.trace + String.fromCharCode(depNr);
16+
let exports = opts.pathFetcher(file, relativeTo, depTrace);
17+
18+
importNode.each(decl => {
19+
if (decl.type === 'decl') {
20+
translations[decl.prop] = exports[decl.value];
21+
}
22+
});
23+
24+
importNode.removeSelf();
25+
}
26+
27+
const fetchAllImports = css => {
28+
let imports = 0;
29+
30+
css.each(node => {
31+
if (node.type === 'rule' && node.selector.match(importRegexp)) {
32+
fetchImport(node, css.source.input.from, imports++);
33+
}
34+
});
35+
}
36+
37+
const linkImportedSymbols = css => css.eachDecl(decl => {
38+
Object.keys(translations).forEach(translation => {
39+
decl.value = decl.value.replace(translation, translations[translation])
40+
});
41+
});
42+
43+
const handleExport = exportNode => {
44+
exportNode.each(decl => {
45+
if (decl.type === 'decl') {
46+
Object.keys(translations).forEach(translation => {
47+
decl.value = decl.value.replace(translation, translations[translation])
48+
});
49+
50+
exportTokens[decl.prop] = decl.value;
51+
}
52+
});
53+
54+
exportNode.removeSelf();
55+
}
56+
57+
const extractExports = css => css.each(node => {
58+
if (node.type === 'rule' && node.selector === ':export') handleExport(node);
59+
});
60+
61+
return css => {
62+
fetchAllImports(css);
63+
linkImportedSymbols(css);
64+
extractExports(css);
65+
}
66+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
:export {
2+
blackShadow: x__single_import_export_colors__blackShadow;
3+
}
4+
5+
.x__single_import_export_colors__blackShadow {
6+
box-shadow: 0 0 10px -2px black;
7+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.x__single_import_export_colors__blackShadow {
2+
box-shadow: 0 0 10px -2px black;
3+
}
4+
.x__single_import_export_source__localName {
5+
color: red;
6+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"localName": "x__single_import_export_source__localName x__single_import_export_colors__blackShadow"
3+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
:import("./colors.css") {
2+
i__tmp_import_djhgdsag: blackShadow;
3+
}
4+
5+
:export {
6+
localName: x__single_import_export_source__localName i__tmp_import_djhgdsag;
7+
}
8+
9+
.x__single_import_export_source__localName {
10+
color: red;
11+
}

test/cssi/pseudo-variables/colors.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:export {
2+
black: #222;
3+
white: #ddd;
4+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
.x__lol {
3+
color: #222;
4+
background: #ddd;
5+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"lol": "x__lol"
3+
}

test/cssi/pseudo-variables/source.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
:import("./colors.css") {
2+
i__black: black;
3+
i__white: white;
4+
}
5+
6+
:export {
7+
lol: x__lol;
8+
}
9+
10+
.x__lol {
11+
color: i__black;
12+
background: i__white;
13+
}

test/example.css

Lines changed: 0 additions & 4 deletions
This file was deleted.

test/index.js

Lines changed: 0 additions & 31 deletions
This file was deleted.

0 commit comments

Comments
 (0)