Skip to content

Commit 7b5e8a5

Browse files
committed
test suite with webpack
1 parent 1f46026 commit 7b5e8a5

28 files changed

+1336
-45
lines changed

.eslintignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/node_modules
2-
/dist
2+
/dist
3+
/runtime

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ yarn-debug.log*
88
/local
99
/reports
1010
/node_modules
11+
/test/.output
1112
.DS_Store
1213
Thumbs.db
1314
.idea
1415
.vscode
1516
*.sublime-project
16-
*.sublime-workspace
17+
*.sublime-workspace

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"security": "nsp check",
1717
"test": "jest",
1818
"test:watch": "jest --watch",
19-
"test:coverage": "jest --collectCoverageFrom='src/**/*.js' --coverage",
19+
"test:coverage": "jest --collectCoverageFrom=\"src/**\" --coverage",
2020
"travis:lint": "yarn run lint && yarn run security",
2121
"travis:test": "yarn run test",
2222
"travis:coverage": "yarn run test:coverage"
@@ -45,9 +45,10 @@
4545
"nsp": "^2.6.3",
4646
"pre-commit": "^1.2.2",
4747
"standard-version": "^4.0.0",
48+
"webpack": "^2.4.1",
4849
"webpack-defaults": "^0.4.6"
4950
},
50-
"main": "dist/cjs.js",
51+
"main": "dist/index.js",
5152
"files": [
5253
"dist"
5354
],

runtime/index.js

+23-11
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
export function create(items) {
22
var array = [];
3-
items.forEach(function(elements) {
4-
elements.forEach(function(item) {
3+
var map = Object.create ? Object.create(null) : {};
4+
items.forEach(function (elements) {
5+
elements.forEach(function (item) {
6+
var id = item[0];
7+
if (id !== null) {
8+
if (map[id]) return;
9+
map[id] = true;
10+
}
511
array.push(item);
612
});
713
});
8-
array.toString = function() {
9-
return this.map(function(item) {
10-
if(item[2]) {
14+
array.toString = function toString() {
15+
return this.map(function (item) {
16+
if (item[2]) {
1117
return '@media ' + item[2] + '{' + item[1] + '}';
1218
}
13-
return item[1]
19+
return item[1];
1420
}).join('\n');
15-
}
21+
};
22+
return array;
1623
}
1724

1825
export function moduleWithSourceMap(id, source, sourceMap) {
@@ -24,14 +31,19 @@ export function moduleWithoutSourceMap(id, source) {
2431
}
2532

2633
export function importStylesheet(data, mediaQuery) {
27-
if(typeof data === 'string')
34+
if (typeof data === 'string') {
2835
return moduleWithoutSourceMap(null, data);
29-
return data.map(function(item) {
36+
}
37+
if (!mediaQuery) {
38+
return data;
39+
}
40+
return data.map(function (item) {
3041
return [item[0], item[1], joinMediaQuery(item[2], mediaQuery), item[3]];
31-
})
42+
});
3243
}
3344

3445
function joinMediaQuery(itemA, itemB) {
35-
if(itemA && itemB) return '(' + itemA + ') and (' + itemB + ')';
46+
// not perfect, but work fine in 99%
47+
if (itemA && itemB) return '(' + itemA + ') and (' + itemB + ')';
3648
return itemA || itemB || '';
3749
}

src/cjs.js

-1
This file was deleted.

src/index.js

+77-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1+
import path from 'path';
12
import OriginalSource from 'webpack-sources/lib/OriginalSource';
23
import RawSource from 'webpack-sources/lib/RawSource';
34
import ReplaceSource from 'webpack-sources/lib/ReplaceSource';
45
import SourceMapSource from 'webpack-sources/lib/SourceMapSource';
56
import loaderUtils from 'loader-utils';
67
import parse from './parse';
78

9+
function toRelativeUrl(p, context) {
10+
const url = path.relative(context, p).replace(/\\/g, '/');
11+
if (/^\.\.?\//.test(url)) {
12+
return url;
13+
}
14+
return `./${url}`;
15+
}
16+
817
export default function loader(source, map) {
918
const options = loaderUtils.getOptions(this) || {};
1019
const remainingRequest = loaderUtils.getRemainingRequest(this);
@@ -19,9 +28,11 @@ export default function loader(source, map) {
1928
replacer = new ReplaceSource(new RawSource(source), remainingRequest);
2029
}
2130

31+
// List of @imports with media queries
2232
const includedStylesheets = new Set();
2333
const includedStylesheetsMediaQuery = new Map();
2434

35+
// Interate parsed @import
2536
parseResult.atImports.forEach((imp) => {
2637
if (loaderUtils.isUrlRequest(imp.url, options.root)) {
2738
const request = loaderUtils.urlToRequest(imp.url, options.root);
@@ -31,17 +42,29 @@ export default function loader(source, map) {
3142
}
3243
});
3344

45+
// Flag if column mappings make sense for this SourceMap
46+
// It only makes sense if we don't replace values from imported values
3447
let columns = true;
48+
49+
// Mapping from css-identifier to import
3550
const importedNames = new Map();
3651

52+
// Interate parsed :import
3753
parseResult.imports.forEach((imp) => {
3854
importedNames.set(imp.alias, imp);
3955
});
4056

57+
// List of all declarates that should be emitted to the output
4158
const declarations = [];
59+
60+
// Mapping from css-identifier to imported identifier
4261
const importReplacements = new Map();
4362

63+
// Counter to generate unique ids for identifiers
4464
let id = 0;
65+
66+
// Iterate all imported names and internal identifiers
67+
// Also make sure that :imports are imported like @imports
4568
for (const pair of importedNames) {
4669
const internalName = `cssLoaderImport${id}_${pair[1].importName}`;
4770
id += 1;
@@ -50,6 +73,7 @@ export default function loader(source, map) {
5073
includedStylesheets.add(pair[1].from);
5174
}
5275

76+
// Iterate all replacements and replace them with a maker token
5377
for (const pair of importReplacements) {
5478
const identifier = parseResult.identifiers.get(pair[0]);
5579
if (identifier) {
@@ -61,10 +85,12 @@ export default function loader(source, map) {
6185
}
6286
}
6387

88+
// Delete all metablocks, they only contain meta information and are not valid css
6489
parseResult.metablocks.forEach((block) => {
6590
replacer.replace(block.start, block.end, '');
6691
});
6792

93+
// Generate declarations for all imports
6894
const includedStylesheetsArray = [];
6995
for (const include of includedStylesheets) {
7096
const internalName = `cssLoaderImport${id}`;
@@ -76,14 +102,62 @@ export default function loader(source, map) {
76102
});
77103
}
78104

105+
// Mapping from exported name to exported value as array (will be joined by spaces)
106+
const exportedNames = new Map();
107+
108+
// Iterate parsed exports
109+
parseResult.exports.forEach((exp) => {
110+
// Note this elimiate duplicates, only last exported value is valid
111+
exportedNames.set(exp.name, exp.value);
112+
});
113+
114+
for (const pair of exportedNames) {
115+
const [name, value] = pair;
116+
const processedValues = value.map((item) => {
117+
const replacement = importReplacements.get(item);
118+
if (replacement) {
119+
return {
120+
name: replacement,
121+
};
122+
}
123+
return item;
124+
}).reduce((arr, item) => {
125+
if (typeof item === 'string' && arr.length > 0 && typeof arr[arr.length - 1] === 'string') {
126+
arr[arr.length - 1] += item; // eslint-disable-line no-param-reassign
127+
return arr;
128+
}
129+
arr.push(item);
130+
return arr;
131+
}, []);
132+
if (processedValues.length === 1 && typeof processedValues[0] !== 'string') {
133+
declarations.push(`export { ${processedValues[0].name} as ${name} };`);
134+
} else {
135+
const valuesJs = processedValues.map((item) => {
136+
if (typeof item === 'string') {
137+
return JSON.stringify(item);
138+
}
139+
return item.name;
140+
}).join(' + ');
141+
declarations.push(`export var ${name} = ${valuesJs};`);
142+
}
143+
}
144+
79145
let css;
80146
let sourceMap;
81147
if (options.sourceMap) {
82148
const sourceAndMap = replacer.sourceAndMap(typeof options.sourceMap === 'object' ? options.sourceMap : {
83149
columns,
84150
});
85-
css = sourceAndMap.code;
151+
css = sourceAndMap.source;
86152
sourceMap = sourceAndMap.map;
153+
if (options.sourceMapContext) {
154+
sourceMap.sources = sourceMap.sources.map(absPath => toRelativeUrl(absPath, options.sourceMapContext));
155+
}
156+
if (options.sourceMapPrefix) {
157+
sourceMap.sources = sourceMap.sources.map(sourcePath => options.sourceMapPrefix + sourcePath);
158+
} else {
159+
sourceMap.sources = sourceMap.sources.map(sourcePath => `webpack-css:///${sourcePath}`);
160+
}
87161
} else {
88162
css = replacer.source();
89163
sourceMap = null;
@@ -94,19 +168,17 @@ export default function loader(source, map) {
94168
return [
95169
'// css runtime',
96170
`import * as runtime from ${loaderUtils.stringifyRequest(this, require.resolve('../runtime'))};`,
97-
'',
98-
'// declarations',
99171
declarations.join('\n'),
100172
'',
101173
'// CSS',
102174
'export default /*#__PURE__*/runtime.create([',
103175
].concat(
104176
includedStylesheetsArray.map((include) => {
105177
if (!include.mediaQuery) return ` ${include.name},`;
106-
return ` runtime.importStylesheet(${include.name}, ${JSON.stringify(include.mediaQuery)},`;
178+
return ` runtime.importStylesheet(${include.name}, ${JSON.stringify(include.mediaQuery)}),`;
107179
}),
108180
).concat([
109-
` runtime.${sourceMap ? 'moduleWithSourceMap' : 'moduleWithoutSourceMap'}(module.id, ${cssJs}${sourceMap ? `, ${sourceMap}` : ''})`,
181+
` runtime.${sourceMap ? 'moduleWithSourceMap' : 'moduleWithoutSourceMap'}(module.id, ${cssJs}${sourceMap ? `, ${JSON.stringify(sourceMap)}` : ''})`,
110182
']);',
111183
]).join('\n');
112184
}

src/parse-common.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
export function throwUnexpectedToken(match, index, length, state) {
2-
throw new Error(`Unexpected token '${match}' at ${index} in ${state}`);
1+
export function throwUnexpectedToken(match, index) {
2+
throw new Error(`Unexpected token '${match}' at ${index}`);
33
}
44

55
export function metablockEndMatch(match, index, length) {

src/parse-export.js

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IDENTIFIER, metablockEndMatch } from './parse-common';
1+
import { CSS_IDENTIFIER, IDENTIFIER, metablockEndMatch } from './parse-common';
22

33
function exportStartMatch(match, index) {
44
const block = {
@@ -24,6 +24,13 @@ function exportValueMatch(match) {
2424
this.currentItem.value.push(match);
2525
}
2626

27+
function exportWhitespaceMatch() {
28+
if (this.currentItem.value.length > 0 && this.currentItem.value[this.currentItem.value.length - 1] === ' ') {
29+
return;
30+
}
31+
this.currentItem.value.push(' ');
32+
}
33+
2734
export default {
2835
exportRuleStart: {
2936
':export': exportStartMatch,
@@ -50,17 +57,19 @@ export default {
5057
'comment',
5158
'whitespace',
5259
{
53-
':': 'export3',
60+
':\\s*': 'export3',
5461
},
5562
'nothingElse',
5663
],
5764
export3: [
5865
'comment',
59-
'whitespace',
6066
{
6167
'\\}\\s*': metablockEndMatch,
6268
';': 'export1',
63-
'[^\\};\\s]+': exportValueMatch,
69+
'\\s+': exportWhitespaceMatch,
70+
'[^\\};\\-_a-zA-Z0-9\\s]+': exportValueMatch,
71+
[CSS_IDENTIFIER]: exportValueMatch,
72+
'[\\-_a-zA-Z0-9]+': exportValueMatch,
6473
},
6574
'nothingElse',
6675
],

0 commit comments

Comments
 (0)