Skip to content

Commit

Permalink
#88: keepImport feature
Browse files Browse the repository at this point in the history
  • Loading branch information
morulus committed Sep 2, 2018
1 parent 55c18ce commit 354e5d3
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 29 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,40 @@ By default we look for `.css` files, but you can also specify the extensions we
}
```

## Keeping import

By default, original import declaration in javascript code will be replaced with an object with styles. But you may keep import declarations by turning the option `keepImport` to `true`.

```json
{
"plugins": [
["transform-postcss", {
"config": "configuration/postcss.config.js",
"extensions": [".scss"],
"keepImport": true
}]
]
}
```

In this case, you will still get the required objects with styles, but import declarations (or require expressions) will be kept above without any assignment expression.

For example, this code:

```js
import styles from './styles';
```

```css
.example { color: cyan; }
```

Will be transformed to:

```js
import './styles';
var styles = {"example":"_example_amfqe_1"};
```

## Details

Expand Down
116 changes: 89 additions & 27 deletions src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ const serverExcutable = join(__dirname, 'postcss-server.js');

let server;

/* eslint-disable id-length */
const findExpressionStatementChild = (path: any, t: any): any => {
const parent = path.parentPath;

if (
t.isExpressionStatement(parent) ||
t.isProgram(parent) ||
t.isBlockStatement(parent)
) {
return path;
}

return findExpressionStatementChild(parent, t);
};
/* eslint-enable id-length */

const startServer = () => {
server = spawn(nodeExecutable, [serverExcutable, socketPath, tmpPath], {
env: process.env, // eslint-disable-line no-process-env
Expand Down Expand Up @@ -96,32 +112,61 @@ export default function transformPostCSS({ types: t }: any): any {
}

const [{ value: stylesheetPath }] = args;
const { config, extensions } = this.opts;
const tokens = getStylesFromStylesheet(
stylesheetPath,
file,
config,
extensions
);

if (tokens !== undefined) {
const expression = path.findParent((test) => (
test.isVariableDeclaration() ||
test.isExpressionStatement()
));

expression.addComment(
'trailing', ` @related-file ${stylesheetPath}`, true
const { config, extensions, keepImport } = this.opts;

if (!t.isExpressionStatement(path.parent)) {
const tokens = getStylesFromStylesheet(
stylesheetPath,
file,
config,
extensions
);

path.replaceWith(t.objectExpression(
Object.keys(tokens).map(
(token) => t.objectProperty(
t.stringLiteral(token),
t.stringLiteral(tokens[token])
if (tokens !== undefined) {
const finalExpression = t.objectExpression(
Object.keys(tokens).map(
(token) => t.objectProperty(
t.stringLiteral(token),
t.stringLiteral(tokens[token])
)
)
)
));
);

path.replaceWith(finalExpression);

if (t.isProperty(path.parentPath)) {
path.parentPath.addComment(
'trailing', ` @related-file ${stylesheetPath}`, true
);
}
else {

// Add comment
const expression = path.findParent((test) => (
test.isVariableDeclaration() ||
test.isExpressionStatement()
));

expression.addComment(
'trailing', ` @related-file ${stylesheetPath}`, true
);
}

// Keeped `require` will be placed before closest expression
// statement child
if (keepImport) {
findExpressionStatementChild(path, t)
.insertBefore(t.expressionStatement(
t.callExpression(
t.identifier('require'),
[t.stringLiteral(stylesheetPath)]
)
));
}
}
}
else if (!keepImport) {
path.remove();
}
},
ImportDeclaration(path: any, { file }: any) {
Expand All @@ -131,7 +176,7 @@ export default function transformPostCSS({ types: t }: any): any {
return;
}

const { config, extensions } = this.opts;
const { config, extensions, keepImport } = this.opts;
const tokens = getStylesFromStylesheet(
stylesheetPath,
file,
Expand All @@ -153,9 +198,26 @@ export default function transformPostCSS({ types: t }: any): any {
const variableDeclaration = t.VariableDeclaration('var',
[t.VariableDeclarator(path.node.specifiers[0].local, styles)]);

/* eslint-enable new-cap */
path.addComment('trailing', ` @related-file ${stylesheetPath}`, true);
path.replaceWith(variableDeclaration);
if (keepImport) {
path.replaceWithMultiple([
t.importDeclaration([], t.stringLiteral(stylesheetPath)),
variableDeclaration,
]);

// Add comment directly to the variable declaration
variableDeclaration.trailingComments.push({
type: 'CommentLine',
value: ` @related-file ${stylesheetPath}`,
});
}
else {
path.replaceWith(variableDeclaration);

// Add comment
path.addComment(
'trailing', ` @related-file ${stylesheetPath}`, true
);
}
}
},
},
Expand Down
9 changes: 9 additions & 0 deletions test/fixtures/keep.import.expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"use strict";

require("./simple.css");

var styles = {
"simple": "_simple_jvai8_1"
}; // @related-file ./simple.css

console.log(styles);
3 changes: 3 additions & 0 deletions test/fixtures/keep.import.no.name.expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

require('simple.css');
7 changes: 7 additions & 0 deletions test/fixtures/keep.require.expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

require('./simple.css');

var styles = {
'simple': '_simple_jvai8_1'
}; // @related-file ./simple.css
11 changes: 11 additions & 0 deletions test/fixtures/keep.require.in.expression.expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

require('./simple.css');

var doc = {
styles: {
'simple': '_simple_jvai8_1'
} // @related-file ./simple.css
,
title: 'test'
};
3 changes: 3 additions & 0 deletions test/fixtures/keep.require.no.name.expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

require('./simple.css');
4 changes: 4 additions & 0 deletions test/fixtures/require.in.expression.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
var doc = {
styles: require('./simple.css'),
title: 'test'
}
1 change: 1 addition & 0 deletions test/fixtures/require.no.name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('./simple.css');
5 changes: 3 additions & 2 deletions test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,18 @@ export const transform = (
filename: string,
babelOptionOverrides: ?{ [string]: mixed },
extensions: ?string[],
advancedOptions: ?{ [string]: mixed }
): Promise<string> => {
const file = path.join(fixtures, filename);

const options = Object.assign({
babelrc: false,
presets: [ ['env', { targets: { node: 'current' } }] ],
plugins: [
['../../src/plugin.js', {
['../../src/plugin.js', Object.assign({
config: 'fixtures/postcss.config.js',
extensions,
}],
}, advancedOptions)],
],
}, babelOptionOverrides);

Expand Down
110 changes: 110 additions & 0 deletions test/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ describe('babel-plugin-transform-postcss', () => {
shouldBehaveLikeSeverIsRunning();
});

describe('when transforming require.no.name.js', () => {
beforeEach(() => transform('require.no.name.js', babelNoModules));

it('does not launch the server', () => {
expect(childProcess.spawn).to.not.have.been.called;
});

it('does not launch a client', () => {
expect(childProcess.execFileSync).to.not.have.been.called;
});
});

describe('when transforming import.js', () => {
let result;

Expand Down Expand Up @@ -208,4 +220,102 @@ describe('babel-plugin-transform-postcss', () => {
});
});

describe('when keepImport enabled with import.js', () => {
let result;

beforeEach(async() => {
result = await transform(
'import.js',
null,
['.css'],
{
keepImport: true,
}
);
});

it('compiles correctly', async() => {
expect(result).to.eql((await read('keep.import.expected.js')).trim());
});
});

describe('when keepImport enabled with require.js', () => {
let result;

beforeEach(async() => {
result = await transform(
'require.js',
null,
['.css'],
{
keepImport: true,
}
);
});

it('compiles correctly', async() => {
expect(result).to.eql((await read('keep.require.expected.js')).trim());
});
});

describe('when keepImport enabled with import.no.name.js', () => {
let result;

beforeEach(async() => {
result = await transform(
'import.no.name.js',
null,
['.css'],
{
keepImport: true,
}
);
});

it('compiles correctly', async() => {
expect(result)
.to.eql((await read('keep.import.no.name.expected.js')).trim());
});
});

describe('when keepImport enabled with require.no.name.js', () => {
let result;

beforeEach(async() => {
result = await transform(
'require.no.name.js',
null,
['.css'],
{
keepImport: true,
}
);
});

it('compiles correctly', async() => {
expect(result)
.to.eql((await read('keep.require.no.name.expected.js')).trim());
});
});

describe('when keepImport enabled with require.in.expression.js', () => {
let result;

beforeEach(async() => {
result = await transform(
'require.in.expression.js',
null,
['.css'],
{
keepImport: true,
}
);
});

it('compiles correctly', async() => {
expect(result)
.to.eql((await read('keep.require.in.expression.expected.js')).trim());
});
});

});

0 comments on commit 354e5d3

Please sign in to comment.