@salesforce/pwa-kit-dev 3.0.0-preview.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.
Files changed (54) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +35 -0
  3. package/bin/pwa-kit-dev.js +461 -0
  4. package/configs/babel/babel-config.js +34 -0
  5. package/configs/eslint/README.md +21 -0
  6. package/configs/eslint/eslint-config.js +11 -0
  7. package/configs/eslint/index.js +11 -0
  8. package/configs/eslint/no-react.js +18 -0
  9. package/configs/eslint/partials/base.js +38 -0
  10. package/configs/eslint/partials/jest.js +24 -0
  11. package/configs/eslint/partials/react.js +29 -0
  12. package/configs/eslint/partials/typescript-permit-any.js +31 -0
  13. package/configs/eslint/partials/typescript.js +17 -0
  14. package/configs/eslint/recommended.js +20 -0
  15. package/configs/eslint/safe-types.js +20 -0
  16. package/configs/jest/jest-babel-transform.js +19 -0
  17. package/configs/jest/jest.config.js +33 -0
  18. package/configs/jest/mocks/fileMock.js +9 -0
  19. package/configs/jest/mocks/styleMock.js +9 -0
  20. package/configs/jest/mocks/svgMock.js +11 -0
  21. package/configs/webpack/config-names.js +24 -0
  22. package/configs/webpack/config.js +425 -0
  23. package/configs/webpack/overrides-plugin.js +120 -0
  24. package/configs/webpack/plugins.js +92 -0
  25. package/package.json +150 -0
  26. package/scripts/version.js +22 -0
  27. package/ssr/server/build-dev-server.js +443 -0
  28. package/ssr/server/build-dev-server.test.js +635 -0
  29. package/ssr/server/loading-screen/css/main.css +272 -0
  30. package/ssr/server/loading-screen/css/normalize.css +349 -0
  31. package/ssr/server/loading-screen/img/cloud-1.svg +1 -0
  32. package/ssr/server/loading-screen/img/cloud-2.svg +1 -0
  33. package/ssr/server/loading-screen/img/cloud-3.svg +1 -0
  34. package/ssr/server/loading-screen/img/cloud.svg +1 -0
  35. package/ssr/server/loading-screen/img/codey-arm.svg +1 -0
  36. package/ssr/server/loading-screen/img/codey-bear.svg +1 -0
  37. package/ssr/server/loading-screen/img/codey-bg.svg +1 -0
  38. package/ssr/server/loading-screen/img/codey-cloud.svg +1 -0
  39. package/ssr/server/loading-screen/img/codey-search.svg +1 -0
  40. package/ssr/server/loading-screen/img/codey.svg +1 -0
  41. package/ssr/server/loading-screen/img/codeyCarry.svg +1 -0
  42. package/ssr/server/loading-screen/img/devDocumentation.svg +1 -0
  43. package/ssr/server/loading-screen/img/devGithub.svg +1 -0
  44. package/ssr/server/loading-screen/img/devTrailhead.svg +1 -0
  45. package/ssr/server/loading-screen/img/logo.svg +1 -0
  46. package/ssr/server/loading-screen/img/slds_spinner_brand_9EA9F1.gif +0 -0
  47. package/ssr/server/loading-screen/index.html +130 -0
  48. package/ssr/server/test_fixtures/app/main.js +6 -0
  49. package/ssr/server/test_fixtures/app/static/favicon.ico +0 -0
  50. package/ssr/server/test_fixtures/localhost.pem +45 -0
  51. package/utils/script-utils.js +312 -0
  52. package/utils/script-utils.test.js +282 -0
  53. package/utils/test-fixtures/minimal-built-app/ssr.js +9 -0
  54. package/utils/test-fixtures/minimal-built-app/static/favicon.ico +0 -0
@@ -0,0 +1,443 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.shouldCompress = exports.setLocalAssetHeaders = exports.makeErrorHandler = exports.DevServerMixin = exports.DevServerFactory = void 0;
7
+ var _compression = _interopRequireDefault(require("compression"));
8
+ var _express = _interopRequireDefault(require("express"));
9
+ var _path = _interopRequireDefault(require("path"));
10
+ var _fs = _interopRequireDefault(require("fs"));
11
+ var _https = _interopRequireDefault(require("https"));
12
+ var _http = _interopRequireDefault(require("http"));
13
+ var _mimeTypes = _interopRequireDefault(require("mime-types"));
14
+ var _webpack = _interopRequireDefault(require("webpack"));
15
+ var _webpackDevMiddleware = _interopRequireDefault(require("webpack-dev-middleware"));
16
+ var _webpackHotServerMiddleware = _interopRequireDefault(require("webpack-hot-server-middleware"));
17
+ var _webpackHotMiddleware = _interopRequireDefault(require("webpack-hot-middleware"));
18
+ var _open = _interopRequireDefault(require("open"));
19
+ var _requireFromString = _interopRequireDefault(require("require-from-string"));
20
+ var _buildRemoteServer = require("@salesforce/pwa-kit-runtime/ssr/server/build-remote-server");
21
+ var _ssrShared = require("@salesforce/pwa-kit-runtime/utils/ssr-shared");
22
+ var _configNames = require("../../configs/webpack/config-names");
23
+ var _crypto = require("crypto");
24
+ var _chalk = _interopRequireDefault(require("chalk"));
25
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
26
+ function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } /*
27
+ * Copyright (c) 2022, Salesforce, Inc.
28
+ * All rights reserved.
29
+ * SPDX-License-Identifier: BSD-3-Clause
30
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
31
+ */
32
+ const CONTENT_TYPE = 'content-type';
33
+ const CONTENT_ENCODING = 'content-encoding';
34
+ const NO_CACHE = 'max-age=0, nocache, nostore, must-revalidate';
35
+
36
+ /**
37
+ * @private
38
+ */
39
+ const DevServerMixin = {
40
+ /**
41
+ * @private
42
+ */
43
+ _logStartupMessage(options) {
44
+ console.log(`Starting the DevServer on ${_chalk.default.cyan(this._getDevServerURL(options))}`);
45
+ },
46
+ /**
47
+ * @private
48
+ */
49
+ _getProtocol(options) {
50
+ return process.env.DEV_SERVER_PROTOCOL || options.protocol;
51
+ },
52
+ /**
53
+ * @private
54
+ */
55
+
56
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
57
+ _getDefaultCacheControl(options) {
58
+ return NO_CACHE;
59
+ },
60
+ /**
61
+ * @private
62
+ */
63
+ _strictSSL(options) {
64
+ return options.strictSSL;
65
+ },
66
+ /**
67
+ * Since dev server does not have access to apiGateway event object,
68
+ * here we generate an uuid and assign it under locals
69
+ * @private
70
+ */
71
+ _setRequestId(app) {
72
+ app.use((req, res, next) => {
73
+ res.locals.requestId = (0, _crypto.randomUUID)();
74
+ next();
75
+ });
76
+ },
77
+ /**
78
+ * @private
79
+ */
80
+ _setCompression(app) {
81
+ app.use((0, _compression.default)({
82
+ level: 9,
83
+ filter: shouldCompress
84
+ }));
85
+ },
86
+ /**
87
+ * @private
88
+ */
89
+ _setupMetricsFlushing(app) {
90
+ // Flush metrics at the end of sending. We do this here to
91
+ // keep the code paths consistent between local and remote
92
+ // servers. For the remote server, the flushing is done
93
+ // by the Lambda integration.
94
+ app.use((req, res, next) => {
95
+ res.on('finish', () => app.metrics.flush());
96
+ next();
97
+ });
98
+ },
99
+ /**
100
+ * @private
101
+ */
102
+
103
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
104
+ _setupProxying(app, options) {
105
+ _ssrShared.proxyConfigs.forEach(config => {
106
+ app.use(config.proxyPath, config.proxy);
107
+ app.use(config.cachingPath, config.cachingProxy);
108
+ });
109
+ },
110
+ /**
111
+ * @private
112
+ */
113
+ _addSDKInternalHandlers(app) {
114
+ // This is separated out because these routes must not have our SSR middleware applied to them.
115
+ // But the SSR render function must!
116
+
117
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
118
+ let config = require('../../configs/webpack/config');
119
+ const projectWebpackPath = _path.default.resolve(app.options.projectDir, 'webpack.config.js');
120
+ if (_fs.default.existsSync(projectWebpackPath)) {
121
+ config = require(projectWebpackPath);
122
+ }
123
+ app.__compiler = (0, _webpack.default)(config);
124
+ app.__devMiddleware = (0, _webpackDevMiddleware.default)(app.__compiler, {
125
+ serverSideRender: true
126
+ });
127
+ app.__webpackReady = () => Boolean(app.__devMiddleware.context.state);
128
+ app.__devMiddleware.waitUntilValid(() => {
129
+ // Be just a little more generous before letting eg. Lighthouse hit it!
130
+ setTimeout(() => {
131
+ console.log(_chalk.default.cyan('First build complete'));
132
+ }, 75);
133
+ });
134
+ if (config.some(cnf => cnf.name === _configNames.SERVER)) {
135
+ app.__hotServerMiddleware = (0, _webpackHotServerMiddleware.default)(app.__compiler);
136
+ }
137
+ app.use('/mobify/bundle/development', app.__devMiddleware);
138
+ app.__hmrMiddleware = (_, res) => res.status(501).send('Hot Module Reloading is disabled.');
139
+ const clientCompiler = app.__compiler.compilers.find(compiler => compiler.name === _configNames.CLIENT);
140
+ if (clientCompiler && process.env.HMR !== 'false') {
141
+ app.__hmrMiddleware = (0, _webpackHotMiddleware.default)(clientCompiler, {
142
+ path: '/'
143
+ }); // path is relative to the endpoint the middleware is attached to
144
+ }
145
+
146
+ app.use('/__mrt/hmr', app.__hmrMiddleware);
147
+ app.use('/__mrt/status', (req, res) => {
148
+ return res.json({
149
+ ready: app.__webpackReady()
150
+ });
151
+ });
152
+ app.use('/__mrt/loading-screen/', _express.default.static(_path.default.resolve(__dirname, 'loading-screen'), {
153
+ dotFiles: 'deny'
154
+ }));
155
+ app.get('/__mrt/clear-browser-data', (_, res) => {
156
+ console.log(_chalk.default.cyan('Clearing browser data'), '(cache, service worker, web storage for browsers supporting Clear-Site-Data header)');
157
+ console.log('For more info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data#browser_compatibility');
158
+ console.log('');
159
+
160
+ // Note: this header value needs the double quotes.
161
+ res.set('Clear-Site-Data', '"cache", "storage"');
162
+ res.send();
163
+ });
164
+ },
165
+ /**
166
+ * @private
167
+ */
168
+ _addStaticAssetServing(app) {
169
+ // Proxy bundle asset requests to the local
170
+ // build directory.
171
+ app.use('/mobify/bundle/development', _express.default.static(_path.default.resolve(process.cwd(), 'src'), {
172
+ dotFiles: 'deny',
173
+ setHeaders: setLocalAssetHeaders,
174
+ fallthrough: true
175
+ }));
176
+ },
177
+ /**
178
+ * @private
179
+ */
180
+ _addDevServerGarbageCollection(app) {
181
+ app.use((req, res, next) => {
182
+ const done = () => {
183
+ // We collect garbage because when a Lambda environment is
184
+ // re-used, we want to start with minimal memory usage. This
185
+ // call typically takes less than 100mS, and can dramatically
186
+ // reduce memory usage, so we accept the runtime cost.
187
+ // For the local dev server, we do this now. For a remote
188
+ // server, we use a different strategy (see createLambdaHandler).
189
+ req.app._collectGarbage();
190
+ };
191
+ res.on('finish', done);
192
+ res.on('close', done);
193
+ next();
194
+ });
195
+ },
196
+ serveStaticFile(filePath, opts = {}) {
197
+ // Warning: Ugly part of the Bundle spec that we need to maintain.
198
+ //
199
+ // This function assumes that an SDK build step will copy all
200
+ // non-webpacked assets from the 'app' dir to the 'build' dir.
201
+ //
202
+ // If you look carefully through the history, this has never
203
+ // been true though – assets get copied from app/static to
204
+ // build/static but this isn't really clear from the API.
205
+ //
206
+ // To see where those assets get copied, see here:
207
+ //
208
+ // packages/pwa-kit-dev/src/configs/webpack/config.js
209
+ //
210
+ // We have plans to make a robust Bundle spec in 246!
211
+ //
212
+ // Discussion here:
213
+ //
214
+ // https://salesforce-internal.slack.com/archives/C8YDDMKFZ/p1677793769255659?thread_ts=1677791840.174309&cid=C8YDDMKFZ
215
+
216
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
217
+ const pkg = require(_path.default.resolve(process.cwd(), 'package.json'));
218
+ return (req, res) => {
219
+ var _pkg$ccExtensibility, _pkg$ccExtensibility$;
220
+ const baseDir = _path.default.resolve(req.app.options.projectDir, (pkg === null || pkg === void 0 ? void 0 : (_pkg$ccExtensibility = pkg.ccExtensibility) === null || _pkg$ccExtensibility === void 0 ? void 0 : (_pkg$ccExtensibility$ = _pkg$ccExtensibility.overridesDir) === null || _pkg$ccExtensibility$ === void 0 ? void 0 : _pkg$ccExtensibility$.replace(/^\//, '')) ?? '', 'app');
221
+ return this._serveStaticFile(req, res, baseDir, filePath, opts);
222
+ };
223
+ },
224
+ serveServiceWorker(req, res) {
225
+ req.app.__devMiddleware.waitUntilValid(() => {
226
+ const sourceMap = req.path.endsWith('.map');
227
+ const file = sourceMap ? 'worker.js.map' : 'worker.js';
228
+ const type = sourceMap ? '.js.map' : '.js';
229
+ const content = DevServerFactory._getWebpackAsset(req, _configNames.CLIENT_OPTIONAL, file);
230
+ if (content === null) {
231
+ // Service worker does not exist. Reminder that SW is optional for MRT apps.
232
+ res.sendStatus(404);
233
+ } else {
234
+ res.type(type);
235
+ res.send(content);
236
+ }
237
+ });
238
+ },
239
+ render(req, res, next) {
240
+ const app = req.app;
241
+ if (app.__webpackReady()) {
242
+ app.__hotServerMiddleware(req, res, next);
243
+ } else {
244
+ this._redirectToLoadingScreen(req, res, next);
245
+ }
246
+ },
247
+ /**
248
+ * @private
249
+ */
250
+
251
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
252
+ _redirectToLoadingScreen(req, res, next) {
253
+ res.redirect('/__mrt/loading-screen/index.html?loading=1');
254
+ },
255
+ /**
256
+ * @private
257
+ */
258
+ _getDevServerHostAndPort(options) {
259
+ const split = options.devServerHostName.split(':');
260
+ const hostname = split.length === 2 ? split[0] : options.devServerHostName;
261
+ const port = split.length === 2 ? split[1] : options.port;
262
+ return {
263
+ hostname,
264
+ port
265
+ };
266
+ },
267
+ /**
268
+ * @private
269
+ */
270
+ _getDevServerURL(options) {
271
+ const {
272
+ protocol
273
+ } = options;
274
+ const {
275
+ hostname,
276
+ port
277
+ } = this._getDevServerHostAndPort(options);
278
+ return `${protocol}://${hostname}:${port}`;
279
+ },
280
+ /**
281
+ * @private
282
+ */
283
+ _createHandler(app) {
284
+ const {
285
+ protocol,
286
+ sslFilePath
287
+ } = app.options;
288
+ const {
289
+ hostname,
290
+ port
291
+ } = this._getDevServerHostAndPort(app.options);
292
+ let server;
293
+ if (protocol === 'https') {
294
+ const sslFile = _fs.default.readFileSync(sslFilePath);
295
+ server = _https.default.createServer({
296
+ key: sslFile,
297
+ cert: sslFile
298
+ }, app);
299
+ } else {
300
+ server = _http.default.createServer(app);
301
+ }
302
+ server.on('error', makeErrorHandler(process, server, console.log));
303
+ server.on('close', () => app.applicationCache.close());
304
+ server.listen({
305
+ hostname,
306
+ port
307
+ }, () => {
308
+ /* istanbul ignore next */
309
+ if (process.env.NODE_ENV !== 'test') {
310
+ (0, _open.default)(`${this._getDevServerURL(app.options)}/__mrt/loading-screen/index.html?loading=1`);
311
+ }
312
+ });
313
+ return {
314
+ handler: undefined,
315
+ server,
316
+ app
317
+ };
318
+ },
319
+ /**
320
+ * Load any request processor code to emulate in the dev server the code
321
+ * that would run on a Lambda Edge function.
322
+ *
323
+ * @private
324
+ */
325
+ _getRequestProcessor(req) {
326
+ const compiled = this._getWebpackAsset(req, _configNames.REQUEST_PROCESSOR, 'request-processor.js');
327
+ if (compiled) {
328
+ const module = (0, _requireFromString.default)(compiled);
329
+ if (!module.processRequest) {
330
+ throw new Error(`Request processor module "request-processor.js" does not export processRequest`);
331
+ }
332
+ return module;
333
+ } else {
334
+ return null;
335
+ }
336
+ },
337
+ /**
338
+ * Return the compiled source for a webpack asset as a string.
339
+ *
340
+ * @param req
341
+ * @param compilerName
342
+ * @param fileName
343
+ * @returns {null|String}
344
+ * @private
345
+ */
346
+ _getWebpackAsset(req, compilerName, fileName) {
347
+ if (req.app.__webpackReady()) {
348
+ const outputFileSystem = req.app.__devMiddleware.context.outputFileSystem;
349
+ const jsonWebpackStats = req.app.__devMiddleware.context.stats.toJson();
350
+ try {
351
+ const rp = jsonWebpackStats.children.find(child => child.name === compilerName);
352
+ const assetPath = _path.default.join(rp.outputPath, fileName);
353
+ return outputFileSystem.readFileSync(assetPath, 'utf-8');
354
+ } catch (e) {
355
+ // The file doesn't exist – this is fine, many are optional
356
+ return null;
357
+ }
358
+ } else {
359
+ // The file isn't compiled yet
360
+ return null;
361
+ }
362
+ }
363
+ };
364
+
365
+ /**
366
+ * Set the headers for a bundle asset. This is used only in local
367
+ * dev server mode.
368
+ *
369
+ * @private
370
+ * @param res - the response object
371
+ * @param assetPath - the path to the asset file (with no query string
372
+ * or other URL elements)
373
+ */
374
+ exports.DevServerMixin = DevServerMixin;
375
+ const setLocalAssetHeaders = (res, assetPath) => {
376
+ const base = _path.default.basename(assetPath);
377
+ const contentType = _mimeTypes.default.lookup(base);
378
+ res.set(CONTENT_TYPE, contentType); // || 'application/octet-stream'
379
+
380
+ // Stat the file and return the last-modified Date
381
+ // in RFC1123 format. Also use that value as the ETag
382
+ // and Last-Modified
383
+ const mtime = _fs.default.statSync(assetPath).mtime;
384
+ const mtimeRFC1123 = mtime.toUTCString();
385
+ res.set('date', mtimeRFC1123);
386
+ res.set('last-modified', mtimeRFC1123);
387
+ res.set('etag', mtime.getTime());
388
+
389
+ // We don't cache local bundle assets
390
+ res.set('cache-control', NO_CACHE);
391
+ };
392
+
393
+ /**
394
+ * Crash the app with a user-friendly message when the port is already in use.
395
+ *
396
+ * @private
397
+ * @param {*} proc - Node's process module
398
+ * @param {*} server - the server to attach the listener to
399
+ * @param {*} log - logging function
400
+ */
401
+ exports.setLocalAssetHeaders = setLocalAssetHeaders;
402
+ const makeErrorHandler = (proc, server, log) => {
403
+ return e => {
404
+ if (e.code === 'EADDRINUSE') {
405
+ log(`This port is already being used by another process.`);
406
+ server.close();
407
+ proc.exit(2);
408
+ }
409
+ };
410
+ };
411
+
412
+ /**
413
+ * Filter function for compression module.
414
+ *
415
+ * @private
416
+ * @param req {IncomingMessage} ExpressJS Request
417
+ * @param res {ServerResponse} ExpressJS Response
418
+ * @returns {Boolean}
419
+ */
420
+ exports.makeErrorHandler = makeErrorHandler;
421
+ const shouldCompress = (req, res) => {
422
+ // If there is already a CONTENT_ENCODING header, then we
423
+ // do not apply any compression. This allows project code
424
+ // to handle encoding, if required.
425
+ if (res.getHeader(CONTENT_ENCODING)) {
426
+ // Set a flag on the response so that the persistent cache logic
427
+ // can tell there was already a content-encoding header.
428
+ res.locals.contentEncodingSet = true;
429
+ return false;
430
+ }
431
+
432
+ // Let the compression module make the decision about compressing.
433
+ // Even if we return true here, the module may still choose
434
+ // not to compress the data.
435
+ return _compression.default.filter(req, res);
436
+ };
437
+
438
+ /**
439
+ * @private
440
+ */
441
+ exports.shouldCompress = shouldCompress;
442
+ const DevServerFactory = _extends({}, _buildRemoteServer.RemoteServerFactory, DevServerMixin);
443
+ exports.DevServerFactory = DevServerFactory;