gatsby
Version:
Blazing fast modern site generator for React
260 lines (248 loc) • 11.2 kB
JavaScript
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.writeAll = exports.startListener = exports.resetLastHash = exports.getComponents = void 0;
var _debounce2 = _interopRequireDefault(require("lodash/debounce"));
var _map2 = _interopRequireDefault(require("lodash/map"));
var _uniqBy2 = _interopRequireDefault(require("lodash/uniqBy"));
var _orderBy2 = _interopRequireDefault(require("lodash/orderBy"));
var _path = _interopRequireDefault(require("path"));
var _fsExtra = _interopRequireDefault(require("fs-extra"));
var _reporter = _interopRequireDefault(require("gatsby-cli/lib/reporter"));
var _reachRouter = require("@gatsbyjs/reach-router");
var _gatsbyCoreUtils = require("gatsby-core-utils");
var _redux = require("../redux/");
var _gatsbyWebpackVirtualModules = require("../utils/gatsby-webpack-virtual-modules");
var _pageMode = require("../utils/page-mode");
var _buildHtml = require("../commands/build-html");
var _rankRoute = require("../utils/rank-route");
const hasContentFilePath = componentPath => componentPath.includes(`?__contentFilePath=`);
let lastHash = null;
const resetLastHash = () => {
lastHash = null;
};
exports.resetLastHash = resetLastHash;
const getComponents = (pages, slices, components) => {
const pickComponentFields = page => {
var _components$get$Head, _components$get;
return {
componentPath: page.componentPath,
componentChunkName: page.componentChunkName,
hasHeadComponent: (_components$get$Head = (_components$get = components.get(page.componentPath)) === null || _components$get === void 0 ? void 0 : _components$get.Head) !== null && _components$get$Head !== void 0 ? _components$get$Head : false
};
};
return (0, _orderBy2.default)((0, _uniqBy2.default)((0, _map2.default)([...pages, ...slices.values()], pickComponentFields), c => c.componentChunkName), c => c.componentChunkName);
};
/**
* Get all dynamic routes and sort them by most specific at the top
* code is based on @reach/router match utility (https://github.com/reach/router/blob/152aff2352bc62cefc932e1b536de9efde6b64a5/src/lib/utils.js#L224-L254)
*/
exports.getComponents = getComponents;
const getMatchPaths = pages => {
const createMatchPathEntry = (page, index) => {
const {
matchPath
} = page;
if (matchPath === undefined) {
return _reporter.default.panic(`Error: matchPath property is undefined for page ${page.path}, should be a string`);
}
return {
...page,
matchPath,
index,
score: (0, _rankRoute.rankRoute)(matchPath)
};
};
const matchPathPages = [];
pages.forEach((page, index) => {
if (page.matchPath && (0, _pageMode.getPageMode)(page) === `SSG`) {
matchPathPages.push(createMatchPathEntry(page, index));
}
});
// Pages can live in matchPaths, to keep them working without doing another network request
// we save them in matchPath. We use `@reach/router` path ranking to score paths/matchPaths
// and sort them so more specific paths are before less specific paths.
// More info in https://github.com/gatsbyjs/gatsby/issues/16097
// small speedup: don't bother traversing when no matchPaths found.
if (matchPathPages.length) {
const newMatches = [];
pages.forEach((page, index) => {
const isInsideMatchPath = !!matchPathPages.find(pageWithMatchPath => !page.matchPath && (0, _reachRouter.match)(pageWithMatchPath.matchPath, page.path));
if (isInsideMatchPath) {
newMatches.push(createMatchPathEntry({
...page,
matchPath: page.path
}, index));
}
});
// Add afterwards because the new matches are not relevant for the existing search
matchPathPages.push(...newMatches);
}
return matchPathPages.sort((a, b) => {
// The higher the score, the higher the specificity of our matchPath
const order = b.score - a.score;
if (order !== 0) {
return order;
}
// if specificity is the same we do lexigraphic comparison of path to ensure
// deterministic order regardless of order pages where created
return a.matchPath.localeCompare(b.matchPath);
}).map(({
path,
matchPath
}) => {
return {
path,
matchPath
};
});
};
// Write out pages information.
const writeAll = async state => {
const {
program,
slices
} = state;
const pages = [...state.pages.values()];
const matchPaths = getMatchPaths(pages);
const components = getComponents(pages, slices, state.components);
let cleanedSSRVisitedPageComponents = [];
if (process.env.GATSBY_EXPERIMENTAL_DEV_SSR) {
var _state$visitedPages$g;
const ssrVisitedPageComponents = [...(((_state$visitedPages$g = state.visitedPages.get(`server`)) === null || _state$visitedPages$g === void 0 ? void 0 : _state$visitedPages$g.values()) || [])];
// Remove any page components that no longer exist.
cleanedSSRVisitedPageComponents = components.filter(c => ssrVisitedPageComponents.some(s => s === c.componentChunkName) || c.componentChunkName.startsWith(`slice---`));
}
const newHash = await (0, _gatsbyCoreUtils.md5)(JSON.stringify({
matchPaths,
components,
cleanedSSRVisitedPageComponents
}));
if (newHash === lastHash) {
// Nothing changed. No need to rewrite files
return false;
}
lastHash = newHash;
if (process.env.GATSBY_EXPERIMENTAL_DEV_SSR) {
// Create file with sync requires of visited page components files.
const lazySyncRequires = `exports.ssrComponents = {\n${cleanedSSRVisitedPageComponents.map(c => ` "${c.componentChunkName}": require("${(0, _gatsbyCoreUtils.joinPath)(c.componentPath)}")`).join(`,\n`)}
}\n\n`;
(0, _gatsbyWebpackVirtualModules.writeModule)(`$virtual/ssr-sync-requires`, lazySyncRequires);
// if this is executed, webpack should mark it as invalid, but sometimes there is some timing race
// so we also explicitly set flag here as well
(0, _buildHtml.devSSRWillInvalidate)();
}
// Create file with sync requires of components/json files.
let syncRequires = `
// prefer default export if available
const preferDefault = m => (m && m.default) || m
\n\n`;
syncRequires += `exports.components = {\n${components.map(c => ` "${c.componentChunkName}": preferDefault(require("${(0, _gatsbyCoreUtils.joinPath)(c.componentPath)}"))`).join(`,\n`)}
}\n\n`;
// Create file with async requires of components/json files.
let asyncRequires = ``;
if (process.env.gatsby_executing_command === `develop` || "5" === `5` && process.env.GATSBY_PARTIAL_HYDRATION) {
asyncRequires = `exports.components = {\n${components.map(c => {
// we need a relative import path to keep contenthash the same if directory changes
const relativeComponentPath = _path.default.relative((0, _gatsbyWebpackVirtualModules.getAbsolutePathForVirtualModule)(`$virtual`), c.componentPath);
const rqPrefix = hasContentFilePath(relativeComponentPath) ? `&` : `?`;
return ` "${c.componentChunkName}": () => import("${(0, _gatsbyCoreUtils.slash)(`./${relativeComponentPath}`)}${rqPrefix}export=default" /* webpackChunkName: "${c.componentChunkName}" */)`;
}).join(`,\n`)}
}\n\n
exports.head = {\n${components.map(c => {
if (!c.hasHeadComponent) {
return undefined;
}
// we need a relative import path to keep contenthash the same if directory changes
const relativeComponentPath = _path.default.relative((0, _gatsbyWebpackVirtualModules.getAbsolutePathForVirtualModule)(`$virtual`), c.componentPath);
const rqPrefix = hasContentFilePath(relativeComponentPath) ? `&` : `?`;
return ` "${c.componentChunkName}": () => import("${(0, _gatsbyCoreUtils.slash)(`./${relativeComponentPath}`)}${rqPrefix}export=head" /* webpackChunkName: "${c.componentChunkName}head" */)`;
}).filter(Boolean).join(`,\n`)}
}\n\n`;
} else {
asyncRequires = `exports.components = {\n${components.map(c => {
// we need a relative import path to keep contenthash the same if directory changes
const relativeComponentPath = _path.default.relative((0, _gatsbyWebpackVirtualModules.getAbsolutePathForVirtualModule)(`$virtual`), c.componentPath);
return ` "${c.componentChunkName}": () => import("${(0, _gatsbyCoreUtils.slash)(`./${relativeComponentPath}`)}" /* webpackChunkName: "${c.componentChunkName}" */)`;
}).join(`,\n`)}
}\n\n`;
}
const writeAndMove = (virtualFilePath, file, data) => {
(0, _gatsbyWebpackVirtualModules.writeModule)(virtualFilePath, data);
// files in .cache are not used anymore as part of webpack builds, but
// still can be used by other tools (for example `gatsby serve` reads
// `match-paths.json` to setup routing)
const destination = (0, _gatsbyCoreUtils.joinPath)(program.directory, `.cache`, file);
const tmp = `${destination}.${Date.now()}`;
return _fsExtra.default.writeFile(tmp, data).then(() => _fsExtra.default.move(tmp, destination, {
overwrite: true
}));
};
await Promise.all([writeAndMove(`$virtual/sync-requires.js`, `sync-requires.js`, syncRequires), writeAndMove(`$virtual/async-requires.js`, `async-requires.js`, asyncRequires), writeAndMove(`$virtual/match-paths.json`, `match-paths.json`, JSON.stringify(matchPaths, null, 4))]);
return true;
};
exports.writeAll = writeAll;
const debouncedWriteAll = (0, _debounce2.default)(async () => {
const activity = _reporter.default.activityTimer(`write out requires`, {
id: `requires-writer`
});
activity.start();
await writeAll(_redux.store.getState());
activity.end();
}, 500, {
// using "leading" can cause double `writeAll` call - particularly
// when refreshing data using `/__refresh` hook.
leading: false
});
/**
* Start listening to CREATE/DELETE_PAGE events so we can rewrite
* files as required
*/
let listenerStarted = false;
const startListener = () => {
// Only start the listener once.
if (listenerStarted) {
return;
}
listenerStarted = true;
if (process.env.GATSBY_EXPERIMENTAL_DEV_SSR) {
/**
* Start listening to CREATE_SERVER_VISITED_PAGE events so we can rewrite
* files as required
*/
_redux.emitter.on(`CREATE_SERVER_VISITED_PAGE`, async () => {
// this event only happen on new additions
(0, _buildHtml.devSSRWillInvalidate)();
_reporter.default.pendingActivity({
id: `requires-writer`
});
debouncedWriteAll();
});
}
_redux.emitter.on(`CREATE_PAGE`, () => {
_reporter.default.pendingActivity({
id: `requires-writer`
});
debouncedWriteAll();
});
_redux.emitter.on(`CREATE_PAGE_END`, () => {
_reporter.default.pendingActivity({
id: `requires-writer`
});
debouncedWriteAll();
});
_redux.emitter.on(`DELETE_PAGE`, () => {
_reporter.default.pendingActivity({
id: `requires-writer`
});
debouncedWriteAll();
});
_redux.emitter.on(`DELETE_PAGE_BY_PATH`, () => {
_reporter.default.pendingActivity({
id: `requires-writer`
});
debouncedWriteAll();
});
};
exports.startListener = startListener;
//# sourceMappingURL=requires-writer.js.map
;