Skip to content

Commit

Permalink
fix: move linaria's babel plugin to linaria-loader
Browse files Browse the repository at this point in the history
Linaria's babel plugin won't work properly with babel-loader's cache.
It's better to move it to the loader so that we don't have to disable the cache.
  • Loading branch information
satya164 committed Oct 2, 2018
1 parent a327e18 commit df90255
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 167 deletions.
27 changes: 3 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,42 +44,21 @@ yarn add linaria

## Setup

Linaria requires you to use a babel plugin along with a webpack loader.

First, add the babel preset to your `.babelrc`:

```json
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"linaria/babel"
]
}
```

Make sure that `linaria/babel` is the last item in your `presets` list.

Next, add the webpack loader to your `webpack.config.js`:
Linaria requires webpack to build your CSS. To set up the build, add the webpack loader to your `webpack.config.js` after `babel-loader` (if you use it):

```js
module: {
rules: [
{
test: /\.js$/,
use: [
{ loader: 'babel-loader' },
{
loader: 'linaria/loader',
options: {
sourceMap: process.env.NODE_ENV !== 'production',
},
},
{
loader: 'babel-loader',
options: {
cacheDirectory: false,
}
}
],
},
{
Expand All @@ -103,7 +82,7 @@ module: {
},
```

Make sure that `linaria/loader` is included before `babel-loader`.
You also need `css-loader` and `mini-css-extract-plugin` in your pipeline. The usage is shown above.

Now, the CSS you write with Linaria will be extracted at build time to the `styles.css` file. Linaria automatically vendor prefixes and strips whitespace from the CSS.

Expand Down
6 changes: 3 additions & 3 deletions docs/BABEL_PRESET.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# `linaria/babel` preset

The preset pre-processes and evaluates the CSS so that it can be extracted by the bundler. In order to have styles in `css` tagged template literals evaluated, you need to add the `linaria/babel` preset to your Babel configuration.
The preset pre-processes and evaluates the CSS. The webpack loader uses this preset under the hood. You don't need to use add this plugin if you're using the webpack loader.

If you need to use this preset for some reason, add `linaria/babel` to your Babel configuration at the end of your presets list:

`.babelrc`:

Expand All @@ -14,8 +16,6 @@ The preset pre-processes and evaluates the CSS so that it can be extracted by th
}
```

Make sure that `linaria/babel` is the last item in your `presets` list.

## Options

* `evaluate: boolean` (default: `true`) - Enabling this will evaluate dynamic expressions in the CSS. You need to enable this if you want to use imported variables in the CSS or interpolate other components. Enabling this also ensures that your styled components wrapping other styled components will have the correct specificity and override styles properly.
Expand Down
11 changes: 4 additions & 7 deletions docs/BUNDLERS_INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,22 @@ module: {
{
test: /\.js$/,
use: [
{ loader: 'babel-loader' },
{
loader: 'linaria/loader',
options: {
sourceMap: process.env.NODE_ENV !== 'production',
},
},
{
loader: 'babel-loader'
options: {
cacheDirectory: false,
},
}
],
},
],
},
```

Make sure that `linaria/loader` is included before `babel-loader`. Setting the `sourceMap` option to `true` will include source maps for the generated CSS so that you can see where source of the class name in devtools. We recommend to enable this only in development mode because the sourcemap is inlined into the CSS files.
Make sure that `linaria/loader` is included after `babel-loader`. Setting the `sourceMap` option to `true` will include source maps for the generated CSS so that you can see where source of the class name in devtools. We recommend to enable this only in development mode because the sourcemap is inlined into the CSS files.

The loader accepts same options as the [babel preset](/docs/BABEL_PRESET.md).

In order to have your styles extracted, you'll also need to use **css-loader** and **MiniCssExtractPlugin**. First, install them:

Expand Down
16 changes: 5 additions & 11 deletions docs/HOW_IT_WORKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Linaria consists of 2 parts:

## Babel plugin

The Babel plugin will look for `css` and `styled` tags in your code and extract the CSS out to a comment at the end of the file. It will also generate unique class names based on the hash of the filename.
The Babel plugin will look for `css` and `styled` tags in your code, extract the CSS out and return it in the file's metadata. It will also generate unique class names based on the hash of the filename.

When using the `styled` tag, dynamic interpolations will be replaced with CSS custom properties. References to constants in the scope will also be inlined. If the same expression is used multiple times, the plugin will create a single CSS custom property for those.

Expand Down Expand Up @@ -62,10 +62,11 @@ const Container = styled('div')({
'c1ugh8t9-2': [props => props.color],
},
});
```

/*
CSS OUTPUT TEXT START
The extracted CSS will look something like this:

```css
.Title_t1ugh8t9 {
font-family: var(--t1ugh8t-0);
}
Expand All @@ -81,13 +82,6 @@ CSS OUTPUT TEXT START
.Container_c1ugh8t9:hover {
border-color: blue;
}
CSS OUTPUT TEXT END
CSS OUTPUT MAPPINGS:[{"generated":{"line":1,"column":0},"original":{"line":3,"column":6},"name":"Title_t1ugh8t9"},{"generated":{"line":5,"column":0},"original":{"line":7,"column":6},"name":"Container_c1ugh8t"}]
CSS OUTPUT DEPENDENCIES:[]
*/
```

If we encounter a valid unit directly after the interpolation, it'll be passed to the helper so that the correct unit is used when setting the property. This allows you to write this:
Expand Down Expand Up @@ -166,4 +160,4 @@ But keep in mind that if you're doing SSR for your app, this won't work with SSR

## Webpack loader

The webpack loader reads the comment output from the Babel plugin and writes it to a CSS file, which can be picked up by `css-loader` to generate the final CSS. It's also responsible for generating the sourcemap from the metadata from the Babel plugin.
The webpack loader uses the Babel plugin internally and writes the CSS text to a CSS file, which can be picked up by `css-loader` to generate the final CSS. It's also responsible for generating the sourcemap from the metadata from the Babel plugin.
17 changes: 1 addition & 16 deletions src/babel/extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,22 +151,7 @@ module.exports = function extract(
cssText += `${stylis(selector, state.rules[selector].cssText)}\n`;
});

// Add the collected styles as a comment to the end of file
path.addComment(
'trailing',
`\nCSS OUTPUT TEXT START\n${cssText}\nCSS OUTPUT TEXT END\n` +
`\nCSS OUTPUT MAPPINGS:${JSON.stringify(
mappings
)}\nCSS OUTPUT DEPENDENCIES:${JSON.stringify(
// Remove duplicate dependencies
state.dependencies.filter(
(d, i, self) => self.indexOf(d) === i
)
)}\n`
);

// Also return the data as the fle metadata
// Bundlers or other tools can use this instead of reading the comment
// Store the result as the file metadata
state.file.metadata = {
linaria: {
rules: state.rules,
Expand Down
1 change: 1 addition & 0 deletions src/babel/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class Module {
// We don't support dynamic imports when evaluating, but don't wanna syntax error
// This will replace dynamic imports with an object that does nothing
require.resolve('./dynamic-import-noop'),
[require.resolve('./extract'), { evaluate: true }],
],
sourceMaps: true,
exclude: /node_modules/,
Expand Down
43 changes: 17 additions & 26 deletions src/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,50 @@ const fs = require('fs');
const path = require('path');
const Module = require('module');
const loaderUtils = require('loader-utils');
const slugify = require('./slugify');
const transform = require('./transform');
const slugify = require('./slugify');

module.exports = function loader(content) {
const options = loaderUtils.getOptions(this) || {};
const { css, dependencies, map } = transform(
this.resourcePath,
content,
options.sourceMap
);
module.exports = function loader(content /* :string */) {
const { sourceMap, ...rest } = loaderUtils.getOptions(this) || {};

let cssText = css;
const result = transform(this.resourcePath, content, rest);

if (result.cssText) {
let { cssText } = result;

if (cssText) {
const slug = slugify(this.resourcePath);
const filename = `${path
.basename(this.resourcePath)
.replace(/\.js$/, '')}_${slug}.css`;

const output = path.join(os.tmpdir(), filename.split(path.sep).join('_'));

if (map) {
map.setSourceContent(
this.resourcePath,
// We need to get the original source before it was processed
this.fs.readFileSync(this.resourcePath).toString()
);

if (sourceMap) {
cssText += `/*# sourceMappingURL=data:application/json;base64,${Buffer.from(
map.toString()
result.sourceMap
).toString('base64')}*/`;
}

if (dependencies) {
dependencies.forEach(dep => {
if (result.dependencies && result.dependencies.length) {
result.dependencies.forEach(dep => {
try {
const file = Module._resolveFilename(dep, {
const f = Module._resolveFilename(dep, {
id: this.resourcePath,
filename: this.resourcePath,
paths: Module._nodeModulePaths(path.dirname(this.resourcePath)),
});

this.addDependency(file);
this.addDependency(f);
} catch (e) {
// Ignore
}
});
}

const output = path.join(os.tmpdir(), filename.split(path.sep).join('_'));

fs.writeFileSync(output, cssText);

return `${content}\n\nrequire("${output}")`;
return `${result.code}\n\nrequire("${output}")`;
}

return content;
return result.code;
};
27 changes: 6 additions & 21 deletions src/stylelint/preprocessor.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* @flow */

const babel = require('@babel/core');
const transform = require('../transform');

/* ::
type LintResult = {
Expand All @@ -13,31 +13,16 @@ function preprocessor() {

return {
code(input /* : string */, filename /* : string */) {
// Check if the file contains `css` or `styled` tag first
// Otherwise we should skip linting
if (!/\b(styled(\([^)]+\)|\.[a-z0-9]+)|css)`/.test(input)) {
return '';
}

let metadata;

try {
// eslint-disable-next-line prefer-destructuring
metadata = babel.transformSync(input, {
filename,
}).metadata;
} catch (e) {
return '';
}
const { rules, replacements } = transform(filename, input, {
evaluate: true,
});

if (!metadata.linaria) {
if (!rules) {
return '';
}

let cssText = '';

// Construct a CSS-ish file from the unprocessed style rules
const { rules, replacements } = metadata.linaria;
let cssText = '';

Object.keys(rules).forEach(selector => {
const rule = rules[selector];
Expand Down
Loading

0 comments on commit df90255

Please sign in to comment.