Skip to content
This repository was archived by the owner on Sep 28, 2020. It is now read-only.

Commit fdbca0e

Browse files
committed
port babel loader fs cache as the default caching engine
1 parent d7f003d commit fdbca0e

File tree

2 files changed

+250
-81
lines changed

2 files changed

+250
-81
lines changed

fs-cache.js

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/**
2+
* Filesystem cache
3+
*
4+
* Given a file and a transform function, cache the result into files
5+
* or retrieve the previously cached files if the given file is already known.
6+
*
7+
* @see https://github.com/babel/babel-loader/issues/34
8+
* @see https://github.com/babel/babel-loader/pull/41
9+
* @see https://github.com/babel/babel-loader/blob/master/src/fs-cache.js
10+
*/
11+
var crypto = require("crypto")
12+
var mkdirp = require("mkdirp")
13+
var findCacheDir = require("find-cache-dir")
14+
var fs = require("fs")
15+
var os = require("os")
16+
var path = require("path")
17+
var zlib = require("zlib")
18+
19+
var defaultCacheDirectory = null // Lazily instantiated when needed
20+
21+
/**
22+
* Read the contents from the compressed file.
23+
*
24+
* @async
25+
* @params {String} filename
26+
* @params {Function} callback
27+
*/
28+
var read = function(filename, callback) {
29+
return fs.readFile(filename, function(err, data) {
30+
if (err) {
31+
return callback(err)
32+
}
33+
34+
return zlib.gunzip(data, function(err, content) {
35+
var result = {}
36+
37+
if (err) {
38+
return callback(err)
39+
}
40+
41+
try {
42+
result = JSON.parse(content)
43+
}
44+
catch (e) {
45+
return callback(e)
46+
}
47+
48+
return callback(null, result)
49+
})
50+
})
51+
}
52+
53+
/**
54+
* Write contents into a compressed file.
55+
*
56+
* @async
57+
* @params {String} filename
58+
* @params {String} result
59+
* @params {Function} callback
60+
*/
61+
var write = function(filename, result, callback) {
62+
var content = JSON.stringify(result)
63+
64+
return zlib.gzip(content, function(err, data) {
65+
if (err) {
66+
return callback(err)
67+
}
68+
69+
return fs.writeFile(filename, data, callback)
70+
})
71+
}
72+
73+
/**
74+
* Build the filename for the cached file
75+
*
76+
* @params {String} source File source code
77+
* @params {Object} options Options used
78+
*
79+
* @return {String}
80+
*/
81+
var filename = function(source, identifier, options) {
82+
var hash = crypto.createHash("SHA1")
83+
var contents = JSON.stringify({
84+
source: source,
85+
options: options,
86+
identifier: identifier,
87+
})
88+
89+
hash.end(contents)
90+
91+
return hash.read().toString("hex") + ".json.gz"
92+
}
93+
94+
/**
95+
* Handle the cache
96+
*
97+
* @params {String} directory
98+
* @params {Object} params
99+
* @params {Function} callback
100+
*/
101+
var handleCache = function(directory, params, callback) {
102+
var source = params.source
103+
var options = params.options || {}
104+
var transform = params.transform
105+
var identifier = params.identifier
106+
var shouldFallback = typeof params.directory !== "string" &&
107+
directory !== os.tmpdir()
108+
109+
// Make sure the directory exists.
110+
mkdirp(directory, function(err) {
111+
// Fallback to tmpdir if node_modules folder not writable
112+
if (err)
113+
return shouldFallback
114+
? handleCache(os.tmpdir(), params, callback)
115+
: callback(err)
116+
117+
var file = path.join(directory, filename(source, identifier, options))
118+
119+
return read(file, function(err, content) {
120+
var result = {}
121+
// No errors mean that the file was previously cached
122+
// we just need to return it
123+
if (!err) return callback(null, content)
124+
125+
// Otherwise just transform the file
126+
// return it to the user asap and write it in cache
127+
try {
128+
result = transform(source, options)
129+
}
130+
catch (error) {
131+
return callback(error)
132+
}
133+
134+
return write(file, result, function(err) {
135+
// Fallback to tmpdir if node_modules folder not writable
136+
if (err)
137+
return shouldFallback
138+
? handleCache(os.tmpdir(), params, callback)
139+
: callback(err)
140+
141+
callback(null, result)
142+
})
143+
})
144+
})
145+
}
146+
147+
/**
148+
* Retrieve file from cache, or create a new one for future reads
149+
*
150+
* @async
151+
* @param {Object} params
152+
* @param {String} params.directory Directory to store cached files
153+
* @param {String} params.identifier Unique identifier to bust cache
154+
* @param {String} params.source Original contents of the file to be cached
155+
* @param {Object} params.options Options to be given to the transform fn
156+
* @param {Function} params.transform Function that will transform the
157+
* original file and whose result will be
158+
* cached
159+
*
160+
* @param {Function<err, result>} callback
161+
*
162+
* @example
163+
*
164+
* cache({
165+
* directory: '.tmp/cache',
166+
* identifier: 'babel-loader-cachefile',
167+
* source: *source code from file*,
168+
* options: {
169+
* experimental: true,
170+
* runtime: true
171+
* },
172+
* transform: function(source, options) {
173+
* var content = *do what you need with the source*
174+
* return content
175+
* }
176+
* }, function(err, result) {
177+
*
178+
* })
179+
*/
180+
181+
module.exports = function(params, callback) {
182+
var directory
183+
184+
if (typeof params.directory === "string") {
185+
directory = params.directory
186+
}
187+
else {
188+
if (defaultCacheDirectory === null) {
189+
defaultCacheDirectory = findCacheDir({
190+
name: "eslint-loader",
191+
}) || os.tmpdir()
192+
}
193+
directory = defaultCacheDirectory
194+
}
195+
handleCache(directory, params, callback)
196+
}

index.js

+54-81
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,21 @@
11
var eslint = require("eslint")
22
var assign = require("object-assign")
33
var loaderUtils = require("loader-utils")
4-
var crypto = require("crypto")
5-
var fs = require("fs")
6-
var findCacheDir = require("find-cache-dir")
74
var objectHash = require("object-hash")
8-
var os = require("os")
5+
var pkg = require("./package.json")
6+
var cache = require("./fs-cache")
97

108
var engines = {}
11-
var rules = {}
12-
var cache = null
13-
var cachePath = null
149

1510
/**
16-
* linter
11+
* printLinterOutput
1712
*
18-
* @param {String|Buffer} input JavaScript string
13+
* @param {Object} eslint.executeOnText return value
1914
* @param {Object} config eslint configuration
2015
* @param {Object} webpack webpack instance
2116
* @return {void}
2217
*/
23-
function lint(input, config, webpack) {
24-
var resourcePath = webpack.resourcePath
25-
var cwd = process.cwd()
26-
27-
// remove cwd from resource path in case webpack has been started from project
28-
// root, to allow having relative paths in .eslintignore
29-
if (resourcePath.indexOf(cwd) === 0) {
30-
resourcePath = resourcePath.substr(cwd.length + 1)
31-
}
32-
33-
// get engine
34-
var configHash = objectHash(config)
35-
var engine = engines[configHash]
36-
var rulesHash = rules[configHash]
37-
38-
var res
39-
// If cache is enable and the data are the same as in the cache, just
40-
// use them
41-
if (config.cache) {
42-
// just get rules hash once per engine for performance reasons
43-
if (!rulesHash) {
44-
rulesHash = objectHash(engine.getConfigForFile(resourcePath))
45-
rules[configHash] = rulesHash
46-
}
47-
var inputMD5 = crypto.createHash("md5").update(input).digest("hex")
48-
if (
49-
cache[resourcePath] &&
50-
cache[resourcePath].hash === inputMD5 &&
51-
cache[resourcePath].rules === rulesHash
52-
) {
53-
res = cache[resourcePath].res
54-
}
55-
}
56-
57-
// Re-lint the text if the cache off or miss
58-
if (!res) {
59-
res = engine.executeOnText(input, resourcePath, true)
60-
61-
// Save new results in the cache
62-
if (config.cache) {
63-
cache[resourcePath] = {
64-
hash: inputMD5,
65-
rules: rulesHash,
66-
res: res,
67-
}
68-
fs.writeFileSync(cachePath, JSON.stringify(cache))
69-
}
70-
}
71-
72-
// executeOnText ensure we will have res.results[0] only
73-
18+
function printLinterOutput(res, config, webpack) {
7419
// skip ignored file warning
7520
if (!(
7621
res.warningCount === 1 &&
@@ -155,45 +100,73 @@ function lint(input, config, webpack) {
155100
* @return {void}
156101
*/
157102
module.exports = function(input, map) {
103+
var webpack = this
158104
var config = assign(
159105
// loader defaults
160106
{
161107
formatter: require("eslint/lib/formatters/stylish"),
108+
cacheIdentifier: JSON.stringify({
109+
"eslint-loader": pkg.version,
110+
"eslint": eslint.version,
111+
}),
162112
},
163113
// user defaults
164114
this.options.eslint || {},
165115
// loader query string
166116
loaderUtils.getOptions(this)
167117
)
168-
this.cacheable()
118+
119+
var cacheDirectory = config.cacheDirectory
120+
var cacheIdentifier = config.cacheIdentifier
121+
122+
delete config.cacheDirectory
123+
delete config.cacheIdentifier
169124

170125
// Create the engine only once per config
171126
var configHash = objectHash(config)
172127
if (!engines[configHash]) {
173128
engines[configHash] = new eslint.CLIEngine(config)
174129
}
175130

176-
// Read the cached information only once and if enable
177-
if (cache === null) {
178-
if (config.cache) {
179-
var thunk = findCacheDir({
180-
name: "eslint-loader",
181-
thunk: true,
182-
create: true,
183-
})
184-
cachePath = thunk("data.json") || os.tmpdir() + "/data.json"
185-
try {
186-
cache = require(cachePath)
187-
}
188-
catch (e) {
189-
cache = {}
131+
this.cacheable()
132+
133+
var resourcePath = webpack.resourcePath
134+
var cwd = process.cwd()
135+
136+
// remove cwd from resource path in case webpack has been started from project
137+
// root, to allow having relative paths in .eslintignore
138+
if (resourcePath.indexOf(cwd) === 0) {
139+
resourcePath = resourcePath.substr(cwd.length + 1)
140+
}
141+
142+
var engine = engines[configHash]
143+
// return early if cached
144+
if (config.cache) {
145+
var callback = this.async()
146+
return cache({
147+
directory: cacheDirectory,
148+
identifier: cacheIdentifier,
149+
options: config,
150+
transform: function() {
151+
return lint(engine, input, resourcePath)
152+
},
153+
}, function(err, res) {
154+
if (err) {
155+
return callback(err)
190156
}
191-
}
192-
else {
193-
cache = false
194-
}
157+
printLinterOutput(res, config, webpack)
158+
return callback(null, input, map)
159+
})
195160
}
196161

197-
lint(input, config, this)
198-
this.callback(null, input, map)
162+
printLinterOutput(
163+
lint(engine, input, resourcePath),
164+
config,
165+
this
166+
)
167+
this.callback(input, map)
168+
}
169+
170+
function lint(engine, input, resourcePath) {
171+
engine.executeOnText(input, resourcePath, true)
199172
}

0 commit comments

Comments
 (0)