@magento/pwa-buildpack
Version:
Build/Layout optimization tooling and Peregrine framework adapters for the Magento PWA
150 lines (140 loc) • 5.69 kB
JavaScript
const debug = require('../../util/debug').makeFileLogger(__filename);
const fetch = require('node-fetch');
const path = require('path');
const https = require('https');
const url = require('url');
const upward = require('@magento/upward-js');
// To be used with `node-fetch` in order to allow self-signed certificates.
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
class UpwardDevServerPlugin {
constructor(devServer, env, upwardPath) {
debug('instantiating with %s', upwardPath);
this.env = Object.assign(
{
NODE_ENV: 'development'
},
env
);
this.upwardPath = upwardPath;
// Compose `after` function if something else has defined it.
const oldAfter = devServer.after;
devServer.after = (app, ...rest) => {
if (oldAfter) oldAfter(app, ...rest);
app.use((req, res, next) => this.handleRequest(req, res, next));
};
}
apply(compiler) {
this.compiler = compiler;
// If a request has run to the devServer before this method has run,
// then there is already a Promise pending for the compiler, and this is
// its resolver.
if (this.resolveCompiler) {
this.resolveCompiler(compiler);
}
}
// Hold the first request (and subsequent requests) until the middleware is
// created, then swap out `handleRequest` for the simplest stack trace.
async handleRequest(req, res, next) {
// Several requests may come in. Only create the middleware once.
if (!this.middlewarePromise) {
this.middlewarePromise = this.createMiddleware();
}
await this.middlewarePromise;
// When the promise is resolved, `this.middleware` will exist.
// Replace this function itself.
this.handleRequest = this.middleware;
// And then call it to finish the response.
this.middleware(req, res, next);
// Further requests will go straight to the middleware.
}
async createMiddleware() {
// The compiler is necessary to build the fallback filesystem
// so UPWARd can use Webpack-generated assets in dev mode.
const compiler = await this.getCompiler();
// Standard filesystem-and-fetch IO.
const defaultIO = upward.IOAdapter.default(this.upwardPath);
// Use Webpack's in-memory file system for UPWARD file retrieval during
// development. Allows for hot reloading of server-side configuration.
const io = {
async readFile(filepath, enc = 'utf8') {
// If a version of the file is output by Webpack into the output folder, prefer that one.
const outputBasedPath = path.resolve(
compiler.options.output.path,
path.relative(compiler.options.context, filepath)
);
// Most likely scenario: UPWARD needs an output asset.
debug(
'compiler.outputFileSystem readFile %s %s',
outputBasedPath,
enc
);
try {
return compiler.outputFileSystem.readFileSync(
outputBasedPath,
enc
);
} catch (e) {
debug(
'outputFileSystem %s %s. Trying defaultIO...',
outputBasedPath,
e.stack
);
}
// Next most likely scenario: UPWARD needs a file on disk.
try {
const fromDefault = await defaultIO.readFile(filepath, enc);
return fromDefault;
} catch (e) {
debug(
'defaultIO %s %s. Trying inputFileSystem...',
filepath,
e.message
);
}
try {
// Fallback: Use Webpack's resolution rules.
return compiler.inputFileSystem.readFileSync(filepath, enc);
} catch (e) {
debug(
'inputFileSystem %s %s. Must throw...',
filepath,
e.message
);
throw e;
}
},
async networkFetch(path, options) {
debug('networkFetch %s, %o', path, options);
const { protocol } = new url.URL(path);
if (protocol === 'https:') {
return fetch(
path,
Object.assign({ agent: httpsAgent }, options)
);
}
return fetch(path, options);
// Use the https.Agent to allow self-signed certificates.
}
};
this.middleware = await upward.middleware(
this.upwardPath,
this.env,
io
);
}
async getCompiler() {
if (this.compiler) {
return this.compiler;
}
if (!this.compilerPromise) {
// Create a promise for the compiler and expose its resolver so it
// can be resolved when the `apply` method runs.
this.compilerPromise = new Promise(resolve => {
this.resolveCompiler = resolve;
});
}
// Share the compiler promise.
return this.compilerPromise;
}
}
module.exports = UpwardDevServerPlugin;