Skip to content

Commit

Permalink
Fix postcss modules composes imports (#2642)
Browse files Browse the repository at this point in the history
* Allow parcel paths for css modules

* Fix duplicate css rules for css modules

Fixes: #2592

* Fix sass imports for css modules

Fixes #2501

* Adjust tests for changed class pattern

* Add postcss composes tests

* Add postcss composes tests for edge cases

* Fix postcss composes dependency regexp to include all kind of class names

* Ensure tests run across different filesystems

* Fix postcss module loading on windows

* Cleanup css modules loader
  • Loading branch information
garthenweb authored and DeMoorJasper committed Feb 26, 2019
1 parent 8afacc3 commit f269e3e
Show file tree
Hide file tree
Showing 22 changed files with 349 additions and 17 deletions.
205 changes: 203 additions & 2 deletions packages/core/integration-tests/test/css.js
Expand Up @@ -289,9 +289,9 @@ describe('css', function() {
assert.equal(typeof output, 'function');

let value = output();
assert(/_index_[0-9a-z]+_1/.test(value));
assert(/_index_[0-9a-z]/.test(value));

let cssClass = value.match(/(_index_[0-9a-z]+_1)/)[1];
let cssClass = value.match(/(_index_[0-9a-z]+)/)[1];

let css = await fs.readFile(
path.join(__dirname, '/dist/index.css'),
Expand All @@ -313,6 +313,207 @@ describe('css', function() {
assert.equal(run1(), run2());
});

it('should support postcss composes imports', async function() {
let b = await bundle(
path.join(__dirname, '/integration/postcss-composes/index.js')
);

await assertBundleTree(b, {
name: 'index.js',
assets: ['index.js', 'composes-1.css', 'composes-2.css', 'mixins.css'],
childBundles: [
{
name: 'index.css',
assets: ['composes-1.css', 'composes-2.css', 'mixins.css'],
childBundles: []
},
{
type: 'map'
}
]
});

let output = await run(b);
assert.equal(typeof output, 'function');

let value = output();
const composes1Classes = value.composes1.split(' ');
const composes2Classes = value.composes2.split(' ');
assert(composes1Classes[0].startsWith('_composes1_'));
assert(composes1Classes[1].startsWith('_test_'));
assert(composes2Classes[0].startsWith('_composes2_'));
assert(composes2Classes[1].startsWith('_test_'));

let css = await fs.readFile(
path.join(__dirname, '/dist/index.css'),
'utf8'
);
let cssClass1 = value.composes1.match(/(_composes1_[0-9a-z]+)/)[1];
assert(css.includes(`.${cssClass1}`));
let cssClass2 = value.composes2.match(/(_composes2_[0-9a-z]+)/)[1];
assert(css.includes(`.${cssClass2}`));
});

it('should not include css twice for postcss composes imports', async function() {
let b = await bundle(
path.join(__dirname, '/integration/postcss-composes/index.js')
);

await run(b);

let css = await fs.readFile(
path.join(__dirname, '/dist/index.css'),
'utf8'
);
assert.equal(
css.indexOf('height: 100px;'),
css.lastIndexOf('height: 100px;')
);
});

it('should support postcss composes imports for sass', async function() {
let b = await bundle(
path.join(__dirname, '/integration/postcss-composes/index2.js')
);

await assertBundleTree(b, {
name: 'index2.js',
assets: ['index2.js', 'composes-3.css', 'mixins.scss'],
childBundles: [
{
name: 'index2.css',
assets: ['composes-3.css', 'mixins.scss'],
childBundles: []
},
{
type: 'map'
}
]
});

let output = await run(b);
assert.equal(typeof output, 'function');

let value = output();
const composes3Classes = value.composes3.split(' ');
assert(composes3Classes[0].startsWith('_composes3_'));
assert(composes3Classes[1].startsWith('_test_'));

let css = await fs.readFile(
path.join(__dirname, '/dist/index2.css'),
'utf8'
);
assert(css.includes('height: 200px;'));
});

it('should support postcss composes imports with custom path names', async function() {
let b = await bundle(
path.join(__dirname, '/integration/postcss-composes/index3.js')
);

await assertBundleTree(b, {
name: 'index3.js',
assets: ['index3.js', 'composes-4.css', 'mixins.css'],
childBundles: [
{
name: 'index3.css',
assets: ['composes-4.css', 'mixins.css'],
childBundles: []
},
{
type: 'map'
}
]
});

let output = await run(b);
assert.equal(typeof output, 'function');

let value = output();
const composes4Classes = value.composes4.split(' ');
assert(composes4Classes[0].startsWith('_composes4_'));
assert(composes4Classes[1].startsWith('_test_'));

let css = await fs.readFile(
path.join(__dirname, '/dist/index3.css'),
'utf8'
);
assert(css.includes('height: 100px;'));
});

it('should support deep nested postcss composes imports', async function() {
let b = await bundle(
path.join(__dirname, '/integration/postcss-composes/index4.js')
);

await assertBundleTree(b, {
name: 'index4.js',
assets: [
'index4.js',
'composes-5.css',
'mixins-intermediate.css',
'mixins.css'
],
childBundles: [
{
name: 'index4.css',
assets: ['composes-5.css', 'mixins-intermediate.css', 'mixins.css'],
childBundles: []
},
{
type: 'map'
}
]
});

let output = await run(b);
assert.equal(typeof output, 'function');

let value = output();
const composes5Classes = value.composes5.split(' ');
assert(composes5Classes[0].startsWith('_composes5_'));
assert(composes5Classes[1].startsWith('_intermediate_'));
assert(composes5Classes[2].startsWith('_test_'));

let css = await fs.readFile(
path.join(__dirname, '/dist/index4.css'),
'utf8'
);
assert(css.includes('height: 100px;'));
assert(css.includes('height: 300px;'));
assert(css.indexOf('._test_') < css.indexOf('._intermediate_'));
});

it('should support postcss composes imports for multiple selectors', async function() {
let b = await bundle(
path.join(__dirname, '/integration/postcss-composes/index5.js')
);

await assertBundleTree(b, {
name: 'index5.js',
assets: ['index5.js', 'composes-6.css', 'mixins.css'],
childBundles: [
{
name: 'index5.css',
assets: ['composes-6.css', 'mixins.css'],
childBundles: []
},
{
type: 'map'
}
]
});

let output = await run(b);
assert.equal(typeof output, 'function');

let value = output();
const composes6Classes = value.composes6.split(' ');
assert(composes6Classes[0].startsWith('_composes6_'));
assert(composes6Classes[1].startsWith('_test_'));
assert(composes6Classes[2].startsWith('_test-2_'));
});

it('should minify CSS in production mode', async function() {
let b = await bundle(
path.join(__dirname, '/integration/cssnano/index.js'),
Expand Down
@@ -0,0 +1,3 @@
{
"modules": true
}
@@ -0,0 +1,4 @@
.composes1 {
composes: test from './mixins.css';
border: 3px solid orange;
}
@@ -0,0 +1,4 @@
.composes2 {
composes: test from './mixins.css';
border: 3px solid red;
}
@@ -0,0 +1,4 @@
.composes3 {
composes: test from './mixins.scss';
border: 3px solid brown;
}
@@ -0,0 +1,4 @@
.composes4 {
composes: test from '~mixins.css';
border: 3px solid black;
}
@@ -0,0 +1,4 @@
.composes5 {
composes: intermediate from './mixins-intermediate.css';
border: 3px solid yellow;
}
@@ -0,0 +1,4 @@
.composes6 {
composes: test test-2 from './mixins.css';
border: 3px solid orangered;
}
@@ -0,0 +1,6 @@
var map1 = require('./composes-1.css');
var map2 = require('./composes-2.css');

module.exports = function () {
return Object.assign({}, map1, map2);
};
@@ -0,0 +1,5 @@
var map3 = require('./composes-3.css');

module.exports = function () {
return map3;
};
@@ -0,0 +1,5 @@
var map4 = require('./composes-4.css');

module.exports = function () {
return map4;
};
@@ -0,0 +1,5 @@
var map5 = require('./composes-5.css');

module.exports = function () {
return map5;
};
@@ -0,0 +1,5 @@
var map6 = require('./composes-6.css');

module.exports = function () {
return map6;
};
@@ -0,0 +1,4 @@
.intermediate {
composes: test from './mixins.css';
height: 300px;
}
@@ -0,0 +1,8 @@
.test {
height: 100px;
width: 100px;
}

.test-2 {
background: red;
}
@@ -0,0 +1,6 @@
$test: 200px;

.test {
height: $test;
width: $test;
}
4 changes: 2 additions & 2 deletions packages/core/integration-tests/test/less.js
Expand Up @@ -198,12 +198,12 @@ describe('less', function() {

let output = await run(b);
assert.equal(typeof output, 'function');
assert.equal(output(), '_index_ku5n8_1');
assert(output().startsWith('_index_'));

let css = await fs.readFile(
path.join(__dirname, '/dist/index.css'),
'utf8'
);
assert(css.includes('._index_ku5n8_1'));
assert(css.includes('._index_'));
});
});
4 changes: 2 additions & 2 deletions packages/core/integration-tests/test/stylus.js
Expand Up @@ -139,13 +139,13 @@ describe('stylus', function() {

let output = await run(b);
assert.equal(typeof output, 'function');
assert.equal(output(), '_index_g9mqo_1');
assert(output().startsWith('_index_'));

let css = await fs.readFile(
path.join(__dirname, '/dist/index.css'),
'utf8'
);
assert(css.includes('._index_g9mqo_1'));
assert(css.includes('._index_'));
});

it('should support requiring stylus files with glob dependencies', async function() {
Expand Down
1 change: 1 addition & 0 deletions packages/core/parcel-bundler/package.json
Expand Up @@ -41,6 +41,7 @@
"command-exists": "^1.2.6",
"commander": "^2.11.0",
"cross-spawn": "^6.0.4",
"css-modules-loader-core": "^1.1.0",
"cssnano": "^4.0.0",
"deasync": "^0.1.14",
"dotenv": "^5.0.0",
Expand Down
27 changes: 17 additions & 10 deletions packages/core/parcel-bundler/src/Asset.js
Expand Up @@ -84,16 +84,7 @@ class Asset {
this.dependencies.set(name, Object.assign({name}, opts));
}

addURLDependency(url, from = this.name, opts) {
if (!url || isURL(url)) {
return url;
}

if (typeof from === 'object') {
opts = from;
from = this.name;
}

resolveDependency(url, from = this.name) {
const parsed = URL.parse(url);
let depName;
let resolved;
Expand All @@ -110,8 +101,24 @@ class Asset {
depName = './' + path.relative(path.dirname(this.name), resolved);
}

return {depName, resolved};
}

addURLDependency(url, from = this.name, opts) {
if (!url || isURL(url)) {
return url;
}

if (typeof from === 'object') {
opts = from;
from = this.name;
}

const {depName, resolved} = this.resolveDependency(url, from);

this.addDependency(depName, Object.assign({dynamic: true, resolved}, opts));

const parsed = URL.parse(url);
parsed.pathname = this.options.parser
.getAsset(resolved, this.options)
.generateBundleName();
Expand Down

0 comments on commit f269e3e

Please sign in to comment.