Skip to content

Commit

Permalink
fix(webpack): prevent double rebuild for each change (fixes #878)
Browse files Browse the repository at this point in the history
  • Loading branch information
Anber committed Nov 30, 2021
1 parent a8ada37 commit 0d2ea98
Show file tree
Hide file tree
Showing 7 changed files with 33 additions and 116 deletions.
9 changes: 0 additions & 9 deletions docs/BUNDLERS_INTEGRATION.md
Expand Up @@ -177,14 +177,6 @@ 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.

- `cacheDirectory: string` (default: `'.linaria-cache'`):

Path to the directory where the loader will output the intermediate CSS files. You can pass a relative or absolute directory path. Make sure the directory is inside the working directory for things to work properly. **You should add this directory to `.gitignore` so you don't accidentally commit them.**

- `extension: string` (default: `'.linaria.css'`):

An extension of the intermediate CSS files.

- `preprocessor: 'none' | 'stylis' | Function` (default: `'stylis'`)

You can override the pre-processor if you want to override how the loader processes the CSS.
Expand Down Expand Up @@ -218,7 +210,6 @@ You can pass options to the loader like so:
loader: '@linaria/webpack-loader',
options: {
sourceMap: false,
cacheDirectory: '.linaria-cache',
},
}
```
Expand Down
7 changes: 1 addition & 6 deletions packages/webpack4-loader/package.json
Expand Up @@ -38,22 +38,17 @@
"watch": "yarn build --watch"
},
"devDependencies": {
"@types/cosmiconfig": "^5.0.3",
"@types/enhanced-resolve": "^3.0.6",
"@types/loader-utils": "^1.1.3",
"@types/mkdirp": "^0.5.2",
"@types/normalize-path": "^3.0.0",
"source-map": "^0.7.3"
},
"dependencies": {
"@linaria/babel-preset": "^3.0.0-beta.15",
"@linaria/logger": "^3.0.0-beta.15",
"cosmiconfig": "^5.1.0",
"enhanced-resolve": "^4.1.0",
"find-yarn-workspace-root": "^1.2.1",
"loader-utils": "^1.2.3",
"mkdirp": "^0.5.1",
"normalize-path": "^3.0.0"
"mkdirp": "^0.5.1"
},
"peerDependencies": {
"@babel/core": ">=7"
Expand Down
55 changes: 7 additions & 48 deletions packages/webpack4-loader/src/index.ts
Expand Up @@ -4,24 +4,13 @@
* returns transformed code without template literals and attaches generated source maps
*/

import fs from 'fs';
import path from 'path';
import mkdirp from 'mkdirp';
import normalize from 'normalize-path';
import loaderUtils from 'loader-utils';
import enhancedResolve from 'enhanced-resolve';
import findYarnWorkspaceRoot from 'find-yarn-workspace-root';
import type { RawSourceMap } from 'source-map';
import cosmiconfig from 'cosmiconfig';
import { EvalCache, Module, transform } from '@linaria/babel-preset';
import { debug, notify } from '@linaria/logger';

const workspaceRoot = findYarnWorkspaceRoot();
const lernaConfig = cosmiconfig('lerna', {
searchPlaces: ['lerna.json'],
}).searchSync();
const lernaRoot =
lernaConfig !== null ? path.dirname(lernaConfig.filepath) : null;
import { addFile } from './outputCssLoader';

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

Expand All @@ -35,6 +24,8 @@ const castSourceMap = <T extends { version: number } | { version: string }>(
}
: undefined;

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

export default function webpack4Loader(
this: LoaderContext,
content: string,
Expand All @@ -51,28 +42,11 @@ export default function webpack4Loader(

const {
sourceMap = undefined,
cacheDirectory = '.linaria-cache',
preprocessor = undefined,
extension = '.linaria.css',
resolveOptions = {},
...rest
} = loaderUtils.getOptions(this) || {};

const root = workspaceRoot || lernaRoot || process.cwd();

const baseOutputFileName = this.resourcePath.replace(/\.[^.]+$/, extension);

const outputFilename = normalize(
path.join(
path.isAbsolute(cacheDirectory)
? cacheDirectory
: path.join(process.cwd(), cacheDirectory),
this.resourcePath.includes(root)
? path.relative(root, baseOutputFileName)
: baseOutputFileName
)
);

// this._compilation is a deprecated API
// However there seems to be no other way to access webpack's resolver
// There is this.resolve, but it's asynchronous
Expand Down Expand Up @@ -130,7 +104,6 @@ export default function webpack4Loader(
result = transform(content, {
filename: path.relative(process.cwd(), this.resourcePath),
inputSourceMap: inputSourceMap ?? undefined,
outputFilename,
pluginOptions: rest,
preprocessor,
});
Expand Down Expand Up @@ -161,28 +134,14 @@ export default function webpack4Loader(
});
}

// 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;

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

if (currentCssText !== cssText) {
mkdirp.sync(path.dirname(outputFilename));
fs.writeFileSync(outputFilename, cssText);
}
const request = `linaria.css!=!${outputCssLoader}!${this.resourcePath}`;
const stringifiedRequest = loaderUtils.stringifyRequest(this, request);

this.callback(
null,
`${result.code}\n\nrequire(${loaderUtils.stringifyRequest(
this,
outputFilename
)});`,
`${result.code}\n\nrequire(${stringifiedRequest});`,
castSourceMap(result.sourceMap)
);
return;
Expand Down
9 changes: 9 additions & 0 deletions packages/webpack4-loader/src/outputCssLoader.ts
@@ -0,0 +1,9 @@
const cssLookup = new Map<string, string>();

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

export default async function outputCssLoader(this: { resourcePath: string }) {
return cssLookup.get(this.resourcePath) ?? '';
}
7 changes: 1 addition & 6 deletions packages/webpack5-loader/package.json
Expand Up @@ -38,23 +38,18 @@
"watch": "yarn build --watch"
},
"devDependencies": {
"@types/cosmiconfig": "^5.0.3",
"@types/enhanced-resolve": "^3.0.6",
"@types/loader-utils": "^1.1.3",
"@types/mkdirp": "^0.5.2",
"@types/normalize-path": "^3.0.0",
"source-map": "^0.7.3",
"webpack": "^5.6.0"
},
"dependencies": {
"@linaria/babel-preset": "^3.0.0-beta.15",
"@linaria/logger": "^3.0.0-beta.15",
"cosmiconfig": "^5.1.0",
"enhanced-resolve": "^5.3.1",
"find-yarn-workspace-root": "^1.2.1",
"loader-utils": "^2.0.0",
"mkdirp": "^0.5.1",
"normalize-path": "^3.0.0"
"mkdirp": "^0.5.1"
},
"peerDependencies": {
"@babel/core": ">=7",
Expand Down
53 changes: 6 additions & 47 deletions packages/webpack5-loader/src/index.ts
Expand Up @@ -4,24 +4,15 @@
* returns transformed code without template literals and attaches generated source maps
*/

import fs from 'fs';
import path from 'path';
import loaderUtils from 'loader-utils';
import mkdirp from 'mkdirp';
import normalize from 'normalize-path';
import enhancedResolve from 'enhanced-resolve';
import findYarnWorkspaceRoot from 'find-yarn-workspace-root';
import type { RawSourceMap } from 'source-map';
import cosmiconfig from 'cosmiconfig';
import { EvalCache, Module, transform } from '@linaria/babel-preset';
import { debug, notify } from '@linaria/logger';
import { addFile } from './outputCssLoader';

const workspaceRoot = findYarnWorkspaceRoot();
const lernaConfig = cosmiconfig('lerna', {
searchPlaces: ['lerna.json'],
}).searchSync();
const lernaRoot =
lernaConfig !== null ? path.dirname(lernaConfig.filepath) : null;
const outputCssLoader = require.resolve('./outputCssLoader');

export default function webpack5Loader(
this: any,
Expand All @@ -39,28 +30,11 @@ export default function webpack5Loader(

const {
sourceMap = undefined,
cacheDirectory = '.linaria-cache',
preprocessor = undefined,
extension = '.linaria.css',
resolveOptions = {},
...rest
} = this.getOptions() || {};

const root = workspaceRoot || lernaRoot || process.cwd();

const baseOutputFileName = this.resourcePath.replace(/\.[^.]+$/, extension);

const outputFilename = normalize(
path.join(
path.isAbsolute(cacheDirectory)
? cacheDirectory
: path.join(process.cwd(), cacheDirectory),
this.resourcePath.includes(root)
? path.relative(root, baseOutputFileName)
: baseOutputFileName
)
);

// this._compilation is a deprecated API
// However there seems to be no other way to access webpack's resolver
// There is this.resolve, but it's asynchronous
Expand Down Expand Up @@ -123,7 +97,6 @@ export default function webpack5Loader(
result = transform(content, {
filename: path.relative(process.cwd(), this.resourcePath),
inputSourceMap: inputSourceMap ?? undefined,
outputFilename,
pluginOptions: rest,
preprocessor,
});
Expand Down Expand Up @@ -154,28 +127,14 @@ export default function webpack5Loader(
});
}

// 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;
addFile(this.resourcePath, cssText);

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

if (currentCssText !== cssText) {
mkdirp.sync(path.dirname(outputFilename));
fs.writeFileSync(outputFilename, cssText);
}
const request = `linaria.css!=!${outputCssLoader}!${this.resourcePath}`;
const stringifiedRequest = loaderUtils.stringifyRequest(this, request);

this.callback(
null,
`${result.code}\n\nrequire(${loaderUtils.stringifyRequest(
this,
outputFilename
)});`,
`${result.code}\n\nrequire(${stringifiedRequest});`,
result.sourceMap ?? undefined
);
return;
Expand Down
9 changes: 9 additions & 0 deletions packages/webpack5-loader/src/outputCssLoader.ts
@@ -0,0 +1,9 @@
const cssLookup = new Map<string, string>();

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

export default async function outputCssLoader(this: { resourcePath: string }) {
return cssLookup.get(this.resourcePath) ?? '';
}

0 comments on commit 0d2ea98

Please sign in to comment.