Skip to content

Commit 2cc5670

Browse files
committed
fix: support usage with file-loader
This commit adds a plugin to replace relative paths inside url(..) expressions to be relative to the output file so that they can be resolved properly with css-loader. It also changes the output folder to be 'node_modules/.linaria-cache' like before. Inspired by #195 Fixes #182
1 parent fb253cf commit 2cc5670

20 files changed

+1315
-2431
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@
5858
"flow-copy-source": "^2.0.2",
5959
"jest": "^23.5.0",
6060
"jest-image-snapshot": "^2.5.0",
61-
"parcel-bundler": "^1.9.7",
6261
"prettier": "^1.14.2",
6362
"puppeteer": "^1.8.0",
6463
"react": "^16.5.1",
@@ -71,6 +70,7 @@
7170
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
7271
"@babel/register": "^7.0.0",
7372
"loader-utils": "^1.1.0",
73+
"mkdirp": "^0.5.1",
7474
"postcss": "^7.0.2",
7575
"react-is": "^16.5.1",
7676
"source-map": "^0.7.3",

src/__tests__/module.test.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,7 @@ it('clears modules from the cache', () => {
106106
it('exports the path for non JS/JSON files', () => {
107107
const mod = new Module(path.resolve(__dirname, '../__fixtures__/test.js'));
108108

109-
expect(mod.require('./sample-asset.png')).toBe(
110-
path.resolve(__dirname, '../__fixtures__/sample-asset.png')
111-
);
109+
expect(mod.require('./sample-asset.png')).toBe('./sample-asset.png');
112110
});
113111

114112
it('throws when requiring native node modules', () => {

src/babel/module.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,10 @@ class Module {
9898
m.evaluate(code);
9999
}
100100
} else {
101-
// For non JS/JSON requires, just export the filename
101+
// For non JS/JSON requires, just export the id
102102
// This is to support importing assets in webpack
103-
m.exports = filename;
103+
// The module will be resolved by css-loader
104+
m.exports = id;
104105
}
105106
}
106107

src/loader.js

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,38 @@
1-
const os = require('os');
21
const fs = require('fs');
32
const path = require('path');
3+
const mkdirp = require('mkdirp');
44
const Module = require('module');
55
const loaderUtils = require('loader-utils');
66
const transform = require('./transform');
7-
const slugify = require('./slugify');
87

9-
module.exports = function loader(
10-
content /* :string */,
11-
inputSourceMap /* :?Object */
12-
) {
8+
module.exports = function loader(content, inputSourceMap) {
139
const { sourceMap, ...rest } = loaderUtils.getOptions(this) || {};
1410

15-
const result = transform(this.resourcePath, content, rest, inputSourceMap);
11+
const outputDirectory = path.join(
12+
process.cwd(),
13+
'node_modules',
14+
'.linaria-cache'
15+
);
16+
17+
const outputFilename = path.join(
18+
outputDirectory,
19+
path.relative(
20+
process.cwd(),
21+
this.resourcePath.replace(/\.[^.]+$/, '.linaria.css')
22+
)
23+
);
24+
25+
const result = transform(
26+
this.resourcePath,
27+
content,
28+
rest,
29+
inputSourceMap,
30+
outputFilename
31+
);
1632

1733
if (result.cssText) {
1834
let { cssText } = result;
1935

20-
const slug = slugify(this.resourcePath);
21-
const filename = `${path
22-
.basename(this.resourcePath)
23-
.replace(/\.js$/, '')}_${slug}.css`;
24-
2536
if (sourceMap) {
2637
cssText += `/*# sourceMappingURL=data:application/json;base64,${Buffer.from(
2738
result.cssSourceMapText
@@ -44,13 +55,25 @@ module.exports = function loader(
4455
});
4556
}
4657

47-
const output = path.join(os.tmpdir(), filename.split(path.sep).join('_'));
58+
// Read the file first to compare the content
59+
// Write the new content only if it's changed
60+
// This will prevent unnecessary WDS reloads
61+
let currentCssText;
4862

49-
fs.writeFileSync(output, cssText);
63+
try {
64+
currentCssText = fs.readFileSync(outputFilename, 'utf-8');
65+
} catch (e) {
66+
// Ignore error
67+
}
68+
69+
if (currentCssText !== cssText) {
70+
mkdirp.sync(path.dirname(outputFilename));
71+
fs.writeFileSync(outputFilename, cssText);
72+
}
5073

5174
this.callback(
5275
null,
53-
`${result.code}\n\nrequire("${output}")`,
76+
`${result.code}\n\nrequire("${outputFilename}")`,
5477
result.sourceMap
5578
);
5679
return;

src/transform.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* @flow */
22

3+
const path = require('path');
34
const babel = require('@babel/core');
45
const stylis = require('stylis');
56
const { SourceMapGenerator } = require('source-map');
@@ -16,7 +17,7 @@ type Result = {
1617
code: string,
1718
sourceMap: ?Object,
1819
cssText?: string,
19-
cssSourceMapText?: ?string,
20+
cssSourceMapText?: string,
2021
dependencies?: string[],
2122
rules?: {
2223
[className: string]: {
@@ -39,11 +40,14 @@ type PluginOptions = {
3940
}
4041
*/
4142

43+
const STYLIS_DECLARATION = 1;
44+
4245
module.exports = function transform(
4346
filename /* :string */,
4447
content /* :string */,
4548
options /* :PluginOptions */,
46-
inputSourceMap /* :?Object */
49+
inputSourceMap /* :?Object */,
50+
outputFilename /* : ?string */
4751
) /* : Result */ {
4852
// Check if the file contains `css` or `styled` tag first
4953
// Otherwise we should skip transforming
@@ -105,6 +109,28 @@ module.exports = function transform(
105109

106110
let cssText = '';
107111

112+
stylis.use(null)((context, decl) => {
113+
if (context === STYLIS_DECLARATION && outputFilename) {
114+
// When writing to a file, we need to adjust the relative paths inside url(..) expressions
115+
// It'll allow css-loader to resolve an imported asset properly
116+
return decl.replace(
117+
/\b(url\()(\.[^)]+)(\))/,
118+
(match, p1, p2, p3) =>
119+
p1 +
120+
// Replace asset path with new path relative to the output CSS
121+
path.relative(
122+
/* $FlowFixMe */
123+
path.dirname(outputFilename),
124+
// Get the absolute path to the asset from the path relative to the JS file
125+
path.resolve(path.dirname(filename), p2)
126+
) +
127+
p3
128+
);
129+
}
130+
131+
return decl;
132+
});
133+
108134
Object.keys(rules).forEach((selector, index) => {
109135
mappings.push({
110136
generated: {

website/.gitignore

Lines changed: 0 additions & 4 deletions
This file was deleted.

0 commit comments

Comments
 (0)