stylelint-webpack-plugin 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = getStylelint;
7
+
8
+ var _os = require("os");
9
+
10
+ var _jestWorker = require("jest-worker");
11
+
12
+ var _worker = require("./worker");
13
+
14
+ var _utils = require("./utils");
15
+
16
+ var _options = require("./options");
17
+
18
+ // @ts-ignore
19
+
20
+ /** @type {{[key: string]: any}} */
21
+ const cache = {};
22
+ /** @typedef {import('stylelint')} Stylelint */
23
+
24
+ /** @typedef {import('stylelint').LintResult} LintResult */
25
+
26
+ /** @typedef {import('./options').Options} Options */
27
+
28
+ /** @typedef {() => Promise<void>} AsyncTask */
29
+
30
+ /** @typedef {(files: string|string[]) => Promise<LintResult[]>} LintTask */
31
+
32
+ /** @typedef {JestWorker & {lintFiles: LintTask}} Worker */
33
+
34
+ /** @typedef {{stylelint: Stylelint, lintFiles: LintTask, cleanup: AsyncTask, threads: number, }} Linter */
35
+
36
+ /**
37
+ * @param {Options} options
38
+ * @returns {Linter}
39
+ */
40
+
41
+ function loadStylelint(options) {
42
+ const stylelint = (0, _worker.setup)(options, (0, _options.getStylelintOptions)(options));
43
+ return {
44
+ stylelint,
45
+ lintFiles: _worker.lintFiles,
46
+ cleanup: async () => {},
47
+ threads: 1
48
+ };
49
+ }
50
+ /**
51
+ * @param {string|undefined} key
52
+ * @param {number} poolSize
53
+ * @param {Options} options
54
+ * @returns {Linter}
55
+ */
56
+
57
+
58
+ function loadStylelintThreaded(key, poolSize, options) {
59
+ const cacheKey = getCacheKey(key, options);
60
+
61
+ const source = require.resolve('./worker');
62
+
63
+ const workerOptions = {
64
+ enableWorkerThreads: true,
65
+ numWorkers: poolSize,
66
+ setupArgs: [options, (0, _options.getStylelintOptions)(options)]
67
+ };
68
+ const local = loadStylelint(options);
69
+ /** @type {Worker?} */
70
+ // prettier-ignore
71
+
72
+ let worker =
73
+ /** @type {Worker} */
74
+ new _jestWorker.Worker(source, workerOptions);
75
+ /** @type {Linter} */
76
+
77
+ const context = { ...local,
78
+ threads: poolSize,
79
+ lintFiles: async files =>
80
+ /* istanbul ignore next */
81
+ worker ? worker.lintFiles(files) : local.lintFiles(files),
82
+ cleanup: async () => {
83
+ cache[cacheKey] = local;
84
+
85
+ context.lintFiles = files => local.lintFiles(files);
86
+ /* istanbul ignore next */
87
+
88
+
89
+ if (worker) {
90
+ worker.end();
91
+ worker = null;
92
+ }
93
+ }
94
+ };
95
+ return context;
96
+ }
97
+ /**
98
+ * @param {string|undefined} key
99
+ * @param {Options} options
100
+ * @returns {Linter}
101
+ */
102
+
103
+
104
+ function getStylelint(key, {
105
+ threads,
106
+ ...options
107
+ }) {
108
+ const max = typeof threads !== 'number' ? threads ? (0, _os.cpus)().length - 1 : 1 : threads;
109
+ const cacheKey = getCacheKey(key, {
110
+ threads,
111
+ ...options
112
+ });
113
+
114
+ if (!cache[cacheKey]) {
115
+ cache[cacheKey] = max > 1 ? loadStylelintThreaded(key, max, options) : loadStylelint(options);
116
+ }
117
+
118
+ return cache[cacheKey];
119
+ }
120
+ /**
121
+ * @param {string|undefined} key
122
+ * @param {Options} options
123
+ * @returns {string}
124
+ */
125
+
126
+
127
+ function getCacheKey(key, options) {
128
+ return JSON.stringify({
129
+ key,
130
+ options
131
+ }, _utils.jsonStringifyReplacerSortKeys);
132
+ }
package/dist/index.js CHANGED
@@ -7,9 +7,13 @@ exports.default = void 0;
7
7
 
8
8
  var _path = require("path");
9
9
 
10
- var _getOptions = _interopRequireDefault(require("./getOptions"));
10
+ var _arrify = _interopRequireDefault(require("arrify"));
11
11
 
12
- var _LintDirtyModulesPlugin = _interopRequireDefault(require("./LintDirtyModulesPlugin"));
12
+ var _globby = _interopRequireDefault(require("globby"));
13
+
14
+ var _micromatch = require("micromatch");
15
+
16
+ var _options = require("./options");
13
17
 
14
18
  var _linter = _interopRequireDefault(require("./linter"));
15
19
 
@@ -17,10 +21,31 @@ var _utils = require("./utils");
17
21
 
18
22
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
19
23
 
24
+ // @ts-ignore
25
+ // @ts-ignore
26
+
20
27
  /** @typedef {import('webpack').Compiler} Compiler */
28
+
29
+ /** @typedef {import('webpack').Module} Module */
30
+
31
+ /** @typedef {import('./options').Options} Options */
32
+
33
+ /** @typedef {Partial<{timestamp:number} | number>} FileSystemInfoEntry */
34
+ const STYLELINT_PLUGIN = 'StylelintWebpackPlugin';
35
+ let counter = 0;
36
+
21
37
  class StylelintWebpackPlugin {
38
+ /**
39
+ * @param {Options} options
40
+ */
22
41
  constructor(options = {}) {
23
- this.options = (0, _getOptions.default)(options);
42
+ this.key = STYLELINT_PLUGIN;
43
+ this.options = (0, _options.getOptions)(options);
44
+ this.run = this.run.bind(this);
45
+ this.startTime = Date.now();
46
+ /** @type {ReadonlyMap<string, null | FileSystemInfoEntry | "ignore" | undefined>} */
47
+
48
+ this.prevTimestamps = new Map();
24
49
  }
25
50
  /**
26
51
  * @param {Compiler} compiler
@@ -29,35 +54,112 @@ class StylelintWebpackPlugin {
29
54
 
30
55
 
31
56
  apply(compiler) {
32
- const options = { ...this.options,
33
- files: (0, _utils.parseFiles)(this.options.files, this.getContext(compiler))
34
- }; // eslint-disable-next-line
35
-
36
- const {
37
- lint
38
- } = require(options.stylelintPath);
39
-
40
- const {
41
- name: plugin
42
- } = this.constructor;
43
-
44
- if (options.lintDirtyModulesOnly) {
45
- const lintDirty = new _LintDirtyModulesPlugin.default(lint, compiler, options);
46
- /* istanbul ignore next */
47
-
48
- compiler.hooks.watchRun.tapAsync(plugin, (compilation, callback) => {
49
- lintDirty.apply(compilation, callback);
50
- });
51
- } else {
52
- compiler.hooks.run.tapAsync(plugin, (compilation, callback) => {
53
- (0, _linter.default)(lint, options, compilation, callback);
54
- });
55
- /* istanbul ignore next */
56
-
57
- compiler.hooks.watchRun.tapAsync(plugin, (compilation, callback) => {
58
- (0, _linter.default)(lint, options, compilation, callback);
59
- });
57
+ // Generate key for each compilation,
58
+ // this differentiates one from the other when being cached.
59
+ this.key = compiler.name || `${this.key}_${counter += 1}`; // If `lintDirtyModulesOnly` is disabled,
60
+ // execute the linter on the build
61
+
62
+ if (!this.options.lintDirtyModulesOnly) {
63
+ compiler.hooks.run.tapPromise(this.key, this.run);
60
64
  }
65
+
66
+ let isFirstRun = this.options.lintDirtyModulesOnly;
67
+ compiler.hooks.watchRun.tapPromise(this.key, c => {
68
+ if (isFirstRun) {
69
+ isFirstRun = false;
70
+ return Promise.resolve();
71
+ }
72
+
73
+ return this.run(c);
74
+ });
75
+ }
76
+ /**
77
+ * @param {Compiler} compiler
78
+ */
79
+
80
+
81
+ async run(compiler) {
82
+ // Do not re-hook
83
+
84
+ /* istanbul ignore if */
85
+ if ( // @ts-ignore
86
+ compiler.hooks.thisCompilation.taps.find(({
87
+ name
88
+ }) => name === this.key)) {
89
+ return;
90
+ }
91
+
92
+ const context = this.getContext(compiler);
93
+ const options = { ...this.options,
94
+ exclude: (0, _utils.parseFiles)(this.options.exclude || ['**/node_modules/**', compiler.options.output.path], context),
95
+ extensions: (0, _arrify.default)(this.options.extensions),
96
+ files: (0, _utils.parseFiles)(this.options.files || '', context)
97
+ };
98
+ const wanted = (0, _utils.parseFoldersToGlobs)(options.files, options.extensions);
99
+ const exclude = (0, _utils.parseFoldersToGlobs)(options.exclude);
100
+ compiler.hooks.thisCompilation.tap(this.key, compilation => {
101
+ /** @type {import('./linter').Linter} */
102
+ let lint;
103
+ /** @type {import('./linter').Reporter} */
104
+
105
+ let report;
106
+ /** @type number */
107
+
108
+ let threads;
109
+
110
+ try {
111
+ ({
112
+ lint,
113
+ report,
114
+ threads
115
+ } = (0, _linter.default)(this.key, options, compilation));
116
+ } catch (e) {
117
+ compilation.errors.push(e);
118
+ return;
119
+ }
120
+
121
+ compilation.hooks.finishModules.tap(this.key, () => {
122
+ const files = this.getFiles(compiler, wanted, exclude);
123
+
124
+ if (threads > 1) {
125
+ for (const file of files) {
126
+ lint((0, _utils.parseFiles)(file, context));
127
+ }
128
+ } else if (files.length > 0) {
129
+ lint((0, _utils.parseFiles)(files, context));
130
+ }
131
+ }); // await and interpret results
132
+
133
+ compilation.hooks.additionalAssets.tapPromise(this.key, processResults);
134
+
135
+ async function processResults() {
136
+ const {
137
+ errors,
138
+ warnings,
139
+ generateReportAsset
140
+ } = await report();
141
+
142
+ if (warnings && !options.failOnWarning) {
143
+ // @ts-ignore
144
+ compilation.warnings.push(warnings);
145
+ } else if (warnings && options.failOnWarning) {
146
+ // @ts-ignore
147
+ compilation.errors.push(warnings);
148
+ }
149
+
150
+ if (errors && options.failOnError) {
151
+ // @ts-ignore
152
+ compilation.errors.push(errors);
153
+ } else if (errors && !options.failOnError) {
154
+ // @ts-ignore
155
+ compilation.warnings.push(errors);
156
+ }
157
+
158
+ if (generateReportAsset) {
159
+ await generateReportAsset(compilation);
160
+ }
161
+ }
162
+ });
61
163
  }
62
164
  /**
63
165
  *
@@ -77,6 +179,88 @@ class StylelintWebpackPlugin {
77
179
 
78
180
  return this.options.context;
79
181
  }
182
+ /**
183
+ * @param {Compiler} compiler
184
+ * @param {string[]} wanted
185
+ * @param {string[]} exclude
186
+ * @returns {string[]}
187
+ */
188
+ // eslint-disable-next-line no-unused-vars
189
+
190
+
191
+ getFiles(compiler, wanted, exclude) {
192
+ // webpack 5
193
+ if (compiler.modifiedFiles) {
194
+ return Array.from(compiler.modifiedFiles).filter(file => (0, _micromatch.isMatch)(file, wanted, {
195
+ dot: true
196
+ }) && !(0, _micromatch.isMatch)(file, exclude, {
197
+ dot: true
198
+ }));
199
+ } // webpack 4
200
+
201
+ /* istanbul ignore next */
202
+
203
+
204
+ if (compiler.fileTimestamps && compiler.fileTimestamps.size > 0) {
205
+ return this.getChangedFiles(compiler.fileTimestamps).filter(file => (0, _micromatch.isMatch)(file, wanted, {
206
+ dot: true
207
+ }) && !(0, _micromatch.isMatch)(file, exclude, {
208
+ dot: true
209
+ }));
210
+ }
211
+
212
+ return _globby.default.sync(wanted, {
213
+ dot: true,
214
+ ignore: exclude
215
+ });
216
+ }
217
+ /**
218
+ * @param {ReadonlyMap<string, null | FileSystemInfoEntry | "ignore" | undefined>} fileTimestamps
219
+ * @returns {string[]}
220
+ */
221
+
222
+ /* istanbul ignore next */
223
+
224
+
225
+ getChangedFiles(fileTimestamps) {
226
+ /**
227
+ * @param {null | FileSystemInfoEntry | "ignore" | undefined} fileSystemInfoEntry
228
+ * @returns {Partial<number>}
229
+ */
230
+ const getTimestamps = fileSystemInfoEntry => {
231
+ // @ts-ignore
232
+ if (fileSystemInfoEntry && fileSystemInfoEntry.timestamp) {
233
+ // @ts-ignore
234
+ return fileSystemInfoEntry.timestamp;
235
+ } // @ts-ignore
236
+
237
+
238
+ return fileSystemInfoEntry;
239
+ };
240
+ /**
241
+ * @param {string} filename
242
+ * @param {null | FileSystemInfoEntry | "ignore" | undefined} fileSystemInfoEntry
243
+ * @returns {boolean}
244
+ */
245
+
246
+
247
+ const hasFileChanged = (filename, fileSystemInfoEntry) => {
248
+ const prevTimestamp = getTimestamps(this.prevTimestamps.get(filename));
249
+ const timestamp = getTimestamps(fileSystemInfoEntry);
250
+ return (prevTimestamp || this.startTime) < (timestamp || Infinity);
251
+ };
252
+
253
+ const changedFiles = [];
254
+
255
+ for (const [filename, timestamp] of fileTimestamps.entries()) {
256
+ if (hasFileChanged(filename, timestamp)) {
257
+ changedFiles.push(filename);
258
+ }
259
+ }
260
+
261
+ this.prevTimestamps = fileTimestamps;
262
+ return changedFiles;
263
+ }
80
264
 
81
265
  }
82
266