Skip to content

Commit

Permalink
fix: support usage with file-loader
Browse files Browse the repository at this point in the history
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
  • Loading branch information
satya164 committed Oct 15, 2018
1 parent fb253cf commit 2cc5670
Show file tree
Hide file tree
Showing 20 changed files with 1,315 additions and 2,431 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
"flow-copy-source": "^2.0.2",
"jest": "^23.5.0",
"jest-image-snapshot": "^2.5.0",
"parcel-bundler": "^1.9.7",
"prettier": "^1.14.2",
"puppeteer": "^1.8.0",
"react": "^16.5.1",
Expand All @@ -71,6 +70,7 @@
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/register": "^7.0.0",
"loader-utils": "^1.1.0",
"mkdirp": "^0.5.1",
"postcss": "^7.0.2",
"react-is": "^16.5.1",
"source-map": "^0.7.3",
Expand Down
4 changes: 1 addition & 3 deletions src/__tests__/module.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,7 @@ it('clears modules from the cache', () => {
it('exports the path for non JS/JSON files', () => {
const mod = new Module(path.resolve(__dirname, '../__fixtures__/test.js'));

expect(mod.require('./sample-asset.png')).toBe(
path.resolve(__dirname, '../__fixtures__/sample-asset.png')
);
expect(mod.require('./sample-asset.png')).toBe('./sample-asset.png');
});

it('throws when requiring native node modules', () => {
Expand Down
5 changes: 3 additions & 2 deletions src/babel/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,10 @@ class Module {
m.evaluate(code);
}
} else {
// For non JS/JSON requires, just export the filename
// For non JS/JSON requires, just export the id
// This is to support importing assets in webpack
m.exports = filename;
// The module will be resolved by css-loader
m.exports = id;
}
}

Expand Down
53 changes: 38 additions & 15 deletions src/loader.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
const os = require('os');
const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');
const Module = require('module');
const loaderUtils = require('loader-utils');
const transform = require('./transform');
const slugify = require('./slugify');

module.exports = function loader(
content /* :string */,
inputSourceMap /* :?Object */
) {
module.exports = function loader(content, inputSourceMap) {
const { sourceMap, ...rest } = loaderUtils.getOptions(this) || {};

const result = transform(this.resourcePath, content, rest, inputSourceMap);
const outputDirectory = path.join(
process.cwd(),
'node_modules',
'.linaria-cache'
);

const outputFilename = path.join(
outputDirectory,
path.relative(
process.cwd(),
this.resourcePath.replace(/\.[^.]+$/, '.linaria.css')
)
);

const result = transform(
this.resourcePath,
content,
rest,
inputSourceMap,
outputFilename
);

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

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

if (sourceMap) {
cssText += `/*# sourceMappingURL=data:application/json;base64,${Buffer.from(
result.cssSourceMapText
Expand All @@ -44,13 +55,25 @@ module.exports = function loader(
});
}

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

fs.writeFileSync(output, cssText);
try {
currentCssText = fs.readFileSync(outputFilename, 'utf-8');
} catch (e) {
// Ignore error
}

if (currentCssText !== cssText) {
mkdirp.sync(path.dirname(outputFilename));
fs.writeFileSync(outputFilename, cssText);
}

this.callback(
null,
`${result.code}\n\nrequire("${output}")`,
`${result.code}\n\nrequire("${outputFilename}")`,
result.sourceMap
);
return;
Expand Down
30 changes: 28 additions & 2 deletions src/transform.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* @flow */

const path = require('path');
const babel = require('@babel/core');
const stylis = require('stylis');
const { SourceMapGenerator } = require('source-map');
Expand All @@ -16,7 +17,7 @@ type Result = {
code: string,
sourceMap: ?Object,
cssText?: string,
cssSourceMapText?: ?string,
cssSourceMapText?: string,
dependencies?: string[],
rules?: {
[className: string]: {
Expand All @@ -39,11 +40,14 @@ type PluginOptions = {
}
*/

const STYLIS_DECLARATION = 1;

module.exports = function transform(
filename /* :string */,
content /* :string */,
options /* :PluginOptions */,
inputSourceMap /* :?Object */
inputSourceMap /* :?Object */,
outputFilename /* : ?string */
) /* : Result */ {
// Check if the file contains `css` or `styled` tag first
// Otherwise we should skip transforming
Expand Down Expand Up @@ -105,6 +109,28 @@ module.exports = function transform(

let cssText = '';

stylis.use(null)((context, decl) => {
if (context === STYLIS_DECLARATION && outputFilename) {
// When writing to a file, we need to adjust the relative paths inside url(..) expressions
// It'll allow css-loader to resolve an imported asset properly
return decl.replace(
/\b(url\()(\.[^)]+)(\))/,
(match, p1, p2, p3) =>
p1 +
// Replace asset path with new path relative to the output CSS
path.relative(
/* $FlowFixMe */
path.dirname(outputFilename),
// Get the absolute path to the asset from the path relative to the JS file
path.resolve(path.dirname(filename), p2)
) +
p3
);
}

return decl;
});

Object.keys(rules).forEach((selector, index) => {
mappings.push({
generated: {
Expand Down
4 changes: 0 additions & 4 deletions website/.gitignore

This file was deleted.

File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
9 changes: 9 additions & 0 deletions website/babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ module.exports = {
['@babel/preset-env', { targets: { node: 8 } }],
require.resolve('../src/babel'),
],
plugins: [
[
'file-loader',
{
publicPath: '/dist',
outputPath: '/dist',
},
],
],
},
},
};
File renamed without changes.
3 changes: 3 additions & 0 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"lint:css": "stylelint src/**/*.js"
},
"dependencies": {
"babel-plugin-file-loader": "^1.1.0",
"dedent": "^0.7.0",
"escape-html": "^1.0.3",
"ignore-styles": "^5.0.1",
Expand All @@ -35,8 +36,10 @@
"babel-loader": "^8.0.2",
"babel-plugin-module-resolver": "^3.1.1",
"cross-env": "^5.2.0",
"css-hot-loader": "^1.4.2",
"css-loader": "^1.0.0",
"del-cli": "^1.1.0",
"file-loader": "^2.0.0",
"mini-css-extract-plugin": "^0.4.2",
"stylelint": "^9.5.0",
"stylelint-config-recommended": "^2.1.0",
Expand Down
2 changes: 1 addition & 1 deletion website/serve.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const path = require('path');

module.exports = {
port: 3242,
content: path.resolve(__dirname, 'static'),
content: path.resolve(__dirname),
devMiddleware: {
publicPath: '/dist/',
},
Expand Down
5 changes: 4 additions & 1 deletion website/src/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import { media } from '../styles/utils';
export default function Header() {
return (
<NavBar>
<LogoImage src="/images/linaria-logo.svg" alt="Linaria Logo" />
<LogoImage
src={require('../../assets/linaria-logo.svg')}
alt="Linaria Logo"
/>
<Links>
<li>
<LinkItem href="https://github.com/callstack/linaria#features">
Expand Down
4 changes: 2 additions & 2 deletions website/src/components/Hero.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default function Hero() {
<RightColumn>
<CodeSample
alt="Linaria code sample"
src="/images/code-sample.png"
src={require('../../assets/code-sample.png')}
/>
</RightColumn>
</Row>
Expand All @@ -42,7 +42,7 @@ const HeroContainer = styled.main`
${media.large} {
padding: 64px 0;
background-image: url('/images/linaria-logomark.svg');
background-image: url(${require('../../assets/linaria-logomark.svg')});
background-repeat: no-repeat;
background-position: bottom right;
}
Expand Down
12 changes: 1 addition & 11 deletions website/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,11 @@ router.get('/', async ctx => {
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Fira+Mono">
<style type="text/css">${critical}</style>
<script src="/dist/app.bundle.js"></script>
</head>
<body>
<div id="root">${html}</div>
<link rel="stylesheet" href="/vendor/prism.css">
<script src="/dist/app.bundle.js"></script>
<link rel="stylesheet" href="/styles/${slug}">
</body>
</html>
Expand All @@ -65,14 +63,6 @@ router.get('/dist/:path+', async ctx => {
await send(ctx, path.join('dist', ctx.params.path));
});

router.get('/vendor/:path+', async ctx => {
await send(ctx, path.join('static', 'vendor', ctx.params.path));
});

router.get('/images/:path+', async ctx => {
await send(ctx, path.join('static', 'images', ctx.params.path));
});

router.get('/styles/:slug', async ctx => {
ctx.type = 'text/css';
ctx.body = cache[ctx.params.slug];
Expand Down
9 changes: 9 additions & 0 deletions website/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ module.exports = {
{
test: /\.css$/,
use: [
'css-hot-loader',
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
Expand All @@ -52,6 +53,14 @@ module.exports = {
},
],
},
{
test: /\.(png|jpg|gif|svg)$/,
use: [
{
loader: 'file-loader',
},
],
},
],
},
};
Loading

0 comments on commit 2cc5670

Please sign in to comment.