Skip to content

Commit

Permalink
fix(webpack): add cacheProvider for Linaria v3 (#889)
Browse files Browse the repository at this point in the history
  • Loading branch information
malash committed Dec 20, 2021
1 parent 3d6b6c5 commit ee656dd
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 49 deletions.
27 changes: 17 additions & 10 deletions docs/BUNDLERS_INTEGRATION.md
@@ -1,6 +1,7 @@
# Bundlers Integration

## Jump To

- [webpack](#webpack)
- [esbuild](#esbuild)
- [Rollup](#Rollup)
Expand All @@ -12,7 +13,7 @@ If you use Babel in your project, make sure to have a [config file for Babel](ht

## Bundlers

Please note, that `@babel/core` is a peer dependency of all loaders. Do not forget to add it to `devDependencies` list in your project.
Please note, that `@babel/core` is a peer dependency of all loaders. Do not forget to add it to `devDependencies` list in your project.

### webpack

Expand Down Expand Up @@ -177,6 +178,16 @@ The loader accepts the following options:

Setting this 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.

- `cacheProvider: undefined | string | ICache` (default: `undefined`):
By default Linaria use a memory cache to store temporary CSS files. But if you are using this loader with [thread-loader](https://www.npmjs.com/package/thread-loader) you should use some consistent cache to prevent [some unexpected issues](https://github.com/callstack/linaria/issues/881). This options support a `ICache` instance or a path to NodeJS module which export a `ICache` instance as `module.exports`

> ```
> interface ICache {
> get: (key: string) => Promise<string>;
> set: (key: string, value: string) => Promise<void>
> }
> ```
- `extension: string` (default: `'.linaria.css'`):

An extension of the intermediate CSS files.
Expand Down Expand Up @@ -264,9 +275,7 @@ import linaria from '@linaria/rollup';
import css from 'rollup-plugin-css-only';

export default {
/* rest of your config */
plugins: [
/* rest of your plugins */
linaria({
sourceMap: process.env.NODE_ENV !== 'production',
}),
Expand All @@ -277,32 +286,31 @@ export default {
};
```


If you are using [@rollup/plugin-babel](https://github.com/rollup/plugins/tree/master/packages/babel) as well, ensure the linaria plugin is declared earlier in the `plugins` array than your babel plugin.
If you are using [@rollup/plugin-babel](https://github.com/rollup/plugins/tree/master/packages/babel) as well, ensure the linaria plugin is declared earlier in the `plugins` array than your babel plugin.

```js
import linaria from '@linaria/rollup';
import css from 'rollup-plugin-css-only';
import babel from "@rollup/plugin-babel";
import babel from '@rollup/plugin-babel';

export default {
/* rest of your config */
plugins: [
linaria({
sourceMap: process.env.NODE_ENV !== 'production',
}),
css({
output: 'styles.css',
}),
babel({/**/}),
/* rest of your plugins */
babel({}),
/* rest of your plugins */
],
};
```

### Svelte

#### Contents

- [Svelte with Rollup](#Rollup-1)
- [Svelte with Webpack](#Webpack-1)

Expand Down Expand Up @@ -383,4 +391,3 @@ module.exports = {
},
};
```

45 changes: 45 additions & 0 deletions packages/webpack4-loader/src/cache.ts
@@ -0,0 +1,45 @@
export interface ICache {
get: (key: string) => Promise<string>;
set: (key: string, value: string) => Promise<void>;
}

// memory cache, which is the default cache implementation in Linaria

class MemoryCache implements ICache {
private cache: Map<string, string> = new Map();

public get(key: string): Promise<string> {
return Promise.resolve(this.cache.get(key) ?? '');
}

public set(key: string, value: string): Promise<void> {
this.cache.set(key, value);
return Promise.resolve();
}
}

export const memoryCache = new MemoryCache();

/**
* return cache instance from `options.cacheProvider`
* @param cacheProvider string | ICache | undefined
* @returns ICache instance
*/
export const getCacheInstance = async (
cacheProvider: string | ICache | undefined
): Promise<ICache> => {
if (!cacheProvider) {
return memoryCache;
}
if (typeof cacheProvider === 'string') {
return require(cacheProvider);
}
if (
typeof cacheProvider === 'object' &&
'get' in cacheProvider &&
'set' in cacheProvider
) {
return cacheProvider;
}
throw new Error(`Invalid cache provider: ${cacheProvider}`);
};
35 changes: 22 additions & 13 deletions packages/webpack4-loader/src/index.ts
Expand Up @@ -8,9 +8,9 @@ import path from 'path';
import loaderUtils from 'loader-utils';
import enhancedResolve from 'enhanced-resolve';
import type { RawSourceMap } from 'source-map';
import { EvalCache, Module, transform } from '@linaria/babel-preset';
import { EvalCache, Module, Result, transform } from '@linaria/babel-preset';
import { debug, notify } from '@linaria/logger';
import { addFile } from './outputCssLoader';
import { getCacheInstance } from './cache';

type LoaderContext = Parameters<typeof loaderUtils.getOptions>[0];

Expand All @@ -31,6 +31,9 @@ export default function webpack4Loader(
content: string,
inputSourceMap: RawSourceMap | null
) {
// tell Webpack this loader is async
this.async();

debug('loader', this.resourcePath);

EvalCache.clearForFile(this.resourcePath);
Expand All @@ -44,6 +47,7 @@ export default function webpack4Loader(
sourceMap = undefined,
preprocessor = undefined,
extension = '.linaria.css',
cacheProvider,
resolveOptions = {},
...rest
} = loaderUtils.getOptions(this) || {};
Expand Down Expand Up @@ -92,7 +96,7 @@ export default function webpack4Loader(
});
}

let result;
let result: Result;

const originalResolveFilename = Module._resolveFilename;

Expand Down Expand Up @@ -137,16 +141,21 @@ export default function webpack4Loader(
});
}

addFile(this.resourcePath, cssText);

const request = `${outputFileName}!=!${outputCssLoader}!${this.resourcePath}`;
const stringifiedRequest = loaderUtils.stringifyRequest(this, request);

this.callback(
null,
`${result.code}\n\nrequire(${stringifiedRequest});`,
castSourceMap(result.sourceMap)
);
getCacheInstance(cacheProvider)
.then((cacheInstance) => cacheInstance.set(this.resourcePath, cssText))
.then(() => {
const request = `${outputFileName}!=!${outputCssLoader}?cacheProvider=${encodeURIComponent(
cacheProvider ?? ''
)}!${this.resourcePath}`;
const stringifiedRequest = loaderUtils.stringifyRequest(this, request);

return this.callback(
null,
`${result.code}\n\nrequire(${stringifiedRequest});`,
castSourceMap(result.sourceMap)
);
})
.catch((err: Error) => this.callback(err));
return;
}

Expand Down
16 changes: 10 additions & 6 deletions packages/webpack4-loader/src/outputCssLoader.ts
@@ -1,9 +1,13 @@
const cssLookup = new Map<string, string>();
import loaderUtils from 'loader-utils';
import { getCacheInstance } from './cache';

export const addFile = (id: string, content: string) => {
cssLookup.set(id, content);
};
type LoaderContext = Parameters<typeof loaderUtils.getOptions>[0];

export default function outputCssLoader(this: { resourcePath: string }) {
return cssLookup.get(this.resourcePath) ?? '';
export default function outputCssLoader(this: LoaderContext) {
this.async();
const { cacheProvider } = loaderUtils.getOptions(this) || {};
getCacheInstance(cacheProvider)
.then((cacheInstance) => cacheInstance.get(this.resourcePath))
.then((result) => this.callback(null, result))
.catch((err: Error) => this.callback(err));
}
45 changes: 45 additions & 0 deletions packages/webpack5-loader/src/cache.ts
@@ -0,0 +1,45 @@
export interface ICache {
get: (key: string) => Promise<string>;
set: (key: string, value: string) => Promise<void>;
}

// memory cache, which is the default cache implementation in Linaria

class MemoryCache implements ICache {
private cache: Map<string, string> = new Map();

public get(key: string): Promise<string> {
return Promise.resolve(this.cache.get(key) ?? '');
}

public set(key: string, value: string): Promise<void> {
this.cache.set(key, value);
return Promise.resolve();
}
}

export const memoryCache = new MemoryCache();

/**
* return cache instance from `options.cacheProvider`
* @param cacheProvider string | ICache | undefined
* @returns ICache instance
*/
export const getCacheInstance = async (
cacheProvider: string | ICache | undefined
): Promise<ICache> => {
if (!cacheProvider) {
return memoryCache;
}
if (typeof cacheProvider === 'string') {
return require(cacheProvider);
}
if (
typeof cacheProvider === 'object' &&
'get' in cacheProvider &&
'set' in cacheProvider
) {
return cacheProvider;
}
throw new Error(`Invalid cache provider: ${cacheProvider}`);
};
35 changes: 22 additions & 13 deletions packages/webpack5-loader/src/index.ts
Expand Up @@ -8,9 +8,9 @@ import path from 'path';
import loaderUtils from 'loader-utils';
import enhancedResolve from 'enhanced-resolve';
import type { RawSourceMap } from 'source-map';
import { EvalCache, Module, transform } from '@linaria/babel-preset';
import { EvalCache, Module, Result, transform } from '@linaria/babel-preset';
import { debug, notify } from '@linaria/logger';
import { addFile } from './outputCssLoader';
import { getCacheInstance } from './cache';

const outputCssLoader = require.resolve('./outputCssLoader');

Expand All @@ -19,6 +19,9 @@ export default function webpack5Loader(
content: string,
inputSourceMap: RawSourceMap | null
) {
// tell Webpack this loader is async
this.async();

debug('loader', this.resourcePath);

EvalCache.clearForFile(this.resourcePath);
Expand All @@ -32,6 +35,7 @@ export default function webpack5Loader(
sourceMap = undefined,
preprocessor = undefined,
extension = '.linaria.css',
cacheProvider,
resolveOptions = {},
...rest
} = this.getOptions() || {};
Expand Down Expand Up @@ -80,7 +84,7 @@ export default function webpack5Loader(
});
}

let result;
let result: Result;

const originalResolveFilename = Module._resolveFilename;

Expand Down Expand Up @@ -130,16 +134,21 @@ export default function webpack5Loader(
});
}

addFile(this.resourcePath, cssText);

const request = `${outputFileName}!=!${outputCssLoader}!${this.resourcePath}`;
const stringifiedRequest = loaderUtils.stringifyRequest(this, request);

this.callback(
null,
`${result.code}\n\nrequire(${stringifiedRequest});`,
result.sourceMap ?? undefined
);
getCacheInstance(cacheProvider)
.then((cacheInstance) => cacheInstance.set(this.resourcePath, cssText))
.then(() => {
const request = `${outputFileName}!=!${outputCssLoader}?cacheProvider=${encodeURIComponent(
cacheProvider ?? ''
)}!${this.resourcePath}`;
const stringifiedRequest = loaderUtils.stringifyRequest(this, request);

return this.callback(
null,
`${result.code}\n\nrequire(${stringifiedRequest});`,
result.sourceMap ?? undefined
);
})
.catch((err: Error) => this.callback(err));
return;
}

Expand Down
18 changes: 11 additions & 7 deletions packages/webpack5-loader/src/outputCssLoader.ts
@@ -1,9 +1,13 @@
const cssLookup = new Map<string, string>();
import webpack from 'webpack';
import { getCacheInstance, ICache } from './cache';

export const addFile = (id: string, content: string) => {
cssLookup.set(id, content);
};

export default function outputCssLoader(this: { resourcePath: string }) {
return cssLookup.get(this.resourcePath) ?? '';
export default function outputCssLoader(
this: webpack.LoaderContext<{ cacheProvider: string | ICache | undefined }>
) {
this.async();
const { cacheProvider } = this.getOptions();
getCacheInstance(cacheProvider)
.then((cacheInstance) => cacheInstance.get(this.resourcePath))
.then((result) => this.callback(null, result))
.catch((err: Error) => this.callback(err));
}

0 comments on commit ee656dd

Please sign in to comment.