随着时间的推移,在Gatsby上进行开发的速度大大减慢-有时需要20到30秒来使热加载器刷新页面。在对页面进行10-20次更新后-它最终总是会在以下错误中出现:
error UNHANDLED REJECTION
RuntimeError: memory access out of bounds
- source-map-consumer.js:335 BasicSourceMapConsumer._parseMappings
[frontend]/[gatsby]/[react-hot-loader]/[source-map]/lib/source-map-consumer.js:335:44
- source-map-consumer.js:315 BasicSourceMapConsumer._getMappingsPtr
[frontend]/[gatsby]/[react-hot-loader]/[source-map]/lib/source-map-consumer.js:315:12
- source-map-consumer.js:387 _wasm.withMappingCallback
[frontend]/[gatsby]/[react-hot-loader]/[source-map]/lib/source-map-consumer.js:387:57
- wasm.js:95 Object.withMappingCallback
[frontend]/[gatsby]/[react-hot-loader]/[source-map]/lib/wasm.js:95:11
- source-map-consumer.js:371 BasicSourceMapConsumer.eachMapping
[frontend]/[gatsby]/[react-hot-loader]/[source-map]/lib/source-map-consumer.js:371:16
- source-node.js:87 Function.fromStringWithSourceMap
[frontend]/[gatsby]/[react-hot-loader]/[source-map]/lib/source-node.js:87:24
- webpack.development.js:150 onSourceMapReady
[frontend]/[gatsby]/[react-hot-loader]/dist/webpack.development.js:150:61
- next_tick.js:68 process._tickCallback
internal/process/next_tick.js:68:7
我们的网站在/pages
中大约有250多个静态页面,并且以编程方式从*.yml
生成的netlify-cms
文件中构建了约30个页面。
我怀疑这与源地图变得过大有关-疯狂的猜测是它可能与我们在gatsby-node.js
或cms.js
中为netlify-cms所做的事情有关'管理页面-尽管我不确定从何处开始调试此类问题。任何指针将不胜感激。
我们已实施的一种临时解决方案是将Gatsby的默认源映射从cheap-module-eval-source-map
更改为eval
,这使热模块的重新编译时间从4-6秒降低到1-2秒。但是,我们显然希望拥有性能正常的适当源地图。
// For local development, we disable proper sourcemaps to speed up
// performance.
if (stage === "develop") {
config = {
...config,
devtool: "eval",
}
}
我们最近还基于gatsby-netlify-cms-plugin
重新构建了分叉的v4.1.6
,其中包含以下显着改进:
https://github.com/gatsbyjs/gatsby/pull/15191
https://github.com/gatsbyjs/gatsby/pull/15591
此更新帮助了很多。但是,理想情况下,我们希望通过适当的源映射使HMR编译时间达到500ms以下。
gatsby-node.js
const path = require("path");
const fs = require("fs");
const remark = require("remark");
const remarkHTML = require("remark-html");
const fetch = require("node-fetch");
const _ = require("lodash");
const {
assertResourcesAreValid,
sortResourcesByDate,
getFeaturedResources,
} = require("./src/utils/resources-data");
const parseForm = require("./src/utils/parseEloquaForm");
const {
ELOQUA_COMPANY,
ELOQUA_USERNAME,
ELOQUA_PASSWORD,
} = require("./src/config-secure");
let elqBaseUrl;
const MARKDOWN_FIELDS = [
"overviewContentLeft",
"overviewContentRight",
"assetContent",
"content",
];
function fetchJson(url, options) {
function checkResponse(res) {
return new Promise(resolve => {
if (res.ok) {
resolve(res);
} else {
throw Error(`Request rejected with status ${res.status}`);
}
});
}
function getJson(res) {
return new Promise(resolve => {
resolve(res.json());
});
}
return fetch(url, options)
.then(res => checkResponse(res))
.then(res => getJson(res));
}
function fetchEloqua(url) {
const base64 = new Buffer(
`${ELOQUA_COMPANY}\\${ELOQUA_USERNAME}:${ELOQUA_PASSWORD}`
).toString("base64");
const auth = `Basic ${base64}`;
return fetchJson(url, { headers: { Authorization: auth } });
}
function getElqBaseUrl() {
return new Promise(resolve => {
if (elqBaseUrl) {
resolve(elqBaseUrl);
} else {
return fetchEloqua("https://login.eloqua.com/id").then(
({ urls }) => {
const baseUrl = urls.base;
elqBaseUrl = baseUrl;
resolve(baseUrl);
}
);
}
});
}
function getElqForm(baseUrl, elqFormId) {
return fetchEloqua(`${baseUrl}/api/rest/2.0/assets/form/${elqFormId}`);
}
function existsPromise(path) {
return new Promise((resolve, reject) => {
return fs.exists(path, (exists, err) =>
err ? reject(err) : resolve(exists)
);
});
}
function mkdirPromise(path) {
return new Promise((resolve, reject) => {
return fs.mkdir(path, err => (err ? reject(err) : resolve()));
});
}
function writeFilePromise(path, data) {
return new Promise((resolve, reject) => {
return fs.writeFile(path, data, err => (err ? reject(err) : resolve()));
});
}
exports.onPreBootstrap = () => {
// Cache the eloqua base URL used to make Eloqua requests
return new Promise(resolve => {
getElqBaseUrl().then(() => {
resolve();
});
});
};
exports.onCreateWebpackConfig = ({ stage, actions, plugins, loaders }) => {
let config = {
resolve: {
modules: [path.resolve(__dirname, "src"), "node_modules"],
},
plugins: [
plugins.provide({
// exposes jquery as global for the Swiftype vendor library
jQuery: "jquery",
// ideally we should eventually remove these and instead use
// explicit imports within files to take advantage of
// treeshaking-friendly lodash imports
$: "jquery",
_: "lodash",
}),
plugins.define({
CMS_PREVIEW: false,
}),
],
};
if (stage === "build-html") {
config = {
...config,
module: {
rules: [
{
// ignore these modules which rely on the window global on build phase
test: /jquery|js-cookie|query-string|tabbable/,
use: loaders.null(),
},
],
},
};
}
actions.setWebpackConfig(config);
};
exports.onCreateNode = ({ node, actions }) => {
const { createNode, createNodeField } = actions;
const { elqFormId, happyHour, resourcesData } = node;
const forms = [];
if (resourcesData) {
assertResourcesAreValid(resourcesData);
const sortedResources = sortResourcesByDate(resourcesData);
const featuredResources = getFeaturedResources(resourcesData);
createNodeField({
name: "sortedResourcesByDate",
node,
value: sortedResources,
});
createNodeField({
name: "featuredResources",
node,
value: featuredResources,
});
}
// Convert markdown-formatted fields to HTML
MARKDOWN_FIELDS.forEach(field => {
const fieldValue = node[field];
if (fieldValue) {
const html = remark()
.use(remarkHTML)
.processSync(fieldValue)
.toString();
createNodeField({
node,
name: field,
value: html,
});
}
});
function createFormFieldsNode({ elqFormId, nodeFieldName }) {
return getElqBaseUrl()
.then(baseUrl => getElqForm(baseUrl, elqFormId))
.then(form => {
createNodeField({
node,
name: nodeFieldName,
value: parseForm(form),
});
return Promise.resolve();
})
.catch(err => {
throw `Eloqua Form ID ${elqFormId} - ${err}`;
});
}
// Fetch main Eloqua form and attach to node referencing elqFormId
if (elqFormId) {
const mainForm = createFormFieldsNode({
elqFormId,
nodeFieldName: "formFields",
});
forms.push(mainForm);
}
// The main event landing page has two forms, the main form and a happy hour
// form. This gets the happy hour form.
if (happyHour && happyHour.elqFormId) {
const happyHourForm = createFormFieldsNode({
elqFormId: happyHour.elqFormId,
nodeFieldName: "happyHourFormFields",
});
forms.push(happyHourForm);
}
return Promise.all(forms);
};
exports.onCreatePage = ({ page, actions }) => {
const { createPage, deletePage } = actions;
// Pass the page path to context so it's available in page queries as
// GraphQL variables
return new Promise(resolve => {
const oldPage = Object.assign({}, page);
deletePage(oldPage);
createPage({
...oldPage,
context: {
slug: oldPage.path,
},
});
resolve();
});
};
exports.createPages = ({ graphql, actions }) => {
const dir = path.resolve("static/compiled");
const file = path.join(dir, "blocked-email-domains.json");
const lpStandardTemplate = path.resolve("src/templates/lp-standard.js");
const lpEbookTemplate = path.resolve("src/templates/lp-ebook.js");
const lpWebinarSeriesTemplate = path.resolve(
"src/templates/lp-webinar-series.js"
);
const lpThankYouTemplate = path.resolve("src/templates/lp-thank-you.js");
const lpEventMainTemplate = path.resolve("src/templates/lp-event-main.js");
const lpEventHappyHourTemplate = path.resolve(
"src/templates/lp-event-happy-hour.js"
);
const lpEventActivityTemplate = path.resolve(
"src/templates/lp-event-activity.js"
);
const lpEventRoadshowTemplate = path.resolve(
"src/templates/lp-event-roadshow.js"
);
const { createPage } = actions;
return graphql(`
{
// a bunch of graphQL queries
}
`).then(result => {
if (result.errors) {
throw result.errors;
}
// Create pages from the data files generated by the CMS
result.data.allLpStandardYaml.edges.forEach(({ node }) => {
createPage({
path: `resources/${node.slug}`,
component: lpStandardTemplate,
context: {
...node,
},
});
});
result.data.allLpThankYouYaml.edges.forEach(({ node }) => {
createPage({
path: `resources/${node.slug}`,
component: lpThankYouTemplate,
context: {
...node,
},
});
});
result.data.allLpEbookYaml.edges.forEach(({ node }) => {
createPage({
path: `resources/${node.slug}`,
component: lpEbookTemplate,
context: {
...node,
},
});
});
result.data.allLpWebinarSeriesYaml.edges.forEach(({ node }) => {
createPage({
path: `resources/${node.slug}`,
component: lpWebinarSeriesTemplate,
context: {
...node,
},
});
});
result.data.allLpEventMainYaml.edges.forEach(({ node }) => {
createPage({
path: `events/${node.slug}`,
component: lpEventMainTemplate,
context: {
...node,
},
});
});
result.data.allLpEventHappyHourYaml.edges.forEach(({ node }) => {
createPage({
path: `events/${node.slug}`,
component: lpEventHappyHourTemplate,
context: {
...node,
},
});
});
result.data.allLpEventActivityYaml.edges.forEach(({ node }) => {
createPage({
path: `events/${node.slug}`,
component: lpEventActivityTemplate,
context: {
...node,
},
});
});
result.data.allLpEventRoadshowYaml.edges.forEach(({ node }) => {
createPage({
path: `events/${node.slug}`,
component: lpEventRoadshowTemplate,
context: {
...node,
},
});
});
// Build copy of blocked-email-domains.yml as JSON in /static
// This is referenced by the Eloqua-hosted forms on go.memsql.com
const { blockedEmailDomains } = result.data.miscYaml;
const domainsArray = blockedEmailDomains
.trim()
.split("\n")
.map(rule => rule.toLowerCase());
return existsPromise(dir)
.then(exists => (exists ? Promise.resolve() : mkdirPromise(dir)))
.then(() => writeFilePromise(file, JSON.stringify(domainsArray)));
});
};
package.json
{
...,
"browserslist": [
">0.25%",
"not dead"
],
"devDependencies": {
"@babel/core": "7.1.5",
"@babel/plugin-syntax-dynamic-import": "7.2.0",
"@storybook/addon-knobs": "5.0.11",
"@storybook/addon-storysource": "5.0.11",
"babel-eslint": "8.2.2",
"babel-loader": "8.0.4",
"eslint": "4.12.1",
"eslint-plugin-babel": "4.1.2",
"eslint-plugin-flowtype": "2.39.1",
"eslint-plugin-import": "2.8.0",
"eslint-plugin-prettier": "2.3.1",
"eslint-plugin-react": "7.5.1",
"file-loader": "2.0.0",
"gatsby": "2.8.8",
"gatsby-link": "2.1.1",
"gatsby-plugin-intercom-spa": "0.1.0",
"gatsby-plugin-netlify-cms": "vai0/gatsby-plugin-netlify-cms#918821c",
"gatsby-plugin-polyfill-io": "1.1.0",
"gatsby-plugin-react-helmet": "3.0.12",
"gatsby-plugin-root-import": "2.0.5",
"gatsby-plugin-sass": "2.1.0",
"gatsby-plugin-sentry": "1.0.1",
"gatsby-plugin-sitemap": "2.1.0",
"gatsby-source-apiserver": "2.1.2",
"gatsby-source-filesystem": "2.0.39",
"gatsby-transformer-json": "2.1.11",
"gatsby-transformer-yaml": "2.1.12",
"html-webpack-plugin": "3.2.0",
"node-fetch": "2.3.0",
"node-sass": "4.12.0",
"react-lorem-component": "0.13.0",
"remark": "10.0.1",
"remark-html": "9.0.0",
"uglify-js": "3.3.28",
"uglifyjs-folder": "1.5.1",
"yup": "0.24.1"
},
"dependencies": {
"@fortawesome/fontawesome-pro": "5.6.1",
"@fortawesome/fontawesome-svg-core": "1.2.6",
"@fortawesome/free-brands-svg-icons": "5.5.0",
"@fortawesome/pro-regular-svg-icons": "5.4.1",
"@fortawesome/pro-solid-svg-icons": "5.4.1",
"@fortawesome/react-fontawesome": "0.1.3",
"@storybook/addon-a11y": "5.0.11",
"@storybook/addon-viewport": "5.0.11",
"@storybook/addons": "5.0.11",
"@storybook/cli": "5.0.11",
"@storybook/react": "5.0.11",
"anchorate": "1.2.3",
"autoprefixer": "8.3.0",
"autosuggest-highlight": "3.1.1",
"balance-text": "3.3.0",
"classnames": "2.2.5",
"flubber": "0.4.2",
"focus-trap-react": "4.0.0",
"formik": "vai0/formik#d524e4c",
"google-map-react": "1.1.2",
"jquery": "3.3.1",
"js-cookie": "2.2.0",
"lodash": "4.17.11",
"minisearch": "2.0.0",
"moment": "2.22.0",
"moment-timezone": "0.5.23",
"netlify-cms": "2.9.0",
"prop-types": "15.6.2",
"query-string": "5.1.1",
"react": "16.6.1",
"react-add-to-calendar-hoc": "1.0.8",
"react-anchor-link-smooth-scroll": "1.0.12",
"react-autosuggest": "9.4.3",
"react-dom": "16.6.1",
"react-helmet": "5.2.0",
"react-player": "1.7.0",
"react-redux": "6.0.0",
"react-remove-scroll-bar": "1.2.0",
"react-select": "1.2.1",
"react-slick": "0.24.0",
"react-spring": "6.1.8",
"react-truncate": "2.4.0",
"react-typekit": "1.1.3",
"react-waypoint": "8.0.3",
"react-youtube": "7.6.0",
"redux": "4.0.1",
"slick-carousel": "1.8.1",
"typeface-inconsolata": "0.0.54",
"typeface-lato": "0.0.54",
"whatwg-fetch": "2.0.4",
"xr": "0.3.0"
}
}
cms.js
import CMS from "netlify-cms";
import "typeface-lato";
import "typeface-inconsolata";
import "scss/global.scss";
import StandardLandingPagePreview from "cms/preview-templates/StandardLandingPagePreview";
import StandardThankYouPagePreview from "cms/preview-templates/StandardThankYouPagePreview";
import EbookLandingPagePreview from "cms/preview-templates/EbookLandingPagePreview";
import WebinarSeriesLandingPagePreview from "cms/preview-templates/WebinarSeriesLandingPagePreview";
import EventMainLandingPagePreview from "cms/preview-templates/EventMainLandingPagePreview";
import EventHappyHourLandingPagePreview from "cms/preview-templates/EventHappyHourLandingPagePreview";
import EventActivityLandingPagePreview from "cms/preview-templates/EventActivityLandingPagePreview";
import EventRoadshowLandingPagePreview from "cms/preview-templates/EventRoadshowLandingPagePreview";
// The following window and global config settings below were taken from here.
// https://github.com/gatsbyjs/gatsby/blob/master/docs/docs/visual-testing-with-storybook.md
// They're required because the netlify-cms runs on a separate webpack config,
// and outside of Gatsby. This ensures any Gatsby components imported into the
// CMS works without errors
// highlight-start
// Gatsby's Link overrides:
// Gatsby defines a global called ___loader to prevent its method calls from creating console errors you override it here
global.___loader = {
enqueue: () => {},
hovering: () => {},
};
// Gatsby internal mocking to prevent unnecessary errors
global.__PATH_PREFIX__ = "";
// This is to utilized to override the window.___navigate method Gatsby defines and uses to report what path a Link would be taking us to
window.___navigate = pathname => {
alert(`This would navigate to: https://www.memsql.com${pathname}`);
};
CMS.registerPreviewTemplate("lp-standard", StandardLandingPagePreview);
CMS.registerPreviewTemplate("lp-ebook", EbookLandingPagePreview);
CMS.registerPreviewTemplate(
"lp-webinar-series",
WebinarSeriesLandingPagePreview
);
CMS.registerPreviewTemplate("content", StandardLandingPagePreview);
CMS.registerPreviewTemplate("content-syndication", StandardLandingPagePreview);
CMS.registerPreviewTemplate("programmatic", StandardLandingPagePreview);
CMS.registerPreviewTemplate("sponsored-webcast", StandardLandingPagePreview);
CMS.registerPreviewTemplate("webcast", StandardLandingPagePreview);
CMS.registerPreviewTemplate("web-forms", StandardLandingPagePreview);
CMS.registerPreviewTemplate("other", StandardLandingPagePreview);
CMS.registerPreviewTemplate("lp-thank-you", StandardThankYouPagePreview);
CMS.registerPreviewTemplate("lp-event-main", EventMainLandingPagePreview);
CMS.registerPreviewTemplate(
"lp-event-happy-hour",
EventHappyHourLandingPagePreview
);
CMS.registerPreviewTemplate(
"lp-event-activity",
EventActivityLandingPagePreview
);
CMS.registerPreviewTemplate(
"lp-event-roadshow",
EventRoadshowLandingPagePreview
);
System:
OS: Linux 4.19 Debian GNU/Linux 10 (buster) 10 (buster)
CPU: (8) x64 Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz
Shell: 5.0.3 - /bin/bash
Binaries:
Node: 10.15.3 - ~/.nvm/versions/node/v10.15.3/bin/node
Yarn: 1.17.3 - /usr/bin/yarn
npm: 6.9.0 - ~/.nvm/versions/node/v10.15.3/bin/npm
Languages:
Python: 2.7.16 - /usr/bin/python
Browsers:
Chrome: 75.0.3770.142
Firefox: 60.8.0
npmPackages:
gatsby: 2.10.4 => 2.10.4
gatsby-cli: 2.7.2 => 2.7.2
gatsby-link: 2.2.0 => 2.2.0
gatsby-plugin-intercom-spa: 0.1.0 => 0.1.0
gatsby-plugin-manifest: 2.1.1 => 2.1.1
gatsby-plugin-netlify-cms: vai0/gatsby-plugin-netlify-cms#e92ec70 => 4.1.6
gatsby-plugin-polyfill-io: 1.1.0 => 1.1.0
gatsby-plugin-react-helmet: 3.1.0 => 3.1.0
gatsby-plugin-root-import: 2.0.5 => 2.0.5
gatsby-plugin-sass: 2.1.0 => 2.1.0
gatsby-plugin-sentry: 1.0.1 => 1.0.1
gatsby-plugin-sitemap: 2.2.0 => 2.2.0
gatsby-source-apiserver: 2.1.3 => 2.1.3
gatsby-source-filesystem: 2.1.0 => 2.1.0
gatsby-transformer-json: 2.2.0 => 2.2.0
gatsby-transformer-yaml: 2.2.0 => 2.2.0
npmGlobalPackages:
gatsby-dev-cli: 2.5.0
答案 0 :(得分:0)
如图所示,gatsby-source-filesystem
盖茨比的文档为并发下载增加了限制,以防止processRemoteNode
过载。为了修复和修改任何自定义配置,它们公开了一个GATSBY_CONCURRENT_DOWNLOAD
环境变量:
为防止并发请求过载
processRemoteNode
,您可以 调整200
的默认并发下载量,方法如下:GATSBY_CONCURRENT_DOWNLOAD
环境变量。
在运行命令中,设置可解决问题的值。就我而言,它是5
:
"develop": "GATSBY_CONCURRENT_DOWNLOAD=5 gatsby develop"