@shopgate/webpack 7.32.2-alpha.9 → 7.33.0-beta.2
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.
- package/lib/createTypeCheckParser.js +67 -0
- package/lib/createTypeCheckParser.spec.js +76 -0
- package/lib/getTypeCheckProjects.js +55 -0
- package/lib/getTypeCheckProjects.spec.js +83 -0
- package/lib/typeCheckWatcher.js +48 -0
- package/local-packages/pwa-extension-kit/src/components/ProductCharacteristics/spec.jsx +15 -7
- package/local-packages/pwa-extension-kit/src/connectors/withHistoryActions.spec.jsx +12 -7
- package/local-packages/pwa-extension-kit/src/connectors/withPageProductId.spec.jsx +11 -9
- package/local-packages/pwa-extension-kit/src/connectors/withPageState.spec.jsx +5 -5
- package/local-packages/pwa-extension-kit/src/connectors/withProductContext.spec.jsx +5 -5
- package/local-packages/pwa-extension-kit/src/connectors/withThemeComponents.spec.jsx +10 -6
- package/local-packages/pwa-extension-kit/src/connectors/withUser.spec.jsx +14 -8
- package/locales/en.json +8 -0
- package/package.json +1 -1
- package/plugins/ShopgateTypeCheckPlugin.js +242 -0
- package/plugins/ShopgateTypeCheckPlugin.spec.js +77 -0
- package/webpack.config.js +14 -2
- package/local-packages/pwa-extension-kit/src/components/ProductCharacteristics/__snapshots__/spec.jsx.snap +0 -584
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
const {
|
|
6
|
+
blue, cyan, green, grey, red, yellow,
|
|
7
|
+
} = require('chalk');
|
|
8
|
+
const logger = require('../lib/logger');
|
|
9
|
+
const i18n = require('../lib/i18n');
|
|
10
|
+
const getTypeCheckProjects = require('../lib/getTypeCheckProjects');
|
|
11
|
+
const createTypeCheckParser = require('../lib/createTypeCheckParser');
|
|
12
|
+
|
|
13
|
+
const t = i18n(__filename);
|
|
14
|
+
const LAST_LINES = 20;
|
|
15
|
+
const WATCHER = path.resolve(__dirname, '..', 'lib', 'typeCheckWatcher.js');
|
|
16
|
+
const NODE_MODULES = `${path.sep}node_modules${path.sep}`;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Reads the attached extensions from the environment.
|
|
20
|
+
* @return {Object}
|
|
21
|
+
*/
|
|
22
|
+
function getAttachedExtensions() {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(process.env.extensions);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Prints the result of a check to the console.
|
|
32
|
+
* @param {Object} project The project.
|
|
33
|
+
* @param {Object[]} errors The errors of the project.
|
|
34
|
+
*/
|
|
35
|
+
function report(project, errors) {
|
|
36
|
+
const prefix = blue(`[${project.name}]`);
|
|
37
|
+
|
|
38
|
+
errors.forEach((error) => {
|
|
39
|
+
const location = error.file
|
|
40
|
+
? `${cyan(path.relative(process.cwd(), error.file))}:${error.line}:${error.column} - `
|
|
41
|
+
: '';
|
|
42
|
+
|
|
43
|
+
logger.log(`${prefix} ${location}${red('error')} ${grey(`${error.code}:`)} ${error.message}`);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
logger.log(`${prefix} ${errors.length > 0
|
|
47
|
+
? red(t('ERRORS', { count: errors.length }))
|
|
48
|
+
: green(t('NO_ERRORS'))}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Type checks the theme and the attached extensions next to a watching build. The errors are
|
|
53
|
+
* printed to the console and optionally shown in the overlay of the dev server; they never fail
|
|
54
|
+
* the build.
|
|
55
|
+
*/
|
|
56
|
+
class ShopgateTypeCheckPlugin {
|
|
57
|
+
/**
|
|
58
|
+
* @param {Object} [options={}] The plugin options.
|
|
59
|
+
* @param {boolean} [options.overlay=false] Whether the errors are also shown in the browser.
|
|
60
|
+
*/
|
|
61
|
+
constructor({ overlay = false } = {}) {
|
|
62
|
+
this.overlay = overlay;
|
|
63
|
+
this.started = false;
|
|
64
|
+
this.results = new Map();
|
|
65
|
+
this.buildErrors = [];
|
|
66
|
+
this.devServer = null;
|
|
67
|
+
this.overlayShown = false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {Object} compiler The webpack compiler
|
|
72
|
+
*/
|
|
73
|
+
apply(compiler) {
|
|
74
|
+
compiler.hooks.watchRun.tap('ShopgateTypeCheckPlugin', () => {
|
|
75
|
+
if (this.started) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.started = true;
|
|
80
|
+
this.start();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
compiler.hooks.done.tap('ShopgateTypeCheckPlugin', (stats) => {
|
|
84
|
+
this.buildErrors = stats.toJson({
|
|
85
|
+
all: false,
|
|
86
|
+
errors: true,
|
|
87
|
+
}).errors || [];
|
|
88
|
+
setImmediate(() => this.updateOverlay());
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Connects the plugin with the dev server, so that it can show the errors in the browser.
|
|
94
|
+
* @param {Object} devServer The dev server.
|
|
95
|
+
*/
|
|
96
|
+
setDevServer(devServer) {
|
|
97
|
+
if (!this.overlay || !devServer.webSocketServer) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.devServer = devServer;
|
|
102
|
+
devServer.webSocketServer.implementation.on('connection', (client) => {
|
|
103
|
+
setImmediate(() => this.updateOverlay([client]));
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Starts a TypeScript compiler in watch mode for every project.
|
|
109
|
+
*/
|
|
110
|
+
start() {
|
|
111
|
+
const projects = getTypeCheckProjects(process.cwd(), getAttachedExtensions());
|
|
112
|
+
|
|
113
|
+
projects.forEach((project) => {
|
|
114
|
+
if (!project.tsc) {
|
|
115
|
+
logger.warn(yellow(` ${t('NO_TYPESCRIPT', { name: project.name })}`));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
logger.log(blue(` ${t('CHECKING', { name: project.name })}`));
|
|
120
|
+
this.watch(project);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Runs the TypeScript compiler of a project in watch mode and collects its errors.
|
|
126
|
+
* @param {Object} project The project.
|
|
127
|
+
*/
|
|
128
|
+
watch(project) {
|
|
129
|
+
const projectPath = fs.realpathSync(project.path);
|
|
130
|
+
const child = spawn(process.execPath, [
|
|
131
|
+
WATCHER,
|
|
132
|
+
project.tsc,
|
|
133
|
+
'--project',
|
|
134
|
+
projectPath,
|
|
135
|
+
'--noEmit',
|
|
136
|
+
'--watch',
|
|
137
|
+
'--preserveWatchOutput',
|
|
138
|
+
'--pretty',
|
|
139
|
+
'false',
|
|
140
|
+
], {
|
|
141
|
+
cwd: projectPath,
|
|
142
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
143
|
+
});
|
|
144
|
+
const parse = createTypeCheckParser();
|
|
145
|
+
const lastLines = [];
|
|
146
|
+
|
|
147
|
+
[child.stdout, child.stderr].forEach((stream) => {
|
|
148
|
+
readline.createInterface({ input: stream }).on('line', (line) => {
|
|
149
|
+
if (line.trim()) {
|
|
150
|
+
lastLines.push(line);
|
|
151
|
+
lastLines.splice(0, lastLines.length - LAST_LINES);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const event = parse(line);
|
|
155
|
+
|
|
156
|
+
if (!event) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (event.type === 'started') {
|
|
161
|
+
this.results.delete(project.name);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const errors = event.errors
|
|
166
|
+
.map(error => ({
|
|
167
|
+
...error,
|
|
168
|
+
file: error.file && path.resolve(projectPath, error.file),
|
|
169
|
+
}))
|
|
170
|
+
.filter(error => project.isTheme || !error.file || (
|
|
171
|
+
error.file.startsWith(`${projectPath}${path.sep}`)
|
|
172
|
+
&& !error.file.slice(projectPath.length).includes(NODE_MODULES)
|
|
173
|
+
));
|
|
174
|
+
|
|
175
|
+
this.results.set(project.name, errors);
|
|
176
|
+
report(project, errors);
|
|
177
|
+
this.updateOverlay();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
child.on('error', (error) => {
|
|
182
|
+
logger.warn(yellow(` ${t('FAILED', {
|
|
183
|
+
name: project.name,
|
|
184
|
+
message: error.message,
|
|
185
|
+
})}`));
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
child.on('exit', (code, signal) => {
|
|
189
|
+
if (code === 0) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
logger.warn(yellow(` ${t('STOPPED', {
|
|
194
|
+
name: project.name,
|
|
195
|
+
reason: signal || code,
|
|
196
|
+
})}`));
|
|
197
|
+
lastLines.forEach(line => logger.warn(`${blue(`[${project.name}]`)} ${line}`));
|
|
198
|
+
this.results.delete(project.name);
|
|
199
|
+
this.updateOverlay();
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Shows the errors of all finished checks in the overlay of the dev server, together with the
|
|
205
|
+
* errors of the build, since the overlay only holds one set of errors. Without type errors the
|
|
206
|
+
* overlay is handed back to the dev server.
|
|
207
|
+
* @param {Object[]} [clients] The browser connections to update. Defaults to all.
|
|
208
|
+
*/
|
|
209
|
+
updateOverlay(clients) {
|
|
210
|
+
if (!this.devServer) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const receivers = clients || this.devServer.webSocketServer.clients;
|
|
215
|
+
const typeErrors = [];
|
|
216
|
+
|
|
217
|
+
this.results.forEach((errors, name) => {
|
|
218
|
+
errors.forEach((error) => {
|
|
219
|
+
typeErrors.push({
|
|
220
|
+
moduleName: error.file ? path.relative(process.cwd(), error.file) : name,
|
|
221
|
+
loc: error.file ? `${error.line}:${error.column}` : '',
|
|
222
|
+
message: `${error.code}: ${error.message}`,
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (typeErrors.length > 0) {
|
|
228
|
+
this.overlayShown = true;
|
|
229
|
+
this.devServer.sendMessage(receivers, 'errors', [...this.buildErrors, ...typeErrors]);
|
|
230
|
+
} else if (this.overlayShown && !clients) {
|
|
231
|
+
this.overlayShown = false;
|
|
232
|
+
|
|
233
|
+
if (this.buildErrors.length > 0) {
|
|
234
|
+
this.devServer.sendMessage(receivers, 'errors', this.buildErrors);
|
|
235
|
+
} else {
|
|
236
|
+
this.devServer.sendMessage(receivers, 'still-ok');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
module.exports = ShopgateTypeCheckPlugin;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const ShopgateTypeCheckPlugin = require('./ShopgateTypeCheckPlugin');
|
|
2
|
+
|
|
3
|
+
const TYPE_ERROR = {
|
|
4
|
+
file: null,
|
|
5
|
+
line: null,
|
|
6
|
+
column: null,
|
|
7
|
+
code: 'TS2322',
|
|
8
|
+
message: 'Wrong type.',
|
|
9
|
+
};
|
|
10
|
+
const BUILD_ERROR = { message: 'Module not found.' };
|
|
11
|
+
|
|
12
|
+
describe('ShopgateTypeCheckPlugin', () => {
|
|
13
|
+
let plugin;
|
|
14
|
+
let devServer;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
devServer = {
|
|
18
|
+
sendMessage: jest.fn(),
|
|
19
|
+
webSocketServer: {
|
|
20
|
+
clients: ['tab'],
|
|
21
|
+
implementation: { on: jest.fn() },
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
plugin = new ShopgateTypeCheckPlugin({ overlay: true });
|
|
25
|
+
plugin.setDevServer(devServer);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('sends nothing as long as there are no type errors', () => {
|
|
29
|
+
plugin.buildErrors = [BUILD_ERROR];
|
|
30
|
+
plugin.updateOverlay();
|
|
31
|
+
|
|
32
|
+
expect(devServer.sendMessage).not.toHaveBeenCalled();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('shows type errors together with the errors of the build', () => {
|
|
36
|
+
plugin.buildErrors = [BUILD_ERROR];
|
|
37
|
+
plugin.results.set('theme', [TYPE_ERROR]);
|
|
38
|
+
plugin.updateOverlay();
|
|
39
|
+
|
|
40
|
+
expect(devServer.sendMessage).toHaveBeenCalledWith(['tab'], 'errors', [
|
|
41
|
+
BUILD_ERROR,
|
|
42
|
+
{
|
|
43
|
+
moduleName: 'theme',
|
|
44
|
+
loc: '',
|
|
45
|
+
message: 'TS2322: Wrong type.',
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('removes the overlay when the last type error is fixed', () => {
|
|
51
|
+
plugin.results.set('theme', [TYPE_ERROR]);
|
|
52
|
+
plugin.updateOverlay();
|
|
53
|
+
plugin.results.set('theme', []);
|
|
54
|
+
plugin.updateOverlay();
|
|
55
|
+
|
|
56
|
+
expect(devServer.sendMessage).toHaveBeenLastCalledWith(['tab'], 'still-ok');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('keeps the errors of the build in the overlay when the last type error is fixed', () => {
|
|
60
|
+
plugin.results.set('theme', [TYPE_ERROR]);
|
|
61
|
+
plugin.updateOverlay();
|
|
62
|
+
plugin.results.set('theme', []);
|
|
63
|
+
plugin.buildErrors = [BUILD_ERROR];
|
|
64
|
+
plugin.updateOverlay();
|
|
65
|
+
|
|
66
|
+
expect(devServer.sendMessage).toHaveBeenLastCalledWith(['tab'], 'errors', [BUILD_ERROR]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('does not show anything without the overlay option', () => {
|
|
70
|
+
plugin = new ShopgateTypeCheckPlugin();
|
|
71
|
+
plugin.setDevServer(devServer);
|
|
72
|
+
plugin.results.set('theme', [TYPE_ERROR]);
|
|
73
|
+
plugin.updateOverlay();
|
|
74
|
+
|
|
75
|
+
expect(devServer.sendMessage).not.toHaveBeenCalled();
|
|
76
|
+
});
|
|
77
|
+
});
|
package/webpack.config.js
CHANGED
|
@@ -13,6 +13,7 @@ const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'
|
|
|
13
13
|
const rxPaths = require('rxjs/_esm5/path-mapping');
|
|
14
14
|
const ShopgateIndexerPlugin = require('./plugins/ShopgateIndexerPlugin');
|
|
15
15
|
const ShopgateThemeConfigValidatorPlugin = require('./plugins/ShopgateThemeConfigValidatorPlugin');
|
|
16
|
+
const ShopgateTypeCheckPlugin = require('./plugins/ShopgateTypeCheckPlugin');
|
|
16
17
|
const { ENV, isDev, PUBLIC_FOLDER } = require('./lib/variables');
|
|
17
18
|
const getAppSettings = require('./lib/getAppSettings');
|
|
18
19
|
const convertLanguageToISO = require('./lib/convertLanguageToISO');
|
|
@@ -35,6 +36,10 @@ const t = i18n(__filename);
|
|
|
35
36
|
const devtool = isDev ? sourceMap : (process.env.SOURCE_MAPS || false);
|
|
36
37
|
const fileSuffix = devtool ? '.sm' : '';
|
|
37
38
|
const addBundleAnalyzer = !!process.env.BUNDLE_ANALYZER;
|
|
39
|
+
const typeCheck = ['0', 'false'].includes(process.env.TYPE_CHECK) ? '' : process.env.TYPE_CHECK;
|
|
40
|
+
const typeCheckPlugin = isDev && typeCheck
|
|
41
|
+
? new ShopgateTypeCheckPlugin({ overlay: typeCheck === 'overlay' })
|
|
42
|
+
: null;
|
|
38
43
|
|
|
39
44
|
/**
|
|
40
45
|
* @type {import('webpack').Configuration}
|
|
@@ -213,6 +218,7 @@ const config = {
|
|
|
213
218
|
...(isDev ? [new ReactRefreshWebpackPlugin({
|
|
214
219
|
overlay: false,
|
|
215
220
|
})] : []),
|
|
221
|
+
...(typeCheckPlugin ? [typeCheckPlugin] : []),
|
|
216
222
|
...(!isDev ? [
|
|
217
223
|
new CompressionWebpackPlugin({
|
|
218
224
|
filename: '[path][base].gz[query]',
|
|
@@ -306,9 +312,15 @@ const config = {
|
|
|
306
312
|
directory: path.resolve(themePath, PUBLIC_FOLDER),
|
|
307
313
|
},
|
|
308
314
|
client: {
|
|
309
|
-
|
|
310
|
-
|
|
315
|
+
overlay: typeCheckPlugin && typeCheckPlugin.overlay ? {
|
|
316
|
+
errors: true,
|
|
317
|
+
warnings: false,
|
|
318
|
+
runtimeErrors: false,
|
|
319
|
+
} : false,
|
|
311
320
|
},
|
|
321
|
+
...(typeCheckPlugin ? {
|
|
322
|
+
onListening: devServer => typeCheckPlugin.setDevServer(devServer),
|
|
323
|
+
} : {}),
|
|
312
324
|
host: '0.0.0.0',
|
|
313
325
|
port: process.env.optionsPort,
|
|
314
326
|
historyApiFallback: true,
|