Files
30-seconds-of-code/node_modules/gatsby/dist/commands/develop.js
2019-08-20 15:52:05 +02:00

573 lines
17 KiB
JavaScript

"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
const url = require(`url`);
const glob = require(`glob`);
const fs = require(`fs`);
const openurl = require(`better-opn`);
const chokidar = require(`chokidar`);
const express = require(`express`);
const graphqlHTTP = require(`express-graphql`);
const graphqlPlayground = require(`graphql-playground-middleware-express`).default;
const graphiqlExplorer = require(`gatsby-graphiql-explorer`);
const {
formatError
} = require(`graphql`);
const got = require(`got`);
const rl = require(`readline`);
const webpack = require(`webpack`);
const webpackConfig = require(`../utils/webpack.config`);
const bootstrap = require(`../bootstrap`);
const {
store,
emitter
} = require(`../redux`);
const {
syncStaticDir
} = require(`../utils/get-static-dir`);
const buildHTML = require(`./build-html`);
const {
withBasePath
} = require(`../utils/path`);
const report = require(`gatsby-cli/lib/reporter`);
const launchEditor = require(`react-dev-utils/launchEditor`);
const formatWebpackMessages = require(`react-dev-utils/formatWebpackMessages`);
const chalk = require(`chalk`);
const address = require(`address`);
const cors = require(`cors`);
const telemetry = require(`gatsby-telemetry`);
const WorkerPool = require(`../utils/worker/pool`);
const withResolverContext = require(`../schema/context`);
const sourceNodes = require(`../utils/source-nodes`);
const websocketManager = require(`../utils/websocket-manager`);
const getSslCert = require(`../utils/get-ssl-cert`);
const slash = require(`slash`);
const {
initTracer
} = require(`../utils/tracer`);
const apiRunnerNode = require(`../utils/api-runner-node`);
const db = require(`../db`);
const detectPortInUseAndPrompt = require(`../utils/detect-port-in-use-and-prompt`);
const onExit = require(`signal-exit`);
const queryUtil = require(`../query`);
const queryQueue = require(`../query/queue`);
const queryWatcher = require(`../query/query-watcher`);
const requiresWriter = require(`../bootstrap/requires-writer`); // const isInteractive = process.stdout.isTTY
// Watch the static directory and copy files to public as they're added or
// changed. Wait 10 seconds so copying doesn't interfere with the regular
// bootstrap.
setTimeout(() => {
syncStaticDir();
}, 10000);
const rlInterface = rl.createInterface({
input: process.stdin,
output: process.stdout
}); // Quit immediately on hearing ctrl-c
rlInterface.on(`SIGINT`, () => {
process.exit();
});
onExit(() => {
telemetry.trackCli(`DEVELOP_STOP`);
});
const waitJobsFinished = () => new Promise((resolve, reject) => {
const onEndJob = () => {
if (store.getState().jobs.active.length === 0) {
resolve();
emitter.off(`END_JOB`, onEndJob);
}
};
emitter.on(`END_JOB`, onEndJob);
onEndJob();
});
async function startServer(program) {
const directory = program.directory;
const directoryPath = withBasePath(directory);
const workerPool = WorkerPool.create();
const createIndexHtml = async () => {
try {
await buildHTML.buildPages({
program,
stage: `develop-html`,
pagePaths: [`/`],
workerPool
});
} catch (err) {
if (err.name !== `WebpackError`) {
report.panic(err);
return;
}
report.panic(report.stripIndent`
There was an error compiling the html.js component for the development server.
See our docs page on debugging HTML builds for help https://gatsby.dev/debug-html
`, err);
}
};
await createIndexHtml();
const devConfig = await webpackConfig(program, directory, `develop`, program.port);
const compiler = webpack(devConfig);
/**
* Set up the express app.
**/
const app = express();
app.use(telemetry.expressMiddleware(`DEVELOP`));
app.use(require(`webpack-hot-middleware`)(compiler, {
log: false,
path: `/__webpack_hmr`,
heartbeat: 10 * 1000
}));
app.use(cors());
/**
* Pattern matching all endpoints with graphql or graphiql with 1 or more leading underscores
*/
const graphqlEndpoint = `/_+graphi?ql`;
if (process.env.GATSBY_GRAPHQL_IDE === `playground`) {
app.get(graphqlEndpoint, graphqlPlayground({
endpoint: `/___graphql`
}), () => {});
} else {
graphiqlExplorer(app, {
graphqlEndpoint
});
}
app.use(graphqlEndpoint, graphqlHTTP(() => {
const {
schema,
schemaCustomization
} = store.getState();
return {
schema,
graphiql: false,
context: withResolverContext({}, schema, schemaCustomization.context),
formatError(err) {
return Object.assign({}, formatError(err), {
stack: err.stack ? err.stack.split(`\n`) : []
});
}
};
}));
/**
* Refresh external data sources.
* This behavior is disabled by default, but the ENABLE_REFRESH_ENDPOINT env var enables it
* If no GATSBY_REFRESH_TOKEN env var is available, then no Authorization header is required
**/
const REFRESH_ENDPOINT = `/__refresh`;
app.use(REFRESH_ENDPOINT, express.json());
app.post(REFRESH_ENDPOINT, (req, res) => {
const enableRefresh = process.env.ENABLE_GATSBY_REFRESH_ENDPOINT;
const refreshToken = process.env.GATSBY_REFRESH_TOKEN;
const authorizedRefresh = !refreshToken || req.headers.authorization === refreshToken;
if (enableRefresh && authorizedRefresh) {
console.log(`Refreshing source data`);
sourceNodes({
webhookBody: req.body
});
}
res.end();
});
app.get(`/__open-stack-frame-in-editor`, (req, res) => {
launchEditor(req.query.fileName, req.query.lineNumber);
res.end();
}); // Disable directory indexing i.e. serving index.html from a directory.
// This can lead to serving stale html files during development.
//
// We serve by default an empty index.html that sets up the dev environment.
app.use(require(`./develop-static`)(`public`, {
index: false
}));
app.use(require(`webpack-dev-middleware`)(compiler, {
logLevel: `trace`,
publicPath: devConfig.output.publicPath,
stats: `errors-only`
})); // Expose access to app for advanced use cases
const {
developMiddleware
} = store.getState().config;
if (developMiddleware) {
developMiddleware(app, program);
} // Set up API proxy.
const {
proxy
} = store.getState().config;
if (proxy) {
const {
prefix,
url
} = proxy;
app.use(`${prefix}/*`, (req, res) => {
const proxiedUrl = url + req.originalUrl;
const {
// remove `host` from copied headers
// eslint-disable-next-line no-unused-vars
method
} = req,
headers = (0, _objectWithoutPropertiesLoose2.default)(req.headers, ["host"]);
req.pipe(got.stream(proxiedUrl, {
headers,
method,
decompress: false
}).on(`response`, response => res.writeHead(response.statusCode, response.headers)).on(`error`, (err, _, response) => {
if (response) {
res.writeHead(response.statusCode, response.headers);
} else {
const message = `Error when trying to proxy request "${req.originalUrl}" to "${proxiedUrl}"`;
report.error(message, err);
res.sendStatus(500);
}
})).pipe(res);
});
}
await apiRunnerNode(`onCreateDevServer`, {
app
}); // In case nothing before handled hot-update - send 404.
// This fixes "Unexpected token < in JSON at position 0" runtime
// errors after restarting development server and
// cause automatic hard refresh in the browser.
app.use(/.*\.hot-update\.json$/i, (req, res) => {
res.status(404).end();
}); // Render an HTML page and serve it.
app.use((req, res, next) => {
res.sendFile(directoryPath(`public/index.html`), err => {
if (err) {
res.status(500).end();
}
});
});
/**
* Set up the HTTP server and socket.io.
**/
let server = require(`http`).Server(app); // If a SSL cert exists in program, use it with `createServer`.
if (program.ssl) {
server = require(`https`).createServer(program.ssl, app);
}
websocketManager.init({
server,
directory: program.directory
});
const socket = websocketManager.getSocket();
const listener = server.listen(program.port, program.host, err => {
if (err) {
if (err.code === `EADDRINUSE`) {
// eslint-disable-next-line max-len
report.panic(`Unable to start Gatsby on port ${program.port} as there's already a process listening on that port.`);
return;
}
report.panic(`There was a problem starting the development server`, err);
}
}); // Register watcher that rebuilds index.html every time html.js changes.
const watchGlobs = [`src/html.js`, `plugins/**/gatsby-ssr.js`].map(path => slash(directoryPath(path)));
chokidar.watch(watchGlobs).on(`change`, async () => {
await createIndexHtml();
socket.to(`clients`).emit(`reload`);
});
return [compiler, listener];
}
module.exports = async program => {
initTracer(program.openTracingConfigFile);
telemetry.trackCli(`DEVELOP_START`);
telemetry.startBackgroundUpdate();
const port = typeof program.port === `string` ? parseInt(program.port, 10) : program.port; // In order to enable custom ssl, --cert-file --key-file and -https flags must all be
// used together
if ((program[`cert-file`] || program[`key-file`]) && !program.https) {
report.panic(`for custom ssl --https, --cert-file, and --key-file must be used together`);
} // Check if https is enabled, then create or get SSL cert.
// Certs are named after `name` inside the project's package.json.
// Scoped names are converted from @npm/package-name to npm--package-name
if (program.https) {
program.ssl = await getSslCert({
name: program.sitePackageJson.name.replace(`@`, ``).replace(`/`, `--`),
certFile: program[`cert-file`],
keyFile: program[`key-file`],
directory: program.directory
});
}
program.port = await detectPortInUseAndPrompt(port, rlInterface); // Start bootstrap process.
const {
graphqlRunner
} = await bootstrap(program); // Start the createPages hot reloader.
require(`../bootstrap/page-hot-reloader`)(graphqlRunner);
const queryIds = queryUtil.calcInitialDirtyQueryIds(store.getState());
const {
staticQueryIds,
pageQueryIds
} = queryUtil.groupQueryIds(queryIds);
let activity = report.activityTimer(`run static queries`);
activity.start();
await queryUtil.processStaticQueries(staticQueryIds, {
activity,
state: store.getState()
});
activity.end();
activity = report.activityTimer(`run page queries`);
activity.start();
await queryUtil.processPageQueries(pageQueryIds, {
activity
});
activity.end();
require(`../redux/actions`).boundActionCreators.setProgramStatus(`BOOTSTRAP_QUERY_RUNNING_FINISHED`);
await waitJobsFinished();
requiresWriter.startListener();
db.startAutosave();
queryUtil.startListening(queryQueue.createDevelopQueue());
queryWatcher.startWatchDeletePage();
const [compiler] = await startServer(program);
function prepareUrls(protocol, host, port) {
const formatUrl = hostname => url.format({
protocol,
hostname,
port,
pathname: `/`
});
const prettyPrintUrl = hostname => url.format({
protocol,
hostname,
port: chalk.bold(port),
pathname: `/`
});
const isUnspecifiedHost = host === `0.0.0.0` || host === `::`;
let lanUrlForConfig, lanUrlForTerminal;
if (isUnspecifiedHost) {
try {
// This can only return an IPv4 address
lanUrlForConfig = address.ip();
if (lanUrlForConfig) {
// Check if the address is a private ip
// https://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
if (/^10[.]|^172[.](1[6-9]|2[0-9]|3[0-1])[.]|^192[.]168[.]/.test(lanUrlForConfig)) {
// Address is private, format it for later use
lanUrlForTerminal = prettyPrintUrl(lanUrlForConfig);
} else {
// Address is not private, so we will discard it
lanUrlForConfig = undefined;
}
}
} catch (_e) {// ignored
}
} // TODO collect errors (GraphQL + Webpack) in Redux so we
// can clear terminal and print them out on every compile.
// Borrow pretty printing code from webpack plugin.
const localUrlForTerminal = prettyPrintUrl(host);
const localUrlForBrowser = formatUrl(host);
return {
lanUrlForConfig,
lanUrlForTerminal,
localUrlForTerminal,
localUrlForBrowser
};
}
function printInstructions(appName, urls, useYarn) {
report._setStage({
stage: `DevelopBootstrapFinished`,
context: {
url: urls.localUrlForBrowser,
appName
}
});
console.log(`You can now view ${chalk.bold(appName)} in the browser.`);
console.log();
if (urls.lanUrlForTerminal) {
console.log(` ${chalk.bold(`Local:`)} ${urls.localUrlForTerminal}`);
console.log(` ${chalk.bold(`On Your Network:`)} ${urls.lanUrlForTerminal}`);
} else {
console.log(` ${urls.localUrlForTerminal}`);
}
console.log();
console.log(`View ${process.env.GATSBY_GRAPHQL_IDE === `playground` ? `the GraphQL Playground` : `GraphiQL`}, an in-browser IDE, to explore your site's data and schema`);
console.log();
if (urls.lanUrlForTerminal) {
console.log(` ${chalk.bold(`Local:`)} ${urls.localUrlForTerminal}___graphql`);
console.log(` ${chalk.bold(`On Your Network:`)} ${urls.lanUrlForTerminal}___graphql`);
} else {
console.log(` ${urls.localUrlForTerminal}___graphql`);
}
console.log();
console.log(`Note that the development build is not optimized.`);
console.log(`To create a production build, use ` + `${chalk.cyan(`npm run build`)}`);
console.log();
}
function printDeprecationWarnings() {
const deprecatedApis = [`boundActionCreators`, `pathContext`];
const fixMap = {
boundActionCreators: {
newName: `actions`,
docsLink: `https://gatsby.dev/boundActionCreators`
},
pathContext: {
newName: `pageContext`,
docsLink: `https://gatsby.dev/pathContext`
}
};
const deprecatedLocations = {};
deprecatedApis.forEach(api => deprecatedLocations[api] = []);
glob.sync(`{,!(node_modules|public)/**/}*.js`, {
nodir: true
}).forEach(file => {
const fileText = fs.readFileSync(file);
const matchingApis = deprecatedApis.filter(api => fileText.indexOf(api) !== -1);
matchingApis.forEach(api => deprecatedLocations[api].push(file));
});
deprecatedApis.forEach(api => {
if (deprecatedLocations[api].length) {
console.log(`%s %s %s %s`, chalk.cyan(api), chalk.yellow(`is deprecated. Please use`), chalk.cyan(fixMap[api].newName), chalk.yellow(`instead. For migration instructions, see ${fixMap[api].docsLink}\nCheck the following files:`));
console.log();
deprecatedLocations[api].forEach(file => console.log(file));
console.log();
}
});
}
let isFirstCompile = true; // "done" event fires when Webpack has finished recompiling the bundle.
// Whether or not you have warnings or errors, you will get this event.
compiler.hooks.done.tapAsync(`print getsby instructions`, (stats, done) => {
// We have switched off the default Webpack output in WebpackDevServer
// options so we are going to "massage" the warnings and errors and present
// them in a readable focused way.
const messages = formatWebpackMessages(stats.toJson({}, true));
const urls = prepareUrls(program.ssl ? `https` : `http`, program.host, program.port);
const isSuccessful = !messages.errors.length; // if (isSuccessful) {
// console.log(chalk.green(`Compiled successfully!`))
// }
// if (isSuccessful && (isInteractive || isFirstCompile)) {
if (isSuccessful && isFirstCompile) {
printInstructions(program.sitePackageJson.name, urls, program.useYarn);
printDeprecationWarnings();
if (program.open) {
Promise.resolve(openurl(urls.localUrlForBrowser)).catch(err => console.log(`${chalk.yellow(`warn`)} Browser not opened because no browser was found`));
}
}
isFirstCompile = false; // If errors exist, only show errors.
// if (messages.errors.length) {
// // Only keep the first error. Others are often indicative
// // of the same problem, but confuse the reader with noise.
// if (messages.errors.length > 1) {
// messages.errors.length = 1
// }
// console.log(chalk.red("Failed to compile.\n"))
// console.log(messages.errors.join("\n\n"))
// return
// }
// Show warnings if no errors were found.
// if (messages.warnings.length) {
// console.log(chalk.yellow("Compiled with warnings.\n"))
// console.log(messages.warnings.join("\n\n"))
// // Teach some ESLint tricks.
// console.log(
// "\nSearch for the " +
// chalk.underline(chalk.yellow("keywords")) +
// " to learn more about each warning."
// )
// console.log(
// "To ignore, add " +
// chalk.cyan("// eslint-disable-next-line") +
// " to the line before.\n"
// )
// }
done();
});
};
//# sourceMappingURL=develop.js.map