Skip to content

Commit 6920830

Browse files
committed
Fixes and enhancements
- [x] Fix issue where build would fail if last 'require' in a file was inside a block scope - [x] Fix issue where 'require's inside a block scope were included outside of that scope - [x] Add ability to access arbitrarily deep properties within a require()'d file e.g. `require("./some-vars.js").deeply.nested.properties` - [x] (Update tests)
1 parent 88e4332 commit 6920830

File tree

9 files changed

+109
-103
lines changed

9 files changed

+109
-103
lines changed

__tests__/index.spec.js

Lines changed: 31 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import path from 'path';
2+
import fs from 'fs';
23
const loader = require('../index').default;
34
jest.mock('decache');
45
const { operator } = require('../index');
@@ -53,43 +54,7 @@ describe('js-to-styles-vars-loader', () => {
5354
});
5455
});
5556

56-
describe('divideContent', () => {
57-
it('divides the require (if it exists) from the content', () => {
58-
const content = "require('colors.js');\n" +
59-
".someClass { color: #fff;}";
60-
expect(operator.divideContent(content)[0]).toEqual("require('colors.js');");
61-
expect(operator.divideContent(content)[1]).toEqual("\n.someClass { color: #fff;}");
62-
});
63-
64-
it('gives back content if there is no require in content', () => {
65-
const content = ".someClass { color: #fff;}";
66-
expect(operator.divideContent(content)[0]).toEqual("");
67-
expect(operator.divideContent(content)[1]).toEqual(content);
68-
});
69-
70-
it('handles more requires when divide', () => {
71-
const content = "require('colors.js');\n" +
72-
"require('sizes.js');\n" +
73-
".someClass { color: #fff;}";
74-
expect(operator.divideContent(content)[0]).toEqual("require('colors.js');\n" + "require('sizes.js');");
75-
expect(operator.divideContent(content)[1]).toEqual("\n.someClass { color: #fff;}");
76-
});
7757

78-
it('handles the form of request("asdf").someProp', () => {
79-
const content = "require('corners.js').typeOne;\n" + ".someClass { color: #fff;}";
80-
expect(operator.divideContent(content)[0]).toEqual("require('corners.js').typeOne;");
81-
});
82-
});
83-
84-
describe('getModulePath', () => {
85-
it('extracts module paths and methodName into an array', () => {
86-
expect(operator.getModulePath('require("./mocks/colors.js");\n')).toEqual([{path: "./mocks/colors.js"}]);
87-
88-
expect(operator.getModulePath('require("./mocks/colors.js");\n' + 'require("./mocks/sizes.js");')).toEqual([{path: "./mocks/colors.js"}, {path:"./mocks/sizes.js"}]);
89-
90-
expect(operator.getModulePath('require("./mocks/corners.js").typeTwo;\n')).toEqual([{path: "./mocks/corners.js", methodName: 'typeTwo'}]);
91-
});
92-
});
9358

9459
describe('getVarData', () => {
9560
const context = {
@@ -98,25 +63,17 @@ describe('js-to-styles-vars-loader', () => {
9863
};
9964

10065
it('gets variable data by modulePath with context', () => {
101-
const varData = operator.getVarData([{path: './mocks/colors.js' }], context);
66+
const varData = operator.getVarData(path.join(context.context, './mocks/colors.js'));
10267
expect(varData).toEqual({ white: '#fff', black: '#000'});
10368
});
10469

105-
it('merges module data if there are more requests', () => {
106-
const varData = operator.getVarData([{path:'./mocks/colors.js'}, {path:'./mocks/sizes.js'}], context);
107-
expect(varData).toEqual({ white: '#fff', black: '#000', small: '10px', large: '50px'});
108-
});
109-
110-
it('handles methodName if it is given', () => {
111-
const varData = operator.getVarData([{ path:'./mocks/corners.js', methodName: 'typeOne'}], context);
70+
it('uses value from property', () => {
71+
const varData = operator.getVarData(path.join(context.context, './mocks/corners.js'), 'typeOne');
11272
expect(varData).toEqual({ tiny: '1%', medium: '3%'});
11373
});
114-
115-
it('call context.addDependecy with modulePath', () => {
116-
spyOn(context, 'addDependency');
117-
const relativePath = './mocks/corners.js';
118-
operator.getVarData([{ path: relativePath, methodName: 'typeOne'}], context);
119-
expect(context.addDependency).toHaveBeenCalledWith(path.resolve(relativePath));
74+
it('uses value from nested property', () => {
75+
const varData = operator.getVarData(path.join(context.context, './mocks/corners.js'), 'deep.nested');
76+
expect(varData).toEqual({ color: '#f00'});
12077
});
12178
});
12279

@@ -139,22 +96,39 @@ describe('js-to-styles-vars-loader', () => {
13996
context: path.resolve(),
14097
addDependency () {}
14198
};
99+
const content = "require('./mocks/colors.js');\n" +
100+
".someClass { color: #fff;}";
101+
102+
const trimmer = (str) => {
103+
return (str.split("\n").filter(a => a).map(s => s.trim()).join(" ")).trim()
104+
};
142105

143106
it('inserts vars to styles content', () => {
144-
const content = "require('./mocks/colors.js');\n" +
145-
".someClass { color: #fff;}";
146-
const [ moduleData, stylesContent ] = operator.divideContent(content);
147-
const modulePath = operator.getModulePath(moduleData);
148-
const varData = operator.getVarData(modulePath, context);
149-
const vars = operator.transformToStyleVars({ type: 'less', varData });
107+
operator.mergeVarsToContent(content, context, 'less')
150108

151-
expect(operator.mergeVarsToContent(content, context, 'less')).toEqual(vars + stylesContent);
109+
expect(trimmer(operator.mergeVarsToContent(content, context, 'less'))).toEqual(trimmer(`
110+
@white: #fff; @black: #000; ; .someClass { color: #fff;}
111+
`));
112+
});
113+
114+
it('call context.addDependecy', () => {
115+
spyOn(context, 'addDependency');
116+
const dependencyPath = path.join(context.context, './mocks/colors.js');
117+
operator.mergeVarsToContent(content, context, 'less')
118+
expect(context.addDependency).toHaveBeenCalledWith(path.resolve(dependencyPath));
152119
});
153120

154121
it('gives back content as is if there is no requre', () => {
155122
const content = ".someClass { color: #fff;}";
156123
expect(operator.mergeVarsToContent(content, context)).toEqual(content);
157124
});
125+
126+
it("inserts variables inside style blocks and does not fail if the last 'require' is inside a block", () => {
127+
const content = fs.readFileSync(path.resolve('./mocks/case1.less'), 'utf8');
128+
const expectedContent = fs.readFileSync(path.resolve('./mocks/case1_expected.less'), 'utf8');
129+
const merged = operator.mergeVarsToContent(content, {...context, context: path.resolve('./mocks/')}, 'less');
130+
expect(merged.trim()).toEqual(expectedContent.trim());
131+
})
158132
});
159133

160134
describe('getResource', () => {

index.js

Lines changed: 27 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,25 @@
11
const path = require('path');
22
const decache = require('decache');
3+
const squba = require('squba')
34

4-
const requireReg = /require\s*\(['|"](.+)['|"]\)(?:\.([^;\s]+))?[;\s]/g;
5+
const requireReg = /require\s*\((["'])([\w.\/]+)(?:\1)\)((?:\.[\w_-]+)*)/igm;
56

67
const operator = {
7-
divideContent (content) {
8-
let match;
9-
let lastIndex;
10-
const reg = new RegExp(requireReg);
11-
while (match = reg.exec(content)) {
12-
lastIndex = reg.lastIndex;
13-
}
14-
if (typeof lastIndex !== 'undefined') {
15-
return [
16-
content.slice(0, lastIndex),
17-
content.slice(lastIndex)
18-
];
19-
}
20-
else {
21-
return ['', content];
22-
}
23-
},
248

25-
getModulePath (modulePart) {
26-
const reg = new RegExp(requireReg);
27-
const modulePaths = [];
28-
let match;
29-
while (match = reg.exec(modulePart)) {
30-
modulePaths.push({
31-
path: match[1],
32-
methodName: match[2]
33-
});
9+
getVarData (relativePath, property) {
10+
const data = require(relativePath);
11+
decache(relativePath);
12+
if (!data) {
13+
throw new Error(`No data in '${relativePath}'`)
3414
}
35-
return modulePaths;
36-
},
37-
38-
getVarData (modulePath, webpackContext) {
39-
return modulePath.reduce( (accumulator, currentPath) => {
40-
const modulePath = path.join(webpackContext.context, currentPath.path);
41-
decache(modulePath);
42-
const moduleData = (currentPath.methodName)? require(modulePath)[currentPath.methodName] : require(modulePath);
43-
webpackContext.addDependency(modulePath);
44-
return Object.assign(accumulator, moduleData);
45-
}, {});
15+
if (property) {
16+
const propVal = squba(data, property);
17+
if (!propVal) {
18+
throw new Error(`Empty property: 'require("${relativePath}")${property}`);
19+
}
20+
return propVal;
21+
}
22+
return data;
4623
},
4724

4825
transformToSassVars (varData) {
@@ -74,14 +51,19 @@ const operator = {
7451
},
7552

7653
mergeVarsToContent (content, webpackContext, preprocessorType) {
77-
const [ moduleData, styleContent ] = this.divideContent(content);
78-
if (moduleData) {
79-
const modulePath = this.getModulePath(moduleData);
80-
const varData = this.getVarData(modulePath, webpackContext);
81-
const vars = this.transformToStyleVars({ type: preprocessorType, varData });
82-
return vars + styleContent;
54+
const replacer = function (m,q, relativePath, property) {
55+
const modulePath = path.join(webpackContext.context, relativePath)
56+
const varData = this.getVarData(modulePath, property);
57+
webpackContext.addDependency(modulePath);
58+
return this.transformToStyleVars({
59+
type: preprocessorType,
60+
varData
61+
});
8362
}
84-
else return content;
63+
return content.replace(
64+
requireReg,
65+
replacer.bind(this)
66+
);
8567
},
8668

8769
getResource (context) {

mocks/case1.less

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
require("./case1_a.js")
3+
4+
.classA {
5+
color: @some_color;
6+
}
7+
8+
.classB {
9+
require("./case1_b.js")
10+
color: @some_color;
11+
}
12+
13+
.classC {
14+
require("./case1_c.js")
15+
color: @some_other_color;
16+
}

mocks/case1_a.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
some_color: "#ff0"
3+
}

mocks/case1_b.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
some_color: "#00f"
3+
}

mocks/case1_c.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
some_other_color: "#f0f"
3+
}

mocks/case1_expected.less

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
@some_color: #ff0;
2+
3+
4+
.classA {
5+
color: @some_color;
6+
}
7+
8+
.classB {
9+
@some_color: #00f;
10+
11+
color: @some_color;
12+
}
13+
14+
.classC {
15+
@some_other_color: #f0f;
16+
17+
color: @some_other_color;
18+
}

mocks/corners.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,9 @@ exports.typeTwo = {
77
tiny: '2%',
88
medium: '5%'
99
};
10+
11+
exports.deep = {
12+
nested: {
13+
color: "#f00"
14+
}
15+
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"jest": "^19.0.2"
3535
},
3636
"dependencies": {
37-
"decache": "^4.1.0"
37+
"decache": "^4.1.0",
38+
"squba": "^0.1.4"
3839
}
3940
}

0 commit comments

Comments
 (0)